fix: bugs

This commit is contained in:
Kilu 2024-06-29 16:18:33 +08:00
parent e211fdce13
commit 215910cd24
22 changed files with 170 additions and 77 deletions

View File

@ -1,7 +1,6 @@
import { YDoc } from '@/application/collab.type'; import { YDoc } from '@/application/collab.type';
import { db } from '@/application/db'; import { db } from '@/application/db';
import { ViewMeta } from '@/application/db/tables/view_metas'; import { ViewMeta } from '@/application/db/tables/view_metas';
import { notify } from '@/components/_shared/notify';
import { AFConfigContext } from '@/components/app/AppConfig'; import { AFConfigContext } from '@/components/app/AppConfig';
import { useLiveQuery } from 'dexie-react-hooks'; import { useLiveQuery } from 'dexie-react-hooks';
import { createContext, useCallback, useContext } from 'react'; import { createContext, useCallback, useContext } from 'react';
@ -50,7 +49,6 @@ export const PublishProvider = ({
navigate(`/${namespace}/${publishName}`); navigate(`/${namespace}/${publishName}`);
} catch (e) { } catch (e) {
notify.error('The view has not been published yet.');
return Promise.reject(e); return Promise.reject(e);
} }
}, },

View File

@ -5,6 +5,12 @@ export const notify = {
error: (message: string) => { error: (message: string) => {
window.toast.error(message); window.toast.error(message);
}, },
default: (message: string) => {
window.toast.default(message);
},
warning: (message: string) => {
window.toast.warning(message);
},
info: (message: string) => { info: (message: string) => {
window.toast.info(message); window.toast.info(message);
}, },

View File

@ -1,4 +1,5 @@
import { useAppLanguage } from '@/components/app/useAppLanguage'; import { useAppLanguage } from '@/components/app/useAppLanguage';
import { useSnackbar } from 'notistack';
import React, { createContext, useEffect, useState } from 'react'; import React, { createContext, useEffect, useState } from 'react';
import { AFService, AFServiceConfig } from '@/application/services/services.type'; import { AFService, AFServiceConfig } from '@/application/services/services.type';
import { getService } from '@/application/services'; import { getService } from '@/application/services';
@ -43,6 +44,35 @@ function AppConfig({ children }: { children: React.ReactNode }) {
})(); })();
}, [appConfig]); }, [appConfig]);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
useEffect(() => {
const commonClasses = 'flex items-center justify-center gap-3 bg-bg-body';
window.toast = {
success: (message: string) => {
enqueueSnackbar(message, { variant: 'success' });
},
error: (message: string) => {
console.log('error', message);
enqueueSnackbar(message, { variant: 'error' });
},
warning: (message: string) => {
enqueueSnackbar(message, { variant: 'warning' });
},
default: (message: string) => {
enqueueSnackbar(message, { variant: 'default' });
},
info: (message: string) => {
enqueueSnackbar(message, { variant: 'info' });
},
clear: () => {
closeSnackbar();
},
};
}, [closeSnackbar, enqueueSnackbar]);
return ( return (
<AFConfigContext.Provider <AFConfigContext.Provider
value={{ value={{

View File

@ -2,37 +2,38 @@ import { ErrorBoundary } from 'react-error-boundary';
import { ErrorHandlerPage } from 'src/components/error/ErrorHandlerPage'; import { ErrorHandlerPage } from 'src/components/error/ErrorHandlerPage';
import AppTheme from '@/components/app/AppTheme'; import AppTheme from '@/components/app/AppTheme';
import AppConfig from '@/components/app/AppConfig'; import AppConfig from '@/components/app/AppConfig';
import { Suspense, useEffect } from 'react'; import { Suspense } from 'react';
import { SnackbarProvider, useSnackbar } from 'notistack'; import { SnackbarProvider } from 'notistack';
import { styled } from '@mui/material';
const StyledSnackbarProvider = styled(SnackbarProvider)`
&.notistack-MuiContent-default {
background-color: var(--fill-toolbar);
}
&.notistack-MuiContent-info {
background-color: var(--function-info);
}
&.notistack-MuiContent-success {
background-color: var(--function-success);
}
&.notistack-MuiContent-error {
background-color: var(--function-error);
}
&.notistack-MuiContent-warning {
background-color: var(--function-warning);
}
`;
export default function withAppWrapper(Component: React.FC): React.FC { export default function withAppWrapper(Component: React.FC): React.FC {
return function AppWrapper(): JSX.Element { return function AppWrapper(): JSX.Element {
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
useEffect(() => {
window.toast = {
success: (message: string) => {
enqueueSnackbar(message, { variant: 'success' });
},
error: (message: string) => {
enqueueSnackbar(message, { variant: 'error' });
},
warning: (message: string) => {
enqueueSnackbar(message, { variant: 'warning' });
},
info: (message: string) => {
enqueueSnackbar(message, { variant: 'info' });
},
clear: () => {
closeSnackbar();
},
};
}, [closeSnackbar, enqueueSnackbar]);
return ( return (
<AppTheme> <AppTheme>
<ErrorBoundary FallbackComponent={ErrorHandlerPage}> <ErrorBoundary FallbackComponent={ErrorHandlerPage}>
<SnackbarProvider <StyledSnackbarProvider
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center',
@ -44,7 +45,7 @@ export default function withAppWrapper(Component: React.FC): React.FC {
<Component /> <Component />
</Suspense> </Suspense>
</AppConfig> </AppConfig>
</SnackbarProvider> </StyledSnackbarProvider>
</ErrorBoundary> </ErrorBoundary>
</AppTheme> </AppTheme>
); );

View File

@ -1,6 +1,9 @@
import { AlignType } from '@/application/collab.type'; import { AlignType } from '@/application/collab.type';
import { notify } from '@/components/_shared/notify';
import { EditorElementProps, ImageBlockNode } from '@/components/editor/editor.type'; import { EditorElementProps, ImageBlockNode } from '@/components/editor/editor.type';
import { copyTextToClipboard } from '@/utils/copy';
import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactEditor, useSelected, useSlateStatic } from 'slate-react'; import { ReactEditor, useSelected, useSlateStatic } from 'slate-react';
import ImageEmpty from './ImageEmpty'; import ImageEmpty from './ImageEmpty';
import ImageRender from './ImageRender'; import ImageRender from './ImageRender';
@ -24,14 +27,22 @@ export const ImageBlock = memo(
return align === AlignType.Center ? 'justify-center' : align === AlignType.Right ? 'justify-end' : 'justify-start'; return align === AlignType.Center ? 'justify-center' : align === AlignType.Right ? 'justify-end' : 'justify-start';
}, [align]); }, [align]);
const { t } = useTranslation();
return ( return (
<div <div
{...attributes} {...attributes}
ref={containerRef} ref={containerRef}
onClick={() => { onClick={async () => {
if (!selected) onFocusNode(); if (!url) return;
try {
await copyTextToClipboard(url);
notify.success(t('document.plugins.image.copiedToPasteBoard'));
} catch (_) {
// do nothing
}
}} }}
className={`${className || ''} image-block relative w-full cursor-pointer py-1`} className={`${className || ''} image-block relative w-full py-1 ${url ? 'cursor-pointer' : 'cursor-default'}`}
> >
<div ref={ref} className={'absolute left-0 top-0 h-full w-full select-none caret-transparent'}> <div ref={ref} className={'absolute left-0 top-0 h-full w-full select-none caret-transparent'}>
{children} {children}

View File

@ -1,5 +1,7 @@
import KatexMath from '@/components/_shared/katex-math/KatexMath'; import KatexMath from '@/components/_shared/katex-math/KatexMath';
import { notify } from '@/components/_shared/notify';
import { EditorElementProps, MathEquationNode } from '@/components/editor/editor.type'; import { EditorElementProps, MathEquationNode } from '@/components/editor/editor.type';
import { copyTextToClipboard } from '@/utils/copy';
import { FunctionsOutlined } from '@mui/icons-material'; import { FunctionsOutlined } from '@mui/icons-material';
import { forwardRef, memo, useRef } from 'react'; import { forwardRef, memo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -16,7 +18,18 @@ export const MathEquation = memo(
<div <div
{...attributes} {...attributes}
ref={containerRef} ref={containerRef}
className={`${className} math-equation-block relative w-full cursor-pointer py-2`} onClick={async () => {
if (!formula) return;
try {
await copyTextToClipboard(formula);
notify.success(t('document.plugins.math.copiedToPasteBoard'));
} catch (_) {
// do nothing
}
}}
className={`${className} math-equation-block relative w-full ${
formula ? 'cursor-pointer' : 'cursor-default'
} py-2`}
> >
<div <div
contentEditable={false} contentEditable={false}

View File

@ -25,7 +25,7 @@ export const Outline = memo(
if (element) { if (element) {
void smoothScrollIntoViewIfNeeded(element, { void smoothScrollIntoViewIfNeeded(element, {
behavior: 'smooth', behavior: 'smooth',
block: 'center', block: 'start',
}); });
} }
}, []); }, []);

View File

@ -1,3 +1,4 @@
import { AFScroller } from '@/components/_shared/scroller';
import { EditorElementProps, TableCellNode, TableNode } from '@/components/editor/editor.type'; import { EditorElementProps, TableCellNode, TableNode } from '@/components/editor/editor.type';
import React, { forwardRef, memo, useMemo } from 'react'; import React, { forwardRef, memo, useMemo } from 'react';
import { Grid } from '@atlaskit/primitives'; import { Grid } from '@atlaskit/primitives';
@ -38,17 +39,23 @@ const Table = memo(
}, [rowGroup, rowDefaultHeight]); }, [rowGroup, rowDefaultHeight]);
return ( return (
<div ref={ref} {...attributes} className={`table-block relative my-2 w-full px-1 ${className || ''}`}> <div
<Grid ref={ref}
id={`table-${node.blockId}`} {...attributes}
rowGap='space.0' className={`table-block relative my-2 w-full overflow-hidden px-1 ${className || ''}`}
autoFlow='column' >
columnGap='space.0' <div className={'h-full w-full overflow-x-auto overflow-y-hidden'}>
templateRows={templateRows} <Grid
templateColumns={templateColumns} id={`table-${node.blockId}`}
> rowGap='space.0'
{children} autoFlow='column'
</Grid> columnGap='space.0'
templateRows={templateRows}
templateColumns={templateColumns}
>
{children}
</Grid>
</div>
</div> </div>
); );
}), }),

View File

@ -9,7 +9,7 @@ function MentionDate({ date, reminder }: { date: string; reminder?: { id: string
}, [date]); }, [date]);
return ( return (
<span className={'mention-inline'}> <span className={'mention-inline cursor-text'}>
{reminder ? <ReminderSvg className={'mention-icon'} /> : <DateSvg className={'mention-icon'} />} {reminder ? <ReminderSvg className={'mention-icon'} /> : <DateSvg className={'mention-icon'} />}
<span className={'mention-content'}>{dateFormat}</span> <span className={'mention-content'}>{dateFormat}</span>

View File

@ -41,7 +41,7 @@ function MentionPage({ pageId }: { pageId: string }) {
contentEditable={false} contentEditable={false}
> >
{unPublished ? ( {unPublished ? (
<span className={'mention-unpublished font-semibold text-text-caption'}>No Access</span> <span className={'mention-unpublished cursor-text font-semibold text-text-caption'}>No Access</span>
) : ( ) : (
<> <>
<span className={'mention-icon icon'}> <span className={'mention-icon icon'}>

View File

@ -1,3 +1,4 @@
@use "src/styles/mixin.scss";
.block-element:not([data-block-type="table/cell"]) { .block-element:not([data-block-type="table/cell"]) {
@apply my-[4px]; @apply my-[4px];
@ -301,3 +302,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
margin-bottom: 0px; margin-bottom: 0px;
} }
} }
.table-block {
@include mixin.scrollbar-style;
}

View File

@ -27,7 +27,7 @@ function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disable
try { try {
await onNavigateToView?.(viewId); await onNavigateToView?.(viewId);
} catch (e) { } catch (e) {
notify.error(t('publish.hasNotBeenPublished')); notify.default(t('publish.hasNotBeenPublished'));
} }
}} }}
> >

View File

@ -72,7 +72,10 @@ function MoreActions() {
<div className={'flex w-[240px] flex-col gap-2 px-2 py-2'}> <div className={'flex w-[240px] flex-col gap-2 px-2 py-2'}>
{actions.map((action, index) => ( {actions.map((action, index) => (
<button <button
onClick={action.onClick} onClick={() => {
action.onClick();
handleClose();
}}
key={index} key={index}
className={ className={
'flex items-center gap-2 rounded-[8px] p-1.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none' 'flex items-center gap-2 rounded-[8px] p-1.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none'

View File

@ -1,9 +1,10 @@
import { usePublishContext } from '@/application/publish'; import { usePublishContext } from '@/application/publish';
import { openOrDownload } from '@/components/publish/header/utils'; import { openOrDownload } from '@/components/publish/header/utils';
import { Divider, IconButton } from '@mui/material'; import { Divider, IconButton, Tooltip } from '@mui/material';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import OutlinePopover from '@/components/publish/outline/OutlinePopover'; import OutlinePopover from '@/components/publish/outline/OutlinePopover';
import { useTranslation } from 'react-i18next';
import Breadcrumb from './Breadcrumb'; import Breadcrumb from './Breadcrumb';
import { ReactComponent as Logo } from '@/assets/logo.svg'; import { ReactComponent as Logo } from '@/assets/logo.svg';
import MoreActions from './MoreActions'; import MoreActions from './MoreActions';
@ -18,6 +19,7 @@ export function PublishViewHeader({
openDrawer: boolean; openDrawer: boolean;
drawerWidth: number; drawerWidth: number;
}) { }) {
const { t } = useTranslation();
const viewMeta = usePublishContext()?.viewMeta; const viewMeta = usePublishContext()?.viewMeta;
const crumbs = useMemo(() => { const crumbs = useMemo(() => {
const ancestors = viewMeta?.ancestor_views.slice(1) || []; const ancestors = viewMeta?.ancestor_views.slice(1) || [];
@ -95,9 +97,11 @@ export function PublishViewHeader({
<div className={'flex items-center gap-2'}> <div className={'flex items-center gap-2'}>
<MoreActions /> <MoreActions />
<Divider orientation={'vertical'} className={'mx-2'} flexItem /> <Divider orientation={'vertical'} className={'mx-2'} flexItem />
<button onClick={openOrDownload}> <Tooltip title={t('publish.downloadApp')}>
<Logo className={'h-6 w-6'} /> <button onClick={openOrDownload}>
</button> <Logo className={'h-6 w-6'} />
</button>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,10 +4,11 @@ import SearchInput from '@/components/publish/outline/SearchInput';
import { filterViews } from '@/components/publish/outline/utils'; import { filterViews } from '@/components/publish/outline/utils';
import { CircularProgress } from '@mui/material'; import { CircularProgress } from '@mui/material';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
function Outline({ viewMeta }: { viewMeta?: PublishViewInfo }) { function Outline({ viewMeta, width }: { viewMeta?: PublishViewInfo; width: number }) {
const hasChildren = Boolean(viewMeta?.child_views?.length); const hasChildren = Boolean(viewMeta?.child_views?.length);
const { t } = useTranslation();
const [children, setChildren] = React.useState<PublishViewInfo[]>([]); const [children, setChildren] = React.useState<PublishViewInfo[]>([]);
useEffect(() => { useEffect(() => {
@ -45,14 +46,16 @@ function Outline({ viewMeta }: { viewMeta?: PublishViewInfo }) {
<SearchInput onSearch={handleSearch} /> <SearchInput onSearch={handleSearch} />
</div> </div>
{hasChildren && ( {hasChildren ? (
<div className={'flex w-full flex-1 flex-col'}> <div className={'flex w-full flex-1 flex-col'}>
{children {children
.filter((view) => view.layout === ViewLayout.Document) .filter((view) => view.layout === ViewLayout.Document)
.map((view: PublishViewInfo) => ( .map((view: PublishViewInfo) => (
<OutlineItem key={view.view_id} view={view} /> <OutlineItem width={width} key={view.view_id} view={view} />
))} ))}
</div> </div>
) : (
<div className={'flex w-full flex-1 items-center justify-center text-text-caption'}>{t('noPagesInside')}</div>
)} )}
</div> </div>
); );

View File

@ -55,7 +55,7 @@ function OutlineDrawer({ open, width, onClose }: { open: boolean; width: number;
</Tooltip> </Tooltip>
</div> </div>
<div className={'flex flex-1 flex-col overflow-y-auto px-4 pb-4'}> <div className={'flex flex-1 flex-col overflow-y-auto px-4 pb-4'}>
<Outline viewMeta={viewMeta} /> <Outline width={width} viewMeta={viewMeta} />
</div> </div>
</div> </div>
</Drawer> </Drawer>

View File

@ -6,12 +6,15 @@ import React, { useCallback, useContext } from 'react';
import { ReactComponent as ChevronDownIcon } from '@/assets/chevron_down.svg'; import { ReactComponent as ChevronDownIcon } from '@/assets/chevron_down.svg';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function OutlineItem({ view }: { view: PublishViewInfo }) { function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width: number; level: number }) {
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const getIcon = useCallback(() => { const getIcon = useCallback(() => {
if (isExpanded) { if (isExpanded) {
return ( return (
<button <button
style={{
paddingLeft: 1.125 * level + 'rem',
}}
onClick={() => { onClick={() => {
setIsExpanded(false); setIsExpanded(false);
}} }}
@ -23,6 +26,9 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
return ( return (
<button <button
style={{
paddingLeft: 1.125 * level + 'rem',
}}
onClick={() => { onClick={() => {
setIsExpanded(true); setIsExpanded(true);
}} }}
@ -30,24 +36,21 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
<ChevronDownIcon className={'h-4 w-4 -rotate-90 transform'} /> <ChevronDownIcon className={'h-4 w-4 -rotate-90 transform'} />
</button> </button>
); );
}, [isExpanded]); }, [isExpanded, level]);
const { t } = useTranslation(); const { t } = useTranslation();
const navigateToView = useContext(PublishContext)?.toView; const navigateToView = useContext(PublishContext)?.toView;
const renderItem = (item: PublishViewInfo) => { const renderItem = (item: PublishViewInfo) => {
const { icon, layout, name, view_id } = item; const { icon, layout, name, view_id } = item;
const hasChildren = Boolean(item.child_views?.length);
return ( return (
<div <div className={'flex h-fit flex-col gap-2'}>
style={{
marginLeft: hasChildren ? '0' : '1.125rem',
}}
className={'flex h-fit flex-col gap-2'}
>
<div <div
style={{
width: width - 32,
}}
className={ className={
'flex w-full items-center gap-0.5 rounded-[8px] p-1.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none' 'flex items-center gap-0.5 rounded-[8px] p-1.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none'
} }
> >
{item.child_views?.length ? getIcon() : null} {item.child_views?.length ? getIcon() : null}
@ -59,7 +62,10 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
notify.error(t('publish.hasNotBeenPublished')); notify.error(t('publish.hasNotBeenPublished'));
} }
}} }}
className={'flex flex-1 cursor-pointer items-center gap-1 overflow-hidden'} style={{
paddingLeft: item.child_views?.length ? 0 : 1.125 * (level + 1) + 'rem',
}}
className={'flex flex-1 cursor-pointer items-center gap-1.5 overflow-hidden'}
> >
<div className={'icon'}>{icon?.value || <ViewIcon layout={layout} size={'small'} />}</div> <div className={'icon'}>{icon?.value || <ViewIcon layout={layout} size={'small'} />}</div>
<div className={'flex-1 truncate'}>{name}</div> <div className={'flex-1 truncate'}>{name}</div>
@ -69,23 +75,20 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
); );
}; };
const hasChildren = Boolean(view.child_views?.length);
return ( return (
<div className={'flex h-fit w-full flex-col'}> <div className={'flex h-fit flex-col'}>
{renderItem(view)} {renderItem(view)}
<div <div
className={'flex transform flex-col gap-2 transition-all'} className={'flex transform flex-col gap-2 transition-all'}
style={{ style={{
height: isExpanded && view.child_views?.length ? 'auto' : 0, height: isExpanded && view.child_views?.length ? 'auto' : 0,
opacity: isExpanded && view.child_views?.length ? 1 : 0, opacity: isExpanded && view.child_views?.length ? 1 : 0,
marginLeft: hasChildren ? '1.125rem' : '2.25rem',
}} }}
> >
{view.child_views {view.child_views
?.filter((view) => view.layout === ViewLayout.Document) ?.filter((view) => view.layout === ViewLayout.Document)
?.map((item, index) => ( ?.map((item, index) => (
<OutlineItem key={index} view={item} /> <OutlineItem level={level + 1} width={width} key={index} view={item} />
))} ))}
</div> </div>
</div> </div>

View File

@ -30,7 +30,7 @@ export function OutlinePopover({
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
className={'flex h-fit max-h-[500px] w-[268px] flex-col overflow-y-auto overflow-x-hidden p-2'} className={'flex h-fit max-h-[500px] w-[268px] flex-col overflow-y-auto overflow-x-hidden p-2'}
> >
<Outline viewMeta={viewMeta} /> <Outline width={268} viewMeta={viewMeta} />
<div <div
style={{ style={{
position: 'sticky', position: 'sticky',

View File

@ -61,7 +61,8 @@ export function ViewMetaPreview({ icon, cover, name }: ViewMetaProps) {
'flex items-center gap-4 px-16 text-[2.25rem] font-bold leading-[1.5em] max-md:px-4 max-sm:text-[7vw]' 'flex items-center gap-4 px-16 text-[2.25rem] font-bold leading-[1.5em] max-md:px-4 max-sm:text-[7vw]'
} }
> >
<div className={`view-icon`}>{icon?.value}</div> {icon?.value ? <div className={'view-icon'}>{icon?.value}</div> : null}
{name || <span className={'text-text-placeholder'}>{t('menuAppHeader.defaultNewPageName')}</span>} {name || <span className={'text-text-placeholder'}>{t('menuAppHeader.defaultNewPageName')}</span>}
</div> </div>
</div> </div>

View File

@ -0,0 +1,3 @@
export async function copyTextToClipboard(text: string) {
await navigator.clipboard.writeText(text);
}

View File

@ -15,6 +15,7 @@ interface Window {
error: (message: string) => void; error: (message: string) => void;
info: (message: string) => void; info: (message: string) => void;
clear: () => void; clear: () => void;
default: (message: string) => void;
warning: (message: string) => void; warning: (message: string) => void;
}; };
} }

View File

@ -1378,6 +1378,9 @@
"imageUploadFailed": "Image upload failed", "imageUploadFailed": "Image upload failed",
"errorCode": "Error code" "errorCode": "Error code"
}, },
"math": {
"copiedToPasteBoard": "The math equation has been copied to the clipboard"
},
"urlPreview": { "urlPreview": {
"copiedToPasteBoard": "The link has been copied to the clipboard", "copiedToPasteBoard": "The link has been copied to the clipboard",
"convertToLink": "Convert to embed link" "convertToLink": "Convert to embed link"
@ -2037,7 +2040,8 @@
"publish": { "publish": {
"hasNotBeenPublished": "This page hasn't been published yet", "hasNotBeenPublished": "This page hasn't been published yet",
"reportPage": "Report page", "reportPage": "Report page",
"databaseHasNotBeenPublished": "This database hasn't been published yet", "databaseHasNotBeenPublished": "Publishing a database is not supported yet.",
"createdWith": "Created with" "createdWith": "Created with",
"downloadApp": "Download AppFlowy"
} }
} }