diff --git a/README.en.md b/README.en.md
index 4cefb21f..f428df7e 100644
--- a/README.en.md
+++ b/README.en.md
@@ -42,12 +42,8 @@ bun run e2e:tests # Run tests
bun run e2e:tests:ci # Run tests in CI
```
-Structure:
-- `/tests/*`: Tests without authentication
-- `/tests-with-auth/*`: Tests with authentication
-
## CI/CD
Tests are executed in GitHub Actions. Make sure `BASE_URL` is correctly configured in CI.
-## Project version: 0.9.7
+## Version: 0.9.7
diff --git a/README.md b/README.md
index a0e885a5..e0bbf1d0 100644
--- a/README.md
+++ b/README.md
@@ -43,12 +43,8 @@ bun run e2e:tests # Запуск тестов
bun run e2e:tests:ci # Запуск тестов в CI
```
-Структура:
-- `/tests/*`: Тесты без аутентификации
-- `/tests-with-auth/*`: Тесты с аутентификацией
-
## CI/CD
Тесты выполняются в GitHub Actions. Убедитесь, что `BASE_URL` корректно настроен в CI.
-## Версия проекта: 0.9.7
\ No newline at end of file
+## Версия: 0.9.7
\ No newline at end of file
diff --git a/src/components/Draft/Draft.tsx b/src/components/Draft/Draft.tsx
index c1a3262f..2cda102e 100644
--- a/src/components/Draft/Draft.tsx
+++ b/src/components/Draft/Draft.tsx
@@ -53,7 +53,7 @@ export const Draft = (props: Props) => {
{props.shout.title || t('Unnamed draft')} {props.shout.subtitle}
-
+
{t('Edit')}
diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx
index 082ad00c..018fec27 100644
--- a/src/components/Editor/Editor.tsx
+++ b/src/components/Editor/Editor.tsx
@@ -1,6 +1,6 @@
import { HocuspocusProvider } from '@hocuspocus/provider'
import { UploadFile } from '@solid-primitives/upload'
-import { Editor, EditorOptions } from '@tiptap/core'
+import { Editor, EditorOptions, isTextSelection } from '@tiptap/core'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { CharacterCount } from '@tiptap/extension-character-count'
import { Collaboration } from '@tiptap/extension-collaboration'
@@ -43,13 +43,8 @@ export const EditorComponent = (props: EditorComponentProps) => {
const { t } = useLocalize()
const { session, requireAuthentication } = useSession()
const author = createMemo(() => session()?.user?.app_data?.profile as Author)
- const [isCommonMarkup, _setIsCommonMarkup] = createSignal(false)
- const createMenuSignal = () => createSignal(false)
- const [shouldShowTextBubbleMenu, _setShouldShowTextBubbleMenu] = createMenuSignal()
- const [shouldShowBlockquoteBubbleMenu, _setShouldShowBlockquoteBubbleMenu] = createMenuSignal()
- const [shouldShowFigureBubbleMenu, _setShouldShowFigureBubbleMenu] = createMenuSignal()
- const [shouldShowIncutBubbleMenu, _setShouldShowIncutBubbleMenu] = createMenuSignal()
- const [shouldShowFloatingMenu, _setShouldShowFloatingMenu] = createMenuSignal()
+ const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
+ const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
const { showSnackbar } = useSnackbar()
const { countWords, setEditing } = useEditorContext()
const [editorOptions, setEditorOptions] = createSignal>({})
@@ -180,14 +175,8 @@ export const EditorComponent = (props: EditorComponentProps) => {
requireAuthentication(() => {
setTimeout(() => {
setupEditor()
-
- // Создаем экземпляр редактора после монтирования
createEditorInstance(editorOptions())
-
- // Инициализируем меню после создания редактора
- if (editor()) {
- initializeMenus()
- }
+ initializeMenus()
}, 1200)
}, 'edit')
})
@@ -196,30 +185,80 @@ export const EditorComponent = (props: EditorComponentProps) => {
if (menusInitialized() || !editor()) return
if (blockquoteBubbleMenuRef() && figureBubbleMenuRef() && incutBubbleMenuRef() && floatingMenuRef()) {
console.log('stage 3: initialize menus when editor instance is ready')
- const menuConfigs = [
- { key: 'textBubbleMenu', ref: textBubbleMenuRef, shouldShow: shouldShowTextBubbleMenu },
- {
- key: 'blockquoteBubbleMenu',
- ref: blockquoteBubbleMenuRef,
- shouldShow: shouldShowBlockquoteBubbleMenu
- },
- { key: 'figureBubbleMenu', ref: figureBubbleMenuRef, shouldShow: shouldShowFigureBubbleMenu },
- { key: 'incutBubbleMenu', ref: incutBubbleMenuRef, shouldShow: shouldShowIncutBubbleMenu },
- { key: 'floatingMenu', ref: floatingMenuRef, shouldShow: shouldShowFloatingMenu, isFloating: true }
+ const menus = [
+ BubbleMenu.configure({
+ pluginKey: 'textBubbleMenu',
+ element: textBubbleMenuRef()!,
+ shouldShow: ({ editor: e, view, state: { doc, selection }, from, to }) => {
+ const isEmptyTextBlock =
+ doc.textBetween(from, to).length === 0 && isTextSelection(selection)
+ if (isEmptyTextBlock) {
+ e?.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
+ }
+ const hasSelection = !selection.empty && from !== to
+ const isFootnoteOrFigcaption = e.isActive('footnote') || (e.isActive('figcaption') && hasSelection)
+
+ setIsCommonMarkup(e?.isActive('figcaption'))
+
+ const result = view.hasFocus() &&
+ hasSelection &&
+ !e.isActive('image') &&
+ !e.isActive('figure') &&
+ (isFootnoteOrFigcaption || !e.isActive('figcaption'))
+
+ setShouldShowTextBubbleMenu(result)
+ return result
+ },
+ tippyOptions: {
+ sticky: true,
+ // onHide: () => { editor()?.commands.focus() }
+ }
+ }),
+ BubbleMenu.configure({
+ pluginKey: 'blockquoteBubbleMenu',
+ element: blockquoteBubbleMenuRef()!,
+ shouldShow: ({ editor: e, state: { selection } }) => e.isFocused && !selection.empty && e.isActive('blockquote'),
+ tippyOptions: {
+ offset: [0, 0],
+ placement: 'top',
+ getReferenceClientRect: () => {
+ const selectedElement = editor()?.view.dom.querySelector('.has-focus')
+ return selectedElement?.getBoundingClientRect() || new DOMRect()
+ }
+ }
+ }),
+ BubbleMenu.configure({
+ pluginKey: 'figureBubbleMenu',
+ element: figureBubbleMenuRef()!,
+ shouldShow: ({ editor: e, view }) => view.hasFocus() && e.isActive('figure')
+ }),
+ BubbleMenu.configure({
+ pluginKey: 'incutBubbleMenu',
+ element: incutBubbleMenuRef()!,
+ shouldShow: ({ editor: e, state: { selection } }) => e.isFocused && !selection.empty && e.isActive('figcaption'),
+ tippyOptions: {
+ offset: [0, -16],
+ placement: 'top',
+ getReferenceClientRect: () => {
+ const selectedElement = editor()?.view.dom.querySelector('.has-focus')
+ return selectedElement?.getBoundingClientRect() || new DOMRect()
+ },
+ },
+ }),
+ FloatingMenu.configure({
+ element: floatingMenuRef()!,
+ pluginKey: 'floatingMenu',
+ shouldShow: ({ editor: e, state: { selection } }) => {
+ const { $anchor, empty } = selection
+ const isRootDepth = $anchor.depth === 1
+ if (!(isRootDepth && empty)) return false
+ return !(e.isActive('codeBlock') || e.isActive('heading'))
+ },
+ tippyOptions: {
+ placement: 'left',
+ }
+ })
]
- const menus = menuConfigs.map((config) =>
- config.isFloating
- ? FloatingMenu.configure({
- pluginKey: config.key,
- element: config.ref(),
- shouldShow: config.shouldShow
- })
- : BubbleMenu.configure({
- pluginKey: config.key,
- element: config.ref(),
- shouldShow: config.shouldShow
- })
- )
setEditorOptions((prev) => ({ ...prev, extensions: [...(prev.extensions || []), ...menus] }))
setMenusInitialized(true)
} else {
@@ -311,12 +350,12 @@ export const EditorComponent = (props: EditorComponentProps) => {
{(ed: Editor) => (
<>
-
+
@@ -338,4 +377,4 @@ export const EditorComponent = (props: EditorComponentProps) => {
>
)
-}
+}
\ No newline at end of file
diff --git a/src/components/Editor/MiniEditor.tsx b/src/components/Editor/MiniEditor.tsx
index 6fe5d0aa..35a19ad7 100644
--- a/src/components/Editor/MiniEditor.tsx
+++ b/src/components/Editor/MiniEditor.tsx
@@ -162,11 +162,14 @@ export function MiniEditor(props: MiniEditorProps): JSX.Element {
editor()?.commands.clearContent()}
+ onClick={() => {
+ editor()?.commands.clearContent()
+ props.onCancel?.()
+ }
+ }
/>
-
+
0}>
diff --git a/src/components/Editor/Toolbar/TextBubbleMenu.tsx b/src/components/Editor/Toolbar/TextBubbleMenu.tsx
index 188de1e3..6fa0c596 100644
--- a/src/components/Editor/Toolbar/TextBubbleMenu.tsx
+++ b/src/components/Editor/Toolbar/TextBubbleMenu.tsx
@@ -16,11 +16,10 @@ import { Icon } from '~/components/_shared/Icon'
import { Popover } from '~/components/_shared/Popover'
import { useLocalize } from '~/context/localize'
import { InsertLinkForm } from './InsertLinkForm'
+import { MiniEditor } from '../MiniEditor'
import styles from './TextBubbleMenu.module.scss'
-const MiniEditor = lazy(() => import('../MiniEditor'))
-
type BubbleMenuProps = {
editor: Editor
isCommonMarkup: boolean
diff --git a/src/components/Inbox/DialogCard.module.scss b/src/components/Inbox/DialogCard.module.scss
index 447f4cee..38c8fff0 100644
--- a/src/components/Inbox/DialogCard.module.scss
+++ b/src/components/Inbox/DialogCard.module.scss
@@ -77,7 +77,7 @@
.name,
.message,
.time {
- color: #fff !important;
+ color: var(--default-color-invert) !important;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/components/_shared/LoadMoreWrapper.module.scss b/src/components/_shared/LoadMoreWrapper.module.scss
new file mode 100644
index 00000000..00524916
--- /dev/null
+++ b/src/components/_shared/LoadMoreWrapper.module.scss
@@ -0,0 +1,8 @@
+
+.loadMoreWrapper {
+ text-align: center;
+
+ button {
+ padding: 0.6em 1.5em;
+ }
+ }
\ No newline at end of file
diff --git a/src/components/_shared/LoadMoreWrapper.tsx b/src/components/_shared/LoadMoreWrapper.tsx
index 5625e077..5f69aa72 100644
--- a/src/components/_shared/LoadMoreWrapper.tsx
+++ b/src/components/_shared/LoadMoreWrapper.tsx
@@ -6,6 +6,8 @@ import { SortFunction } from '~/types/common'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { byCreated } from '~/utils/sort'
+import styles from './LoadMoreWrapper.module.scss'
+
export type LoadMoreItems = Shout[] | Author[] | Reaction[]
type LoadMoreProps = {
@@ -58,7 +60,7 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => {
{props.children}