+
{'/'}
+
+
- {
- setFocused(true);
- }}
- onBlur={() => {
- setFocused(false);
- }}
- onKeyDown={async (e) => {
- if (e.key === 'Enter') {
- void handlePublish();
- }
- }}
- size={'small'}
- value={publishName}
- onChange={e => {
- setPublishName(e.target.value);
- }}
- className={'flex-1 truncate'}
- />
+
+ {
+ setFocused(true);
+ }}
+ onBlur={() => {
+ setFocused(false);
+ }}
+ onKeyDown={async (e) => {
+ if (e.key === 'Enter') {
+ void handlePublish();
+ }
+ }}
+ size={'small'}
+ value={publishName}
+ onChange={e => {
+ setPublishName(e.target.value);
+ }}
+ className={'flex-1 truncate'}
+ />
+
{(isOwner || isPublisher) &&
{
+ await copyTextToClipboard(url);
+ notify.success(t('shareAction.copyLinkSuccess'));
+ }}
color={'inherit'}
size={'small'}
>
diff --git a/src/components/app/share/PublishPanel.tsx b/src/components/app/share/PublishPanel.tsx
index 9af091bc..cb68e054 100644
--- a/src/components/app/share/PublishPanel.tsx
+++ b/src/components/app/share/PublishPanel.tsx
@@ -3,11 +3,11 @@ import { useAppHandlers } from '@/components/app/app.hooks';
import { useLoadPublishInfo } from '@/components/app/share/publish.hooks';
import PublishLinkPreview from '@/components/app/share/PublishLinkPreview';
import { Button, CircularProgress, Typography } from '@mui/material';
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as PublishIcon } from '@/assets/publish.svg';
-function PublishPanel ({ viewId, onClose }: { viewId: string; onClose: () => void }) {
+function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: () => void; opened: boolean }) {
const { t } = useTranslation();
const {
publish,
@@ -22,27 +22,40 @@ function PublishPanel ({ viewId, onClose }: { viewId: string; onClose: () => voi
isOwner,
isPublisher,
} = useLoadPublishInfo(viewId);
+ const [unpublishLoading, setUnpublishLoading] = React.useState(false);
+ const [publishLoading, setPublishLoading] = React.useState(false);
+
+ useEffect(() => {
+ if (opened) {
+ void loadPublishInfo();
+ }
+ }, [loadPublishInfo, opened]);
const handlePublish = useCallback(async (publishName?: string) => {
if (!publish || !view) return;
+ setPublishLoading(true);
try {
- await publish(view, publishName);
- void loadPublishInfo();
+ await publish(view, publishName || publishInfo?.publishName);
+ await loadPublishInfo();
notify.success(t('publish.publishSuccessfully'));
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
+ } finally {
+ setPublishLoading(false);
}
- }, [loadPublishInfo, publish, t, view]);
+ }, [loadPublishInfo, publish, t, view, publishInfo]);
const handleUnpublish = useCallback(async () => {
if (!view || !unpublish) return;
if (!isOwner && !isPublisher) {
- notify.error(t('settings.sites.error.publishPermissionDenied'));
+ notify.error(t('settings.sites.error.unPublishPermissionDenied'));
return;
}
+ setUnpublishLoading(true);
+
try {
await unpublish(viewId);
await loadPublishInfo();
@@ -50,6 +63,8 @@ function PublishPanel ({ viewId, onClose }: { viewId: string; onClose: () => voi
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
+ } finally {
+ setUnpublishLoading(false);
}
}, [isOwner, isPublisher, loadPublishInfo, t, unpublish, view, viewId]);
@@ -73,7 +88,10 @@ function PublishPanel ({ viewId, onClose }: { viewId: string; onClose: () => voi
}}
color={'inherit'}
variant={'outlined'}
- >{t('shareAction.unPublish')}
+ startIcon={unpublishLoading ? : undefined}
+ >{
+ t('shareAction.unPublish')
+ }
;
- }, [handlePublish, handleUnpublish, isOwner, isPublisher, onClose, publishInfo, t, url, view]);
+ }, [handlePublish, handleUnpublish, isOwner, isPublisher, onClose, publishInfo, t, unpublishLoading, url, view]);
const renderUnpublished = useCallback(() => {
return
;
- }, [handlePublish, t]);
+ startIcon={publishLoading ?
: undefined}
+ >{
+ t('shareAction.publish')
+ };
+ }, [handlePublish, publishLoading, t]);
return (
diff --git a/src/components/app/share/ShareTabs.tsx b/src/components/app/share/ShareTabs.tsx
index ec11b9da..9e0a9951 100644
--- a/src/components/app/share/ShareTabs.tsx
+++ b/src/components/app/share/ShareTabs.tsx
@@ -1,13 +1,14 @@
import { useAppView } from '@/components/app/app.hooks';
-import PublishPanel from '@/components/app/share/PublishPanel';
+// import PublishPanel from '@/components/app/share/PublishPanel';
import TemplatePanel from '@/components/app/share/TemplatePanel';
import SharePanel from '@/components/app/share/SharePanel';
import { useCurrentUser } from '@/components/main/app.hooks';
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ViewTabs, ViewTab, TabPanel } from 'src/components/_shared/tabs/ViewTabs';
import { ReactComponent as Templates } from '@/assets/template.svg';
-import { ReactComponent as PublishedWithChanges } from '@/assets/published_with_changes.svg';
+
+// import { ReactComponent as PublishedWithChanges } from '@/assets/published_with_changes.svg';
enum TabKey {
SHARE = 'share',
@@ -26,21 +27,23 @@ function ShareTabs ({ opened, viewId, onClose }: { opened: boolean, viewId: stri
value: TabKey.SHARE,
label: t('shareAction.shareTab'),
Panel: SharePanel,
- }, {
- value: TabKey.PUBLISH,
- label: t('shareAction.publish'),
- icon: view?.is_published ?
: undefined,
- Panel: PublishPanel,
- }, currentUser?.email?.endsWith('appflowy.io') && view?.is_published && {
- value: TabKey.TEMPLATE,
- label: t('template.asTemplate'),
- icon:
,
- Panel: TemplatePanel,
- }].filter(Boolean) as {
+ },
+ // {
+ // value: TabKey.PUBLISH,
+ // label: t('shareAction.publish'),
+ // icon: view?.is_published ?
: undefined,
+ // Panel: PublishPanel,
+ // },
+ currentUser?.email?.endsWith('appflowy.io') && view?.is_published && {
+ value: TabKey.TEMPLATE,
+ label: t('template.asTemplate'),
+ icon:
,
+ Panel: TemplatePanel,
+ }].filter(Boolean) as {
value: TabKey;
label: string;
icon?: React.JSX.Element;
- Panel: React.FC<{ viewId: string; onClose: () => void }>
+ Panel: React.FC<{ viewId: string; onClose: () => void; opened: boolean }>
}[];
}, [currentUser?.email, t, view?.is_published]);
@@ -49,6 +52,12 @@ function ShareTabs ({ opened, viewId, onClose }: { opened: boolean, viewId: stri
setValue(newValue);
}, []);
+ useEffect(() => {
+ if (opened) {
+ setValue(TabKey.SHARE);
+ }
+ }, [opened]);
+
return (
<>
{options.map((option) => (
))}
diff --git a/src/components/database/components/grid/grid-calculation-cell/CalculationCell.tsx b/src/components/database/components/grid/grid-calculation-cell/CalculationCell.tsx
index cd3f5de7..5fc1bed8 100644
--- a/src/components/database/components/grid/grid-calculation-cell/CalculationCell.tsx
+++ b/src/components/database/components/grid/grid-calculation-cell/CalculationCell.tsx
@@ -17,7 +17,7 @@ export interface CalculationCellProps {
cell?: ICalculationCell;
}
-export function CalculationCell({ cell }: CalculationCellProps) {
+export function CalculationCell ({ cell }: CalculationCellProps) {
const { t } = useTranslation();
const fieldId = cell?.fieldId || '';
@@ -27,7 +27,7 @@ export function CalculationCell({ cell }: CalculationCellProps) {
field && Number(field?.get(YjsDatabaseKey.type)) === FieldType.Number
? parseNumberTypeOptions(field).format
: undefined,
- [field]
+ [field],
);
const prefix = useMemo(() => {
@@ -56,7 +56,7 @@ export function CalculationCell({ cell }: CalculationCellProps) {
const num = useMemo(() => {
const value = cell?.value;
- if (value === undefined || isNaN(value)) return '';
+ if (value === undefined || isNaN(parseInt(value))) return '';
if (format && currencyFormaterMap[format]) {
return currencyFormaterMap[format](new Decimal(value).toNumber());
diff --git a/src/components/editor/components/block-popover/index.tsx b/src/components/editor/components/block-popover/index.tsx
index 58e5c25d..6d4881ea 100644
--- a/src/components/editor/components/block-popover/index.tsx
+++ b/src/components/editor/components/block-popover/index.tsx
@@ -1,12 +1,11 @@
import { YjsEditor } from '@/application/slate-yjs';
import { findSlateEntryByBlockId } from '@/application/slate-yjs/utils/editor';
import { BlockType } from '@/application/types';
-import { Origins, Popover } from '@/components/_shared/popover';
+import { calculateOptimalOrigins, Origins, Popover } from '@/components/_shared/popover';
import { usePopoverContext } from '@/components/editor/components/block-popover/BlockPopoverContext';
import FileBlockPopoverContent from '@/components/editor/components/block-popover/FileBlockPopoverContent';
import ImageBlockPopoverContent from '@/components/editor/components/block-popover/ImageBlockPopoverContent';
import { useEditorContext } from '@/components/editor/EditorContext';
-import { debounce } from 'lodash-es';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { ReactEditor, useSlateStatic } from 'slate-react';
import MathEquationPopoverContent from './MathEquationPopoverContent';
@@ -79,37 +78,33 @@ function BlockPopover () {
}
}, [blockId, setSelectedBlockIds]);
- const debouncePosition = useMemo(() => {
- return debounce(() => {
- if (!anchorEl || !paperRef.current) return;
-
- const rect = anchorEl.getBoundingClientRect();
- const paperRect = paperRef.current.getBoundingClientRect();
- const isImage = type === BlockType.ImageBlock;
- const paperHeight = isImage ? paperRect.height + 100 : paperRect.height;
-
- if (rect.bottom + paperHeight > window.innerHeight) {
- setOrigins({
- anchorOrigin: {
- vertical: -8,
- horizontal: 'center',
- },
- transformOrigin: {
- vertical: 'bottom',
- horizontal: 'center',
- },
- });
- return;
- }
-
- }, 50);
- }, [anchorEl, type]);
-
useEffect(() => {
if (!open) return;
editor.deselect();
}, [open, editor]);
+ useEffect(() => {
+ const panelPosition = anchorEl?.getBoundingClientRect();
+
+ if (open && panelPosition) {
+ const origins = calculateOptimalOrigins({
+ top: panelPosition.bottom,
+ left: panelPosition.left,
+ }, 560, type === BlockType.ImageBlock ? 400 : 200, defaultOrigins, 16);
+
+ setOrigins({
+ transformOrigin: {
+ vertical: origins.transformOrigin.vertical,
+ horizontal: 'center',
+ },
+ anchorOrigin: {
+ vertical: origins.anchorOrigin.vertical,
+ horizontal: 'center',
+ },
+ });
+ }
+ }, [open, anchorEl, type]);
+
return {
- setOrigins(defaultOrigins);
- }}
- onTransitionEnd={debouncePosition}
{...origins}
disableRestoreFocus={true}
>
diff --git a/src/components/editor/components/blocks/callout/Callout.tsx b/src/components/editor/components/blocks/callout/Callout.tsx
index 30d678c5..fd9791cf 100644
--- a/src/components/editor/components/blocks/callout/Callout.tsx
+++ b/src/components/editor/components/blocks/callout/Callout.tsx
@@ -8,7 +8,7 @@ export const Callout = memo(
{children}
diff --git a/src/components/editor/components/blocks/callout/CalloutIcon.tsx b/src/components/editor/components/blocks/callout/CalloutIcon.tsx
index a41a125a..4e56bb62 100644
--- a/src/components/editor/components/blocks/callout/CalloutIcon.tsx
+++ b/src/components/editor/components/blocks/callout/CalloutIcon.tsx
@@ -7,7 +7,7 @@ import React, { useCallback, useRef } from 'react';
import { useReadOnly, useSlateStatic } from 'slate-react';
import { Element } from 'slate';
-function CalloutIcon ({ block: node, className }: { block: CalloutNode; className: string }) {
+function CalloutIcon ({ block: node }: { block: CalloutNode; className: string }) {
const ref = useRef(null);
const editor = useSlateStatic();
const readOnly = useReadOnly() || editor.isElementReadOnly(node as unknown as Element);
@@ -34,13 +34,13 @@ function CalloutIcon ({ block: node, className }: { block: CalloutNode; classNam
}}
contentEditable={false}
ref={ref}
- className={`icon ${className} ${readOnly ? '' : 'cursor-pointer'} flex h-[24px] max-h-full items-center`}
+ className={`${readOnly ? '' : 'cursor-pointer'} relative flex items-start justify-center`}
style={{
width: '58px',
}}
>
{node.data.icon || `📌`}
diff --git a/src/components/editor/components/blocks/code/MermaidChat.tsx b/src/components/editor/components/blocks/code/MermaidChat.tsx
index 093976d4..621aa16b 100644
--- a/src/components/editor/components/blocks/code/MermaidChat.tsx
+++ b/src/components/editor/components/blocks/code/MermaidChat.tsx
@@ -94,7 +94,7 @@ function MermaidChat ({ node }: {
}, [diagram, id, isDark]);
const deboucenUpdateMermaid = useMemo(() => {
- return debounce(updateMermaid, 300);
+ return debounce(updateMermaid, 1000);
}, [updateMermaid]);
useEffect(() => {
@@ -121,6 +121,7 @@ function MermaidChat ({ node }: {
display: 'flex',
flexDirection: 'row',
placeContent: 'center',
+ minHeight: '250px',
}}
contentEditable={false}
ref={ref}
diff --git a/src/components/editor/components/panels/mention-panel/MentionPanel.tsx b/src/components/editor/components/panels/mention-panel/MentionPanel.tsx
index 3ab8e83d..a718ba29 100644
--- a/src/components/editor/components/panels/mention-panel/MentionPanel.tsx
+++ b/src/components/editor/components/panels/mention-panel/MentionPanel.tsx
@@ -7,6 +7,7 @@ import { usePanelContext } from '@/components/editor/components/panels/Panels.ho
import { PanelType } from '@/components/editor/components/panels/PanelsContext';
import { useEditorContext } from '@/components/editor/EditorContext';
import { Button, Divider } from '@mui/material';
+import { PopoverOrigin } from '@mui/material/Popover/Popover';
import { sortBy, uniqBy } from 'lodash-es';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -15,7 +16,7 @@ import { ReactEditor, useSlateStatic } from 'slate-react';
import { ReactComponent as AddIcon } from '@/assets/add.svg';
import { ReactComponent as ArrowIcon } from '@/assets/north_east.svg';
import { ReactComponent as MoreIcon } from '@/assets/more.svg';
-import { Popover } from '@/components/_shared/popover';
+import { calculateOptimalOrigins, Popover } from '@/components/_shared/popover';
import dayjs from 'dayjs';
import PageIcon from '@/components/_shared/view-icon/PageIcon';
@@ -33,7 +34,7 @@ interface Option {
index: number;
}
-function createMentionOptions({
+function createMentionOptions ({
showMore,
viewsLength,
dateLength,
@@ -67,7 +68,7 @@ function createMentionOptions({
return options;
}
-export function MentionPanel() {
+export function MentionPanel () {
const {
isPanelOpen,
panelPosition,
@@ -327,10 +328,23 @@ export function MentionPanel() {
slateDom.removeEventListener('keydown', handleKeyDown);
};
}, [editor, handleClickMore, handleAddPage, handleSelectedPage, open, selectedOptionRef, splicedViews, dateOptions, showMore]);
+ const [transformOrigin, setTransformOrigin] = React.useState(undefined);
+
+ useEffect(() => {
+ if (open && panelPosition) {
+ const origins = calculateOptimalOrigins(panelPosition, 320, 560, undefined, 16);
+ const isAlignBottom = origins.transformOrigin.vertical === 'bottom';
+
+ setTransformOrigin(isAlignBottom ? origins.transformOrigin : {
+ vertical: -30,
+ horizontal: origins.transformOrigin.horizontal,
+ });
+ }
+ }, [open, panelPosition]);
return (
e.preventDefault()}
>
+
}
className={`justify-start truncate scroll-m-2 min-h-[32px] hover:bg-fill-list-hover ${selectedOption?.index === index && selectedOption?.category === MentionTag.Page ? 'bg-fill-list-hover' : ''}`}
onClick={() => handleSelectedPage(view.view_id)}
@@ -374,15 +388,19 @@ export function MentionPanel() {
) :
{t('findAndReplace.noResult')}
+ className={'text-text-caption text-sm flex justify-center items-center p-2'}
+ >{t('findAndReplace.noResult')}
+ {showDate &&
{t('inlineActions.date')}
{
dateOptions.map((option, index) => (
@@ -416,10 +437,10 @@ export function MentionPanel() {
data-option-category={MentionTag.NewPage}
className={'flex w-full flex-col gap-2'}
>
-
+
diff --git a/src/components/main/AppTheme.tsx b/src/components/main/AppTheme.tsx
index 691a4921..c24a5300 100644
--- a/src/components/main/AppTheme.tsx
+++ b/src/components/main/AppTheme.tsx
@@ -10,7 +10,7 @@ import 'src/styles/tailwind.css';
import 'src/styles/template.css';
import { I18nextProvider } from 'react-i18next';
-function AppTheme({ children }: { children: React.ReactNode; }) {
+function AppTheme ({ children }: { children: React.ReactNode; }) {
const { isDark, setIsDark } = useAppThemeMode();
const theme = useMemo(
@@ -104,6 +104,7 @@ function AppTheme({ children }: { children: React.ReactNode; }) {
},
'&.MuiButton-containedSecondary': {
backgroundColor: 'var(--billing-primary)',
+ color: 'white',
'&:hover': {
backgroundColor: 'var(--billing-primary-hover)',
},
diff --git a/src/components/publish/header/duplicate/DuplicateModal.tsx b/src/components/publish/header/duplicate/DuplicateModal.tsx
index 4c4193e8..4ae4705b 100644
--- a/src/components/publish/header/duplicate/DuplicateModal.tsx
+++ b/src/components/publish/header/duplicate/DuplicateModal.tsx
@@ -51,10 +51,10 @@ function DuplicateModal ({ open, onClose }: { open: boolean; onClose: () => void
}, [loadWorkspaces, open]);
useEffect(() => {
- if (selectedWorkspaceId) {
+ if (selectedWorkspaceId && open) {
void loadSpaces(selectedWorkspaceId);
}
- }, [loadSpaces, selectedWorkspaceId]);
+ }, [loadSpaces, selectedWorkspaceId, open]);
const handleDuplicate = useCallback(async () => {
if (!viewId) return;
diff --git a/src/components/publish/header/duplicate/useDuplicate.ts b/src/components/publish/header/duplicate/useDuplicate.ts
index e41225d0..db59967f 100644
--- a/src/components/publish/header/duplicate/useDuplicate.ts
+++ b/src/components/publish/header/duplicate/useDuplicate.ts
@@ -111,7 +111,7 @@ export function useLoadWorkspaces () {
setSpaceList([]);
}
} catch (e) {
- notify.error('Failed to load spaces');
+ console.error('Failed to load spaces');
} finally {
setSelectedSpaceId('');
setSpaceLoading(false);
diff --git a/src/components/view-meta/ViewMetaPreview.tsx b/src/components/view-meta/ViewMetaPreview.tsx
index 14bc9357..96173563 100644
--- a/src/components/view-meta/ViewMetaPreview.tsx
+++ b/src/components/view-meta/ViewMetaPreview.tsx
@@ -8,7 +8,7 @@ import PageIcon from '@/components/_shared/view-icon/PageIcon';
const AddIconCover = lazy(() => import('@/components/view-meta/AddIconCover'));
-export function ViewMetaPreview({
+export function ViewMetaPreview ({
icon: iconProp,
cover: coverProp,
name,
@@ -121,6 +121,31 @@ export function ViewMetaPreview({
}
}, [extra, icon, name, updatePage, viewId]);
+ const ref = React.useRef
(null);
+
+ useEffect(() => {
+ const el = ref.current;
+ const handleMouseEnter = () => {
+ setIsHover(true);
+ };
+
+ const handleMouseLeave = () => {
+ setIsHover(false);
+ };
+
+ if (el) {
+ el.addEventListener('mouseenter', handleMouseEnter);
+ el.addEventListener('mouseleave', handleMouseLeave);
+ }
+
+ return () => {
+ if (el) {
+ el.removeEventListener('mouseenter', handleMouseEnter);
+ el.removeEventListener('mouseleave', handleMouseLeave);
+ }
+ };
+ }, []);
+
return (
{cover &&
}
setIsHover(true)}
- onMouseLeave={() => setIsHover(false)}
+ ref={ref}
className={'flex mt-2 flex-col relative w-full overflow-hidden'}
>