mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: bugs
This commit is contained in:
parent
e211fdce13
commit
215910cd24
@ -1,7 +1,6 @@
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { db } from '@/application/db';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { AFConfigContext } from '@/components/app/AppConfig';
|
||||
import { useLiveQuery } from 'dexie-react-hooks';
|
||||
import { createContext, useCallback, useContext } from 'react';
|
||||
@ -50,7 +49,6 @@ export const PublishProvider = ({
|
||||
|
||||
navigate(`/${namespace}/${publishName}`);
|
||||
} catch (e) {
|
||||
notify.error('The view has not been published yet.');
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
|
@ -5,6 +5,12 @@ export const notify = {
|
||||
error: (message: string) => {
|
||||
window.toast.error(message);
|
||||
},
|
||||
default: (message: string) => {
|
||||
window.toast.default(message);
|
||||
},
|
||||
warning: (message: string) => {
|
||||
window.toast.warning(message);
|
||||
},
|
||||
info: (message: string) => {
|
||||
window.toast.info(message);
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useAppLanguage } from '@/components/app/useAppLanguage';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import React, { createContext, useEffect, useState } from 'react';
|
||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||
import { getService } from '@/application/services';
|
||||
@ -43,6 +44,35 @@ function AppConfig({ children }: { children: React.ReactNode }) {
|
||||
})();
|
||||
}, [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 (
|
||||
<AFConfigContext.Provider
|
||||
value={{
|
||||
|
@ -2,37 +2,38 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ErrorHandlerPage } from 'src/components/error/ErrorHandlerPage';
|
||||
import AppTheme from '@/components/app/AppTheme';
|
||||
import AppConfig from '@/components/app/AppConfig';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import { SnackbarProvider, useSnackbar } from 'notistack';
|
||||
import { Suspense } from 'react';
|
||||
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 {
|
||||
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 (
|
||||
<AppTheme>
|
||||
<ErrorBoundary FallbackComponent={ErrorHandlerPage}>
|
||||
<SnackbarProvider
|
||||
<StyledSnackbarProvider
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
@ -44,7 +45,7 @@ export default function withAppWrapper(Component: React.FC): React.FC {
|
||||
<Component />
|
||||
</Suspense>
|
||||
</AppConfig>
|
||||
</SnackbarProvider>
|
||||
</StyledSnackbarProvider>
|
||||
</ErrorBoundary>
|
||||
</AppTheme>
|
||||
);
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { AlignType } from '@/application/collab.type';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { EditorElementProps, ImageBlockNode } from '@/components/editor/editor.type';
|
||||
import { copyTextToClipboard } from '@/utils/copy';
|
||||
import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactEditor, useSelected, useSlateStatic } from 'slate-react';
|
||||
import ImageEmpty from './ImageEmpty';
|
||||
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';
|
||||
}, [align]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
{...attributes}
|
||||
ref={containerRef}
|
||||
onClick={() => {
|
||||
if (!selected) onFocusNode();
|
||||
onClick={async () => {
|
||||
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'}>
|
||||
{children}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import KatexMath from '@/components/_shared/katex-math/KatexMath';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { EditorElementProps, MathEquationNode } from '@/components/editor/editor.type';
|
||||
import { copyTextToClipboard } from '@/utils/copy';
|
||||
import { FunctionsOutlined } from '@mui/icons-material';
|
||||
import { forwardRef, memo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -16,7 +18,18 @@ export const MathEquation = memo(
|
||||
<div
|
||||
{...attributes}
|
||||
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
|
||||
contentEditable={false}
|
||||
|
@ -25,7 +25,7 @@ export const Outline = memo(
|
||||
if (element) {
|
||||
void smoothScrollIntoViewIfNeeded(element, {
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { AFScroller } from '@/components/_shared/scroller';
|
||||
import { EditorElementProps, TableCellNode, TableNode } from '@/components/editor/editor.type';
|
||||
import React, { forwardRef, memo, useMemo } from 'react';
|
||||
import { Grid } from '@atlaskit/primitives';
|
||||
@ -38,7 +39,12 @@ const Table = memo(
|
||||
}, [rowGroup, rowDefaultHeight]);
|
||||
|
||||
return (
|
||||
<div ref={ref} {...attributes} className={`table-block relative my-2 w-full px-1 ${className || ''}`}>
|
||||
<div
|
||||
ref={ref}
|
||||
{...attributes}
|
||||
className={`table-block relative my-2 w-full overflow-hidden px-1 ${className || ''}`}
|
||||
>
|
||||
<div className={'h-full w-full overflow-x-auto overflow-y-hidden'}>
|
||||
<Grid
|
||||
id={`table-${node.blockId}`}
|
||||
rowGap='space.0'
|
||||
@ -50,6 +56,7 @@ const Table = memo(
|
||||
{children}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
(prevProps, nextProps) => isEqual(prevProps.node, nextProps.node)
|
||||
|
@ -9,7 +9,7 @@ function MentionDate({ date, reminder }: { date: string; reminder?: { id: string
|
||||
}, [date]);
|
||||
|
||||
return (
|
||||
<span className={'mention-inline'}>
|
||||
<span className={'mention-inline cursor-text'}>
|
||||
{reminder ? <ReminderSvg className={'mention-icon'} /> : <DateSvg className={'mention-icon'} />}
|
||||
|
||||
<span className={'mention-content'}>{dateFormat}</span>
|
||||
|
@ -41,7 +41,7 @@ function MentionPage({ pageId }: { pageId: string }) {
|
||||
contentEditable={false}
|
||||
>
|
||||
{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'}>
|
||||
|
@ -1,3 +1,4 @@
|
||||
@use "src/styles/mixin.scss";
|
||||
|
||||
.block-element:not([data-block-type="table/cell"]) {
|
||||
@apply my-[4px];
|
||||
@ -301,3 +302,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-block {
|
||||
@include mixin.scrollbar-style;
|
||||
}
|
@ -27,7 +27,7 @@ function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disable
|
||||
try {
|
||||
await onNavigateToView?.(viewId);
|
||||
} catch (e) {
|
||||
notify.error(t('publish.hasNotBeenPublished'));
|
||||
notify.default(t('publish.hasNotBeenPublished'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -72,7 +72,10 @@ function MoreActions() {
|
||||
<div className={'flex w-[240px] flex-col gap-2 px-2 py-2'}>
|
||||
{actions.map((action, index) => (
|
||||
<button
|
||||
onClick={action.onClick}
|
||||
onClick={() => {
|
||||
action.onClick();
|
||||
handleClose();
|
||||
}}
|
||||
key={index}
|
||||
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'
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { usePublishContext } from '@/application/publish';
|
||||
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 React, { useCallback, useMemo } from 'react';
|
||||
import OutlinePopover from '@/components/publish/outline/OutlinePopover';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Breadcrumb from './Breadcrumb';
|
||||
import { ReactComponent as Logo } from '@/assets/logo.svg';
|
||||
import MoreActions from './MoreActions';
|
||||
@ -18,6 +19,7 @@ export function PublishViewHeader({
|
||||
openDrawer: boolean;
|
||||
drawerWidth: number;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const viewMeta = usePublishContext()?.viewMeta;
|
||||
const crumbs = useMemo(() => {
|
||||
const ancestors = viewMeta?.ancestor_views.slice(1) || [];
|
||||
@ -95,9 +97,11 @@ export function PublishViewHeader({
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<MoreActions />
|
||||
<Divider orientation={'vertical'} className={'mx-2'} flexItem />
|
||||
<Tooltip title={t('publish.downloadApp')}>
|
||||
<button onClick={openOrDownload}>
|
||||
<Logo className={'h-6 w-6'} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,10 +4,11 @@ import SearchInput from '@/components/publish/outline/SearchInput';
|
||||
import { filterViews } from '@/components/publish/outline/utils';
|
||||
import { CircularProgress } from '@mui/material';
|
||||
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 { t } = useTranslation();
|
||||
const [children, setChildren] = React.useState<PublishViewInfo[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -45,14 +46,16 @@ function Outline({ viewMeta }: { viewMeta?: PublishViewInfo }) {
|
||||
<SearchInput onSearch={handleSearch} />
|
||||
</div>
|
||||
|
||||
{hasChildren && (
|
||||
{hasChildren ? (
|
||||
<div className={'flex w-full flex-1 flex-col'}>
|
||||
{children
|
||||
.filter((view) => view.layout === ViewLayout.Document)
|
||||
.map((view: PublishViewInfo) => (
|
||||
<OutlineItem key={view.view_id} view={view} />
|
||||
<OutlineItem width={width} key={view.view_id} view={view} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={'flex w-full flex-1 items-center justify-center text-text-caption'}>{t('noPagesInside')}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -55,7 +55,7 @@ function OutlineDrawer({ open, width, onClose }: { open: boolean; width: number;
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={'flex flex-1 flex-col overflow-y-auto px-4 pb-4'}>
|
||||
<Outline viewMeta={viewMeta} />
|
||||
<Outline width={width} viewMeta={viewMeta} />
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
@ -6,12 +6,15 @@ import React, { useCallback, useContext } from 'react';
|
||||
import { ReactComponent as ChevronDownIcon } from '@/assets/chevron_down.svg';
|
||||
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 getIcon = useCallback(() => {
|
||||
if (isExpanded) {
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
paddingLeft: 1.125 * level + 'rem',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsExpanded(false);
|
||||
}}
|
||||
@ -23,6 +26,9 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
|
||||
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
paddingLeft: 1.125 * level + 'rem',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsExpanded(true);
|
||||
}}
|
||||
@ -30,24 +36,21 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
|
||||
<ChevronDownIcon className={'h-4 w-4 -rotate-90 transform'} />
|
||||
</button>
|
||||
);
|
||||
}, [isExpanded]);
|
||||
}, [isExpanded, level]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigateToView = useContext(PublishContext)?.toView;
|
||||
const renderItem = (item: PublishViewInfo) => {
|
||||
const { icon, layout, name, view_id } = item;
|
||||
const hasChildren = Boolean(item.child_views?.length);
|
||||
|
||||
return (
|
||||
<div className={'flex h-fit flex-col gap-2'}>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: hasChildren ? '0' : '1.125rem',
|
||||
width: width - 32,
|
||||
}}
|
||||
className={'flex h-fit flex-col gap-2'}
|
||||
>
|
||||
<div
|
||||
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}
|
||||
@ -59,7 +62,10 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
|
||||
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={'flex-1 truncate'}>{name}</div>
|
||||
@ -69,23 +75,20 @@ function OutlineItem({ view }: { view: PublishViewInfo }) {
|
||||
);
|
||||
};
|
||||
|
||||
const hasChildren = Boolean(view.child_views?.length);
|
||||
|
||||
return (
|
||||
<div className={'flex h-fit w-full flex-col'}>
|
||||
<div className={'flex h-fit flex-col'}>
|
||||
{renderItem(view)}
|
||||
<div
|
||||
className={'flex transform flex-col gap-2 transition-all'}
|
||||
style={{
|
||||
height: isExpanded && view.child_views?.length ? 'auto' : 0,
|
||||
opacity: isExpanded && view.child_views?.length ? 1 : 0,
|
||||
marginLeft: hasChildren ? '1.125rem' : '2.25rem',
|
||||
}}
|
||||
>
|
||||
{view.child_views
|
||||
?.filter((view) => view.layout === ViewLayout.Document)
|
||||
?.map((item, index) => (
|
||||
<OutlineItem key={index} view={item} />
|
||||
<OutlineItem level={level + 1} width={width} key={index} view={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@ export function OutlinePopover({
|
||||
onMouseLeave={onMouseLeave}
|
||||
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
|
||||
style={{
|
||||
position: 'sticky',
|
||||
|
@ -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]'
|
||||
}
|
||||
>
|
||||
<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>}
|
||||
</div>
|
||||
</div>
|
||||
|
3
frontend/appflowy_web_app/src/utils/copy.ts
Normal file
3
frontend/appflowy_web_app/src/utils/copy.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export async function copyTextToClipboard(text: string) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
1
frontend/appflowy_web_app/src/vite-env.d.ts
vendored
1
frontend/appflowy_web_app/src/vite-env.d.ts
vendored
@ -15,6 +15,7 @@ interface Window {
|
||||
error: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
clear: () => void;
|
||||
default: (message: string) => void;
|
||||
warning: (message: string) => void;
|
||||
};
|
||||
}
|
||||
|
@ -1378,6 +1378,9 @@
|
||||
"imageUploadFailed": "Image upload failed",
|
||||
"errorCode": "Error code"
|
||||
},
|
||||
"math": {
|
||||
"copiedToPasteBoard": "The math equation has been copied to the clipboard"
|
||||
},
|
||||
"urlPreview": {
|
||||
"copiedToPasteBoard": "The link has been copied to the clipboard",
|
||||
"convertToLink": "Convert to embed link"
|
||||
@ -2037,7 +2040,8 @@
|
||||
"publish": {
|
||||
"hasNotBeenPublished": "This page hasn't been published yet",
|
||||
"reportPage": "Report page",
|
||||
"databaseHasNotBeenPublished": "This database hasn't been published yet",
|
||||
"createdWith": "Created with"
|
||||
"databaseHasNotBeenPublished": "Publishing a database is not supported yet.",
|
||||
"createdWith": "Created with",
|
||||
"downloadApp": "Download AppFlowy"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user