mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
web: improve web app loading performance by 10x (#5845)
* web: improve web app loading performance by 10x * web: update test snapshots
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=0">
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=1690887574068">
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=0">
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=1690887574068">
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=0">
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=1690887574068">
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
4986
apps/web/package-lock.json
generated
4986
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@swc/core": "1.3.61",
|
||||
"@swc/core": "^1.5.24",
|
||||
"@swc/plugin-react-remove-properties": "^2.0.4",
|
||||
"@trpc/server": "10.38.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/event-source-polyfill": "^1.0.1",
|
||||
@@ -104,8 +105,8 @@
|
||||
"@types/react-scroll-sync": "^0.9.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/wicg-file-system-access": "^2020.9.6",
|
||||
"@vitejs/plugin-react-swc": "3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"better-sqlite3-multiple-ciphers": "^9.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
@@ -116,14 +117,13 @@
|
||||
"ip": "^1.1.8",
|
||||
"lorem-ipsum": "^2.0.4",
|
||||
"otplib": "^12.0.1",
|
||||
"rollup": "^3.29.4",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"swc-plugin-react-remove-properties": "^0.1.4",
|
||||
"vite": "^4.5.0",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
"vite-plugin-pwa": "^0.16.3",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.34.6",
|
||||
"rollup": "^4.18.0",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"vite": "^5.2.12",
|
||||
"vite-plugin-env-compatible": "^2.0.1",
|
||||
"vite-plugin-pwa": "^0.20.0",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vitest": "^1.6.0",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
|
||||
@@ -22,10 +22,8 @@ import { Box, Flex } from "@theme-ui/components";
|
||||
import { ScopedThemeProvider } from "./components/theme-provider";
|
||||
import useMobile from "./hooks/use-mobile";
|
||||
import useTablet from "./hooks/use-tablet";
|
||||
import useDatabase from "./hooks/use-database";
|
||||
import { useStore } from "./stores/app-store";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { ViewLoader } from "./components/loaders/view-loader";
|
||||
import NavigationMenu from "./components/navigation-menu";
|
||||
import StatusBar from "./components/status-bar";
|
||||
import { EditorLoader } from "./components/loaders/editor-loader";
|
||||
@@ -38,12 +36,13 @@ import {
|
||||
PanelResizeHandle,
|
||||
ImperativePanelHandle
|
||||
} from "react-resizable-panels";
|
||||
import GlobalMenuWrapper from "./components/global-menu-wrapper";
|
||||
|
||||
new WebExtensionRelay();
|
||||
|
||||
const GlobalMenuWrapper = React.lazy(
|
||||
() => import("./components/global-menu-wrapper")
|
||||
);
|
||||
// const GlobalMenuWrapper = React.lazy(
|
||||
// () => import("./components/global-menu-wrapper")
|
||||
// );
|
||||
const AppEffects = React.lazy(() => import("./app-effects"));
|
||||
const MobileAppEffects = React.lazy(() => import("./app-effects.mobile"));
|
||||
const HashRouter = React.lazy(() => import("./components/hash-router"));
|
||||
@@ -51,26 +50,24 @@ const HashRouter = React.lazy(() => import("./components/hash-router"));
|
||||
function App() {
|
||||
const isMobile = useMobile();
|
||||
const [show, setShow] = useState(true);
|
||||
const [isAppLoaded] = useDatabase();
|
||||
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAppLoaded && (
|
||||
<Suspense fallback={<div style={{ display: "none" }} />}>
|
||||
<div id="menu-wrapper">
|
||||
<GlobalMenuWrapper />
|
||||
</div>
|
||||
<AppEffects setShow={setShow} />
|
||||
{isMobile && (
|
||||
<MobileAppEffects
|
||||
sliderId="slider"
|
||||
overlayId="overlay"
|
||||
setShow={setShow}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
)}
|
||||
<Suspense fallback={<div style={{ display: "none" }} />}>
|
||||
<div id="menu-wrapper">
|
||||
<GlobalMenuWrapper />
|
||||
</div>
|
||||
<AppEffects setShow={setShow} />
|
||||
{isMobile && (
|
||||
<MobileAppEffects
|
||||
sliderId="slider"
|
||||
overlayId="overlay"
|
||||
setShow={setShow}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
|
||||
<Flex
|
||||
id="app"
|
||||
bg="background"
|
||||
@@ -78,13 +75,9 @@ function App() {
|
||||
sx={{ overflow: "hidden", flexDirection: "column", height: "100%" }}
|
||||
>
|
||||
{isMobile ? (
|
||||
<MobileAppContents isAppLoaded={isAppLoaded} />
|
||||
<MobileAppContents />
|
||||
) : (
|
||||
<DesktopAppContents
|
||||
isAppLoaded={isAppLoaded}
|
||||
setShow={setShow}
|
||||
show={show}
|
||||
/>
|
||||
<DesktopAppContents setShow={setShow} show={show} />
|
||||
)}
|
||||
<Toaster containerClassName="toasts-container" />
|
||||
</Flex>
|
||||
@@ -121,15 +114,10 @@ function SuspenseLoader<TComponent extends React.JSXElementConstructor<any>>({
|
||||
}
|
||||
|
||||
type DesktopAppContentsProps = {
|
||||
isAppLoaded: boolean;
|
||||
show: boolean;
|
||||
setShow: (show: boolean) => void;
|
||||
};
|
||||
function DesktopAppContents({
|
||||
isAppLoaded,
|
||||
show,
|
||||
setShow
|
||||
}: DesktopAppContentsProps) {
|
||||
function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
|
||||
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||
const isTablet = useTablet();
|
||||
const [isNarrow, setIsNarrow] = useState(isTablet || false);
|
||||
@@ -194,7 +182,7 @@ function DesktopAppContents({
|
||||
borderRight: "1px solid var(--separator)"
|
||||
}}
|
||||
>
|
||||
{isAppLoaded && <CachedRouter />}
|
||||
<CachedRouter />
|
||||
</ScopedThemeProvider>
|
||||
</Panel>
|
||||
<PanelResizeHandle className="panel-resize-handle" />
|
||||
@@ -208,7 +196,7 @@ function DesktopAppContents({
|
||||
bg: "background"
|
||||
}}
|
||||
>
|
||||
{isAppLoaded && <HashRouter />}
|
||||
{<HashRouter />}
|
||||
</Flex>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
@@ -218,7 +206,7 @@ function DesktopAppContents({
|
||||
);
|
||||
}
|
||||
|
||||
function MobileAppContents({ isAppLoaded }: { isAppLoaded: boolean }) {
|
||||
function MobileAppContents() {
|
||||
return (
|
||||
<FlexScrollContainer
|
||||
id="slider"
|
||||
@@ -257,11 +245,7 @@ function MobileAppContents({ isAppLoaded }: { isAppLoaded: boolean }) {
|
||||
width: "100vw"
|
||||
}}
|
||||
>
|
||||
<SuspenseLoader
|
||||
condition={isAppLoaded}
|
||||
component={CachedRouter}
|
||||
fallback={<ViewLoader />}
|
||||
/>
|
||||
<CachedRouter />
|
||||
<Box
|
||||
id="overlay"
|
||||
sx={{
|
||||
@@ -290,7 +274,7 @@ function MobileAppContents({ isAppLoaded }: { isAppLoaded: boolean }) {
|
||||
<SuspenseLoader
|
||||
fallback={<EditorLoader />}
|
||||
component={HashRouter}
|
||||
condition={isAppLoaded}
|
||||
condition={true}
|
||||
/>
|
||||
</Flex>
|
||||
</FlexScrollContainer>
|
||||
|
||||
@@ -18,11 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./polyfills";
|
||||
import "@notesnook/core/dist/types";
|
||||
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
||||
import Config from "./utils/config";
|
||||
|
||||
import { initializeLogger, logger } from "./utils/logger";
|
||||
// import { initializeLogger, logger } from "./utils/logger";
|
||||
import type { AuthProps } from "./views/auth";
|
||||
import { initializeFeatureChecks } from "./utils/feature-check";
|
||||
|
||||
@@ -102,7 +101,7 @@ const sessionExpiryExceptions: Routes[] = [
|
||||
|
||||
function getRoute(): RouteWithPath<AuthProps> | RouteWithPath {
|
||||
const path = getCurrentPath() as Routes;
|
||||
logger.info(`Getting route for path: ${path}`);
|
||||
// logger.info(`Getting route for path: ${path}`);
|
||||
|
||||
const signup = redirectToRegistration(path);
|
||||
const sessionExpired = isSessionExpired(path);
|
||||
@@ -129,7 +128,7 @@ function redirectToRegistration(path: Routes): RouteWithPath<AuthProps> | null {
|
||||
function isSessionExpired(path: Routes): RouteWithPath<AuthProps> | null {
|
||||
const isSessionExpired = Config.get("sessionExpired", false);
|
||||
if (isSessionExpired && !sessionExpiryExceptions.includes(path)) {
|
||||
logger.info(`User session has expired. Routing to /sessionexpired`);
|
||||
// logger.info(`User session has expired. Routing to /sessionexpired`);
|
||||
|
||||
window.history.replaceState(
|
||||
{},
|
||||
@@ -143,7 +142,10 @@ function isSessionExpired(path: Routes): RouteWithPath<AuthProps> | null {
|
||||
|
||||
export async function init() {
|
||||
await initializeFeatureChecks();
|
||||
await initializeLogger();
|
||||
|
||||
await await import("./utils/logger").then(({ initializeLogger }) =>
|
||||
initializeLogger()
|
||||
);
|
||||
|
||||
const { path, route } = getRoute();
|
||||
return { ...route, path };
|
||||
|
||||
@@ -25,7 +25,7 @@ import { database } from "@notesnook/common";
|
||||
import { createDialect } from "./sqlite";
|
||||
import { isFeatureSupported } from "../utils/feature-check";
|
||||
import { generatePassword } from "../utils/password-generator";
|
||||
import { deriveKey } from "../interfaces/key-store";
|
||||
import { deriveKey, useKeyStore } from "../interfaces/key-store";
|
||||
import { logManager } from "@notesnook/core/dist/logger";
|
||||
|
||||
const db = database;
|
||||
@@ -34,7 +34,6 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
|
||||
const { FileStorage } = await import("../interfaces/fs");
|
||||
const { Compressor } = await import("../utils/compressor");
|
||||
const { useKeyStore } = await import("../interfaces/key-store");
|
||||
|
||||
let databaseKey = await useKeyStore.getState().getValue("databaseKey");
|
||||
if (!databaseKey) {
|
||||
@@ -59,7 +58,8 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
|
||||
database.setup({
|
||||
sqliteOptions: {
|
||||
dialect: (name, init) => createDialect(name, true, init),
|
||||
dialect: (name, init) =>
|
||||
createDialect(persistence === "memory" ? ":memory:" : name, true, init),
|
||||
...(IS_DESKTOP_APP || isFeatureSupported("opfs")
|
||||
? { journalMode: "WAL", lockingMode: "exclusive" }
|
||||
: {
|
||||
@@ -98,7 +98,9 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
// });
|
||||
// }
|
||||
|
||||
console.log("loading db");
|
||||
await db.init();
|
||||
console.log("db loaded");
|
||||
|
||||
window.addEventListener("beforeunload", async () => {
|
||||
if (IS_DESKTOP_APP) {
|
||||
|
||||
@@ -128,10 +128,7 @@ export default function TabsView() {
|
||||
return (
|
||||
<>
|
||||
{!hasNativeTitlebar ? (
|
||||
ReactDOM.createPortal(
|
||||
<EditorActionBar />,
|
||||
document.getElementById("titlebar-portal-container")!
|
||||
)
|
||||
<EditorActionBarPortal />
|
||||
) : (
|
||||
<Flex sx={{ px: 1 }}>
|
||||
<EditorActionBar />
|
||||
@@ -153,12 +150,7 @@ export default function TabsView() {
|
||||
<PanelGroup direction="horizontal" autoSaveId={"editor-panels"}>
|
||||
<Panel id="editor-panel" className="editor-pane" order={1}>
|
||||
{sessions.map((session) => (
|
||||
<Freeze
|
||||
key={session.id}
|
||||
freeze={
|
||||
session.needsHydration || session.id !== activeSessionId
|
||||
}
|
||||
>
|
||||
<Freeze key={session.id} freeze={session.id !== activeSessionId}>
|
||||
{session.type === "locked" ? (
|
||||
<UnlockNoteView session={session} />
|
||||
) : session.type === "conflicted" || session.type === "diff" ? (
|
||||
@@ -233,7 +225,8 @@ const MemoizedEditorView = React.memo(
|
||||
EditorView,
|
||||
(prev, next) =>
|
||||
prev.session.id === next.session.id &&
|
||||
prev.session.type === next.session.type
|
||||
prev.session.type === next.session.type &&
|
||||
prev.session.needsHydration === next.session.needsHydration
|
||||
);
|
||||
function EditorView({
|
||||
session
|
||||
@@ -302,6 +295,12 @@ function EditorView({
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session.needsHydration && session.content) {
|
||||
editor?.updateContent(session.content.data);
|
||||
}
|
||||
}, [editor, session.needsHydration]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={root}
|
||||
@@ -459,6 +458,7 @@ export function Editor(props: EditorProps) {
|
||||
<EditorChrome {...props}>
|
||||
<Tiptap
|
||||
id={id}
|
||||
isHydrating={!!session.needsHydration}
|
||||
nonce={nonce}
|
||||
readonly={readonly}
|
||||
content={content}
|
||||
@@ -466,7 +466,8 @@ export function Editor(props: EditorProps) {
|
||||
corsHost: Config.get("corsProxy", "https://cors.notesnook.com")
|
||||
}}
|
||||
onLoad={(editor) => {
|
||||
restoreSelection(editor, id);
|
||||
editor = editor || useEditorManager.getState().getEditor(id)?.editor;
|
||||
if (editor) restoreSelection(editor, id);
|
||||
restoreScrollPosition(session);
|
||||
}}
|
||||
onSelectionChange={({ from, to }) =>
|
||||
@@ -773,8 +774,10 @@ function restoreScrollPosition(session: EditorSession) {
|
||||
}
|
||||
|
||||
function restoreSelection(editor: IEditor, id: string) {
|
||||
editor.focus({
|
||||
position: Config.get(`${id}:selection`, { from: 0, to: 0 })
|
||||
setTimeout(() => {
|
||||
editor.focus({
|
||||
position: Config.get(`${id}:selection`, { from: 0, to: 0 })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -826,3 +829,9 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EditorActionBarPortal() {
|
||||
const container = document.getElementById("titlebar-portal-container");
|
||||
if (!container) return null;
|
||||
return ReactDOM.createPortal(<EditorActionBar />, container);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export type OnChangeHandler = (
|
||||
type TipTapProps = {
|
||||
id: string;
|
||||
editorContainer: () => HTMLElement | undefined;
|
||||
onLoad?: (editor: IEditor) => void;
|
||||
onLoad?: (editor?: IEditor) => void;
|
||||
onChange?: OnChangeHandler;
|
||||
onContentChange?: () => void;
|
||||
onSelectionChange?: (range: { from: number; to: number }) => void;
|
||||
@@ -371,8 +371,11 @@ function TipTap(props: TipTapProps) {
|
||||
function TiptapWrapper(
|
||||
props: PropsWithChildren<
|
||||
Omit<TipTapProps, "editorContainer" | "theme" | "fontSize" | "fontFamily">
|
||||
>
|
||||
> & {
|
||||
isHydrating?: boolean;
|
||||
}
|
||||
) {
|
||||
const { onLoad, isHydrating } = props;
|
||||
const theme = useThemeStore((store) =>
|
||||
store.colorScheme === "dark" ? store.darkTheme : store.lightTheme
|
||||
);
|
||||
@@ -397,22 +400,35 @@ function TiptapWrapper(
|
||||
theme.scopes.base.primary.paragraph;
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHydrating) {
|
||||
onLoad?.();
|
||||
containerRef.current
|
||||
?.querySelector(".editor-loading-container")
|
||||
?.classList.add("hidden");
|
||||
}
|
||||
}, [isHydrating]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
".tiptap.ProseMirror": { pb: 150 }
|
||||
".tiptap.ProseMirror": { pb: 150 },
|
||||
".editor-container": { opacity: isHydrating ? 0 : 1 },
|
||||
".editor-loading-container.hidden": { display: "none" }
|
||||
}}
|
||||
>
|
||||
<TipTap
|
||||
{...props}
|
||||
onLoad={(editor) => {
|
||||
props.onLoad?.(editor);
|
||||
containerRef.current
|
||||
?.querySelector(".editor-loading-container")
|
||||
?.remove();
|
||||
if (!isHydrating) {
|
||||
onLoad?.(editor);
|
||||
containerRef.current
|
||||
?.querySelector(".editor-loading-container")
|
||||
?.classList.add("hidden");
|
||||
}
|
||||
}}
|
||||
editorContainer={() => {
|
||||
if (editorContainerRef.current) return editorContainerRef.current;
|
||||
|
||||
@@ -17,15 +17,42 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PropsWithChildren } from "react";
|
||||
import { PropsWithChildren, useEffect } from "react";
|
||||
import { ErrorText } from "../error-text";
|
||||
import { BaseThemeProvider } from "../theme-provider";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import {
|
||||
ErrorBoundary as RErrorBoundary,
|
||||
FallbackProps
|
||||
FallbackProps,
|
||||
useErrorBoundary
|
||||
} from "react-error-boundary";
|
||||
import { createDialect } from "../../common/sqlite";
|
||||
import { useKeyStore } from "../../interfaces/key-store";
|
||||
|
||||
export function GlobalErrorHandler(props: PropsWithChildren) {
|
||||
const { showBoundary } = useErrorBoundary();
|
||||
|
||||
useEffect(() => {
|
||||
function handleError(e: ErrorEvent) {
|
||||
const error = new Error(e.message);
|
||||
error.stack = `${e.filename}:${e.lineno}:${e.colno}`;
|
||||
showBoundary(e.error || error);
|
||||
}
|
||||
function handleUnhandledRejection(e: PromiseRejectionEvent) {
|
||||
showBoundary(e.reason);
|
||||
}
|
||||
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
||||
window.addEventListener("error", handleError);
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
"unhandledrejection",
|
||||
handleUnhandledRejection
|
||||
);
|
||||
window.removeEventListener("error", handleError);
|
||||
};
|
||||
}, [showBoundary]);
|
||||
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
export function ErrorBoundary(props: PropsWithChildren) {
|
||||
return (
|
||||
@@ -172,10 +199,9 @@ function getErrorHelp(props: FallbackProps) {
|
||||
action:
|
||||
"This error can only be fixed by wiping & reseting the database. Beware that this will wipe all your data inside the database with no way to recover it later on.",
|
||||
fix: async () => {
|
||||
const { useKeyStore } = await import("../../interfaces/key-store");
|
||||
|
||||
const { createDialect } = await import("../../common/sqlite");
|
||||
await useKeyStore.getState().clear();
|
||||
const dialect = createDialect("notesnook");
|
||||
const dialect = createDialect("notesnook", true);
|
||||
const driver = dialect.createDriver();
|
||||
await driver.delete();
|
||||
resetErrorBoundary();
|
||||
@@ -187,10 +213,9 @@ function getErrorHelp(props: FallbackProps) {
|
||||
action:
|
||||
"This error can only be fixed by wiping & reseting the Key Store and the database.",
|
||||
fix: async () => {
|
||||
const { useKeyStore } = await import("../../interfaces/key-store");
|
||||
|
||||
const { createDialect } = await import("../../common/sqlite");
|
||||
await useKeyStore.getState().clear();
|
||||
const dialect = createDialect("notesnook");
|
||||
const dialect = createDialect("notesnook", true);
|
||||
const driver = dialect.createDriver();
|
||||
await driver.delete();
|
||||
resetErrorBoundary();
|
||||
|
||||
@@ -27,28 +27,6 @@ const Lines = [1, 2].map(() => getRandomArbitrary(40, 90));
|
||||
export const ListLoader = memo(function ListLoader() {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
sx={{ py: 1, alignItems: "center", justifyContent: "center", px: 1 }}
|
||||
>
|
||||
<Box sx={{ height: 38 }}>
|
||||
<Skeleton enableAnimation={false} width={38} height={38} circle />
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
flex: 1,
|
||||
ml: 1,
|
||||
flexDirection: "column",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Box sx={{ height: 14 }}>
|
||||
<Skeleton enableAnimation={false} inline height={14} />
|
||||
</Box>
|
||||
<Box sx={{ mt: 1, height: 10 }}>
|
||||
<Skeleton enableAnimation={false} inline height={10} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{Lines.map((width) => (
|
||||
<Box key={width} sx={{ py: 2, px: 1 }}>
|
||||
<Skeleton
|
||||
|
||||
@@ -31,6 +31,7 @@ function Notice() {
|
||||
if (!notices) return null;
|
||||
return notices.slice().sort((a, b) => a.priority - b.priority)[0];
|
||||
}, [notices]);
|
||||
|
||||
if (!notice) return null;
|
||||
const NoticeData = NoticesData[notice.type];
|
||||
return (
|
||||
|
||||
@@ -57,7 +57,7 @@ function Placeholder(props: PlaceholderProps) {
|
||||
</Flex>
|
||||
|
||||
<Text variant="subBody" sx={{ fontSize: "body", mt: 1 }}>
|
||||
{toTitleCase(syncStatus.type || "syncing")}ing {syncStatus.progress}{" "}
|
||||
{toTitleCase(syncStatus.type || "sync")}ing {syncStatus.progress}{" "}
|
||||
items
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -23,9 +23,9 @@ import { Cross, Check, Loading } from "../../components/icons";
|
||||
import { useStore as useUserStore } from "../../stores/user-store";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { ReactComponent as Rocket } from "../../assets/rocket.svg";
|
||||
import { ReactComponent as WorkAnywhere } from "../../assets/workanywhere.svg";
|
||||
import { ReactComponent as WorkLate } from "../../assets/worklate.svg";
|
||||
import Rocket from "../../assets/rocket.svg?react";
|
||||
import WorkAnywhere from "../../assets/workanywhere.svg?react";
|
||||
import WorkLate from "../../assets/worklate.svg?react";
|
||||
import Field from "../../components/field";
|
||||
import { hardNavigate } from "../../navigation";
|
||||
import { Features } from "./features";
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Text, Flex, Button } from "@theme-ui/components";
|
||||
import { Loading } from "../../components/icons";
|
||||
import { ReactComponent as Nomad } from "../../assets/nomad.svg";
|
||||
import Nomad from "../../assets/nomad.svg?react";
|
||||
import { Period, Plan } from "./types";
|
||||
import { PLAN_METADATA, usePlans } from "./plans";
|
||||
import { useEffect } from "react";
|
||||
|
||||
@@ -46,8 +46,8 @@ import { phone } from "phone";
|
||||
import { db } from "../../common/db";
|
||||
import FileSaver from "file-saver";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
import { ReactComponent as MFA } from "../../assets/mfa.svg";
|
||||
import { ReactComponent as Fallback2FA } from "../../assets/fallback2fa.svg";
|
||||
import MFA from "../../assets/mfa.svg?react";
|
||||
import Fallback2FA from "../../assets/fallback2fa.svg?react";
|
||||
import {
|
||||
Authenticator,
|
||||
StepComponent,
|
||||
|
||||
@@ -27,11 +27,11 @@ import {
|
||||
Github,
|
||||
Loading
|
||||
} from "../components/icons";
|
||||
import { ReactComponent as E2E } from "../assets/e2e.svg";
|
||||
import { ReactComponent as Note } from "../assets/note2.svg";
|
||||
import { ReactComponent as Nomad } from "../assets/nomad.svg";
|
||||
import { ReactComponent as WorkAnywhere } from "../assets/workanywhere.svg";
|
||||
import { ReactComponent as Friends } from "../assets/cause.svg";
|
||||
import E2E from "../assets/e2e.svg?react";
|
||||
import Note from "../assets/note2.svg?react";
|
||||
import Nomad from "../assets/nomad.svg?react";
|
||||
import WorkAnywhere from "../assets/workanywhere.svg?react";
|
||||
import Friends from "../assets/cause.svg?react";
|
||||
import LightUI from "../assets/light1.png";
|
||||
import DarkUI from "../assets/dark1.png";
|
||||
import GooglePlay from "../assets/play.png";
|
||||
|
||||
@@ -17,50 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { initializeDatabase } from "../common/db";
|
||||
import { useErrorBoundary } from "react-error-boundary";
|
||||
import "../utils/analytics";
|
||||
import "../app.css";
|
||||
|
||||
// if (import.meta.env.PROD) {
|
||||
// console.log = () => {};
|
||||
// }
|
||||
|
||||
const memory = {
|
||||
isDatabaseLoaded: false
|
||||
};
|
||||
export default function useDatabase(persistence: "db" | "memory" = "db") {
|
||||
const [isAppLoaded, setIsAppLoaded] = useState(memory.isDatabaseLoaded);
|
||||
const { showBoundary } = useErrorBoundary();
|
||||
|
||||
useEffect(() => {
|
||||
loadDatabase(persistence)
|
||||
.then(() => setIsAppLoaded(true))
|
||||
.catch((e) => showBoundary(e));
|
||||
|
||||
function handleError(e: ErrorEvent) {
|
||||
const error = new Error(e.message);
|
||||
error.stack = `${e.filename}:${e.lineno}:${e.colno}`;
|
||||
showBoundary(e.error || error);
|
||||
}
|
||||
function handleUnhandledRejection(e: PromiseRejectionEvent) {
|
||||
showBoundary(e.reason);
|
||||
}
|
||||
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
||||
window.addEventListener("error", handleError);
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
"unhandledrejection",
|
||||
handleUnhandledRejection
|
||||
);
|
||||
window.removeEventListener("error", handleError);
|
||||
};
|
||||
}, [persistence]);
|
||||
|
||||
return [isAppLoaded];
|
||||
}
|
||||
|
||||
export async function loadDatabase(persistence: "db" | "memory" = "db") {
|
||||
if (memory.isDatabaseLoaded) return;
|
||||
|
||||
|
||||
@@ -38,28 +38,8 @@
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<title>Notesnook</title>
|
||||
|
||||
<style id="theme-colors">
|
||||
#splash {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
<script type="module">
|
||||
import { themeToCSS } from "@notesnook/theme";
|
||||
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
);
|
||||
const root = document.querySelector("html");
|
||||
if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
const theme = window.localStorage.getItem(`theme:${colorScheme}`);
|
||||
if (theme) {
|
||||
const css = themeToCSS(JSON.parse(theme));
|
||||
const stylesheet = document.getElementById("theme-colors");
|
||||
if (stylesheet) stylesheet.innerHTML = css;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
<style id="theme-colors"></style>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
<style>
|
||||
html {
|
||||
overscroll-behavior: none;
|
||||
|
||||
68
apps/web/src/index.ts
Normal file
68
apps/web/src/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./app.css";
|
||||
import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { register } from "./service-worker-registration";
|
||||
import { getServiceWorkerVersion } from "./utils/version";
|
||||
import { register as registerStreamSaver } from "./utils/stream-saver/mitm";
|
||||
import { ThemeDark, ThemeLight, themeToCSS } from "@notesnook/theme";
|
||||
import Config from "./utils/config";
|
||||
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
);
|
||||
const root = document.querySelector("html");
|
||||
if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
const theme =
|
||||
colorScheme === "dark"
|
||||
? Config.get("theme:dark", ThemeDark)
|
||||
: Config.get("theme:light", ThemeLight);
|
||||
const stylesheet = document.getElementById("theme-colors");
|
||||
if (theme) {
|
||||
const css = themeToCSS(theme);
|
||||
if (stylesheet) stylesheet.innerHTML = css;
|
||||
} else stylesheet?.remove();
|
||||
|
||||
if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
// logger.info("Initializing service worker...");
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
register({
|
||||
onUpdate: async (registration: ServiceWorkerRegistration) => {
|
||||
if (!registration.waiting) return;
|
||||
const { formatted } = await getServiceWorkerVersion(registration.waiting);
|
||||
AppEventManager.publish(AppEvents.updateDownloadCompleted, {
|
||||
version: formatted
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
registerStreamSaver();
|
||||
}
|
||||
});
|
||||
|
||||
// window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||
}
|
||||
|
||||
import("./root").then(({ startApp }) => {
|
||||
startApp();
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Routes, init } from "./bootstrap";
|
||||
import { logger } from "./utils/logger";
|
||||
import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { BaseThemeProvider } from "./components/theme-provider";
|
||||
import { register } from "./utils/stream-saver/mitm";
|
||||
import { getServiceWorkerVersion } from "./utils/version";
|
||||
import { ErrorBoundary, ErrorComponent } from "./components/error-boundary";
|
||||
import { TitleBar } from "./components/title-bar";
|
||||
import { desktop } from "./common/desktop-bridge";
|
||||
|
||||
renderApp();
|
||||
|
||||
async function renderApp() {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
|
||||
window.hasNativeTitlebar =
|
||||
!IS_DESKTOP_APP ||
|
||||
!!(await desktop?.integration.desktopIntegration
|
||||
.query()
|
||||
?.then((s) => s.nativeTitlebar));
|
||||
|
||||
try {
|
||||
const { component, props, path } = await init();
|
||||
|
||||
const { useKeyStore } = await import("./interfaces/key-store");
|
||||
await useKeyStore.getState().init();
|
||||
|
||||
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
||||
|
||||
const { default: Component } = await component();
|
||||
const { default: AppLock } = await import("./views/app-lock");
|
||||
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<ErrorBoundary>
|
||||
<BaseThemeProvider
|
||||
onRender={() => document.getElementById("splash")?.remove()}
|
||||
sx={{ bg: "background", flex: 1, overflow: "hidden" }}
|
||||
>
|
||||
<AppLock>
|
||||
<Component route={props?.route || "login:email"} />
|
||||
</AppLock>
|
||||
</BaseThemeProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
} catch (e) {
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const serviceWorkerWhitelist: Routes[] = ["default"];
|
||||
async function initializeServiceWorker() {
|
||||
if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
logger.info("Initializing service worker...");
|
||||
const serviceWorker = await import("./service-worker-registration");
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.register({
|
||||
onUpdate: async (registration: ServiceWorkerRegistration) => {
|
||||
if (!registration.waiting) return;
|
||||
const { formatted } = await getServiceWorkerVersion(
|
||||
registration.waiting
|
||||
);
|
||||
AppEventManager.publish(AppEvents.updateDownloadCompleted, {
|
||||
version: formatted
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
register();
|
||||
}
|
||||
});
|
||||
// window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) import.meta.hot.accept();
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useState } from "react";
|
||||
import EventManager from "@notesnook/core/dist/utils/event-manager";
|
||||
import Config from "../utils/config";
|
||||
import { HashRoute } from "./hash-routes";
|
||||
import { ReplaceParametersInPath } from "./types";
|
||||
import type { HashRoute } from "./hash-routes";
|
||||
import type { ReplaceParametersInPath } from "./types";
|
||||
|
||||
export function navigate(
|
||||
url: string,
|
||||
|
||||
122
apps/web/src/root.tsx
Normal file
122
apps/web/src/root.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { init } from "./bootstrap";
|
||||
// import { logger } from "./utils/logger";
|
||||
// import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { BaseThemeProvider } from "./components/theme-provider";
|
||||
// import { register as registerStreamSaver } from "./utils/stream-saver/mitm";
|
||||
// import { getServiceWorkerVersion } from "./utils/version";
|
||||
import {
|
||||
ErrorBoundary,
|
||||
ErrorComponent,
|
||||
GlobalErrorHandler
|
||||
} from "./components/error-boundary";
|
||||
import { TitleBar } from "./components/title-bar";
|
||||
import { desktop } from "./common/desktop-bridge";
|
||||
// import { register } from "./service-worker-registration";
|
||||
import { useKeyStore } from "./interfaces/key-store";
|
||||
import Config from "./utils/config";
|
||||
|
||||
export async function startApp() {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
|
||||
window.hasNativeTitlebar =
|
||||
!IS_DESKTOP_APP ||
|
||||
!!(await desktop?.integration.desktopIntegration
|
||||
.query()
|
||||
?.then((s) => s.nativeTitlebar));
|
||||
|
||||
try {
|
||||
const { component, props, path } = await init();
|
||||
|
||||
await useKeyStore.getState().init();
|
||||
|
||||
await import("./hooks/use-database").then(({ loadDatabase }) =>
|
||||
loadDatabase(
|
||||
path !== "/sessionexpired" || Config.get("sessionExpired", false)
|
||||
? "db"
|
||||
: "memory"
|
||||
)
|
||||
);
|
||||
|
||||
const { default: Component } = await component();
|
||||
const { default: AppLock } = await import("./views/app-lock");
|
||||
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<ErrorBoundary>
|
||||
<GlobalErrorHandler>
|
||||
<BaseThemeProvider
|
||||
onRender={() => document.getElementById("splash")?.remove()}
|
||||
sx={{ bg: "background", flex: 1, overflow: "hidden" }}
|
||||
>
|
||||
<AppLock>
|
||||
<Component route={props?.route || "login:email"} />
|
||||
</AppLock>
|
||||
</BaseThemeProvider>
|
||||
</GlobalErrorHandler>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
root.render(
|
||||
<>
|
||||
{hasNativeTitlebar ? null : <TitleBar />}
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// const serviceWorkerWhitelist: Routes[] = ["default"];
|
||||
// async function initializeServiceWorker() {
|
||||
// if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
// // logger.info("Initializing service worker...");
|
||||
|
||||
// // If you want your app to work offline and load faster, you can change
|
||||
// // unregister() to register() below. Note this comes with some pitfalls.
|
||||
// // Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
// register({
|
||||
// onUpdate: async (registration: ServiceWorkerRegistration) => {
|
||||
// if (!registration.waiting) return;
|
||||
// const { formatted } = await getServiceWorkerVersion(
|
||||
// registration.waiting
|
||||
// );
|
||||
// AppEventManager.publish(AppEvents.updateDownloadCompleted, {
|
||||
// version: formatted
|
||||
// });
|
||||
// },
|
||||
// onSuccess() {
|
||||
// registerStreamSaver();
|
||||
// }
|
||||
// });
|
||||
// // window.addEventListener("beforeinstallprompt", () => showInstallNotice());
|
||||
// }
|
||||
// }
|
||||
|
||||
if (import.meta.hot) import.meta.hot.accept();
|
||||
@@ -21,7 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { clientsClaim } from "workbox-core";
|
||||
import { ExpirationPlugin } from "workbox-expiration";
|
||||
import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
|
||||
import {
|
||||
precacheAndRoute,
|
||||
createHandlerBoundToURL,
|
||||
cleanupOutdatedCaches
|
||||
} from "workbox-precaching";
|
||||
import { registerRoute } from "workbox-routing";
|
||||
import { StaleWhileRevalidate } from "workbox-strategies";
|
||||
|
||||
@@ -29,15 +33,8 @@ declare var self: ServiceWorkerGlobalScope & typeof globalThis;
|
||||
|
||||
clientsClaim();
|
||||
|
||||
const precacheRoutes = self.__WB_MANIFEST;
|
||||
const filters = [/KaTeX/i, /hack/i, /code-lang-/i];
|
||||
precacheAndRoute(
|
||||
precacheRoutes.filter((route) => {
|
||||
return filters.every(
|
||||
(filter) => !filter.test(typeof route === "string" ? route : route.url)
|
||||
);
|
||||
})
|
||||
);
|
||||
cleanupOutdatedCaches();
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// Set up App Shell-style routing, so that all navigation requests
|
||||
// are fulfilled with your index.html shell. Learn more at
|
||||
|
||||
@@ -20,12 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useEffect } from "react";
|
||||
import { useStore } from "../stores/note-store";
|
||||
import ListContainer from "../components/list-container";
|
||||
import { hashNavigate } from "../navigation";
|
||||
import useNavigate from "../hooks/use-navigate";
|
||||
import Placeholder from "../components/placeholders";
|
||||
import { useSearch } from "../hooks/use-search";
|
||||
import { db } from "../common/db";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
function Home() {
|
||||
const notes = useStore((store) => store.notes);
|
||||
@@ -60,7 +60,7 @@ function Home() {
|
||||
// })();
|
||||
// }, []);
|
||||
|
||||
if (!notes) return <Placeholder context="notes" />;
|
||||
if (!notes) return <ListLoader />;
|
||||
return (
|
||||
<ListContainer
|
||||
group="home"
|
||||
|
||||
@@ -34,7 +34,6 @@ import { getQueryParams, hardNavigate, makeURL } from "../navigation";
|
||||
import { store as userstore } from "../stores/user-store";
|
||||
import { db } from "../common/db";
|
||||
import Config from "../utils/config";
|
||||
import useDatabase from "../hooks/use-database";
|
||||
import { Loader } from "../components/loader";
|
||||
import { showToast } from "../utils/toast";
|
||||
import AuthContainer from "../components/auth-container";
|
||||
@@ -173,16 +172,13 @@ function Auth(props: AuthProps) {
|
||||
window.history.replaceState({}, "", makeURL(routePaths[route]));
|
||||
}, [route]);
|
||||
|
||||
const [isAppLoaded] = useDatabase();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAppLoaded) return;
|
||||
db.user.getUser().then((user) => {
|
||||
if (user && authorizedRoutes.includes(route) && !isSessionExpired())
|
||||
return openURL("/");
|
||||
setIsReady(true);
|
||||
});
|
||||
}, [isAppLoaded, route]);
|
||||
}, [route]);
|
||||
|
||||
if (!isReady) return <></>;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import Placeholder from "../components/placeholders";
|
||||
import { useEffect } from "react";
|
||||
import { db } from "../common/db";
|
||||
import { useSearch } from "../hooks/use-search";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
function Notebooks() {
|
||||
const notebooks = useStore((state) => state.notebooks);
|
||||
@@ -36,7 +37,7 @@ function Notebooks() {
|
||||
store.get().refresh();
|
||||
}, []);
|
||||
|
||||
if (!notebooks) return <Placeholder context="notebooks" />;
|
||||
if (!notebooks) return <ListLoader />;
|
||||
return (
|
||||
<>
|
||||
<ListContainer
|
||||
|
||||
@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import ListContainer from "../components/list-container";
|
||||
import {
|
||||
notesFromContext,
|
||||
@@ -28,6 +27,7 @@ import { useSearch } from "../hooks/use-search";
|
||||
import { db } from "../common/db";
|
||||
import { handleDrop } from "../common/drop-handler";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
type NotesProps = { header?: JSX.Element };
|
||||
function Notes(props: NotesProps) {
|
||||
@@ -47,7 +47,7 @@ function Notes(props: NotesProps) {
|
||||
[context, contextNotes]
|
||||
);
|
||||
|
||||
if (!context || !contextNotes) return <Placeholder context="notes" />;
|
||||
if (!context || !contextNotes) return <ListLoader />;
|
||||
return (
|
||||
<ListContainer
|
||||
group={type}
|
||||
|
||||
@@ -21,7 +21,6 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { makeURL, useQueryParams } from "../navigation";
|
||||
import { db } from "../common/db";
|
||||
import useDatabase from "../hooks/use-database";
|
||||
import { Loader } from "../components/loader";
|
||||
import { showToast } from "../utils/toast";
|
||||
import AuthContainer from "../components/auth-container";
|
||||
@@ -122,11 +121,9 @@ function useAuthenticateUser({
|
||||
code: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const [isAppLoaded] = useDatabase(isSessionExpired() ? "db" : "memory");
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(true);
|
||||
const [user, setUser] = useState<User>();
|
||||
useEffect(() => {
|
||||
if (!isAppLoaded) return;
|
||||
async function authenticateUser() {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
@@ -148,7 +145,7 @@ function useAuthenticateUser({
|
||||
}
|
||||
|
||||
authenticateUser();
|
||||
}, [code, userId, isAppLoaded]);
|
||||
}, [code, userId]);
|
||||
return { isAuthenticating, user };
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import useNavigate from "../hooks/use-navigate";
|
||||
import Placeholder from "../components/placeholders";
|
||||
import { db } from "../common/db";
|
||||
import { useSearch } from "../hooks/use-search";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
function Reminders() {
|
||||
useNavigate("reminders", () => store.refresh());
|
||||
@@ -33,7 +34,7 @@ function Reminders() {
|
||||
db.lookup.reminders(query).sorted()
|
||||
);
|
||||
|
||||
if (!reminders) return <Placeholder context="reminders" />;
|
||||
if (!reminders) return <ListLoader />;
|
||||
return (
|
||||
<>
|
||||
<ListContainer
|
||||
|
||||
@@ -23,6 +23,7 @@ import useNavigate from "../hooks/use-navigate";
|
||||
import Placeholder from "../components/placeholders";
|
||||
import { useSearch } from "../hooks/use-search";
|
||||
import { db } from "../common/db";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
function Tags() {
|
||||
useNavigate("tags", () => store.refresh());
|
||||
@@ -32,7 +33,7 @@ function Tags() {
|
||||
db.lookup.tags(query).sorted()
|
||||
);
|
||||
|
||||
if (!tags) return <Placeholder context="tags" />;
|
||||
if (!tags) return <ListLoader />;
|
||||
return (
|
||||
<ListContainer
|
||||
group="tags"
|
||||
|
||||
@@ -25,6 +25,7 @@ import useNavigate from "../hooks/use-navigate";
|
||||
import Placeholder from "../components/placeholders";
|
||||
import { useSearch } from "../hooks/use-search";
|
||||
import { db } from "../common/db";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
|
||||
function Trash() {
|
||||
useNavigate("trash", store.refresh);
|
||||
@@ -35,7 +36,7 @@ function Trash() {
|
||||
db.lookup.trash(query).sorted()
|
||||
);
|
||||
|
||||
if (!items) return <Placeholder context="trash" />;
|
||||
if (!items) return <ListLoader />;
|
||||
return (
|
||||
<ListContainer
|
||||
group="trash"
|
||||
|
||||
@@ -44,7 +44,6 @@ const isTesting =
|
||||
const isDesktop = process.env.PLATFORM === "desktop";
|
||||
const isThemeBuilder = process.env.THEME_BUILDER === "true";
|
||||
const isAnalyzing = process.env.ANALYZING === "true";
|
||||
process.env.NN_BUILD_TIMESTAMP = isTesting ? "0" : `${Date.now()}`;
|
||||
|
||||
export default defineConfig({
|
||||
envPrefix: "NN_",
|
||||
@@ -61,7 +60,16 @@ export default defineConfig({
|
||||
output: {
|
||||
plugins: [emitEditorStyles()],
|
||||
assetFileNames: "assets/[name]-[hash:12][extname]",
|
||||
chunkFileNames: "assets/[name]-[hash:12].js"
|
||||
chunkFileNames: "assets/[name]-[hash:12].js",
|
||||
manualChunks: (id: string) => {
|
||||
if (
|
||||
(id.includes("/editor/languages/") ||
|
||||
id.includes("/html/languages/")) &&
|
||||
path.basename(id) !== "index.js"
|
||||
)
|
||||
return `code-lang-${path.basename(id, "js")}`;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -107,6 +115,8 @@ export default defineConfig({
|
||||
format: "es",
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: "assets/[name]-[hash:12][extname]",
|
||||
chunkFileNames: "assets/[name]-[hash:12].js",
|
||||
inlineDynamicImports: true
|
||||
}
|
||||
}
|
||||
@@ -135,13 +145,30 @@ export default defineConfig({
|
||||
manifest: WEB_MANIFEST,
|
||||
injectRegister: null,
|
||||
srcDir: "",
|
||||
filename: "service-worker.ts"
|
||||
filename: "service-worker.ts",
|
||||
mode: "production",
|
||||
workbox: { mode: "production" },
|
||||
injectManifest: {
|
||||
globPatterns: ["**/*.{js,css,html,wasm}", "**/open-sans-*.woff2"],
|
||||
globIgnores: [
|
||||
"**/node_modules/**/*",
|
||||
"**/code-lang-*.js",
|
||||
"pdf.worker.min.js"
|
||||
]
|
||||
}
|
||||
})
|
||||
]),
|
||||
react({
|
||||
plugins: isTesting
|
||||
? undefined
|
||||
: [["swc-plugin-react-remove-properties", {}]]
|
||||
: [
|
||||
[
|
||||
"@swc/plugin-react-remove-properties",
|
||||
{
|
||||
properties: ["^data-test-id$"]
|
||||
}
|
||||
]
|
||||
]
|
||||
}),
|
||||
envCompatible({
|
||||
prefix: "NN_",
|
||||
@@ -149,7 +176,8 @@ export default defineConfig({
|
||||
}),
|
||||
svgrPlugin({
|
||||
svgrOptions: {
|
||||
icon: true
|
||||
icon: true,
|
||||
namedExport: "ReactComponent"
|
||||
// ...svgr options (https://react-svgr.com/docs/options/)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -43,9 +43,7 @@ export function template(data: TemplateData) {
|
||||
? `<meta name="tags" content="${data.tags.join(", ")}" />`
|
||||
: ""
|
||||
}
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=${
|
||||
process.env.NN_BUILD_TIMESTAMP || "1690887574068"
|
||||
}">
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=1690887574068">
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user