diff --git a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx index 10a740a7..af0e0e5b 100644 --- a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx +++ b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx @@ -9,7 +9,7 @@ import { SharePopup, getShareUrl } from '../SharePopup' import styles from './AudioPlayer.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) +const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor')) const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) type Props = { @@ -171,10 +171,9 @@ export const PlayerPlaylist = (props: Props) => { } >
- handleMediaItemFieldChange('body', value)} /> import('../../Editor/SimplifiedEditor')) +const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor')) type Props = { comment: Reaction @@ -41,7 +41,6 @@ export const Comment = (props: Props) => { const [isReplyVisible, setIsReplyVisible] = createSignal(false) const [loading, setLoading] = createSignal(false) const [editMode, setEditMode] = createSignal(false) - const [clearEditor, setClearEditor] = createSignal(false) const [editedBody, setEditedBody] = createSignal() const { session, client } = useSession() const author = createMemo(() => session()?.user?.app_data?.profile as Author) @@ -104,13 +103,11 @@ export const Comment = (props: Props) => { shout: props.comment.shout.id } } as MutationCreate_ReactionArgs) - setClearEditor(true) setIsReplyVisible(false) setLoading(false) } catch (error) { console.error('[handleCreate reaction]:', error) } - setClearEditor(false) } const toggleEditMode = () => { @@ -189,16 +186,11 @@ export const Comment = (props: Props) => {
}> {t('Loading')}

}> - handleUpdate(value)} - submitByCtrlEnter={true} onCancel={() => setEditMode(false)} - setClear={clearEditor()} />
@@ -258,12 +250,9 @@ export const Comment = (props: Props) => { {t('Loading')}

}> - handleCreate(value)} - submitByCtrlEnter={true} />
diff --git a/src/components/Article/CommentsTree.tsx b/src/components/Article/CommentsTree.tsx index e53a9f39..ca3b9e43 100644 --- a/src/components/Article/CommentsTree.tsx +++ b/src/components/Article/CommentsTree.tsx @@ -9,11 +9,12 @@ import { Author, Reaction, ReactionKind, ReactionSort } from '~/graphql/schema/c import { SortFunction } from '~/types/common' import { byCreated, byStat } from '~/utils/sort' import { Button } from '../_shared/Button' +import { Loading } from '../_shared/Loading' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' import styles from './Article.module.scss' import { Comment } from './Comment' -const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor')) +const MiniEditor = lazy(() => import('../Editor/MiniEditor/MiniEditor')) type Props = { articleAuthors: Author[] @@ -27,7 +28,6 @@ export const CommentsTree = (props: Props) => { const [commentsOrder, setCommentsOrder] = createSignal(ReactionSort.Newest) const [onlyNew, setOnlyNew] = createSignal(false) const [newReactions, setNewReactions] = createSignal([]) - const [clearEditor, setClearEditor] = createSignal(false) const [clickedReplyId, setClickedReplyId] = createSignal() const { reactionEntities, createShoutReaction, loadReactionsBy } = useReactions() @@ -70,6 +70,7 @@ export const CommentsTree = (props: Props) => { setCookie() } }) + const [posting, setPosting] = createSignal(false) const handleSubmitComment = async (value: string) => { setPosting(true) @@ -81,12 +82,10 @@ export const CommentsTree = (props: Props) => { shout: props.shoutId } }) - setClearEditor(true) await loadReactionsBy({ by: { shout: props.shoutSlug } }) } catch (error) { console.error('[handleCreate reaction]:', error) } - setClearEditor(false) setPosting(false) } @@ -155,16 +154,13 @@ export const CommentsTree = (props: Props) => {
} > - handleSubmitComment(value)} - setClear={clearEditor()} - isPosting={posting()} /> + + + ) diff --git a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx index 706c9048..2ac02b7a 100644 --- a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx @@ -1,14 +1,13 @@ import type { Editor } from '@tiptap/core' - -import { renderUploadedImage } from '~/components/Editor/renderUploadedImage' +import { renderUploadedImage } from '~/components/Upload/renderUploadedImage' import { Icon } from '~/components/_shared/Icon' import { Popover } from '~/components/_shared/Popover' import { useLocalize } from '~/context/localize' -import { UploadedFile } from '~/types/upload' -import { Modal } from '../../_shared/Modal' -import { UploadModalContent } from '../UploadModalContent' - import { useUI } from '~/context/ui' +import { UploadedFile } from '~/types/upload' +import { UploadModalContent } from '../../Upload/UploadModalContent' +import { Modal } from '../../_shared/Modal' + import styles from './BubbleMenu.module.scss' type Props = { diff --git a/src/components/Editor/Editor.stories.tsx b/src/components/Editor/Editor.stories.tsx index e7d0f255..3203bf14 100644 --- a/src/components/Editor/Editor.stories.tsx +++ b/src/components/Editor/Editor.stories.tsx @@ -1,105 +1,28 @@ -import { Editor, EditorOptions } from '@tiptap/core' -import { createSignal } from 'solid-js' -import { createStore } from 'solid-js/store' import { Meta, StoryObj } from 'storybook-solidjs' -import { EditorContext, EditorContextType, ShoutForm } from '~/context/editor' -import { LocalizeContext, LocalizeContextType } from '~/context/localize' -import { SessionContext, SessionContextType } from '~/context/session' -import { SnackbarContext, SnackbarContextType } from '~/context/ui' -import { EditorComponent, EditorComponentProps } from './Editor' - -// Mock data -const mockSession = { - session: () => ({ - user: { - app_data: { - profile: { - name: 'Test User', - slug: 'test-user' - } - } - }, - access_token: 'mock-access-token' - }) -} - -const mockLocalize = { - t: (key: string) => key, - lang: () => 'en' -} - -const [_form, setForm] = createStore({ - body: '', - slug: '', - shoutId: 0, - title: '', - selectedTopics: [] -}) -const [_formErrors, setFormErrors] = createStore({} as Record) -const [editor, setEditor] = createSignal() - -const mockEditorContext: EditorContextType = { - countWords: () => 0, - isEditorPanelVisible: () => false, - wordCounter: () => ({ characters: 0, words: 0 }), - form: _form, - formErrors: _formErrors, - createEditor: (opts?: Partial) => { - const newEditor = new Editor(opts) - setEditor(newEditor) - return newEditor - }, - editor, - saveShout: async (_form: ShoutForm) => { - // Simulate save - }, - saveDraft: async (_form: ShoutForm) => { - // Simulate save draft - }, - saveDraftToLocalStorage: (_form: ShoutForm) => { - // Simulate save to local storage - }, - getDraftFromLocalStorage: (_shoutId: number): ShoutForm => _form, - publishShout: async (_form: ShoutForm) => { - // Simulate publish - }, - publishShoutById: async (_shoutId: number) => { - // Simulate publish by ID - }, - deleteShout: async (_shoutId: number): Promise => true, - toggleEditorPanel: () => { - // Simulate toggle - }, - setForm, - setFormErrors -} - -const mockSnackbarContext = { - showSnackbar: console.log -} +import { EditorComponent } from './Editor' const meta: Meta = { title: 'Components/Editor', component: EditorComponent, argTypes: { - shoutId: { - control: 'number', - description: 'Unique identifier for the shout (document)', - defaultValue: 1 - }, - initialContent: { + content: { control: 'text', description: 'Initial content for the editor', defaultValue: '' }, - onChange: { - action: 'contentChanged', - description: 'Callback when the content changes' + limit: { + control: 'number', + description: 'Character limit for the editor', + defaultValue: 500 }, - disableCollaboration: { - control: 'boolean', - description: 'Disable collaboration features for Storybook', - defaultValue: true + placeholder: { + control: 'text', + description: 'Placeholder text when the editor is empty', + defaultValue: 'Start typing here...' + }, + onChange: { + action: 'changed', + description: 'Callback when the content changes' } } } @@ -109,38 +32,33 @@ export default meta type Story = StoryObj export const Default: Story = { - render: (props: EditorComponentProps) => { - const [_content, setContent] = createSignal(props.initialContent || '') - - return ( - - - - - { - props.onChange(text) - setContent(text) - }} - /> - - - - - ) - }, args: { - shoutId: 1, - initialContent: '', - disableCollaboration: true + content: '', + limit: 500, + placeholder: 'Start typing here...' } } export const WithInitialContent: Story = { - ...Default, args: { - ...Default.args, - initialContent: '

This is some initial content in the editor.

' + content: 'This is some initial content', + limit: 500, + placeholder: 'Start typing here...' + } +} + +export const WithCharacterLimit: Story = { + args: { + content: '', + limit: 50, + placeholder: 'You have a 50 character limit...' + } +} + +export const WithCustomPlaceholder: Story = { + args: { + content: '', + limit: 500, + placeholder: 'Custom placeholder here...' } } diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 04879023..4a05a420 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -16,10 +16,10 @@ import { useSnackbar } from '~/context/ui' import { Author } from '~/graphql/schema/core.gen' import { base, custom, extended } from '~/lib/editorExtensions' import { handleImageUpload } from '~/lib/handleImageUpload' +import { renderUploadedImage } from '../Upload/renderUploadedImage' import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu' import { EditorFloatingMenu } from './EditorFloatingMenu' import { TextBubbleMenu } from './TextBubbleMenu' -import { renderUploadedImage } from './renderUploadedImage' import './Prosemirror.scss' diff --git a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx index c455a053..b2a2876a 100644 --- a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx +++ b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx @@ -1,15 +1,14 @@ import type { Editor } from '@tiptap/core' import { Show, createEffect, createSignal } from 'solid-js' - -import { renderUploadedImage } from '~/components/Editor/renderUploadedImage' +import { renderUploadedImage } from '~/components/Upload/renderUploadedImage' import { Icon } from '~/components/_shared/Icon' import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import { UploadedFile } from '~/types/upload' +import { UploadModalContent } from '../../Upload/UploadModalContent' +import { InlineForm } from '../../_shared/InlineForm' import { Modal } from '../../_shared/Modal' -import { InlineForm } from '../InlineForm' -import { UploadModalContent } from '../UploadModalContent' import { Menu } from './Menu' import type { MenuItem } from './Menu/Menu' diff --git a/src/components/Editor/EditorToolbar/MicroToolbar.tsx b/src/components/Editor/EditorToolbar/MicroToolbar.tsx new file mode 100644 index 00000000..8e9b42db --- /dev/null +++ b/src/components/Editor/EditorToolbar/MicroToolbar.tsx @@ -0,0 +1,113 @@ +import { Editor } from '@tiptap/core' +import { Show, createEffect, createSignal, on } from 'solid-js' +import { createEditorTransaction } from 'solid-tiptap' +import { Icon } from '~/components/_shared/Icon/Icon' +import { useLocalize } from '~/context/localize' +import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm' +import { ToolbarControl as Control } from './ToolbarControl' + +import styles from '../SimplifiedEditor.module.scss' + +export interface MicroToolbarProps { + showing?: boolean + editor?: Editor +} + +export const MicroToolbar = (props: MicroToolbarProps) => { + const { t } = useLocalize() + + // show / hide for menu + const [showSimpleMenu, setShowSimpleMenu] = createSignal(!props.showing) + const selection = createEditorTransaction( + () => props.editor, + (instance) => instance?.state.selection + ) + + // show / hide for link input + const [showLinkInput, setShowLinkInput] = createSignal(false) + + // change visibility on selection if not in link input mode + createEffect(on([selection, showLinkInput], ([s, l]) => !l && setShowSimpleMenu(!s?.empty))) + + // focus on link input when it shows up + createEffect(on(showLinkInput, (x?: boolean) => x && props.editor?.chain().focus().run())) + + const [storedSelection, setStoredSelection] = createSignal() + const recoverSelection = () => { + if (!storedSelection()?.empty) { + createEditorTransaction( + () => props.editor, + (instance?: Editor) => { + const r = selection() + if (instance && r) { + instance.state.selection.from === r.from + instance.state.selection.to === r.to + } + } + ) + } + } + const storeSelection = () => { + const selection = props.editor?.state.selection + if (!selection?.empty) { + setStoredSelection(selection) + } + } + const toggleShowLink = () => { + if (showLinkInput()) { + props.editor?.chain().focus().run() + recoverSelection() + } else { + storeSelection() + } + setShowLinkInput(!showLinkInput()) + } + return ( + + {(instance) => ( + +
+
+
+ instance.chain().focus().toggleBold().run()} + title={t('Bold')} + > + + + instance.chain().focus().toggleItalic().run()} + title={t('Italic')} + > + + + + + +
+ + + +
+
+
+ )} +
+ ) +} diff --git a/src/components/Editor/EditorToolbar/MiniToolbar.tsx b/src/components/Editor/EditorToolbar/MiniToolbar.tsx new file mode 100644 index 00000000..e4e7f036 --- /dev/null +++ b/src/components/Editor/EditorToolbar/MiniToolbar.tsx @@ -0,0 +1,117 @@ +import { Editor } from '@tiptap/core' +import { Show, createEffect, createSignal, on } from 'solid-js' +import { Icon } from '~/components/_shared/Icon/Icon' +import { useLocalize } from '~/context/localize' +import { useUI } from '~/context/ui' +import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm' +import { ToolbarControl as Control } from './ToolbarControl' + +import { createEditorTransaction } from 'solid-tiptap' +import styles from '../SimplifiedEditor.module.scss' + +interface MiniToolbarProps { + editor?: Editor +} + +export const MiniToolbar = (props: MiniToolbarProps) => { + const { t } = useLocalize() + const { showModal } = useUI() + + // show / hide for link input + const [showLinkInput, setShowLinkInput] = createSignal(false) + + // focus on link input when it shows up + createEffect(on(showLinkInput, (x?: boolean) => x && props.editor?.chain().focus().run())) + + const selection = createEditorTransaction( + () => props.editor, + (instance) => instance?.state.selection + ) + const [storedSelection, setStoredSelection] = createSignal() + const recoverSelection = () => { + if (!storedSelection()?.empty) { + createEditorTransaction( + () => props.editor, + (instance?: Editor) => { + const r = selection() + if (instance && r) { + instance.state.selection.from === r.from + instance.state.selection.to === r.to + } + } + ) + } + } + const storeSelection = () => { + const selection = props.editor?.state.selection + if (!selection?.empty) { + setStoredSelection(selection) + } + } + const toggleShowLink = () => { + if (showLinkInput()) { + props.editor?.chain().focus().run() + recoverSelection() + } else { + storeSelection() + } + setShowLinkInput(!showLinkInput()) + } + + return ( +
+ + {(instance) => ( +
+
+ instance.chain().focus().toggleBold().run()} + title={t('Bold')} + > + + + instance.chain().focus().toggleItalic().run()} + title={t('Italic')} + > + + + + + + instance.chain().focus().toggleBlockquote().run()} + title={t('Add blockquote')} + > + + + showModal('simplifiedEditorUploadImage')} + title={t('Add image')} + > + + +
+ + + +
+ )} +
+
+ ) +} diff --git a/src/components/Editor/EditorToolbar.tsx b/src/components/Editor/EditorToolbar/SimplifiedToolbar.tsx similarity index 93% rename from src/components/Editor/EditorToolbar.tsx rename to src/components/Editor/EditorToolbar/SimplifiedToolbar.tsx index ec4c2f2b..1ea1d449 100644 --- a/src/components/Editor/EditorToolbar.tsx +++ b/src/components/Editor/EditorToolbar/SimplifiedToolbar.tsx @@ -4,13 +4,13 @@ import { createEditorTransaction, useEditorHTML, useEditorIsEmpty } from 'solid- import { useEditorContext } from '~/context/editor' import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { Button } from '../_shared/Button' -import { Icon } from '../_shared/Icon' -import { Loading } from '../_shared/Loading' -import { Popover } from '../_shared/Popover' -import { SimplifiedEditorProps } from './SimplifiedEditor' +import { Button } from '../../_shared/Button' +import { Icon } from '../../_shared/Icon' +import { Loading } from '../../_shared/Loading' +import { Popover } from '../../_shared/Popover' +import { SimplifiedEditorProps } from '../SimplifiedEditor' -import styles from './SimplifiedEditor.module.scss' +import styles from '../SimplifiedEditor.module.scss' export const ToolbarControls = ( props: SimplifiedEditorProps & { setShouldShowLinkBubbleMenu: (x: boolean) => void } diff --git a/src/components/Editor/EditorToolbar/ToolbarControl.tsx b/src/components/Editor/EditorToolbar/ToolbarControl.tsx new file mode 100644 index 00000000..b4046e67 --- /dev/null +++ b/src/components/Editor/EditorToolbar/ToolbarControl.tsx @@ -0,0 +1,40 @@ +import { Editor } from '@tiptap/core' +import clsx from 'clsx' +import { JSX } from 'solid-js' +import { Popover } from '~/components/_shared/Popover' + +import styles from '../SimplifiedEditor.module.scss' + +interface ControlProps { + editor: Editor + title: string + key: string + onChange: () => void + isActive?: (editor: Editor) => boolean + children: JSX.Element +} + +export const ToolbarControl = (props: ControlProps): JSX.Element => { + const handleClick = (ev?: MouseEvent) => { + ev?.preventDefault() + ev?.stopPropagation() + props.onChange?.() + } + + return ( + + {(triggerRef: (el: HTMLElement) => void) => ( + + )} + + ) +} + +export default ToolbarControl diff --git a/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx b/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx index 825be6ff..9d7c479f 100644 --- a/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx +++ b/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx @@ -3,7 +3,7 @@ import { createEffect, createSignal, onCleanup } from 'solid-js' import { useLocalize } from '~/context/localize' import { validateUrl } from '~/utils/validate' -import { InlineForm } from '../InlineForm' +import { InlineForm } from '../../_shared/InlineForm' type Props = { editor: Editor diff --git a/src/components/Editor/MicroEditor/MicroEditor.tsx b/src/components/Editor/MicroEditor/MicroEditor.tsx index 48233335..906f4193 100644 --- a/src/components/Editor/MicroEditor/MicroEditor.tsx +++ b/src/components/Editor/MicroEditor/MicroEditor.tsx @@ -1,68 +1,21 @@ -import type { Editor } from '@tiptap/core' import Placeholder from '@tiptap/extension-placeholder' import clsx from 'clsx' -import { type JSX, Show, createEffect, createReaction, createSignal, on, onCleanup } from 'solid-js' -import { - createEditorTransaction, - createTiptapEditor, - useEditorHTML, - useEditorIsEmpty, - useEditorIsFocused -} from 'solid-tiptap' -import { Icon } from '~/components/_shared/Icon/Icon' -import { Popover } from '~/components/_shared/Popover/Popover' -import { useLocalize } from '~/context/localize' +import { type JSX, createEffect, createSignal, on } from 'solid-js' +import { createTiptapEditor, useEditorHTML, useEditorIsEmpty, useEditorIsFocused } from 'solid-tiptap' import { minimal } from '~/lib/editorExtensions' -import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm' +import { MicroToolbar } from '../EditorToolbar/MicroToolbar' import styles from '../SimplifiedEditor.module.scss' -interface ControlProps { - editor: Editor - title: string - key: string - onChange: () => void - isActive?: (editor: Editor) => boolean - children: JSX.Element -} - -function Control(props: ControlProps): JSX.Element { - const handleClick = (ev?: MouseEvent) => { - ev?.preventDefault() - ev?.stopPropagation() - props.onChange?.() - } - - return ( - - {(triggerRef: (el: HTMLElement) => void) => ( - - )} - - ) -} - interface MicroEditorProps { content?: string onChange?: (content: string) => void + onSubmit?: (content: string) => void placeholder?: string } -const prevent = (e: Event) => e.preventDefault() - export const MicroEditor = (props: MicroEditorProps): JSX.Element => { - const { t } = useLocalize() const [editorElement, setEditorElement] = createSignal() - const [showLinkInput, setShowLinkInput] = createSignal(false) - const [showSimpleMenu, setShowSimpleMenu] = createSignal(false) - const [toolbarElement, setToolbarElement] = createSignal() const editor = createTiptapEditor(() => ({ element: editorElement()!, @@ -78,36 +31,10 @@ export const MicroEditor = (props: MicroEditorProps): JSX.Element => { content: props.content || '' })) - const selection = createEditorTransaction(editor, (instance) => instance?.state.selection) - const [storedSelection, setStoredSelection] = createSignal() - const recoverSelection = () => { - if (!storedSelection()?.empty) { - // TODO set selection range from stored - createEditorTransaction(editor, (instance?: Editor) => { - const r = selection() - if (instance && r) { - instance.state.selection.from === r.from - instance.state.selection.to === r.to - } - }) - } - } - const storeSelection = (event: Event) => { - event.preventDefault() - const selection = editor()?.state.selection - if (!selection?.empty) { - setStoredSelection(selection) - } - } - const isEmpty = useEditorIsEmpty(editor) const isFocused = useEditorIsFocused(editor) const html = useEditorHTML(editor) - createEffect(on([selection, showLinkInput], ([s, l]) => !l && setShowSimpleMenu(!s?.empty))) createEffect(on(html, (c?: string) => c && props.onChange?.(c))) - createEffect(on(showLinkInput, (x?: boolean) => x && editor()?.chain().focus().run())) - createReaction(on(toolbarElement, (t?: HTMLElement) => t?.addEventListener('mousedown', prevent))) - onCleanup(() => toolbarElement()?.removeEventListener('mousedown', prevent)) return (
{ })} >
- - {(instance) => ( - -
-
-
- instance.chain().focus().toggleBold().run()} - title={t('Bold')} - > - - - instance.chain().focus().toggleItalic().run()} - title={t('Italic')} - > - - - setShowLinkInput(!showLinkInput())} - title={t('Add url')} - isActive={showLinkInput} - > - - -
- - { - setShowLinkInput(false) - recoverSelection() - }} - /> - -
-
-
- )} -
+ -
+
) diff --git a/src/components/Editor/MiniEditor/MiniEditor.tsx b/src/components/Editor/MiniEditor/MiniEditor.tsx index 4393f0b4..32f383b2 100644 --- a/src/components/Editor/MiniEditor/MiniEditor.tsx +++ b/src/components/Editor/MiniEditor/MiniEditor.tsx @@ -1,54 +1,18 @@ -import type { Editor } from '@tiptap/core' import CharacterCount from '@tiptap/extension-character-count' import Placeholder from '@tiptap/extension-placeholder' import clsx from 'clsx' -import { type JSX, Show, createEffect, createSignal, on, onCleanup } from 'solid-js' -import { createTiptapEditor, useEditorHTML } from 'solid-tiptap' -import { Toolbar } from 'terracotta' -import { Icon } from '~/components/_shared/Icon/Icon' -import { Popover } from '~/components/_shared/Popover/Popover' -import { useLocalize } from '~/context/localize' -import { useUI } from '~/context/ui' +import { type JSX, Show, createEffect, createSignal, on } from 'solid-js' +import { createEditorTransaction, createTiptapEditor, useEditorHTML } from 'solid-tiptap' import { base } from '~/lib/editorExtensions' -import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm' +import { MiniToolbar } from '../EditorToolbar/MiniToolbar' import styles from '../SimplifiedEditor.module.scss' -interface ControlProps { - editor: Editor - title: string - key: string - onChange: () => void - isActive?: (editor: Editor) => boolean - children: JSX.Element -} - -function Control(props: ControlProps): JSX.Element { - const handleClick = (ev?: MouseEvent) => { - ev?.preventDefault() - ev?.stopPropagation() - props.onChange?.() - } - - return ( - - {(triggerRef: (el: HTMLElement) => void) => ( - - )} - - ) -} - interface MiniEditorProps { content?: string onChange?: (content: string) => void + onSubmit?: (content: string) => void + onCancel?: () => void limit?: number placeholder?: string } @@ -56,9 +20,6 @@ interface MiniEditorProps { export default function MiniEditor(props: MiniEditorProps): JSX.Element { const [editorElement, setEditorElement] = createSignal() const [counter, setCounter] = createSignal(0) - const [showLinkInput, setShowLinkInput] = createSignal(false) - const { t } = useLocalize() - const { showModal } = useUI() const editor = createTiptapEditor(() => ({ element: editorElement()!, @@ -77,7 +38,6 @@ export default function MiniEditor(props: MiniEditorProps): JSX.Element { const html = useEditorHTML(editor) createEffect(on(html, (c?: string) => c && props.onChange?.(c))) - createEffect(on(showLinkInput, (x?: boolean) => x && editor()?.chain().focus().run())) createEffect(() => { const textLength = editor()?.getText().length || 0 @@ -86,85 +46,14 @@ export default function MiniEditor(props: MiniEditorProps): JSX.Element { content && props.onChange?.(content) }) - const handleLinkClick = () => { - setShowLinkInput(!showLinkInput()) - editor()?.chain().focus().run() - } + const isFocused = createEditorTransaction(editor, (instance) => instance?.isFocused) - // Prevent focus loss when clicking inside the toolbar - const handleMouseDownOnToolbar = (event: MouseEvent) => { - event.preventDefault() // Prevent the default focus shift - } - const [toolbarElement, setToolbarElement] = createSignal() - // Attach the event handler to the toolbar - onCleanup(() => { - toolbarElement()?.removeEventListener('mousedown', handleMouseDownOnToolbar) - }) return ( -
+
- - - {(instance) => ( -
- setShowLinkInput(false)} />} - > -
- instance.chain().focus().toggleBold().run()} - title={t('Bold')} - > - - - instance.chain().focus().toggleItalic().run()} - title={t('Italic')} - > - - - - - - instance.chain().focus().toggleBlockquote().run()} - title={t('Add blockquote')} - > - - - showModal('simplifiedEditorUploadImage')} - title={t('Add image')} - > - - -
-
-
- )} -
-
+ 0}> diff --git a/src/components/Editor/SimplifiedEditor.tsx b/src/components/Editor/SimplifiedEditor.tsx index a474f9c7..f09c55c6 100644 --- a/src/components/Editor/SimplifiedEditor.tsx +++ b/src/components/Editor/SimplifiedEditor.tsx @@ -11,15 +11,15 @@ import { useUI } from '~/context/ui' import { base, custom } from '~/lib/editorExtensions' import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler' import { UploadedFile } from '~/types/upload' +import { UploadModalContent } from '../Upload/UploadModalContent' +import { renderUploadedImage } from '../Upload/renderUploadedImage' import { Modal } from '../_shared/Modal/Modal' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' -import { ToolbarControls } from './EditorToolbar' +import { ToolbarControls } from './EditorToolbar/SimplifiedToolbar' import { LinkBubbleMenuModule } from './LinkBubbleMenu' import { TextBubbleMenu } from './TextBubbleMenu' -import { UploadModalContent } from './UploadModalContent' -import { renderUploadedImage } from './renderUploadedImage' -import styles from './SimplifiedEditor.module.scss' +import styles from './Editor.module.scss' export type SimplifiedEditorProps = { placeholder: string diff --git a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx index 7d72ab0a..8352c69a 100644 --- a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx +++ b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx @@ -11,7 +11,7 @@ import { InsertLinkForm } from '../InsertLinkForm' import styles from './TextBubbleMenu.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) +const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor')) type BubbleMenuProps = { editor: Editor @@ -146,18 +146,13 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { - handleAddFootnote(value)} - variant={'bordered'} - initialContent={footNote()} + onSubmit={(value: string) => handleAddFootnote(value)} + content={footNote()} onCancel={() => { setFootnoteEditorOpen(false) }} - submitButtonText={t('Send')} /> diff --git a/src/components/Editor/index.ts b/src/components/Editor/index.ts index e2327288..4f9a58ab 100644 --- a/src/components/Editor/index.ts +++ b/src/components/Editor/index.ts @@ -1,4 +1,4 @@ export { EditorComponent as Editor } from './Editor' export { Panel } from './Panel' -export { TopicSelect } from './TopicSelect' -export { UploadModalContent } from './UploadModalContent' +export { TopicSelect } from '../TopicSelect' +export { UploadModalContent } from '../Upload/UploadModalContent' diff --git a/src/components/Editor/TopicSelect/TopicSelect.module.scss b/src/components/TopicSelect/TopicSelect.module.scss similarity index 100% rename from src/components/Editor/TopicSelect/TopicSelect.module.scss rename to src/components/TopicSelect/TopicSelect.module.scss diff --git a/src/components/Editor/TopicSelect/TopicSelect.tsx b/src/components/TopicSelect/TopicSelect.tsx similarity index 100% rename from src/components/Editor/TopicSelect/TopicSelect.tsx rename to src/components/TopicSelect/TopicSelect.tsx diff --git a/src/components/Editor/TopicSelect/index.ts b/src/components/TopicSelect/index.ts similarity index 100% rename from src/components/Editor/TopicSelect/index.ts rename to src/components/TopicSelect/index.ts diff --git a/src/components/Editor/AudioUploader/AudioUploader.module.scss b/src/components/Upload/AudioUploader/AudioUploader.module.scss similarity index 100% rename from src/components/Editor/AudioUploader/AudioUploader.module.scss rename to src/components/Upload/AudioUploader/AudioUploader.module.scss diff --git a/src/components/Editor/AudioUploader/AudioUploader.tsx b/src/components/Upload/AudioUploader/AudioUploader.tsx similarity index 100% rename from src/components/Editor/AudioUploader/AudioUploader.tsx rename to src/components/Upload/AudioUploader/AudioUploader.tsx diff --git a/src/components/Editor/AudioUploader/index.ts b/src/components/Upload/AudioUploader/index.ts similarity index 100% rename from src/components/Editor/AudioUploader/index.ts rename to src/components/Upload/AudioUploader/index.ts diff --git a/src/components/Editor/UploadModalContent/UploadModalContent.module.scss b/src/components/Upload/UploadModalContent/UploadModalContent.module.scss similarity index 100% rename from src/components/Editor/UploadModalContent/UploadModalContent.module.scss rename to src/components/Upload/UploadModalContent/UploadModalContent.module.scss diff --git a/src/components/Editor/UploadModalContent/UploadModalContent.tsx b/src/components/Upload/UploadModalContent/UploadModalContent.tsx similarity index 98% rename from src/components/Editor/UploadModalContent/UploadModalContent.tsx rename to src/components/Upload/UploadModalContent/UploadModalContent.tsx index 31ddc7fe..69b9f01f 100644 --- a/src/components/Editor/UploadModalContent/UploadModalContent.tsx +++ b/src/components/Upload/UploadModalContent/UploadModalContent.tsx @@ -10,7 +10,7 @@ import { useSession } from '~/context/session' import { useUI } from '~/context/ui' import { handleImageUpload } from '~/lib/handleImageUpload' import { UploadedFile } from '~/types/upload' -import { InlineForm } from '../InlineForm' +import { InlineForm } from '../../_shared/InlineForm' import styles from './UploadModalContent.module.scss' diff --git a/src/components/Editor/UploadModalContent/index.ts b/src/components/Upload/UploadModalContent/index.ts similarity index 100% rename from src/components/Editor/UploadModalContent/index.ts rename to src/components/Upload/UploadModalContent/index.ts diff --git a/src/components/Editor/VideoUploader/VideoUploader.module.scss b/src/components/Upload/VideoUploader/VideoUploader.module.scss similarity index 100% rename from src/components/Editor/VideoUploader/VideoUploader.module.scss rename to src/components/Upload/VideoUploader/VideoUploader.module.scss diff --git a/src/components/Editor/VideoUploader/VideoUploader.tsx b/src/components/Upload/VideoUploader/VideoUploader.tsx similarity index 100% rename from src/components/Editor/VideoUploader/VideoUploader.tsx rename to src/components/Upload/VideoUploader/VideoUploader.tsx diff --git a/src/components/Editor/VideoUploader/index.ts b/src/components/Upload/VideoUploader/index.ts similarity index 100% rename from src/components/Editor/VideoUploader/index.ts rename to src/components/Upload/VideoUploader/index.ts diff --git a/src/components/Editor/renderUploadedImage.ts b/src/components/Upload/renderUploadedImage.ts similarity index 100% rename from src/components/Editor/renderUploadedImage.ts rename to src/components/Upload/renderUploadedImage.ts diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 0e22f777..25dd9d7c 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -21,14 +21,14 @@ import { LayoutType } from '~/types/common' import { MediaItem } from '~/types/mediaitem' import { clone } from '~/utils/clone' import { Editor as EditorComponent, Panel } from '../../Editor' -import { AudioUploader } from '../../Editor/AudioUploader' import { AutoSaveNotice } from '../../Editor/AutoSaveNotice' -import { VideoUploader } from '../../Editor/VideoUploader' +import { AudioUploader } from '../../Upload/AudioUploader' +import { VideoUploader } from '../../Upload/VideoUploader' import { Modal } from '../../_shared/Modal' import { TableOfContents } from '../../_shared/TableOfContents' import styles from './EditView.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) +const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor')) const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) type Props = { @@ -358,13 +358,10 @@ export const EditView = (props: Props) => { /> - handleInputChange('lead', value)} + content={form.lead} + onChange={(value: string) => handleInputChange('lead', value)} /> diff --git a/src/components/Views/Inbox/Inbox.tsx b/src/components/Views/Inbox/Inbox.tsx index 87dd02eb..ffc4e7c8 100644 --- a/src/components/Views/Inbox/Inbox.tsx +++ b/src/components/Views/Inbox/Inbox.tsx @@ -1,6 +1,6 @@ import { useNavigate } from '@solidjs/router' import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' +import { For, Show, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js' import QuotedMessage from '~/components/Inbox/QuotedMessage' import { Icon } from '~/components/_shared/Icon' import { InviteMembers } from '~/components/_shared/InviteMembers' @@ -17,7 +17,6 @@ import type { } from '~/graphql/schema/chat.gen' import type { Author } from '~/graphql/schema/core.gen' import { getShortDate } from '~/utils/date' -import SimplifiedEditor from '../../Editor/SimplifiedEditor' import DialogCard from '../../Inbox/DialogCard' import DialogHeader from '../../Inbox/DialogHeader' import { Message } from '../../Inbox/Message' @@ -26,6 +25,8 @@ import Search from '../../Inbox/Search' import { Modal } from '../../_shared/Modal' import styles from './Inbox.module.scss' +const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor')) + const userSearch = (array: Author[], keyword: string) => { return array.filter((value) => new RegExp(keyword.trim(), 'gi').test(value.name || '')) } @@ -38,7 +39,6 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => { const [sortByPerToPer, setSortByPerToPer] = createSignal(false) const [currentDialog, setCurrentDialog] = createSignal() const [messageToReply, setMessageToReply] = createSignal(null) - const [isClear, setClear] = createSignal(false) const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false) const { session } = useSession() const authorId = createMemo(() => session()?.user?.app_data?.profile?.id || 0) @@ -77,11 +77,9 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => { reply_to: messageToReply()?.id, chat_id: currentDialog()?.id || '' } as MutationCreate_MessageArgs) - setClear(true) setMessageToReply(null) if (messagesContainerRef) (messagesContainerRef as HTMLDivElement).scrollTop = messagesContainerRef?.scrollHeight || 0 - setClear(false) } createEffect( @@ -291,15 +289,7 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => { />
- handleSubmit(message)} - submitByCtrlEnter={true} - /> +
diff --git a/src/components/Views/Profile/ProfileSettings.tsx b/src/components/Views/Profile/ProfileSettings.tsx index daa0e22a..7cfab5d9 100644 --- a/src/components/Views/Profile/ProfileSettings.tsx +++ b/src/components/Views/Profile/ProfileSettings.tsx @@ -14,7 +14,6 @@ import { onMount } from 'solid-js' import { createStore } from 'solid-js/store' -import SimplifiedEditor from '~/components/Editor/SimplifiedEditor' import { useLocalize } from '~/context/localize' import { useProfile } from '~/context/profile' import { useSession } from '~/context/session' @@ -35,7 +34,7 @@ import { SocialNetworkInput } from '../../_shared/SocialNetworkInput' import styles from './Settings.module.scss' import { profileSocialLinks } from './profileSocialLinks' -// const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor')) +const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor')) const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) function filterNulls(arr: InputMaybe[]): string[] { @@ -340,18 +339,7 @@ export const ProfileSettings = () => { />

