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 { 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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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={{
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -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>
|
||||||
|
@ -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'}>
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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'));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -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'
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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',
|
||||||
|
@ -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>
|
||||||
|
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;
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user