editor-refactoring
This commit is contained in:
parent
b393810f7a
commit
595e2b8a4b
|
@ -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) => {
|
|||
}
|
||||
>
|
||||
<div class={styles.descriptionBlock}>
|
||||
<SimplifiedEditor
|
||||
initialContent={mi.body}
|
||||
<MicroEditor
|
||||
content={mi.body}
|
||||
placeholder={`${t('Description')}...`}
|
||||
smallHeight={true}
|
||||
onChange={(value) => handleMediaItemFieldChange('body', value)}
|
||||
/>
|
||||
<GrowingTextarea
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CommentDate } from '../CommentDate'
|
|||
import { CommentRatingControl } from '../CommentRatingControl'
|
||||
import styles from './Comment.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => 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<string>()
|
||||
const { session, client } = useSession()
|
||||
const author = createMemo<Author>(() => 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) => {
|
|||
<div class={styles.commentBody}>
|
||||
<Show when={editMode()} fallback={<div innerHTML={body()} />}>
|
||||
<Suspense fallback={<p>{t('Loading')}</p>}>
|
||||
<SimplifiedEditor
|
||||
initialContent={editedBody() || props.comment.body || ''}
|
||||
submitButtonText={t('Save')}
|
||||
quoteEnabled={true}
|
||||
imageEnabled={true}
|
||||
<MiniEditor
|
||||
content={editedBody() || props.comment.body || ''}
|
||||
placeholder={t('Write a comment...')}
|
||||
onSubmit={(value) => handleUpdate(value)}
|
||||
submitByCtrlEnter={true}
|
||||
onCancel={() => setEditMode(false)}
|
||||
setClear={clearEditor()}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
@ -258,12 +250,9 @@ export const Comment = (props: Props) => {
|
|||
|
||||
<Show when={isReplyVisible() && props.clickedReplyId === props.comment.id}>
|
||||
<Suspense fallback={<p>{t('Loading')}</p>}>
|
||||
<SimplifiedEditor
|
||||
quoteEnabled={true}
|
||||
imageEnabled={true}
|
||||
<MiniEditor
|
||||
placeholder={t('Write a comment...')}
|
||||
onSubmit={(value) => handleCreate(value)}
|
||||
submitByCtrlEnter={true}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
|
|
@ -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>(ReactionSort.Newest)
|
||||
const [onlyNew, setOnlyNew] = createSignal(false)
|
||||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||
const [clearEditor, setClearEditor] = createSignal(false)
|
||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||
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) => {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<SimplifiedEditor
|
||||
quoteEnabled={true}
|
||||
imageEnabled={true}
|
||||
autoFocus={false}
|
||||
submitByCtrlEnter={true}
|
||||
<MiniEditor
|
||||
placeholder={t('Write a comment...')}
|
||||
onSubmit={(value) => handleSubmitComment(value)}
|
||||
setClear={clearEditor()}
|
||||
isPosting={posting()}
|
||||
/>
|
||||
<Show when={posting()}>
|
||||
<Loading />
|
||||
</Show>
|
||||
</ShowIfAuthenticated>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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<ShoutForm>({
|
||||
body: '',
|
||||
slug: '',
|
||||
shoutId: 0,
|
||||
title: '',
|
||||
selectedTopics: []
|
||||
})
|
||||
const [_formErrors, setFormErrors] = createStore({} as Record<keyof ShoutForm, string>)
|
||||
const [editor, setEditor] = createSignal<Editor | undefined>()
|
||||
|
||||
const mockEditorContext: EditorContextType = {
|
||||
countWords: () => 0,
|
||||
isEditorPanelVisible: () => false,
|
||||
wordCounter: () => ({ characters: 0, words: 0 }),
|
||||
form: _form,
|
||||
formErrors: _formErrors,
|
||||
createEditor: (opts?: Partial<EditorOptions>) => {
|
||||
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<boolean> => true,
|
||||
toggleEditorPanel: () => {
|
||||
// Simulate toggle
|
||||
},
|
||||
setForm,
|
||||
setFormErrors
|
||||
}
|
||||
|
||||
const mockSnackbarContext = {
|
||||
showSnackbar: console.log
|
||||
}
|
||||
import { EditorComponent } from './Editor'
|
||||
|
||||
const meta: Meta<typeof EditorComponent> = {
|
||||
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<typeof EditorComponent>
|
||||
|
||||
export const Default: Story = {
|
||||
render: (props: EditorComponentProps) => {
|
||||
const [_content, setContent] = createSignal(props.initialContent || '')
|
||||
|
||||
return (
|
||||
<SessionContext.Provider value={mockSession as SessionContextType}>
|
||||
<LocalizeContext.Provider value={mockLocalize as LocalizeContextType}>
|
||||
<SnackbarContext.Provider value={mockSnackbarContext as SnackbarContextType}>
|
||||
<EditorContext.Provider value={mockEditorContext as EditorContextType}>
|
||||
<EditorComponent
|
||||
{...props}
|
||||
onChange={(text: string) => {
|
||||
props.onChange(text)
|
||||
setContent(text)
|
||||
}}
|
||||
/>
|
||||
</EditorContext.Provider>
|
||||
</SnackbarContext.Provider>
|
||||
</LocalizeContext.Provider>
|
||||
</SessionContext.Provider>
|
||||
)
|
||||
},
|
||||
args: {
|
||||
shoutId: 1,
|
||||
initialContent: '',
|
||||
disableCollaboration: true
|
||||
content: '',
|
||||
limit: 500,
|
||||
placeholder: 'Start typing here...'
|
||||
}
|
||||
}
|
||||
|
||||
export const WithInitialContent: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
initialContent: '<p>This is some initial content in the editor.</p>'
|
||||
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...'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
113
src/components/Editor/EditorToolbar/MicroToolbar.tsx
Normal file
113
src/components/Editor/EditorToolbar/MicroToolbar.tsx
Normal file
|
@ -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<Editor['state']['selection']>()
|
||||
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 (
|
||||
<Show when={props.editor} keyed>
|
||||
{(instance) => (
|
||||
<Show when={!showSimpleMenu()}>
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
background: 'var(--editor-bubble-menu-background)',
|
||||
border: '1px solid black'
|
||||
}}
|
||||
>
|
||||
<div class={styles.controls}>
|
||||
<div class={styles.actions}>
|
||||
<Control
|
||||
key="bold"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBold().run()}
|
||||
title={t('Bold')}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</Control>
|
||||
<Control
|
||||
key="italic"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleItalic().run()}
|
||||
title={t('Italic')}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</Control>
|
||||
<Control
|
||||
key="link"
|
||||
editor={instance}
|
||||
onChange={toggleShowLink}
|
||||
title={t('Add url')}
|
||||
isActive={showLinkInput}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</Control>
|
||||
</div>
|
||||
<Show when={showLinkInput()}>
|
||||
<InsertLinkForm editor={instance} onClose={toggleShowLink} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
)
|
||||
}
|
117
src/components/Editor/EditorToolbar/MiniToolbar.tsx
Normal file
117
src/components/Editor/EditorToolbar/MiniToolbar.tsx
Normal file
|
@ -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<Editor['state']['selection']>()
|
||||
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 (
|
||||
<div style={{ 'background-color': 'white', display: 'inline-flex' }}>
|
||||
<Show when={props.editor} keyed>
|
||||
{(instance) => (
|
||||
<div class={styles.controls}>
|
||||
<div class={styles.actions}>
|
||||
<Control
|
||||
key="bold"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBold().run()}
|
||||
title={t('Bold')}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</Control>
|
||||
<Control
|
||||
key="italic"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleItalic().run()}
|
||||
title={t('Italic')}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</Control>
|
||||
<Control
|
||||
key="link"
|
||||
editor={instance}
|
||||
onChange={toggleShowLink}
|
||||
title={t('Add url')}
|
||||
isActive={showLinkInput}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</Control>
|
||||
<Control
|
||||
key="blockquote"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBlockquote().run()}
|
||||
title={t('Add blockquote')}
|
||||
>
|
||||
<Icon name="editor-quote" />
|
||||
</Control>
|
||||
<Control
|
||||
key="image"
|
||||
editor={instance}
|
||||
onChange={() => showModal('simplifiedEditorUploadImage')}
|
||||
title={t('Add image')}
|
||||
>
|
||||
<Icon name="editor-image-dd-full" />
|
||||
</Control>
|
||||
</div>
|
||||
<Show when={showLinkInput()}>
|
||||
<InsertLinkForm editor={instance} onClose={toggleShowLink} />
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -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 }
|
40
src/components/Editor/EditorToolbar/ToolbarControl.tsx
Normal file
40
src/components/Editor/EditorToolbar/ToolbarControl.tsx
Normal file
|
@ -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 (
|
||||
<Popover content={props.title}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: props.editor.isActive(props.key) })}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolbarControl
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
<Popover content={props.title}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: props.editor.isActive(props.key) })}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
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<HTMLDivElement>()
|
||||
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
||||
const [showSimpleMenu, setShowSimpleMenu] = createSignal(false)
|
||||
const [toolbarElement, setToolbarElement] = createSignal<HTMLElement>()
|
||||
|
||||
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<Editor['state']['selection']>()
|
||||
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 (
|
||||
<div
|
||||
|
@ -116,61 +43,9 @@ export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
|
|||
})}
|
||||
>
|
||||
<div>
|
||||
<Show when={editor()} keyed>
|
||||
{(instance) => (
|
||||
<Show when={showSimpleMenu()}>
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
background: 'var(--editor-bubble-menu-background)',
|
||||
border: '1px solid black'
|
||||
}}
|
||||
ref={setToolbarElement}
|
||||
>
|
||||
<div class={styles.controls}>
|
||||
<div class={styles.actions}>
|
||||
<Control
|
||||
key="bold"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBold().run()}
|
||||
title={t('Bold')}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</Control>
|
||||
<Control
|
||||
key="italic"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleItalic().run()}
|
||||
title={t('Italic')}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</Control>
|
||||
<Control
|
||||
key="link"
|
||||
editor={instance}
|
||||
onChange={() => setShowLinkInput(!showLinkInput())}
|
||||
title={t('Add url')}
|
||||
isActive={showLinkInput}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</Control>
|
||||
</div>
|
||||
<Show when={showLinkInput()}>
|
||||
<InsertLinkForm
|
||||
editor={instance}
|
||||
onClose={() => {
|
||||
setShowLinkInput(false)
|
||||
recoverSelection()
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
<MicroToolbar />
|
||||
|
||||
<div id="micro-editor" ref={setEditorElement} style={styles.minimal} onFocusOut={storeSelection} />
|
||||
<div id="micro-editor" ref={setEditorElement} style={styles.minimal} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -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 (
|
||||
<Popover content={props.title}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: props.editor.isActive(props.key) })}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
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<HTMLDivElement>()
|
||||
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<HTMLElement>()
|
||||
// Attach the event handler to the toolbar
|
||||
onCleanup(() => {
|
||||
toolbarElement()?.removeEventListener('mousedown', handleMouseDownOnToolbar)
|
||||
})
|
||||
return (
|
||||
<div class={clsx(styles.SimplifiedEditor, styles.bordered, styles.isFocused)}>
|
||||
<div class={clsx(styles.SimplifiedEditor, styles.bordered, { [styles.isFocused]: isFocused() })}>
|
||||
<div>
|
||||
<div id="mini-editor" ref={setEditorElement} />
|
||||
|
||||
<Toolbar
|
||||
style={{ 'background-color': 'white', display: 'inline-flex' }}
|
||||
ref={setToolbarElement}
|
||||
horizontal
|
||||
>
|
||||
<Show when={editor()} keyed>
|
||||
{(instance) => (
|
||||
<div class={styles.controls}>
|
||||
<Show
|
||||
when={!showLinkInput()}
|
||||
fallback={<InsertLinkForm editor={instance} onClose={() => setShowLinkInput(false)} />}
|
||||
>
|
||||
<div class={styles.actions}>
|
||||
<Control
|
||||
key="bold"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBold().run()}
|
||||
title={t('Bold')}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</Control>
|
||||
<Control
|
||||
key="italic"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleItalic().run()}
|
||||
title={t('Italic')}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</Control>
|
||||
<Control
|
||||
key="link"
|
||||
editor={instance}
|
||||
onChange={handleLinkClick}
|
||||
title={t('Add url')}
|
||||
isActive={showLinkInput}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</Control>
|
||||
<Control
|
||||
key="blockquote"
|
||||
editor={instance}
|
||||
onChange={() => instance.chain().focus().toggleBlockquote().run()}
|
||||
title={t('Add blockquote')}
|
||||
>
|
||||
<Icon name="editor-quote" />
|
||||
</Control>
|
||||
<Control
|
||||
key="image"
|
||||
editor={instance}
|
||||
onChange={() => showModal('simplifiedEditorUploadImage')}
|
||||
title={t('Add image')}
|
||||
>
|
||||
<Icon name="editor-image-dd-full" />
|
||||
</Control>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</Toolbar>
|
||||
<MiniToolbar />
|
||||
|
||||
<Show when={counter() > 0}>
|
||||
<small class={styles.limit}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
|||
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
|
||||
</Match>
|
||||
<Match when={footnoteEditorOpen()}>
|
||||
<SimplifiedEditor
|
||||
maxHeight={180}
|
||||
controlsAlwaysVisible={true}
|
||||
imageEnabled={true}
|
||||
<MiniEditor
|
||||
placeholder={t('Enter footnote text')}
|
||||
onSubmit={(value) => handleAddFootnote(value)}
|
||||
variant={'bordered'}
|
||||
initialContent={footNote()}
|
||||
onSubmit={(value: string) => handleAddFootnote(value)}
|
||||
content={footNote()}
|
||||
onCancel={() => {
|
||||
setFootnoteEditorOpen(false)
|
||||
}}
|
||||
submitButtonText={t('Send')}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
@ -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) => {
|
|||
/>
|
||||
</Show>
|
||||
<Show when={isLeadVisible()}>
|
||||
<SimplifiedEditor
|
||||
variant="minimal"
|
||||
hideToolbar={true}
|
||||
smallHeight={true}
|
||||
<MicroEditor
|
||||
placeholder={t('A short introduction to keep the reader interested')}
|
||||
initialContent={form.lead}
|
||||
onChange={(value) => handleInputChange('lead', value)}
|
||||
content={form.lead}
|
||||
onChange={(value: string) => handleInputChange('lead', value)}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
|
|
|
@ -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<Chat>()
|
||||
const [messageToReply, setMessageToReply] = createSignal<MessageType | null>(null)
|
||||
const [isClear, setClear] = createSignal(false)
|
||||
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
|
||||
const { session } = useSession()
|
||||
const authorId = createMemo<number>(() => 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 }) => {
|
|||
/>
|
||||
</Show>
|
||||
<div class={styles.wrapper}>
|
||||
<SimplifiedEditor
|
||||
smallHeight={true}
|
||||
imageEnabled={true}
|
||||
isCancelButtonVisible={false}
|
||||
placeholder={t('New message')}
|
||||
setClear={isClear()}
|
||||
onSubmit={(message) => handleSubmit(message)}
|
||||
submitByCtrlEnter={true}
|
||||
/>
|
||||
<MiniEditor placeholder={t('New message')} onSubmit={handleSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -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>[]): string[] {
|
||||
|
@ -340,18 +339,7 @@ export const ProfileSettings = () => {
|
|||
/>
|
||||
|
||||
<h4>{t('About')}</h4>
|
||||
<SimplifiedEditor
|
||||
resetToInitial={true}
|
||||
noLimits={true}
|
||||
variant="bordered"
|
||||
hideToolbar={true}
|
||||
smallHeight={true}
|
||||
label={t('About')}
|
||||
initialContent={about() || ''}
|
||||
autoFocus={false}
|
||||
onChange={setAbout}
|
||||
placeholder={t('About')}
|
||||
/>
|
||||
<MicroEditor content={about() || ''} onChange={setAbout} placeholder={t('About')} />
|
||||
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
|
||||
<div class={styles.multipleControlsHeader}>
|
||||
<h4>{t('Social networks')}</h4>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
<SimplifiedEditor
|
||||
variant="bordered"
|
||||
hideToolbar={true}
|
||||
smallHeight={true}
|
||||
<MicroEditor
|
||||
placeholder={t('Write a short introduction')}
|
||||
label={t('Description')}
|
||||
initialContent={composeDescription()}
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
onChange={(value: any) => setForm('description', value)}
|
||||
maxLength={DESCRIPTION_MAX_LENGTH}
|
||||
content={composeDescription()}
|
||||
onChange={(value?: string) => value && setForm('description', value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
<SimplifiedEditor
|
||||
initialContent={props.images[slideIndex()]?.body}
|
||||
smallHeight={true}
|
||||
<MicroEditor
|
||||
content={props.images[slideIndex()]?.body}
|
||||
placeholder={t('Enter image description')}
|
||||
onChange={(value) => setSlideBody(value)}
|
||||
/>
|
||||
|
|
|
@ -9,3 +9,5 @@ mount(() => <StartClient />, document.getElementById('app') || document.body)
|
|||
// navigator.serviceWorker.register(`/sw.js`);
|
||||
// });
|
||||
// }
|
||||
|
||||
export default {}
|
||||
|
|
Loading…
Reference in New Issue
Block a user