{t('About')}

- +

{t('Social networks')}

diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 77db6d1b..9c848a52 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -18,7 +18,7 @@ import { Modal } from '../../_shared/Modal' import stylesBeside from '../../Feed/Beside.module.scss' import styles from './PublishSettings.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) +const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor')) const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) const DESCRIPTION_MAX_LENGTH = 400 @@ -224,16 +224,10 @@ export const PublishSettings = (props: Props) => { allowEnterKey={false} maxLength={100} /> - - onChange={(value: any) => setForm('description', value)} - maxLength={DESCRIPTION_MAX_LENGTH} + content={composeDescription()} + onChange={(value?: string) => value && setForm('description', value)} />
diff --git a/src/components/Editor/InlineForm/InlineForm.module.scss b/src/components/_shared/InlineForm/InlineForm.module.scss similarity index 100% rename from src/components/Editor/InlineForm/InlineForm.module.scss rename to src/components/_shared/InlineForm/InlineForm.module.scss diff --git a/src/components/Editor/InlineForm/InlineForm.tsx b/src/components/_shared/InlineForm/InlineForm.tsx similarity index 100% rename from src/components/Editor/InlineForm/InlineForm.tsx rename to src/components/_shared/InlineForm/InlineForm.tsx diff --git a/src/components/Editor/InlineForm/index.ts b/src/components/_shared/InlineForm/index.ts similarity index 100% rename from src/components/Editor/InlineForm/index.ts rename to src/components/_shared/InlineForm/index.ts diff --git a/src/components/_shared/SolidSwiper/EditorSwiper.tsx b/src/components/_shared/SolidSwiper/EditorSwiper.tsx index c65af966..303086d7 100644 --- a/src/components/_shared/SolidSwiper/EditorSwiper.tsx +++ b/src/components/_shared/SolidSwiper/EditorSwiper.tsx @@ -3,7 +3,6 @@ import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, lazy, on, onMount } from 'solid-js' import SwiperCore from 'swiper' import { Manipulation, Navigation, Pagination } from 'swiper/modules' - import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' import { composeMediaItems } from '~/lib/composeMediaItems' @@ -23,7 +22,7 @@ import { MediaItem } from '~/types/mediaitem' import { UploadedFile } from '~/types/upload' import styles from './Swiper.module.scss' -const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) +const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor')) type Props = { images: MediaItem[] @@ -316,9 +315,8 @@ export const EditorSwiper = (props: Props) => { value={props.images[slideIndex()]?.source} onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)} /> - setSlideBody(value)} /> diff --git a/src/entry-client.tsx b/src/entry-client.tsx index a7df337b..540bffc0 100644 --- a/src/entry-client.tsx +++ b/src/entry-client.tsx @@ -9,3 +9,5 @@ mount(() => , document.getElementById('app') || document.body) // navigator.serviceWorker.register(`/sw.js`); // }); // } + +export default {}