From 2fad5b8db94de8021f664b44e223a77d3aa5166c Mon Sep 17 00:00:00 2001
From: Untone
Date: Sun, 15 Sep 2024 21:47:21 +0300
Subject: [PATCH] bad update 2
---
.../Editor/MiniEditor/MiniEditor.stories.tsx | 64 ++++++
.../Editor/MiniEditor/MiniEditor.tsx | 191 ++++++++++++++++++
.../Feed/ArticleCard/ArticleCard.tsx | 6 +-
.../FeedArticlePopup/FeedArticlePopup.tsx | 11 +-
src/components/Topic/Full.tsx | 4 +-
.../Views/AllAuthors/AllAuthors.tsx | 2 +-
.../Views/Profile/ProfileSecurity.tsx | 2 +-
.../Views/Profile/ProfileSettings.tsx | 55 +++--
src/context/following.tsx | 21 +-
src/lib/editorOptions.ts | 103 ++++++++++
10 files changed, 404 insertions(+), 55 deletions(-)
create mode 100644 src/components/Editor/MiniEditor/MiniEditor.stories.tsx
create mode 100644 src/components/Editor/MiniEditor/MiniEditor.tsx
create mode 100644 src/lib/editorOptions.ts
diff --git a/src/components/Editor/MiniEditor/MiniEditor.stories.tsx b/src/components/Editor/MiniEditor/MiniEditor.stories.tsx
new file mode 100644
index 00000000..80e227de
--- /dev/null
+++ b/src/components/Editor/MiniEditor/MiniEditor.stories.tsx
@@ -0,0 +1,64 @@
+import { Meta, StoryObj } from 'storybook-solidjs'
+import MiniEditor from './MiniEditor'
+
+const meta: Meta = {
+ title: 'Components/MiniEditor',
+ component: MiniEditor,
+ argTypes: {
+ content: {
+ control: 'text',
+ description: 'Initial content for the editor',
+ defaultValue: ''
+ },
+ limit: {
+ control: 'number',
+ description: 'Character limit for the editor',
+ defaultValue: 500
+ },
+ placeholder: {
+ control: 'text',
+ description: 'Placeholder text when the editor is empty',
+ defaultValue: 'Start typing here...'
+ },
+ onChange: {
+ action: 'changed',
+ description: 'Callback when the content changes'
+ }
+ }
+}
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ content: '',
+ limit: 500,
+ placeholder: 'Start typing here...'
+ }
+}
+
+export const WithInitialContent: Story = {
+ args: {
+ 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/MiniEditor/MiniEditor.tsx b/src/components/Editor/MiniEditor/MiniEditor.tsx
new file mode 100644
index 00000000..26c86482
--- /dev/null
+++ b/src/components/Editor/MiniEditor/MiniEditor.tsx
@@ -0,0 +1,191 @@
+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, onCleanup } from 'solid-js'
+import {
+ createEditorTransaction,
+ createTiptapEditor,
+ useEditorHTML,
+ useEditorIsEmpty,
+ useEditorIsFocused
+} from 'solid-tiptap'
+import { Toolbar } from 'terracotta'
+
+import { useLocalize } from '~/context/localize'
+import { useUI } from '~/context/ui'
+import { base, custom } from '~/lib/editorOptions'
+import { Icon } from '../_shared/Icon/Icon'
+import { Popover } from '../_shared/Popover/Popover'
+import { InsertLinkForm } from './InsertLinkForm/InsertLinkForm'
+
+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
+ limit?: number
+ placeholder?: string
+}
+
+export default function MiniEditor(props: MiniEditorProps): JSX.Element {
+ const [editorElement, setEditorElement] = createSignal()
+ const [counter, setCounter] = createSignal(0)
+ const [showLinkInput, setShowLinkInput] = createSignal(false)
+ const [showSimpleMenu, setShowSimpleMenu] = createSignal(false)
+ const { t } = useLocalize()
+ const { showModal } = useUI()
+
+ const editor = createTiptapEditor(() => ({
+ element: editorElement()!,
+ extensions: [
+ ...base,
+ ...custom,
+ Placeholder.configure({ emptyNodeClass: styles.emptyNode, placeholder: props.placeholder }),
+ CharacterCount.configure({ limit: props.limit })
+ ],
+ editorProps: {
+ attributes: {
+ class: styles.simplifiedEditorField
+ }
+ },
+ content: props.content || ''
+ }))
+
+ const isEmpty = useEditorIsEmpty(editor)
+ const isFocused = useEditorIsFocused(editor)
+ const isTextSelection = createEditorTransaction(editor, (instance) => !instance?.state.selection.empty)
+ const html = useEditorHTML(editor)
+
+ createEffect(() => setShowSimpleMenu(isTextSelection()))
+
+ createEffect(() => {
+ const textLength = editor()?.getText().length || 0
+ setCounter(textLength)
+ const content = html()
+ content && props.onChange?.(content)
+ })
+
+ const handleLinkClick = () => {
+ setShowLinkInput(!showLinkInput())
+ editor()?.chain().focus().run()
+ }
+
+ // 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}>
+
+ {counter()} / {props.limit || '∞'}
+
+
+
+
+ )
+}
diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx
index 635fe7d2..28e953f6 100644
--- a/src/components/Feed/ArticleCard/ArticleCard.tsx
+++ b/src/components/Feed/ArticleCard/ArticleCard.tsx
@@ -55,7 +55,7 @@ const desktopCoverImageWidths: Record = {
M: 600,
L: 800
}
-
+const titleSeparator = /{!|\?|:|;}\s/
const getTitleAndSubtitle = (
article: Shout
): {
@@ -69,7 +69,7 @@ const getTitleAndSubtitle = (
let titleParts = article.title?.split('. ') || []
if (titleParts?.length === 1) {
- titleParts = article.title?.split(/{!|\?|:|;}\s/) || []
+ titleParts = article.title?.split(titleSeparator) || []
}
if (titleParts && titleParts.length > 1) {
@@ -88,7 +88,7 @@ const getMainTopicTitle = (article: Shout, lng: string) => {
const mainTopicSlug = article.main_topic || ''
const mainTopic = (article.topics || []).find((tpc: Maybe) => tpc?.slug === mainTopicSlug)
const mainTopicTitle =
- mainTopicSlug && lng === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
+ mainTopicSlug && lng === 'en' ? mainTopicSlug.replaceAll('-', ' ') : mainTopic?.title || ''
return [mainTopicTitle, mainTopicSlug]
}
diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx
index 1aaf587b..d155780b 100644
--- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx
+++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx
@@ -33,7 +33,6 @@ export const FeedArticlePopup = (props: Props) => {