fix: lazy load

This commit is contained in:
Kilu 2024-07-29 23:11:09 +08:00
parent 38784b9d73
commit d6e339158c
33 changed files with 171 additions and 134 deletions

View File

@ -31,7 +31,6 @@
<body id="body">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
@ -64,8 +63,7 @@
}
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
</body>
</html>

View File

@ -49,6 +49,7 @@
"emoji-regex": "^10.2.1",
"events": "^3.3.0",
"google-protobuf": "^3.15.12",
"highlight.js": "^11.10.0",
"i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-resources-to-backend": "^1.1.4",

View File

@ -80,6 +80,9 @@ dependencies:
google-protobuf:
specifier: ^3.15.12
version: 3.21.2
highlight.js:
specifier: ^11.10.0
version: 11.10.0
i18next:
specifier: ^22.4.10
version: 22.5.1
@ -7068,6 +7071,11 @@ packages:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
dev: true
/highlight.js@11.10.0:
resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
engines: {node: '>=12.0.0'}
dev: false
/hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:

View File

@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z"
stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5V8L10 9" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 2.5L13.5 4.5" stroke="currentColor" stroke-linecap="round"/>
<path d="M4.5 2.5L2.5 4.5" stroke="currentColor" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 562 B

View File

@ -10,7 +10,7 @@ const defaultProps: Partial<PopoverComponentProps> = {
},
};
function Popover({ children, ...props }: PopoverComponentProps) {
export function Popover({ children, ...props }: PopoverComponentProps) {
return (
<PopoverComponent {...defaultProps} {...props}>
{children}

View File

@ -1,4 +1,2 @@
import { lazy } from 'react';
export const RichTooltip = lazy(() => import('./RichTooltip'));
export const Popover = lazy(() => import('./Popover'));
export * from './Popover';
export * from './RichTooltip';

View File

@ -2,8 +2,9 @@ import { clearData } from '@/application/db';
import { EventType, on } from '@/application/session';
import { isTokenValid } from '@/application/session/token';
import { useAppLanguage } from '@/components/app/useAppLanguage';
import { LoginModal } from '@/components/login';
import { useSnackbar } from 'notistack';
import React, { createContext, useEffect, useState } from 'react';
import React, { createContext, useCallback, useEffect, useState } from 'react';
import { AFService, AFServiceConfig } from '@/application/services/services.type';
import { getService } from '@/application/services';
import { InfoSnackbarProps } from '@/components/_shared/notify';
@ -26,6 +27,7 @@ export const AFConfigContext = createContext<
service: AFService | undefined;
isAuthenticated: boolean;
currentUser?: User;
openLoginModal: (redirectTo?: string) => void;
}
| undefined
>(undefined);
@ -35,6 +37,13 @@ function AppConfig({ children }: { children: React.ReactNode }) {
const [service, setService] = useState<AFService>();
const [isAuthenticated, setIsAuthenticated] = React.useState<boolean>(isTokenValid());
const [currentUser, setCurrentUser] = React.useState<User>();
const [loginOpen, setLoginOpen] = React.useState(false);
const [loginCompletedRedirectTo, setLoginCompletedRedirectTo] = React.useState<string>('');
const openLoginModal = useCallback((redirectTo?: string) => {
setLoginOpen(true);
setLoginCompletedRedirectTo(redirectTo || '');
}, []);
useEffect(() => {
return on(EventType.SESSION_VALID, () => {
@ -135,9 +144,19 @@ function AppConfig({ children }: { children: React.ReactNode }) {
service,
isAuthenticated,
currentUser,
openLoginModal,
}}
>
{children}
{loginOpen && (
<LoginModal
redirectTo={loginCompletedRedirectTo}
open={loginOpen}
onClose={() => {
setLoginOpen(false);
}}
/>
)}
</AFConfigContext.Provider>
);
}

View File

@ -14,9 +14,6 @@ export function Calendar() {
toolbar: (props) => <Toolbar {...props} emptyEvents={emptyEvents} />,
eventWrapper: Event,
}}
style={{
marginBottom: '24px',
}}
events={events}
views={['month']}
localizer={localizer}

View File

@ -30,6 +30,7 @@ $today-highlight-bg: transparent;
.rbc-month-view {
border: none;
.rbc-month-row {
border: 1px solid var(--line-divider);
border-top: none;

View File

@ -2,7 +2,7 @@ import { FieldType } from '@/application/database-yjs';
import { useDateTypeCellDispatcher } from '@/components/database/components/cell/Cell.hooks';
import { CellProps, DateTimeCell as DateTimeCellType } from '@/application/database-yjs/cell.type';
import React, { useMemo } from 'react';
import { ReactComponent as ReminderSvg } from '$icons/16x/clock_alarm.svg';
import { ReactComponent as ReminderSvg } from '@/assets/clock_alarm.svg';
export function DateTimeCell({ cell, fieldId, style, placeholder }: CellProps<DateTimeCellType>) {
const { getDateTimeStr } = useDateTypeCellDispatcher(fieldId);

View File

@ -4,7 +4,7 @@ import Cell from '@/components/database/components/cell/Cell';
import React, { CSSProperties, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
function CardField({ rowId, fieldId }: { rowId: string; fieldId: string; index: number }) {
export function CardField({ rowId, fieldId }: { rowId: string; fieldId: string; index: number }) {
const { t } = useTranslation();
const { field } = useFieldSelector(fieldId);
const cell = useCellSelector({

View File

@ -3,39 +3,49 @@ import { useEditorContext } from '@/components/editor/EditorContext';
import { useCallback, useEffect } from 'react';
import { ReactEditor, useSlateStatic } from 'slate-react';
import { Element as SlateElement, Transforms } from 'slate';
const Prism = window.Prism;
const hljs = window.hljs;
import Prism from 'prismjs';
export function useCodeBlock(node: CodeNode) {
const language = node.data.language;
const editor = useSlateStatic() as ReactEditor;
const addCodeGrammars = useEditorContext().addCodeGrammars;
useEffect(() => {
const path = ReactEditor.findPath(editor, node);
let detectedLanguage = language;
void (async () => {
const path = ReactEditor.findPath(editor, node);
let detectedLanguage = language;
if (!language) {
const codeSnippet = editor.string(path);
if (!language) {
const codeSnippet = editor.string(path);
const script = document.createElement('script');
detectedLanguage = hljs.highlightAuto(codeSnippet).language;
}
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js';
document.body.appendChild(script);
const promise = new Promise((resolve) => {
script.onload = () => {
resolve(true);
};
});
const prismLanguage = Prism.languages[detectedLanguage.toLowerCase()];
await promise;
if (!prismLanguage) {
const script = document.createElement('script');
detectedLanguage = window.hljs.highlightAuto(codeSnippet).language || 'plaintext';
}
script.src = `https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/components/prism-${detectedLanguage.toLowerCase()}.min.js`;
document.head.appendChild(script);
script.onload = () => {
const prismLanguage = Prism.languages[detectedLanguage.toLowerCase()];
if (!prismLanguage) {
const script = document.createElement('script');
script.src = `https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/components/prism-${detectedLanguage.toLowerCase()}.min.js`;
document.body.appendChild(script);
script.onload = () => {
addCodeGrammars?.(node.blockId, detectedLanguage);
};
} else {
addCodeGrammars?.(node.blockId, detectedLanguage);
};
} else {
addCodeGrammars?.(node.blockId, detectedLanguage);
}
}
})();
}, [addCodeGrammars, editor, language, node]);
const handleChangeLanguage = useCallback(

View File

@ -1,8 +1,5 @@
import { BaseRange, NodeEntry, Text, Path } from 'slate';
const Prism = window.Prism;
Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/components/';
import Prism, { Grammar } from 'prismjs';
const push_string = (
token: string | Prism.Token,
@ -86,7 +83,7 @@ export const decorateCode = ([node, path]: NodeEntry, language: string) => {
return ranges;
}
const highlightCode = (code: string, language: string) => {
const highlightCode = (code: string, language: Grammar) => {
try {
const tokens = Prism.tokenize(code, language);

View File

@ -1,3 +1 @@
import { lazy } from 'react';
export const MathEquation = lazy(() => import('./MathEquation'));
export * from './MathEquation';

View File

@ -1,5 +1 @@
import { lazy } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
export const Formula = lazy(() => import('./Formula?chunkName=formula'));
export * from './Formula';

View File

@ -1,7 +1,7 @@
import { renderDate } from '@/utils/time';
import React, { useMemo } from 'react';
import { ReactComponent as DateSvg } from '@/assets/date.svg';
import { ReactComponent as ReminderSvg } from '$icons/16x/clock_alarm.svg';
import { ReactComponent as ReminderSvg } from '@/assets/clock_alarm.svg';
function MentionDate({ date, reminder }: { date: string; reminder?: { id: string; option: string } }) {
const dateFormat = useMemo(() => {

View File

@ -3,7 +3,6 @@ import { notify } from '@/components/_shared/notify';
import { AFConfigContext } from '@/components/app/AppConfig';
import { useGlobalCommentContext } from '@/components/global-comment/GlobalComment.hooks';
import ReplyComment from '@/components/global-comment/ReplyComment';
import { LoginModal } from '@/components/login';
import { Button, TextareaAutosize } from '@mui/material';
import CircularProgress from '@mui/material/CircularProgress';
import React, { memo, useCallback, useContext, useEffect, useRef } from 'react';
@ -16,24 +15,19 @@ function AddComment() {
const { t } = useTranslation();
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated;
const openLoginModal = useContext(AFConfigContext)?.openLoginModal;
const createCommentOnPublishView = useContext(AFConfigContext)?.service?.createCommentOnPublishView;
const viewId = useContext(PublishContext)?.viewMeta?.view_id;
const [content, setContent] = React.useState('');
const [loading, setLoading] = React.useState(false);
const [loginOpen, setLoginOpen] = React.useState(false);
const [focus, setFocus] = React.useState(false);
const url = window.location.href + '#addComment';
const ref = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const handleOnFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
const handleOnFocus = () => {
setFocus(true);
if (!isAuthenticated) {
e.preventDefault();
setLoginOpen(true);
}
};
useEffect(() => {
@ -118,6 +112,12 @@ function AddComment() {
ref={inputRef}
autoComplete={'off'}
spellCheck={false}
onMouseDown={() => {
if (!isAuthenticated && openLoginModal) {
openLoginModal(url);
}
}}
readOnly={!isAuthenticated}
onFocus={handleOnFocus}
onBlur={() => setFocus(false)}
value={content}
@ -128,8 +128,9 @@ function AddComment() {
placeholder={t('globalComment.addComment')}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey) {
if (!content) return;
e.preventDefault();
if (!content) return;
void handleSubmit();
}
@ -167,15 +168,6 @@ function AddComment() {
</Button>
</div>
)}
{loginOpen && (
<LoginModal
redirectTo={url}
open={loginOpen}
onClose={() => {
setLoginOpen(false);
}}
/>
)}
</div>
);
}

View File

@ -1,8 +1,9 @@
import { AFConfigContext } from '@/components/app/AppConfig';
import CommentActions from '@/components/global-comment/actions/CommentActions';
import Comment from './Comment';
import { useGlobalCommentContext } from '@/components/global-comment/GlobalComment.hooks';
import ReplyComment from '@/components/global-comment/ReplyComment';
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
import smoothScrollIntoViewIfNeeded from 'smooth-scroll-into-view-if-needed';
export interface CommentWrapProps {
@ -16,6 +17,7 @@ export function CommentWrap({ commentId, isHovered, onHovered }: CommentWrapProp
const comment = useMemo(() => getComment(commentId), [commentId, getComment]);
const ref = React.useRef<HTMLDivElement>(null);
const [highLight, setHighLight] = React.useState(false);
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated;
useEffect(() => {
const hashHasComment = window.location.hash.includes(`#comment-${commentId}`);
@ -29,15 +31,17 @@ export function CommentWrap({ commentId, isHovered, onHovered }: CommentWrapProp
void (async () => {
window.location.hash = '';
await smoothScrollIntoViewIfNeeded(element, {
behavior: 'smooth',
block: 'center',
});
setHighLight(true);
timeout = setTimeout(() => {
setHighLight(false);
}, 10000);
void smoothScrollIntoViewIfNeeded(element, {
behavior: 'smooth',
block: 'center',
});
setHighLight(true);
timeout = setTimeout(() => {
setHighLight(false);
}, 10000);
}, 500);
})();
return () => {
@ -70,7 +74,7 @@ export function CommentWrap({ commentId, isHovered, onHovered }: CommentWrapProp
}}
>
<Comment comment={comment} />
{isHovered && !comment.isDeleted && (
{isHovered && isAuthenticated && !comment.isDeleted && (
<div className={'absolute right-2 top-2'}>
<CommentActions comment={comment} />
</div>

View File

@ -1,3 +1 @@
import { lazy } from 'react';
export const GlobalCommentProvider = lazy(() => import('./GlobaclCommentProvider'));
export * from './GlobalCommentProvider';

View File

@ -6,7 +6,9 @@ import { useTranslation } from 'react-i18next';
function Reaction({ reaction, onClick }: { reaction: ReactionType; onClick: (reaction: ReactionType) => void }) {
const { t } = useTranslation();
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated;
const openLoginModal = useContext(AFConfigContext)?.openLoginModal;
const url = window.location.href + '#comment-' + reaction.commentId;
const reactCount = useMemo(() => {
return reaction.reactUsers.length;
}, [reaction.reactUsers]);
@ -64,7 +66,14 @@ function Reaction({ reaction, onClick }: { reaction: ReactionType; onClick: (rea
>
<div
style={style}
onClick={() => onClick(reaction)}
onClick={() => {
if (!isAuthenticated && openLoginModal) {
openLoginModal(url);
return;
}
onClick(reaction);
}}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
className={

View File

@ -3,7 +3,7 @@ import { Dialog, IconButton } from '@mui/material';
import { Login } from './Login';
import { ReactComponent as CloseIcon } from '@/assets/close.svg';
function LoginModal({ redirectTo, open, onClose }: { redirectTo: string; open: boolean; onClose: () => void }) {
export function LoginModal({ redirectTo, open, onClose }: { redirectTo: string; open: boolean; onClose: () => void }) {
return (
<Dialog open={open} onClose={onClose}>
<div className={'relative px-6'}>

View File

@ -1,5 +1,3 @@
import { lazy } from 'react';
export * from './LoginModal';
export const Login = lazy(() => import('./Login'));
export const LoginModal = lazy(() => import('./LoginModal'));
export * from './Login';

View File

@ -45,7 +45,7 @@ function DatabaseView({ viewMeta, ...props }: DatabaseProps) {
return (
<div
style={{
height: 'calc(100vh - 48px)',
minHeight: 'calc(100vh - 48px)',
}}
className={'relative flex h-full w-full flex-col px-16 max-md:px-4'}
>

View File

@ -86,13 +86,13 @@ export function PublishView({ namespace, publishName }: PublishViewProps) {
/>
<CollabView doc={doc} />
<Suspense fallback={<ComponentLoading />}>
<GlobalCommentProvider />
</Suspense>
{doc && (
<Suspense fallback={<ComponentLoading />}>
<GlobalCommentProvider />
</Suspense>
)}
</AFScroller>
<Suspense fallback={null}>
{open && <OutlineDrawer width={drawerWidth} open={open} onClose={() => setOpen(false)} />}
</Suspense>
{open && <OutlineDrawer width={drawerWidth} open={open} onClose={() => setOpen(false)} />}
</div>
</PublishProvider>
);

View File

@ -66,28 +66,26 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
className={'appflowy-top-bar sticky top-0 z-10 flex px-5'}
>
<div className={'flex w-full items-center justify-between gap-2 overflow-hidden'}>
<Suspense fallback={null}>
{!openDrawer && openPopover && (
<OutlinePopover
{!openDrawer && openPopover && (
<OutlinePopover
onMouseEnter={handleOpenPopover}
onMouseLeave={debounceClosePopover}
open={openPopover}
onClose={debounceClosePopover}
>
<IconButton
className={'hidden'}
onClick={() => {
setOpenPopover(false);
onOpenDrawer();
}}
onMouseEnter={handleOpenPopover}
onMouseLeave={debounceClosePopover}
open={openPopover}
onClose={debounceClosePopover}
>
<IconButton
className={'hidden'}
onClick={() => {
setOpenPopover(false);
onOpenDrawer();
}}
onMouseEnter={handleOpenPopover}
onMouseLeave={debounceClosePopover}
>
<SideOutlined className={'h-4 w-4'} />
</IconButton>
</OutlinePopover>
)}
</Suspense>
<SideOutlined className={'h-4 w-4'} />
</IconButton>
</OutlinePopover>
)}
<div className={'h-full flex-1 overflow-hidden'}>
<Breadcrumb crumbs={crumbs} />

View File

@ -6,7 +6,7 @@ import { useSearchParams } from 'react-router-dom';
import { useDuplicate } from '@/components/publish/header/duplicate/useDuplicate';
import DuplicateModal from '@/components/publish/header/duplicate/DuplicateModal';
function Duplicate() {
export function Duplicate() {
const { t } = useTranslation();
const { loginOpen, duplicateOpen, handleDuplicateClose, handleLoginClose, url } = useDuplicate();
const [search, setSearch] = useSearchParams();

View File

@ -1,3 +1 @@
import { lazy } from 'react';
export const Duplicate = lazy(() => import('./Duplicate'));
export * from './Duplicate';

View File

@ -7,7 +7,7 @@ import { createHotKeyLabel, HOT_KEY_NAME } from '@/utils/hotkeys';
import { Drawer, IconButton, Tooltip } from '@mui/material';
import { useTranslation } from 'react-i18next';
function OutlineDrawer({ open, width, onClose }: { open: boolean; width: number; onClose: () => void }) {
export function OutlineDrawer({ open, width, onClose }: { open: boolean; width: number; onClose: () => void }) {
const { t } = useTranslation();
const viewMeta = usePublishContext()?.viewMeta;

View File

@ -1,4 +1,2 @@
import { lazy } from 'react';
export const OutlineDrawer = lazy(() => import('./OutlineDrawer'));
export const OutlinePopover = lazy(() => import('./OutlinePopover'));
export * from './OutlinePopover';
export * from './OutlineDrawer';

View File

@ -1,6 +1,7 @@
import { usePublishContext } from '@/application/publish';
import { EditorLayoutStyle } from '@/components/editor/EditorContext';
import { ViewMetaCover } from '@/components/view-meta';
import { getFontFamily } from '@/utils/font';
import { useEffect, useMemo } from 'react';
export function useViewMeta() {
@ -21,7 +22,7 @@ export function useViewMeta() {
lineHeightLayout: extra?.lineHeightLayout,
};
}, [extra]);
const layout = viewMeta?.layout;
const style = useMemo(() => {
const fontSizeMap = {
@ -56,11 +57,7 @@ export function useViewMeta() {
useEffect(() => {
if (!layoutStyle.font) return;
void window.WebFont?.load({
google: {
families: [layoutStyle.font],
},
});
void getFontFamily(layoutStyle.font);
}, [layoutStyle.font]);
const icon = viewMeta?.icon || undefined;

View File

@ -7,10 +7,22 @@ export function getFontFamily(attribute: string) {
return fontFamily;
}
window.WebFont?.load({
google: {
families: [fontFamily],
},
});
void (async () => {
const script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js';
document.body.appendChild(script);
await new Promise((resolve) => {
script.onload = () => {
resolve(true);
};
});
window.WebFont?.load({
google: {
families: [fontFamily],
},
});
})();
return fontFamily;
}

View File

@ -114,7 +114,10 @@ export default defineConfig({
id.includes('/y-indexeddb@') ||
id.includes('/dexie') ||
id.includes('/redux') ||
id.includes('/react-custom-scrollbars')
id.includes('/react-custom-scrollbars') ||
id.includes('/dayjs') ||
id.includes('/smooth-scroll-into-view-if-needed') ||
id.includes('/react-virtualized-auto-sizer')
) {
return 'common';
}