diff --git a/src/components/toast/showOpenToasts.tsx b/src/components/toast/showOpenToasts.tsx new file mode 100644 index 000000000..392800647 --- /dev/null +++ b/src/components/toast/showOpenToasts.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import InteractiveToast from '@ui/components/InteractiveToast' +import toast from 'react-hot-toast' +import { useNavigation } from '@hooks/useNavigation' +import { NavigationType, ROUTE_PATHS, useRouting } from '@views/routes/urls' +import { useSnippetStore } from '@store/useSnippetStore' + +export const useOpenToast = () => { + const { push } = useNavigation() + const { goTo } = useRouting() + const loadSnippet = useSnippetStore((store) => store.loadSnippet) + + const openNoteToast = (nodeid: string, title: string) => { + toast.custom((t) => ( + { + push(nodeid) + goTo(ROUTE_PATHS.node, NavigationType.push, nodeid) + // console.log('We are here') + }} + /> + )) + } + + const openSnippetToast = (snippetid: string, title: string) => { + toast.custom((t) => ( + { + loadSnippet(snippetid) + goTo(ROUTE_PATHS.snippet, NavigationType.push, snippetid) + // console.log('We are here') + }} + /> + )) + } + + return { openNoteToast, openSnippetToast } +} diff --git a/src/editor/Components/BalloonToolbar/BalloonToolbar.styles.ts b/src/editor/Components/BalloonToolbar/BalloonToolbar.styles.ts index d33b3b3dd..91589b4ad 100644 --- a/src/editor/Components/BalloonToolbar/BalloonToolbar.styles.ts +++ b/src/editor/Components/BalloonToolbar/BalloonToolbar.styles.ts @@ -137,3 +137,15 @@ export const BalloonToolbarBase = styled(ToolbarBase)` border-radius: ${({ theme }) => theme.borderRadius.tiny}; } ` + +export const BalloonToolbarInputWrapper = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing.small}; + padding: ${({ theme }) => theme.spacing.tiny}; + + svg { + width: 1.2rem; + height: 1.2rem; + } +` diff --git a/src/editor/Components/BalloonToolbar/components/SelectionToNode.tsx b/src/editor/Components/BalloonToolbar/components/SelectionToNode.tsx index d34afcca7..b1678f5d0 100644 --- a/src/editor/Components/BalloonToolbar/components/SelectionToNode.tsx +++ b/src/editor/Components/BalloonToolbar/components/SelectionToNode.tsx @@ -1,20 +1,29 @@ import { ToolbarButton, ToolbarButtonProps } from '@udecode/plate' import { getPreventDefaultHandler, usePlateEditorState } from '@udecode/plate-core' -import React from 'react' +import React, { useEffect } from 'react' import { useTransform } from './useTransform' +import fileList2Line from '@iconify/icons-ri/file-list-2-line' + +import { BalloonToolbarInputWrapper, useBalloonToolbarStore } from '../../BalloonToolbar' +import quillPenLine from '@iconify/icons-ri/quill-pen-line' +import { Icon } from '@iconify/react' +import { Input } from '@style/Form' /** * Toolbar button to Create new note from editor selection */ export const SelectionToNode = ({ ...props }: ToolbarButtonProps) => { const editor = usePlateEditorState()! - const { selectionToNode, isConvertable } = useTransform() + const { isConvertable } = useTransform() + const setToolbarState = useBalloonToolbarStore((s) => s.setToolbarState) return ( setToolbarState('new-note')) + : undefined } // Fade out when sync is selected styles={{ root: { opacity: !!editor?.selection && isConvertable(editor) ? 1 : 0.25 } }} @@ -22,3 +31,46 @@ export const SelectionToNode = ({ ...props }: ToolbarButtonProps) => { /> ) } + +export const SelectionToNodeInput = () => { + const editor = usePlateEditorState()! + const setOpen = useBalloonToolbarStore((s) => s.setOpen) + const { selectionToNode, isConvertable } = useTransform() + + const inputRef = React.useRef(null) + + useEffect(() => { + const timeoutId = setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus() + } + }, 1) + return () => clearTimeout(timeoutId) + }, [inputRef]) + + return ( + + + { + if (e.key === 'Enter') { + if (!!editor?.selection && isConvertable(editor)) { + const inputVal = e.currentTarget?.value + // console.log('We got that val', { inputVal }) + if (inputVal !== '') { + selectionToNode(editor, inputVal ?? undefined) + } else selectionToNode(editor) + } + setOpen(false) + } + if (e.key === 'Escape') { + setOpen(false) + // setToolbarState('normal') + } + }} + /> + + ) +} diff --git a/src/editor/Components/BalloonToolbar/components/SelectionToSnippet.tsx b/src/editor/Components/BalloonToolbar/components/SelectionToSnippet.tsx index c4d6f4772..e306a8c23 100644 --- a/src/editor/Components/BalloonToolbar/components/SelectionToSnippet.tsx +++ b/src/editor/Components/BalloonToolbar/components/SelectionToSnippet.tsx @@ -1,20 +1,27 @@ +import { Input } from '@style/Form' +import quillPenLine from '@iconify/icons-ri/quill-pen-line' +import { BalloonToolbarInputWrapper, useBalloonToolbarStore } from '../../BalloonToolbar' import { ToolbarButton, ToolbarButtonProps } from '@udecode/plate' import { getPreventDefaultHandler, usePlateEditorState } from '@udecode/plate-core' -import React from 'react' +import React, { useEffect } from 'react' import { useTransform } from './useTransform' +import { Icon } from '@iconify/react' /** * Toolbar button to Create new note from editor selection */ export const SelectionToSnippet = ({ ...props }: ToolbarButtonProps) => { const editor = usePlateEditorState()! - const { selectionToSnippet, isConvertable } = useTransform() + const { isConvertable } = useTransform() + const setToolbarState = useBalloonToolbarStore((s) => s.setToolbarState) return ( setToolbarState('new-snippet')) + : undefined } // Fade out when sync is selected styles={{ root: { opacity: !!editor?.selection && isConvertable(editor) ? 1 : 0.25 } }} @@ -22,3 +29,46 @@ export const SelectionToSnippet = ({ ...props }: ToolbarButtonProps) => { /> ) } + +export const SelectionToSnippetInput = () => { + const editor = usePlateEditorState()! + const setOpen = useBalloonToolbarStore((s) => s.setOpen) + const { selectionToSnippet, isConvertable } = useTransform() + + const inputRef = React.useRef(null) + + useEffect(() => { + const timeoutId = setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus() + } + }, 1) + return () => clearTimeout(timeoutId) + }, [inputRef]) + + return ( + + + { + if (e.key === 'Enter') { + if (!!editor?.selection && isConvertable(editor)) { + const inputVal = e.currentTarget?.value + // console.log('We got that val', { inputVal }) + if (inputVal !== '') { + selectionToSnippet(editor, inputVal ?? undefined) + } else selectionToSnippet(editor) + } + setOpen(false) + } + if (e.key === 'Escape') { + setOpen(false) + // setToolbarState('normal') + } + }} + /> + + ) +} diff --git a/src/editor/Components/BalloonToolbar/components/useTransform.ts b/src/editor/Components/BalloonToolbar/components/useTransform.ts index 6016f8efa..f9c03d9b6 100644 --- a/src/editor/Components/BalloonToolbar/components/useTransform.ts +++ b/src/editor/Components/BalloonToolbar/components/useTransform.ts @@ -1,16 +1,10 @@ +import { useOpenToast } from '@components/toast/showOpenToasts' import { useCreateNewNote } from '@hooks/useCreateNewNote' import { useSnippets } from '@hooks/useSnippets' -import { useToast } from '@hooks/useToast' import { useUpdater } from '@hooks/useUpdater' import { - getSelectionText, - insertNodes, - TEditor, - getNodeEntries, - removeNodes, - deleteText, - getPath, - withoutNormalizing + deleteText, getNodeEntries, getPath, getSelectionText, + insertNodes, removeNodes, TEditor, withoutNormalizing } from '@udecode/plate' import { convertValueToTasks } from '@utils/lib/contentConvertTask' import genereateName from 'project-name-generator' @@ -18,7 +12,6 @@ import { SEPARATOR } from '../../../../components/mex/Sidebar/treeUtils' import { defaultContent } from '../../../../data/Defaults/baseData' import { generateSnippetId, generateTempId } from '../../../../data/Defaults/idPrefixes' import { useEditorStore } from '../../../../store/useEditorStore' -import { useSnippetStore } from '../../../../store/useSnippetStore' import { NodeEditorContent } from '../../../../types/Types' import { getSlug, NODE_PATH_CHAR_LENGTH, NODE_PATH_SPACER } from '../../../../utils/lib/strings' import { convertContentToRawText } from '../../../../utils/search/parseData' @@ -28,12 +21,13 @@ import { ELEMENT_QA_BLOCK } from '../../QABlock/createQAPlugin' import { ELEMENT_SYNC_BLOCK } from '../../SyncBlock' export const useTransform = () => { - const addSnippet = useSnippetStore((s) => s.addSnippet) - const node = useEditorStore((s) => s.node) + // const addSnippet = useSnippetStore((s) => s.addSnippet) + // const node = useEditorStore((s) => s.node) + const { openNoteToast, openSnippetToast } = useOpenToast() const { updateSnippet } = useSnippets() const { createNewNote } = useCreateNewNote() const { updater } = useUpdater() - const { toast } = useToast() + // const { toast } = useToast() // Checks whether a node is a flowblock const isFlowBlock = (node: any): boolean => { @@ -159,7 +153,7 @@ export const useTransform = () => { * Inserts the link of new node in place of the selection * @param editor */ - const selectionToNode = (editor: TEditor) => { + const selectionToNode = (editor: TEditor, title?: string) => { if (!editor.selection) return if (!isConvertable(editor)) return @@ -186,20 +180,27 @@ export const useTransform = () => { return node }) const isInline = lowest.length === 1 - const putContent = selText.length > NODE_PATH_CHAR_LENGTH + const putContent = selText.length > NODE_PATH_CHAR_LENGTH && title !== undefined const text = convertContentToRawText(value, NODE_PATH_SPACER) - const parentPath = useEditorStore.getState().node.title - const path = parentPath + SEPARATOR + (isInline ? getSlug(selText) : getSlug(text)) + const parentPath = useEditorStore.getState().node.path + const namespace = useEditorStore.getState().node.namespace + const childTitle = title ?? (isInline ? getSlug(selText) : getSlug(text)) + const path = parentPath + SEPARATOR + childTitle const note = createNewNote({ path, noteContent: putContent ? value : defaultContent.content, - namespace: node?.namespace, + namespace: namespace, noRedirect: true }) replaceSelectionWithLink(editor, note?.nodeid, isInline) + + if (note) { + openNoteToast(note.nodeid, note.path) + } + // mog('Replace Selection with node We are here', { // lowest, // selText, @@ -247,7 +248,7 @@ export const useTransform = () => { * Shows notification of snippet creation * @param editor */ - const selectionToSnippet = (editor: TEditor) => { + const selectionToSnippet = (editor: TEditor, title?: string) => { if (!editor.selection) return if (!isConvertable(editor)) return @@ -266,7 +267,7 @@ export const useTransform = () => { }) const snippetId = generateSnippetId() - const snippetTitle = genereateName().dashed + const snippetTitle = title ?? genereateName().dashed const newSnippet = { id: snippetId, title: snippetTitle, @@ -278,8 +279,9 @@ export const useTransform = () => { // addSnippet() // mog('We are here', { esl: editor.selection, selectionPath, nodes, value }) + // + openSnippetToast(snippetId, snippetTitle) - toast(`Snippet created [[${snippetTitle}]]`) // setContent(nodeid, value) // saveData() // mog('We are here', { esl: editor.selection, selectionPath, nodes, value, text, path }) diff --git a/src/editor/Components/BalloonToolbar/useBalloonToolbarPopper.ts b/src/editor/Components/BalloonToolbar/useBalloonToolbarPopper.ts index 4d8dc3f64..c13b8a6d3 100644 --- a/src/editor/Components/BalloonToolbar/useBalloonToolbarPopper.ts +++ b/src/editor/Components/BalloonToolbar/useBalloonToolbarPopper.ts @@ -18,18 +18,28 @@ import { import { useFocused } from 'slate-react' import create from 'zustand' +type ToolbarState = 'normal' | 'new-note' | 'new-snippet' + interface BalloonToolbarStore { - isHidden: boolean - setIsHidden: (isHidden: boolean) => void - isFocused: boolean - setIsFocused: (isFocused: boolean) => void + open: boolean + setOpen: (open: boolean) => void + // isHidden: boolean + // setIsHidden: (isHidden: boolean) => void + // isFocused: boolean + // setIsFocused: (isFocused: boolean) => void + toolbarState: ToolbarState + setToolbarState: (state: ToolbarState) => void } export const useBalloonToolbarStore = create((set, get) => ({ - isHidden: true, - setIsHidden: (isHidden) => set({ isHidden }), - isFocused: false, - setIsFocused: (isFocused) => set({ isFocused }) + open: false, + setOpen: (open) => set({ open }), + // isHidden: true, + // setIsHidden: (isHidden) => set({ isHidden }), + // isFocused: false, + // setIsFocused: (isFocused) => set({ isFocused }), + toolbarState: 'normal', + setToolbarState: (state) => set({ toolbarState: state }) })) export const useFloatingToolbar = ({ @@ -42,10 +52,14 @@ export const useFloatingToolbar = ({ const focusedEditorId = useEventEditorSelectors.focus() const editor = useEditorState() const focused = useFocused() + const toolbarState = useBalloonToolbarStore((s) => s.toolbarState) + const setToolbarState = useBalloonToolbarStore((s) => s.setToolbarState) const [waitForCollapsedSelection, setWaitForCollapsedSelection] = useState(false) - const [open, setOpen] = useState(false) + // const [open, setOpen] = useState(false) + const open = useBalloonToolbarStore((s) => s.open) + const setOpen = useBalloonToolbarStore((s) => s.setOpen) const selectionExpanded = editor && isSelectionExpanded(editor) const selectionText = editor && getSelectionText(editor) @@ -63,7 +77,9 @@ export const useFloatingToolbar = ({ }, [focused, selectionExpanded]) useEffect(() => { - if (!selectionExpanded || !selectionText || editor.id !== focusedEditorId) { + if ((!selectionExpanded || !selectionText || editor.id !== focusedEditorId) && toolbarState === 'normal') { + setOpen(false) + } else if (toolbarState !== 'normal' && editor.id === focusedEditorId) { setOpen(false) } else if (selectionText && selectionExpanded && !waitForCollapsedSelection) { setOpen(true) @@ -93,6 +109,12 @@ export const useFloatingToolbar = ({ const selectionTextLength = selectionText?.length ?? 0 + useEffect(() => { + if (!open) { + setToolbarState('normal') + } + }, [open]) + useEffect(() => { if (selectionTextLength > 0) { update?.() diff --git a/src/editor/Components/EditorBalloonToolbar.tsx b/src/editor/Components/EditorBalloonToolbar.tsx index 2bf140c7c..87872f77b 100644 --- a/src/editor/Components/EditorBalloonToolbar.tsx +++ b/src/editor/Components/EditorBalloonToolbar.tsx @@ -37,12 +37,15 @@ import { } from '@udecode/plate' import React from 'react' import { ButtonSeparator } from '../../style/Toolbar' -import { BalloonToolbar } from './BalloonToolbar' -import { SelectionToNode } from './BalloonToolbar/components/SelectionToNode' -import { SelectionToSnippet } from './BalloonToolbar/components/SelectionToSnippet' +import { BalloonToolbar, useBalloonToolbarStore } from './BalloonToolbar' +import { SelectionToNode, SelectionToNodeInput } from './BalloonToolbar/components/SelectionToNode' +import { SelectionToSnippet, SelectionToSnippetInput } from './BalloonToolbar/components/SelectionToSnippet' import { SelectionToTask } from './BalloonToolbar/components/SelectionToTask' const BallonMarkToolbarButtons = () => { + const toolbarState = useBalloonToolbarStore((s) => s.toolbarState) + // const setToolbarState = useBalloonToolbarStore((s) => s.setToolbarState) + const editor = usePlateEditorRef() const arrow = false @@ -65,98 +68,105 @@ const BallonMarkToolbarButtons = () => { return ( - } - tooltip={{ content: 'Heading 1', ...tooltip }} - /> - - } - tooltip={{ content: 'Heading 2', ...tooltip }} - /> - - } - tooltip={{ content: 'Heading 3', ...tooltip }} - /> - - - } - /> - } - /> - } - /> - - - - } - tooltip={{ content: 'Quote', ...tooltip }} - /> - - } - tooltip={{ content: 'Bullet List', ...tooltip }} - /> - - } - tooltip={{ content: 'Ordered List', ...tooltip }} - /> - - - - } - tooltip={{ content: 'Bold (⌘B)', ...tooltip }} - /> - } - /> - } - tooltip={{ content: 'Italic (⌘I)', ...tooltip }} - /> - } /> - - - - } - tooltip={{ content: 'Convert Blocks to Task', ...tooltip }} - /> - - } - tooltip={{ content: 'Convert Blocks to New Node', ...tooltip }} - /> - - } - tooltip={{ content: 'Convert Blocks to New Snippet', ...tooltip }} - /> - {/* - } /> */} - {/* Looses focus when used. */} + { + { + normal: ( + <> + } + tooltip={{ content: 'Heading 1', ...tooltip }} + /> + + } + tooltip={{ content: 'Heading 2', ...tooltip }} + /> + + } + tooltip={{ content: 'Heading 3', ...tooltip }} + /> + + + } + /> + } + /> + } + /> + + + + } + tooltip={{ content: 'Quote', ...tooltip }} + /> + + } + tooltip={{ content: 'Bullet List', ...tooltip }} + /> + + } + tooltip={{ content: 'Ordered List', ...tooltip }} + /> + + + + } + tooltip={{ content: 'Bold (⌘B)', ...tooltip }} + /> + } + /> + } + tooltip={{ content: 'Italic (⌘I)', ...tooltip }} + /> + } /> + + + + } + tooltip={{ content: 'Convert Blocks to Task', ...tooltip }} + /> + + } + tooltip={{ content: 'Convert Blocks to New Node', ...tooltip }} + /> + + } + tooltip={{ content: 'Convert Blocks to New Snippet', ...tooltip }} + /> + + ), + 'new-note': , + 'new-snippet': + }[toolbarState] + } ) } diff --git a/src/style/Editor.tsx b/src/style/Editor.tsx index 63826c13e..cac045959 100644 --- a/src/style/Editor.tsx +++ b/src/style/Editor.tsx @@ -31,6 +31,7 @@ export const InfoTools = styled.div` align-items: center; gap: ${({ theme }) => theme.spacing.small}; ${Button} { + background: transparent; margin: 0; } ${(props) => focusStyles(props)}