mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +01:00
refactor: improve app startup perf many times
This commit is contained in:
@@ -7,8 +7,8 @@ const gitHash = execSync("git rev-parse --short HEAD").toString().trim();
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
all: {
|
all: {
|
||||||
UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2,
|
UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2,
|
||||||
GENERATE_SOURCEMAP: false,
|
// GENERATE_SOURCEMAP: false,
|
||||||
INLINE_RUNTIME_CHUNK: false,
|
// INLINE_RUNTIME_CHUNK: false,
|
||||||
DISABLE_ESLINT_PLUGIN: true,
|
DISABLE_ESLINT_PLUGIN: true,
|
||||||
REACT_APP_GIT_HASH: gitHash,
|
REACT_APP_GIT_HASH: gitHash,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ const { createNote, NOTE, getTestId } = require("./utils");
|
|||||||
*/
|
*/
|
||||||
var page = null;
|
var page = null;
|
||||||
global.page = null;
|
global.page = null;
|
||||||
test.beforeEach(async ({ page: _page }) => {
|
test.beforeEach(async ({ page: _page, baseURL }) => {
|
||||||
global.page = _page;
|
global.page = _page;
|
||||||
page = _page;
|
page = _page;
|
||||||
await page.goto("http://localhost:3000/");
|
await page.goto(baseURL);
|
||||||
|
await page.waitForSelector(getTestId("routeHeader"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("focus mode", async () => {
|
test("focus mode", async () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const { getTestId } = require("./utils");
|
|||||||
|
|
||||||
test.beforeEach(async ({ page, baseURL }) => {
|
test.beforeEach(async ({ page, baseURL }) => {
|
||||||
await page.goto(baseURL);
|
await page.goto(baseURL);
|
||||||
|
await page.waitForSelector(getTestId("routeHeader"));
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRoute(key, header) {
|
function createRoute(key, header) {
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ const { checkNotePresence, isPresent } = require("./utils/conditions");
|
|||||||
*/
|
*/
|
||||||
global.page = null;
|
global.page = null;
|
||||||
|
|
||||||
test.beforeEach(async ({ page: _page }) => {
|
test.beforeEach(async ({ page: _page, baseURL }) => {
|
||||||
global.page = _page;
|
global.page = _page;
|
||||||
await page.goto("http://localhost:3000/");
|
await page.goto(baseURL);
|
||||||
|
await page.waitForSelector(getTestId("routeHeader"));
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fillNotebookDialog(notebook) {
|
async function fillNotebookDialog(notebook) {
|
||||||
|
|||||||
@@ -203,9 +203,10 @@ test.describe("run tests independently", () => {
|
|||||||
* @type {Page}
|
* @type {Page}
|
||||||
*/
|
*/
|
||||||
global.page = null;
|
global.page = null;
|
||||||
test.beforeEach(async ({ page: _page }) => {
|
test.beforeEach(async ({ page: _page, baseURL }) => {
|
||||||
global.page = _page;
|
global.page = _page;
|
||||||
await page.goto("http://localhost:3000/");
|
await page.goto(baseURL);
|
||||||
|
await page.waitForSelector(getTestId("routeHeader"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create a note", async () => {
|
test("create a note", async () => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ test.beforeEach(async ({ page: _page, baseURL }) => {
|
|||||||
global.page = _page;
|
global.page = _page;
|
||||||
page = _page;
|
page = _page;
|
||||||
await page.goto(baseURL);
|
await page.goto(baseURL);
|
||||||
|
await page.waitForSelector(getTestId("routeHeader"));
|
||||||
});
|
});
|
||||||
|
|
||||||
const USER = {
|
const USER = {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ async function configureAutoUpdater() {
|
|||||||
sendMessageToRenderer(EVENTS.updateAvailable, info);
|
sendMessageToRenderer(EVENTS.updateAvailable, info);
|
||||||
});
|
});
|
||||||
autoUpdater.addListener("download-progress", (progress) => {
|
autoUpdater.addListener("download-progress", (progress) => {
|
||||||
console.log("Downloading", progress);
|
|
||||||
sendMessageToRenderer(EVENTS.updateDownloadProgress, progress);
|
sendMessageToRenderer(EVENTS.updateDownloadProgress, progress);
|
||||||
});
|
});
|
||||||
autoUpdater.addListener("update-downloaded", (info) => {
|
autoUpdater.addListener("update-downloaded", (info) => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"emotion-theming": "^10.0.19",
|
"emotion-theming": "^10.0.19",
|
||||||
"fast-sort": "^2.1.1",
|
"fast-sort": "^2.1.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^3.3.0",
|
"framer-motion": "^4.1.17",
|
||||||
"hash-wasm": "^4.9.0",
|
"hash-wasm": "^4.9.0",
|
||||||
"hotkeys-js": "^3.8.3",
|
"hotkeys-js": "^3.8.3",
|
||||||
"immer": "^9.0.6",
|
"immer": "^9.0.6",
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"source-map-explorer": "^2.5.2",
|
"source-map-explorer": "^2.5.2",
|
||||||
"typescript": "^4.1.5",
|
"typescript": "^4.1.5",
|
||||||
|
"webpack-bundle-analyzer": "^4.5.0",
|
||||||
"worker-loader": "^3.0.8",
|
"worker-loader": "^3.0.8",
|
||||||
"zx": "^2.0.0"
|
"zx": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
/* open-sans-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local(""),
|
|
||||||
url("/fonts/open-sans-v20-latin-regular.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open-sans-italic - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local(""), url("/fonts/open-sans-v20-latin-italic.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open-sans-600 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
src: local(""), url("/fonts/open-sans-v20-latin-600.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open-sans-600italic - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 600;
|
|
||||||
src: local(""),
|
|
||||||
url("/fonts/open-sans-v20-latin-600italic.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-600italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open-sans-700 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: local(""), url("/fonts/open-sans-v20-latin-700.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open-sans-700italic - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Open Sans";
|
|
||||||
font-display: swap;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 700;
|
|
||||||
src: local(""),
|
|
||||||
url("/fonts/open-sans-v20-latin-700italic.woff2") format("woff2"),
|
|
||||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/fonts/open-sans-v20-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
sizes="32x32"
|
sizes="32x32"
|
||||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAHUUExURUxpcf////////////////////////////////////////////////////////////////////////////////////39/f39/f////////////////////////////////z8/O3s7P7+/vTz8wAAAPz7++/v7/Py8u/u7vDv7+7t7dfX1/79/ezr66Kiovv6+vf29vX09OTj4w0NDfv7++Ti4vr6+vT09Ovq6vn5+f39/fb19dnX1/b29tvb29/f3xMTE/j4+Pn4+O7s7MnJyerq6uno6M/Pz8jIyN3d3dnZ2d7e3kZGRvj39/38/Pf39+zs7PDw8OPi4ubl5cvLy+rp6czMzPLy8vPz887Ozujo6MrKyuDf3+fn59PT0+bm5uXl5c3NzfX19evr6wsLC+/t7ePj4/Ty8u3r6/Lx8eXk5OPh4dDQ0Nza2tjX19vZ2fHw8Nva2tLR0djW1tbW1tra2tLS0uHh4evp6eLi4tXV1djY2L+/vysrK7q6utHR0aGhoa6urrm5uQICAigoKCEhIZCQkFxcXBgYGFRUVBISEm1tbQ4ODklJSbu7uyAgID09PZqamkVFRXh4eLGxsTg4OOTk5FFRUQEBAQoKCnx8fBcXF7/+rP8AAAAfdFJOUwBOqO2h6KT9Mtyr5+w3Str+6TTZOjXb31M24O8BOd0PapSsAAABxElEQVQ4y2NgYOBgYWWXxwLYBdmEGICAjx9Z1NJJVQfB42QE6hdG0RaoaaCqhKSCl4EF1VxNVAXyTAzcaArUUBXwMLBjKDBBViDAII+uwBZFgTy6AjU1awIKNKz1aa0g1VBfB58CDS08CuIb40AKzLAqsJKXN2tXacChwKS+rTtB3tJHRU9ewxmbFZ6tKiqTrSAKdIO1LDEUaPeoqMzsUwAp0NIt8Uj0ijJXQFVgnDJVZcK0XrACXcXY0sjoHM8AfyUkBbOn9KvMmgRSYFEW4hUVqJagHeaShqTAwX7ijJQusAkW5VXRHu6eruFqaigKkqarqAAVKFbYxWiYq1XGedijuMHBWz6xo7m2Rl7RwtvdJaw6NjzCFl2BVVNdshJQgbqBsqtdpIu7nasjXIFXS2cyRL2CW6i6JTCgFCLitUPs4QrM7cDmKZi7FRYVm6o5QeLbDGICLNEqJAVk59rk2djY5Me4FThawQOKFSzrrxmaZWpqBAamQUGmwRaakNTNzMAm7+ekrKmlqOjsrIgAGcAA1ci0NVPiYhAV8zVURgbp6nBg7isuwsDAKKfkp4AEVOHASklKApS9JZmY5bECaS5ZGQYA3vqpy6NoYh0AAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
@@ -25,7 +25,11 @@
|
|||||||
sizes="16x16"
|
sizes="16x16"
|
||||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
|
<link
|
||||||
|
rel="manifest"
|
||||||
|
href='data:application/manifest+json,{"name":"Notesnook - Your private note taking space","short_name":"Notesnook","icons":[{"src":"favicon.ico","sizes":"64x64 32x32 24x24 16x16","type":"image/x-icon"},{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"start_url":".","theme_color":"rgb(255,255,255)","background_color":"rgb(255,255,255)","display":"standalone"}'
|
||||||
|
/>
|
||||||
|
<!-- <link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" /> -->
|
||||||
<link
|
<link
|
||||||
rel="mask-icon"
|
rel="mask-icon"
|
||||||
href="%PUBLIC_URL%/safari-pinned-tab.svg"
|
href="%PUBLIC_URL%/safari-pinned-tab.svg"
|
||||||
@@ -110,8 +114,73 @@
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 480px) {
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root,
|
||||||
|
body,
|
||||||
|
#root {
|
||||||
|
margin: 0 !important;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeUp {
|
||||||
|
0% {
|
||||||
|
transform: translateY(500px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateY(0px);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.ReactModal__Overlay {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReactModal__Overlay--after-open {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReactModal__Overlay--before-close {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel.carousel-slider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/fonts.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
@@ -8,16 +8,21 @@ import { db } from "./common/db";
|
|||||||
import { CHECK_IDS, EV, EVENTS } from "notes-core/common";
|
import { CHECK_IDS, EV, EVENTS } from "notes-core/common";
|
||||||
import { registerKeyMap } from "./common/key-map";
|
import { registerKeyMap } from "./common/key-map";
|
||||||
import { isUserPremium } from "./hooks/use-is-user-premium";
|
import { isUserPremium } from "./hooks/use-is-user-premium";
|
||||||
|
import { loadTrackerScript } from "./utils/analytics";
|
||||||
|
import Modal from "react-modal";
|
||||||
|
|
||||||
function AppEffects({ isMobile, isTablet, setShow, slideToIndex }) {
|
Modal.setAppElement("#root");
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
loadTrackerScript();
|
||||||
|
console.log = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppEffects({ setShow }) {
|
||||||
const refreshColors = useStore((store) => store.refreshColors);
|
const refreshColors = useStore((store) => store.refreshColors);
|
||||||
const refreshMenuPins = useStore((store) => store.refreshMenuPins);
|
const refreshMenuPins = useStore((store) => store.refreshMenuPins);
|
||||||
const updateLastSynced = useStore((store) => store.updateLastSynced);
|
const updateLastSynced = useStore((store) => store.updateLastSynced);
|
||||||
const setProcessingStatus = useStore((store) => store.setProcessingStatus);
|
const setProcessingStatus = useStore((store) => store.setProcessingStatus);
|
||||||
const isFocusMode = useStore((store) => store.isFocusMode);
|
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||||
const isEditorOpen = useStore((store) => store.isEditorOpen);
|
|
||||||
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
|
|
||||||
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
|
|
||||||
const addReminder = useStore((store) => store.addReminder);
|
const addReminder = useStore((store) => store.addReminder);
|
||||||
const initUser = useUserStore((store) => store.init);
|
const initUser = useUserStore((store) => store.init);
|
||||||
const initNotes = useNotesStore((store) => store.init);
|
const initNotes = useNotesStore((store) => store.init);
|
||||||
@@ -115,25 +120,8 @@ function AppEffects({ isMobile, isTablet, setShow, slideToIndex }) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isMobile) return;
|
|
||||||
slideToIndex(isSideMenuOpen ? 0 : 1);
|
|
||||||
}, [isMobile, slideToIndex, isSideMenuOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isMobile) return;
|
|
||||||
slideToIndex(isEditorOpen ? 2 : 1);
|
|
||||||
}, [isMobile, slideToIndex, isEditorOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
toggleSideMenu(!isMobile);
|
|
||||||
if (!isMobile && !isTablet && !isFocusMode) setShow(true);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isMobile, isTablet, isFocusMode, toggleSideMenu]);
|
|
||||||
|
|
||||||
return <React.Fragment />;
|
return <React.Fragment />;
|
||||||
}
|
}
|
||||||
export default AppEffects;
|
|
||||||
|
|
||||||
function getProcessingStatusFromType(type) {
|
function getProcessingStatusFromType(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
64
apps/web/src/app-effects.mobile.js
Normal file
64
apps/web/src/app-effects.mobile.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useStore } from "./stores/app-store";
|
||||||
|
import useSlider from "./hooks/use-slider";
|
||||||
|
import useMobile from "./utils/use-mobile";
|
||||||
|
import useTablet from "./utils/use-tablet";
|
||||||
|
|
||||||
|
export default function MobileAppEffects({ sliderId, overlayId, setShow }) {
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const isTablet = useTablet();
|
||||||
|
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
|
||||||
|
const setIsEditorOpen = useStore((store) => store.setIsEditorOpen);
|
||||||
|
const isEditorOpen = useStore((store) => store.isEditorOpen);
|
||||||
|
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
|
||||||
|
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||||
|
|
||||||
|
const [slideToIndex] = useSlider(sliderId, {
|
||||||
|
onSliding: (e, { lastSlide, position, lastPosition }) => {
|
||||||
|
if (!isMobile) return;
|
||||||
|
const offset = 70;
|
||||||
|
const width = 180;
|
||||||
|
|
||||||
|
const percent = offset - (position / width) * offset;
|
||||||
|
const overlay = document.getElementById("overlay");
|
||||||
|
if (percent > 0) {
|
||||||
|
overlay.style.opacity = `${percent}%`;
|
||||||
|
overlay.style.pointerEvents = "all";
|
||||||
|
} else {
|
||||||
|
overlay.style.pointerEvents = "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange: (e, { slide, lastSlide }) => {
|
||||||
|
if (!lastSlide || !isMobile) return;
|
||||||
|
toggleSideMenu(slide?.index === 0 ? true : false);
|
||||||
|
setIsEditorOpen(slide?.index === 2 ? true : false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) return;
|
||||||
|
slideToIndex(isSideMenuOpen ? 0 : 1);
|
||||||
|
}, [isMobile, slideToIndex, isSideMenuOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) return;
|
||||||
|
slideToIndex(isEditorOpen ? 2 : 1);
|
||||||
|
}, [isMobile, slideToIndex, isEditorOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
toggleSideMenu(!isMobile);
|
||||||
|
if (!isMobile && !isTablet && !isFocusMode) setShow(true);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isMobile, isTablet, isFocusMode, toggleSideMenu]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!overlayId) return;
|
||||||
|
const overlay = document.getElementById(overlayId);
|
||||||
|
overlay.onclick = () => toggleSideMenu(false);
|
||||||
|
return () => {
|
||||||
|
overlay.onclick = null;
|
||||||
|
};
|
||||||
|
}, [overlayId, toggleSideMenu]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,3 +1,75 @@
|
|||||||
|
/* open-sans-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-regular.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open-sans-italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-italic.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open-sans-600 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-600.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open-sans-600italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-600italic.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-600italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open-sans-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-700.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open-sans-700italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
font-display: swap;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(""),
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-700italic.woff2") format("woff2"),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url("./assets/fonts/open-sans-v20-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
.unselectable {
|
.unselectable {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
|||||||
@@ -1,81 +1,48 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, Suspense } from "react";
|
||||||
import "./app.css";
|
import { Box, Flex, Text } from "rebass";
|
||||||
import { Box, Flex } from "rebass";
|
|
||||||
import { MotionConfig, AnimationFeature, GesturesFeature } from "framer-motion";
|
|
||||||
import ThemeProvider from "./components/theme-provider";
|
import ThemeProvider from "./components/theme-provider";
|
||||||
import StatusBar from "./components/statusbar";
|
import { AnimatedFlex } from "./components/animated";
|
||||||
import Animated from "./components/animated";
|
import NavigationMenuPlaceholder from "./components/navigationmenu/index.lite";
|
||||||
import NavigationMenu from "./components/navigationmenu";
|
import StatusBarPlaceholder from "./components/statusbar/index.lite";
|
||||||
import GlobalMenuWrapper from "./components/globalmenuwrapper";
|
|
||||||
import { NavigationEvents } from "./navigation";
|
|
||||||
import rootroutes from "./navigation/rootroutes";
|
|
||||||
import { useStore } from "./stores/app-store";
|
|
||||||
import { Suspense } from "react";
|
|
||||||
import useMobile from "./utils/use-mobile";
|
import useMobile from "./utils/use-mobile";
|
||||||
import useTablet from "./utils/use-tablet";
|
import useTablet from "./utils/use-tablet";
|
||||||
import HashRouter from "./components/hashrouter";
|
import { Loading } from "./components/icons";
|
||||||
import useSlider from "./hooks/use-slider";
|
import { LazyMotion, domAnimation } from "framer-motion";
|
||||||
import useRoutes from "./utils/use-routes";
|
import useDatabase from "./hooks/use-database";
|
||||||
import { clearRouteCache } from "./components/cachedrouter";
|
import Loader from "./components/loader";
|
||||||
|
|
||||||
|
const GlobalMenuWrapper = React.lazy(() =>
|
||||||
|
import("./components/global-menu-wrapper")
|
||||||
|
);
|
||||||
const AppEffects = React.lazy(() => import("./app-effects"));
|
const AppEffects = React.lazy(() => import("./app-effects"));
|
||||||
|
const MobileAppEffects = React.lazy(() => import("./app-effects.mobile"));
|
||||||
const CachedRouter = React.lazy(() => import("./components/cached-router"));
|
const CachedRouter = React.lazy(() => import("./components/cached-router"));
|
||||||
|
const HashRouter = React.lazy(() => import("./components/hash-router"));
|
||||||
|
const NavigationMenu = React.lazy(() => import("./components/navigation-menu"));
|
||||||
|
const StatusBar = React.lazy(() => import("./components/status-bar"));
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const isTablet = useTablet();
|
const isTablet = useTablet();
|
||||||
const [isAppLoaded, setIsAppLoaded] = useState(false);
|
const [isAppLoaded] = useDatabase();
|
||||||
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
|
|
||||||
const setIsEditorOpen = useStore((store) => store.setIsEditorOpen);
|
|
||||||
const [sliderRef, slideToIndex] = useSlider({
|
|
||||||
onSliding: (e, { lastSlide, position, lastPosition }) => {
|
|
||||||
if (!isMobile) return;
|
|
||||||
const offset = 70;
|
|
||||||
const width = 180;
|
|
||||||
|
|
||||||
const percent = offset - (position / width) * offset;
|
|
||||||
const overlay = document.getElementById("overlay");
|
|
||||||
if (percent > 0) {
|
|
||||||
overlay.style.opacity = `${percent}%`;
|
|
||||||
overlay.style.pointerEvents = "all";
|
|
||||||
} else {
|
|
||||||
overlay.style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChange: (e, { slide, lastSlide }) => {
|
|
||||||
if (!lastSlide || !isMobile) return;
|
|
||||||
toggleSideMenu(slide?.index === 0 ? true : false);
|
|
||||||
setIsEditorOpen(slide?.index === 2 ? true : false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function onNavigate() {
|
|
||||||
NavigationEvents.unsubscribe("onNavigate", onNavigate);
|
|
||||||
setIsAppLoaded(true);
|
|
||||||
}
|
|
||||||
NavigationEvents.subscribe("onNavigate", onNavigate);
|
|
||||||
return () => {
|
|
||||||
NavigationEvents.unsubscribe("onNavigate", onNavigate);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MotionConfig features={[AnimationFeature, GesturesFeature]}>
|
<LazyMotion features={domAnimation} strict>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
{isAppLoaded && (
|
{isAppLoaded && (
|
||||||
<Suspense fallback={<div style={{ display: "none" }} />}>
|
<Suspense fallback={<div style={{ display: "none" }} />}>
|
||||||
<AppEffects
|
<GlobalMenuWrapper />
|
||||||
slideToIndex={slideToIndex}
|
<AppEffects setShow={setShow} />
|
||||||
|
{isMobile && (
|
||||||
|
<MobileAppEffects
|
||||||
|
sliderId="slider"
|
||||||
|
overlayId="overlay"
|
||||||
setShow={setShow}
|
setShow={setShow}
|
||||||
isMobile={isMobile}
|
|
||||||
isTablet={isTablet}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<GlobalMenuWrapper />
|
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
id="app"
|
id="app"
|
||||||
@@ -84,7 +51,7 @@ function App() {
|
|||||||
sx={{ overflow: "hidden" }}
|
sx={{ overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
ref={sliderRef}
|
id="slider"
|
||||||
variant="rowFill"
|
variant="rowFill"
|
||||||
overflowX={["auto", "hidden"]}
|
overflowX={["auto", "hidden"]}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -99,13 +66,18 @@ function App() {
|
|||||||
scrollSnapAlign: "start",
|
scrollSnapAlign: "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NavigationMenu
|
<SuspenseLoader
|
||||||
toggleNavigationContainer={(state) => {
|
condition={isAppLoaded}
|
||||||
|
component={NavigationMenu}
|
||||||
|
props={{
|
||||||
|
toggleNavigationContainer: (state) => {
|
||||||
if (!isMobile) setShow(state || !show);
|
if (!isMobile) setShow(state || !show);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
fallback={<NavigationMenuPlaceholder />}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
className="listMenu"
|
className="listMenu"
|
||||||
variant="columnFill"
|
variant="columnFill"
|
||||||
initial={{
|
initial={{
|
||||||
@@ -134,9 +106,16 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<Suspense fallback={<div />}>
|
<SuspenseLoader
|
||||||
<CachedRouter />
|
condition={isAppLoaded}
|
||||||
</Suspense>
|
component={CachedRouter}
|
||||||
|
fallback={
|
||||||
|
<Loader
|
||||||
|
title="Did you know?"
|
||||||
|
text="All your notes are encrypted on your device."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Box
|
<Box
|
||||||
id="overlay"
|
id="overlay"
|
||||||
@@ -152,12 +131,9 @@ function App() {
|
|||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
bg="black"
|
bg="black"
|
||||||
onClick={() => {
|
|
||||||
toggleSideMenu(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
<Flex
|
<Flex
|
||||||
width={["100vw", "100%"]}
|
width={["100vw", "100%"]}
|
||||||
flexShrink={[0, 1]}
|
flexShrink={[0, 1]}
|
||||||
@@ -166,20 +142,37 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
<HashRouter />
|
<SuspenseLoader
|
||||||
|
fallback={
|
||||||
|
<Loader
|
||||||
|
title="Fun fact"
|
||||||
|
text="Notesnook was released in January 2021 by a team of only 3 people."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
component={HashRouter}
|
||||||
|
condition={isAppLoaded}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<StatusBar />
|
<SuspenseLoader
|
||||||
|
fallback={<StatusBarPlaceholder />}
|
||||||
|
component={StatusBar}
|
||||||
|
condition={isAppLoaded}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MotionConfig>
|
</LazyMotion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Root() {
|
export default App;
|
||||||
const route = useRoutes(rootroutes);
|
|
||||||
if (route) clearRouteCache();
|
|
||||||
return route?.component || <App />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Root;
|
function SuspenseLoader({ condition, props, component: Component, fallback }) {
|
||||||
|
if (!condition) return fallback;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={fallback}>
|
||||||
|
<Component {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
import StorageInterface from "../interfaces/storage";
|
|
||||||
import FS from "../interfaces/fs";
|
|
||||||
import Config from "../utils/config";
|
|
||||||
import http from "notes-core/utils/http";
|
|
||||||
import { EV, EVENTS } from "notes-core/common";
|
|
||||||
import { getCurrentHash, hashNavigate } from "../navigation";
|
|
||||||
import { isTesting } from "../utils/platform";
|
|
||||||
|
|
||||||
global.HTMLParser = new DOMParser().parseFromString(
|
global.HTMLParser = new DOMParser().parseFromString(
|
||||||
"<body></body>",
|
"<body></body>",
|
||||||
"text/html"
|
"text/html"
|
||||||
@@ -16,67 +8,31 @@ global.HTMLParser = new DOMParser().parseFromString(
|
|||||||
var db;
|
var db;
|
||||||
async function initializeDatabase() {
|
async function initializeDatabase() {
|
||||||
const { default: Database } = await import("notes-core/api");
|
const { default: Database } = await import("notes-core/api");
|
||||||
db = new Database(StorageInterface, EventSource, FS);
|
const { default: Storage } = await import("../interfaces/storage");
|
||||||
|
const { default: FS } = await import("../interfaces/fs");
|
||||||
|
db = new Database(Storage, EventSource, FS);
|
||||||
|
|
||||||
if (isTesting()) {
|
// if (isTesting()) {
|
||||||
db.host({
|
|
||||||
API_HOST: "https://api.notesnook.com",
|
|
||||||
AUTH_HOST: "https://auth.streetwriters.co",
|
|
||||||
SSE_HOST: "https://events.streetwriters.co",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
db.host({
|
|
||||||
API_HOST: "http://localhost:5264",
|
|
||||||
AUTH_HOST: "http://localhost:8264",
|
|
||||||
SSE_HOST: "http://localhost:7264",
|
|
||||||
});
|
|
||||||
// db.host({
|
// db.host({
|
||||||
// API_HOST: "http://192.168.10.29:5264",
|
// API_HOST: "https://api.notesnook.com",
|
||||||
// AUTH_HOST: "http://192.168.10.29:8264",
|
// AUTH_HOST: "https://auth.streetwriters.co",
|
||||||
// SSE_HOST: "http://192.168.10.29:7264",
|
// SSE_HOST: "https://events.streetwriters.co",
|
||||||
// });
|
// });
|
||||||
}
|
// } else {
|
||||||
|
// db.host({
|
||||||
|
// API_HOST: "http://localhost:5264",
|
||||||
|
// AUTH_HOST: "http://localhost:8264",
|
||||||
|
// SSE_HOST: "http://localhost:7264",
|
||||||
|
// });
|
||||||
|
// // db.host({
|
||||||
|
// // API_HOST: "http://192.168.10.29:5264",
|
||||||
|
// // AUTH_HOST: "http://192.168.10.29:8264",
|
||||||
|
// // SSE_HOST: "http://192.168.10.29:7264",
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
|
||||||
await db.init();
|
await db.init();
|
||||||
|
|
||||||
if (!isAppHydrated() && !isTesting()) {
|
|
||||||
try {
|
|
||||||
loadDefaultNotes(db);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { db, initializeDatabase };
|
export { db, initializeDatabase };
|
||||||
|
|
||||||
function isAppHydrated() {
|
|
||||||
return Config.get("hydrated", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAppHydrated() {
|
|
||||||
return Config.set("hydrated", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDefaultNotes(db) {
|
|
||||||
const notes = await http.get("/notes/index_v14.json");
|
|
||||||
if (!notes) return;
|
|
||||||
let autoOpenId;
|
|
||||||
const hash = getCurrentHash().replaceAll("#", "");
|
|
||||||
for (let note of notes) {
|
|
||||||
const content = await http.get(note.webContent);
|
|
||||||
let id = await db.notes.add({
|
|
||||||
title: note.title,
|
|
||||||
headline: note.headline,
|
|
||||||
localOnly: true,
|
|
||||||
content: { type: "tiny", data: content },
|
|
||||||
});
|
|
||||||
if (note.autoOpen) autoOpenId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoOpenId) {
|
|
||||||
hashNavigate(`/notes/${autoOpenId}/edit`);
|
|
||||||
if (hash) setTimeout(() => hashNavigate(hash), 100);
|
|
||||||
}
|
|
||||||
setAppHydrated();
|
|
||||||
EV.publish(EVENTS.appRefreshRequested);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const SINGLE_LINE_HEIGHT = 1.4;
|
const SINGLE_LINE_HEIGHT = 1.4;
|
||||||
const DEFAULT_FONT_SIZE = document.getElementById("p").clientHeight - 1;
|
const DEFAULT_FONT_SIZE = document.getElementById("p").clientHeight - 1;
|
||||||
console.log("HEIGHT", DEFAULT_FONT_SIZE);
|
|
||||||
const MAX_HEIGHTS = {
|
const MAX_HEIGHTS = {
|
||||||
note: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,
|
note: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,
|
||||||
notebook: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,
|
notebook: SINGLE_LINE_HEIGHT * 7 * DEFAULT_FONT_SIZE,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { m as motion } from "framer-motion";
|
import { m } from "framer-motion";
|
||||||
import { Flex, Box, Image, Text } from "rebass";
|
import { Flex, Box, Image, Text } from "rebass";
|
||||||
import { Input } from "@rebass/forms";
|
import { Input } from "@rebass/forms";
|
||||||
|
|
||||||
const Animated = {
|
export const AnimatedFlex = m(Flex);
|
||||||
Flex: motion.custom(Flex),
|
export const AnimatedBox = m(Box);
|
||||||
Box: motion.custom(Box),
|
export const AnimatedImage = m(Image);
|
||||||
Image: motion.custom(Image),
|
export const AnimatedText = m(Text);
|
||||||
Text: motion.custom(Text),
|
export const AnimatedInput = m(Input);
|
||||||
Input: motion.custom(Input),
|
|
||||||
};
|
|
||||||
export default Animated;
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { NavigationEvents } from "../../navigation";
|
import { getHomeRoute, NavigationEvents } from "../../navigation";
|
||||||
import useRoutes from "../../utils/use-routes";
|
import useRoutes from "../../utils/use-routes";
|
||||||
import RouteContainer from "../route-container";
|
import RouteContainer from "../route-container";
|
||||||
import ThemeProvider from "../theme-provider";
|
import ThemeProvider from "../theme-provider";
|
||||||
@@ -8,10 +8,12 @@ import routes from "../../navigation/routes";
|
|||||||
|
|
||||||
var cache = {};
|
var cache = {};
|
||||||
function CachedRouter() {
|
function CachedRouter() {
|
||||||
const RouteResult = useRoutes(routes, { fallbackRoute: "/" });
|
const [RouteResult, location] = useRoutes(routes, {
|
||||||
|
fallbackRoute: getHomeRoute(),
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!RouteResult) return;
|
if (!RouteResult) return;
|
||||||
NavigationEvents.publish("onNavigate", RouteResult);
|
NavigationEvents.publish("onNavigate", RouteResult, location);
|
||||||
window.currentViewType = RouteResult.type;
|
window.currentViewType = RouteResult.type;
|
||||||
window.currentViewKey = RouteResult.key;
|
window.currentViewKey = RouteResult.key;
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ function CachedRouter() {
|
|||||||
route
|
route
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [RouteResult]);
|
}, [RouteResult, location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteContainer
|
<RouteContainer
|
||||||
|
|||||||
@@ -4,13 +4,6 @@ import Dialog from "./dialog";
|
|||||||
import * as Icon from "../icons";
|
import * as Icon from "../icons";
|
||||||
import { useStore as useUserStore } from "../../stores/user-store";
|
import { useStore as useUserStore } from "../../stores/user-store";
|
||||||
import { getCouponData, upgrade } from "../../common/checkout";
|
import { getCouponData, upgrade } from "../../common/checkout";
|
||||||
import { ReactComponent as Personalization } from "../../assets/accent.svg";
|
|
||||||
import { ReactComponent as Backups } from "../../assets/backup.svg";
|
|
||||||
import { ReactComponent as Export } from "../../assets/export.svg";
|
|
||||||
import { ReactComponent as Organize } from "../../assets/organize.svg";
|
|
||||||
import { ReactComponent as RichText } from "../../assets/richtext.svg";
|
|
||||||
import { ReactComponent as Sync } from "../../assets/sync.svg";
|
|
||||||
import { ReactComponent as Vault } from "../../assets/vault.svg";
|
|
||||||
import getSymbolFromCurrency from "currency-symbol-map";
|
import getSymbolFromCurrency from "currency-symbol-map";
|
||||||
import { ANALYTICS_EVENTS, trackEvent } from "../../utils/analytics";
|
import { ANALYTICS_EVENTS, trackEvent } from "../../utils/analytics";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
@@ -19,77 +12,41 @@ const premiumDetails = [
|
|||||||
{
|
{
|
||||||
title: "Unlimited attachments",
|
title: "Unlimited attachments",
|
||||||
description: "Your notes will be automatically synced to all your devices.",
|
description: "Your notes will be automatically synced to all your devices.",
|
||||||
illustration: {
|
|
||||||
icon: Sync,
|
|
||||||
width: "40%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Unlimited storage",
|
title: "Unlimited storage",
|
||||||
description: "Your notes will be automatically synced to all your devices.",
|
description: "Your notes will be automatically synced to all your devices.",
|
||||||
illustration: {
|
|
||||||
icon: Sync,
|
|
||||||
width: "40%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Unlimited notebooks & tags",
|
title: "Unlimited notebooks & tags",
|
||||||
description:
|
description:
|
||||||
"Make unlimited notebooks and tags, and assign colors to your notes for quick access.",
|
"Make unlimited notebooks and tags, and assign colors to your notes for quick access.",
|
||||||
illustration: {
|
|
||||||
icon: Organize,
|
|
||||||
width: "40%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Automatic syncing",
|
title: "Automatic syncing",
|
||||||
description: "Your notes will be automatically synced to all your devices.",
|
description: "Your notes will be automatically synced to all your devices.",
|
||||||
illustration: {
|
|
||||||
icon: Sync,
|
|
||||||
width: "40%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Secure vault for notes",
|
title: "Secure vault for notes",
|
||||||
description:
|
description:
|
||||||
"Lock any note with a password and keep sensitive data under lock and key.",
|
"Lock any note with a password and keep sensitive data under lock and key.",
|
||||||
illustration: {
|
|
||||||
icon: Vault,
|
|
||||||
width: "35%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Full rich text editor + markdown support",
|
title: "Full rich text editor + markdown support",
|
||||||
description:
|
description:
|
||||||
"Add images, links, tables and lists to your notes, and use markdown for fast editing.",
|
"Add images, links, tables and lists to your notes, and use markdown for fast editing.",
|
||||||
illustration: {
|
|
||||||
icon: RichText,
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multi-format exports",
|
title: "Multi-format exports",
|
||||||
description: "Export your notes in PDF, Markdown, or HTML formats.",
|
description: "Export your notes in PDF, Markdown, or HTML formats.",
|
||||||
illustration: {
|
|
||||||
icon: Export,
|
|
||||||
width: "25%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Automatic encrypted backups",
|
title: "Automatic encrypted backups",
|
||||||
description: "Enable daily or weekly backups with automatic encryption.",
|
description: "Enable daily or weekly backups with automatic encryption.",
|
||||||
illustration: {
|
|
||||||
icon: Backups,
|
|
||||||
width: "25%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Customize Notesnook",
|
title: "Customize Notesnook",
|
||||||
description: "Change app colors and turn on automatic theme switching.",
|
description: "Change app colors and turn on automatic theme switching.",
|
||||||
illustration: {
|
|
||||||
icon: Personalization,
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: (
|
||||||
@@ -100,10 +57,6 @@ const premiumDetails = [
|
|||||||
),
|
),
|
||||||
description:
|
description:
|
||||||
"Pro users get access to special channels and priority support on our Discord server.",
|
"Pro users get access to special channels and priority support on our Discord server.",
|
||||||
illustration: {
|
|
||||||
icon: Personalization,
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
store as editorstore,
|
store as editorstore,
|
||||||
} from "../../stores/editor-store";
|
} from "../../stores/editor-store";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
import Animated from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
import Header from "./header";
|
import Header from "./header";
|
||||||
import useMobile from "../../utils/use-mobile";
|
import useMobile from "../../utils/use-mobile";
|
||||||
import useTablet from "../../utils/use-tablet";
|
import useTablet from "../../utils/use-tablet";
|
||||||
@@ -126,7 +126,7 @@ function Editor({ noteId, nonce }) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
variant="columnFill"
|
variant="columnFill"
|
||||||
className="editor"
|
className="editor"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -179,7 +179,7 @@ function Editor({ noteId, nonce }) {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Properties noteId={noteId} />
|
<Properties noteId={noteId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useStore as useAppStore } from "../../stores/app-store";
|
|||||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||||
import { useStore, store } from "../../stores/editor-store";
|
import { useStore, store } from "../../stores/editor-store";
|
||||||
import { showToast } from "../../utils/toast";
|
import { showToast } from "../../utils/toast";
|
||||||
import Animated from "../animated";
|
import { AnimatedInput } from "../animated";
|
||||||
import { showPublishView } from "../publish-view";
|
import { showPublishView } from "../publish-view";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ function Toolbar(props) {
|
|||||||
clearSession();
|
clearSession();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Animated.Input
|
<AnimatedInput
|
||||||
ml={[2, 2, 0]}
|
ml={[2, 2, 0]}
|
||||||
initial={{
|
initial={{
|
||||||
opacity: isTitleVisible ? 1 : 0,
|
opacity: isTitleVisible ? 1 : 0,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as Icon from "../icons";
|
import * as Icon from "../icons";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Button, Flex, Text } from "rebass";
|
import { Button, Flex, Text } from "rebass";
|
||||||
import Animated from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
import { useOpenContextMenu } from "../../utils/useContextMenu";
|
import { useOpenContextMenu } from "../../utils/useContextMenu";
|
||||||
import { useStore as useNoteStore } from "../../stores/note-store";
|
import { useStore as useNoteStore } from "../../stores/note-store";
|
||||||
@@ -99,7 +99,7 @@ function GroupHeader(props) {
|
|||||||
if (!title) return null;
|
if (!title) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
transition={{ duration: 0.3, repeatType: "reverse", repeat: 3 }}
|
transition={{ duration: 0.3, repeatType: "reverse", repeat: 3 }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (groups.length <= 0) return;
|
if (groups.length <= 0) return;
|
||||||
@@ -165,7 +165,7 @@ function GroupHeader(props) {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default GroupHeader;
|
export default GroupHeader;
|
||||||
|
|||||||
@@ -1,8 +1,101 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import MDIIcon from "@mdi/react";
|
import MDIIcon from "@mdi/react";
|
||||||
import * as Icons from "@mdi/js";
|
import {
|
||||||
|
mdiPlus,
|
||||||
|
mdiHomeVariantOutline,
|
||||||
|
mdiMinus,
|
||||||
|
mdiBookOutline,
|
||||||
|
mdiNotebookOutline,
|
||||||
|
mdiArrowLeft,
|
||||||
|
mdiArrowRight,
|
||||||
|
mdiArrowDown,
|
||||||
|
mdiBookPlusMultipleOutline,
|
||||||
|
mdiBookmarkOutline,
|
||||||
|
mdiAlert,
|
||||||
|
mdiShieldOutline,
|
||||||
|
mdiLockOpenOutline,
|
||||||
|
mdiLockOutline,
|
||||||
|
mdiStar,
|
||||||
|
mdiStarOutline,
|
||||||
|
mdiCircle,
|
||||||
|
mdiCircleOutline,
|
||||||
|
mdiUpdate,
|
||||||
|
mdiCheck,
|
||||||
|
mdiClose,
|
||||||
|
mdiDotsVertical,
|
||||||
|
mdiTrashCanOutline,
|
||||||
|
mdiBookRemoveOutline,
|
||||||
|
mdiMagnify,
|
||||||
|
mdiMenu,
|
||||||
|
mdiLoginVariant,
|
||||||
|
mdiEmailAlertOutline,
|
||||||
|
mdiAccountOutline,
|
||||||
|
mdiLogoutVariant,
|
||||||
|
mdiSunglasses,
|
||||||
|
mdiGlasses,
|
||||||
|
mdiCogOutline,
|
||||||
|
mdiHomeOutline,
|
||||||
|
mdiRecycle,
|
||||||
|
mdiSync,
|
||||||
|
mdiLoading,
|
||||||
|
mdiExportVariant,
|
||||||
|
mdiArrowExpandDown,
|
||||||
|
mdiArrowTopRightThick,
|
||||||
|
mdiChevronLeft,
|
||||||
|
mdiChevronRight,
|
||||||
|
mdiPound,
|
||||||
|
mdiPinOutline,
|
||||||
|
mdiPin,
|
||||||
|
mdiWeatherNight,
|
||||||
|
mdiWeatherSunny,
|
||||||
|
mdiThemeLightDark,
|
||||||
|
mdiCheckCircle,
|
||||||
|
mdiLanguageMarkdownOutline,
|
||||||
|
mdiFilePdfOutline,
|
||||||
|
mdiLanguageHtml5,
|
||||||
|
mdiFormatTitle,
|
||||||
|
mdiAlertCircle,
|
||||||
|
mdiInformation,
|
||||||
|
mdiToggleSwitchOffOutline,
|
||||||
|
mdiToggleSwitchOutline,
|
||||||
|
mdiBackupRestore,
|
||||||
|
mdiCurrencyUsdCircleOutline,
|
||||||
|
mdiPencil,
|
||||||
|
mdiUndoVariant,
|
||||||
|
mdiRedoVariant,
|
||||||
|
mdiTune,
|
||||||
|
mdiChevronDown,
|
||||||
|
mdiChevronUp,
|
||||||
|
mdiSortAscending,
|
||||||
|
mdiSortDescending,
|
||||||
|
mdiEye,
|
||||||
|
mdiEyeOff,
|
||||||
|
mdiFullscreen,
|
||||||
|
mdiFullscreenExit,
|
||||||
|
mdiBullhorn,
|
||||||
|
mdiCloudUploadOutline,
|
||||||
|
mdiCloudCheckOutline,
|
||||||
|
mdiContentCopy,
|
||||||
|
mdiCheckboxMultipleMarkedCircleOutline,
|
||||||
|
mdiBookEditOutline,
|
||||||
|
mdiDeleteForeverOutline,
|
||||||
|
mdiTextBoxMultipleOutline,
|
||||||
|
mdiRocketLaunchOutline,
|
||||||
|
mdiShareVariantOutline,
|
||||||
|
mdiFormTextboxPassword,
|
||||||
|
mdiBomb,
|
||||||
|
mdiViewHeadline,
|
||||||
|
mdiViewSequentialOutline,
|
||||||
|
mdiEmailCheckOutline,
|
||||||
|
mdiDiscord,
|
||||||
|
mdiTwitter,
|
||||||
|
mdiReddit,
|
||||||
|
mdiFileOutline,
|
||||||
|
mdiImage,
|
||||||
|
mdiNoteOutline,
|
||||||
|
} from "@mdi/js";
|
||||||
import { useTheme } from "emotion-theming";
|
import { useTheme } from "emotion-theming";
|
||||||
import Animated from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
|
|
||||||
function Icon({ title, name, size = 24, color = "icon", stroke, rotate }) {
|
function Icon({ title, name, size = 24, color = "icon", stroke, rotate }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -25,7 +118,7 @@ function createIcon(name, rotate = false) {
|
|||||||
return function (props) {
|
return function (props) {
|
||||||
const [isHovering, setIsHovering] = useState();
|
const [isHovering, setIsHovering] = useState();
|
||||||
return (
|
return (
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
id={props.id}
|
id={props.id}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
variant={props.variant}
|
variant={props.variant}
|
||||||
@@ -49,138 +142,109 @@ function createIcon(name, rotate = false) {
|
|||||||
}
|
}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Plus = createIcon(Icons.mdiPlus);
|
export const Plus = createIcon(mdiPlus);
|
||||||
export const Note = createIcon(Icons.mdiHomeVariantOutline);
|
export const Note = createIcon(mdiNoteOutline);
|
||||||
export const Minus = createIcon(Icons.mdiMinus);
|
export const Minus = createIcon(mdiMinus);
|
||||||
export const Notebook = createIcon(Icons.mdiBookOutline);
|
export const Notebook = createIcon(mdiBookOutline);
|
||||||
export const Notebook2 = createIcon(Icons.mdiNotebookOutline);
|
export const Notebook2 = createIcon(mdiNotebookOutline);
|
||||||
export const ArrowLeft = createIcon(Icons.mdiArrowLeft);
|
export const ArrowLeft = createIcon(mdiArrowLeft);
|
||||||
export const ArrowRight = createIcon(Icons.mdiArrowRight);
|
export const ArrowRight = createIcon(mdiArrowRight);
|
||||||
export const ArrowDown = createIcon(Icons.mdiArrowDown);
|
export const ArrowDown = createIcon(mdiArrowDown);
|
||||||
export const Move = createIcon(Icons.mdiBookPlusMultipleOutline);
|
export const Move = createIcon(mdiBookPlusMultipleOutline);
|
||||||
export const Topic = createIcon(Icons.mdiBookmarkOutline);
|
export const Topic = createIcon(mdiBookmarkOutline);
|
||||||
export const Alert = createIcon(Icons.mdiAlert);
|
export const Alert = createIcon(mdiAlert);
|
||||||
export const Vault = createIcon(Icons.mdiShieldOutline);
|
export const Vault = createIcon(mdiShieldOutline);
|
||||||
export const Unlock = createIcon(Icons.mdiLockOpenOutline);
|
export const Unlock = createIcon(mdiLockOpenOutline);
|
||||||
export const Lock = createIcon(Icons.mdiLockOutline);
|
export const Lock = createIcon(mdiLockOutline);
|
||||||
export const Star = createIcon(Icons.mdiStar);
|
export const Star = createIcon(mdiStar);
|
||||||
export const StarOutline = createIcon(Icons.mdiStarOutline);
|
export const StarOutline = createIcon(mdiStarOutline);
|
||||||
export const Circle = createIcon(Icons.mdiCircle);
|
export const Circle = createIcon(mdiCircle);
|
||||||
export const CircleEmpty = createIcon(Icons.mdiCircleOutline);
|
export const CircleEmpty = createIcon(mdiCircleOutline);
|
||||||
export const Update = createIcon(Icons.mdiUpdate);
|
export const Update = createIcon(mdiUpdate);
|
||||||
export const Check = createIcon(Icons.mdiCheck);
|
export const Check = createIcon(mdiCheck);
|
||||||
export const Cross = createIcon(Icons.mdiClose);
|
export const Cross = createIcon(mdiClose);
|
||||||
export const MoreVertical = createIcon(Icons.mdiDotsVertical);
|
export const MoreVertical = createIcon(mdiDotsVertical);
|
||||||
export const Trash = createIcon(Icons.mdiTrashCanOutline);
|
export const Trash = createIcon(mdiTrashCanOutline);
|
||||||
export const TopicRemove = createIcon(Icons.mdiBookRemoveOutline);
|
export const TopicRemove = createIcon(mdiBookRemoveOutline);
|
||||||
export const Search = createIcon(Icons.mdiMagnify);
|
export const Search = createIcon(mdiMagnify);
|
||||||
export const Menu = createIcon(Icons.mdiMenu);
|
export const Menu = createIcon(mdiMenu);
|
||||||
export const Login = createIcon(Icons.mdiLoginVariant);
|
export const Login = createIcon(mdiLoginVariant);
|
||||||
export const Email = createIcon(Icons.mdiEmailAlertOutline);
|
export const Email = createIcon(mdiEmailAlertOutline);
|
||||||
export const Signup = createIcon(Icons.mdiAccountOutline);
|
export const Signup = createIcon(mdiAccountOutline);
|
||||||
export const Logout = createIcon(Icons.mdiLogoutVariant);
|
export const Logout = createIcon(mdiLogoutVariant);
|
||||||
export const FocusMode = createIcon(Icons.mdiSunglasses);
|
export const FocusMode = createIcon(mdiSunglasses);
|
||||||
export const NormalMode = createIcon(Icons.mdiGlasses);
|
export const NormalMode = createIcon(mdiGlasses);
|
||||||
export const Settings = createIcon(Icons.mdiCogOutline);
|
export const Settings = createIcon(mdiCogOutline);
|
||||||
export const Home = createIcon(Icons.mdiHomeOutline);
|
export const Home = createIcon(mdiHomeOutline);
|
||||||
export const Restore = createIcon(Icons.mdiRecycle);
|
export const Restore = createIcon(mdiRecycle);
|
||||||
export const Sync = createIcon(Icons.mdiSync);
|
export const Sync = createIcon(mdiSync);
|
||||||
export const Loading = createIcon(Icons.mdiLoading, true);
|
export const Loading = createIcon(mdiLoading, true);
|
||||||
export const Export = createIcon(Icons.mdiExportVariant);
|
export const Export = createIcon(mdiExportVariant);
|
||||||
export const AddToNotebook = createIcon(Icons.mdiBookPlusMultipleOutline);
|
export const AddToNotebook = createIcon(mdiBookPlusMultipleOutline);
|
||||||
export const Expand = createIcon(Icons.mdiArrowExpandDown);
|
export const Expand = createIcon(mdiArrowExpandDown);
|
||||||
export const Shortcut = createIcon(Icons.mdiArrowTopRightThick);
|
export const Shortcut = createIcon(mdiArrowTopRightThick);
|
||||||
|
export const ChevronLeft = createIcon(mdiChevronLeft);
|
||||||
/** Properties Icons */
|
export const ChevronRight = createIcon(mdiChevronRight);
|
||||||
export const ChevronLeft = createIcon(Icons.mdiChevronLeft);
|
export const Close = createIcon(mdiClose);
|
||||||
export const ChevronRight = createIcon(Icons.mdiChevronRight);
|
export const Tag = createIcon(mdiPound);
|
||||||
export const Close = createIcon(Icons.mdiClose);
|
export const Tag2 = createIcon(mdiPound);
|
||||||
export const Tag = createIcon(Icons.mdiPound);
|
export const Pin = createIcon(mdiPinOutline);
|
||||||
export const Tag2 = createIcon(Icons.mdiPound);
|
export const PinFilled = createIcon(mdiPin);
|
||||||
export const Pin = createIcon(Icons.mdiPinOutline);
|
export const User = createIcon(mdiAccountOutline);
|
||||||
export const PinFilled = createIcon(Icons.mdiPin);
|
export const DarkMode = createIcon(mdiWeatherNight);
|
||||||
|
export const LightMode = createIcon(mdiWeatherSunny);
|
||||||
/** Settings Icons */
|
export const Theme = createIcon(mdiThemeLightDark);
|
||||||
export const User = createIcon(Icons.mdiAccountOutline);
|
export const Checkmark = createIcon(mdiCheck);
|
||||||
export const DarkMode = createIcon(Icons.mdiWeatherNight);
|
export const CheckCircle = createIcon(mdiCheckCircle);
|
||||||
export const LightMode = createIcon(Icons.mdiWeatherSunny);
|
export const Properties = createIcon(mdiDotsVertical);
|
||||||
export const Theme = createIcon(Icons.mdiThemeLightDark);
|
export const Markdown = createIcon(mdiLanguageMarkdownOutline);
|
||||||
export const Checkmark = createIcon(Icons.mdiCheck);
|
export const PDF = createIcon(mdiFilePdfOutline);
|
||||||
export const CheckCircle = createIcon(Icons.mdiCheckCircle);
|
export const HTML = createIcon(mdiLanguageHtml5);
|
||||||
|
export const Text = createIcon(mdiFormatTitle);
|
||||||
export const Properties = createIcon(Icons.mdiDotsVertical);
|
export const Success = createIcon(mdiCheckCircle);
|
||||||
|
export const Error = createIcon(mdiAlertCircle);
|
||||||
// FORMATS
|
export const Warn = createIcon(mdiAlert);
|
||||||
|
export const Info = createIcon(mdiInformation);
|
||||||
export const Markdown = createIcon(Icons.mdiLanguageMarkdownOutline);
|
export const ToggleUnchecked = createIcon(mdiToggleSwitchOffOutline);
|
||||||
export const PDF = createIcon(Icons.mdiFilePdfOutline);
|
export const ToggleChecked = createIcon(mdiToggleSwitchOutline);
|
||||||
export const HTML = createIcon(Icons.mdiLanguageHtml5);
|
export const Backup = createIcon(mdiBackupRestore);
|
||||||
export const Text = createIcon(Icons.mdiFormatTitle);
|
export const Buy = createIcon(mdiCurrencyUsdCircleOutline);
|
||||||
|
export const Edit = createIcon(mdiPencil);
|
||||||
// TOAST
|
export const Undo = createIcon(mdiUndoVariant);
|
||||||
export const Success = createIcon(Icons.mdiCheckCircle);
|
export const Redo = createIcon(mdiRedoVariant);
|
||||||
export const Error = createIcon(Icons.mdiAlertCircle);
|
export const Filter = createIcon(mdiTune);
|
||||||
export const Warn = createIcon(Icons.mdiAlert);
|
export const ChevronDown = createIcon(mdiChevronDown);
|
||||||
export const Info = createIcon(Icons.mdiInformation);
|
export const ChevronUp = createIcon(mdiChevronUp);
|
||||||
|
export const SortAsc = createIcon(mdiSortAscending);
|
||||||
export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOffOutline);
|
export const SortDesc = createIcon(mdiSortDescending);
|
||||||
export const ToggleChecked = createIcon(Icons.mdiToggleSwitchOutline);
|
export const PasswordInvisible = createIcon(mdiEye);
|
||||||
|
export const PasswordVisible = createIcon(mdiEyeOff);
|
||||||
export const Backup = createIcon(Icons.mdiBackupRestore);
|
export const Fullscreen = createIcon(mdiFullscreen);
|
||||||
export const Buy = createIcon(Icons.mdiCurrencyUsdCircleOutline);
|
export const ExitFullscreen = createIcon(mdiFullscreenExit);
|
||||||
|
export const Announcement = createIcon(mdiBullhorn);
|
||||||
export const Edit = createIcon(Icons.mdiPencil);
|
export const Publish = createIcon(mdiCloudUploadOutline);
|
||||||
|
export const Published = createIcon(mdiCloudCheckOutline);
|
||||||
export const Undo = createIcon(Icons.mdiUndoVariant);
|
export const Copy = createIcon(mdiContentCopy);
|
||||||
export const Redo = createIcon(Icons.mdiRedoVariant);
|
export const Select = createIcon(mdiCheckboxMultipleMarkedCircleOutline);
|
||||||
|
export const NotebookEdit = createIcon(mdiBookEditOutline);
|
||||||
export const Filter = createIcon(Icons.mdiTune);
|
export const DeleteForver = createIcon(mdiDeleteForeverOutline);
|
||||||
|
export const Monographs = createIcon(mdiTextBoxMultipleOutline);
|
||||||
export const ChevronDown = createIcon(Icons.mdiChevronDown);
|
export const Rocket = createIcon(mdiRocketLaunchOutline);
|
||||||
export const ChevronUp = createIcon(Icons.mdiChevronUp);
|
export const Share = createIcon(mdiShareVariantOutline);
|
||||||
|
export const Password = createIcon(mdiFormTextboxPassword);
|
||||||
export const SortAsc = createIcon(Icons.mdiSortAscending);
|
export const Destruct = createIcon(mdiBomb);
|
||||||
export const SortDesc = createIcon(Icons.mdiSortDescending);
|
export const CompactView = createIcon(mdiViewHeadline);
|
||||||
|
export const DetailedView = createIcon(mdiViewSequentialOutline);
|
||||||
export const PasswordInvisible = createIcon(Icons.mdiEye);
|
export const MailCheck = createIcon(mdiEmailCheckOutline);
|
||||||
export const PasswordVisible = createIcon(Icons.mdiEyeOff);
|
export const Discord = createIcon(mdiDiscord);
|
||||||
|
export const Twitter = createIcon(mdiTwitter);
|
||||||
export const Fullscreen = createIcon(Icons.mdiFullscreen);
|
export const Reddit = createIcon(mdiReddit);
|
||||||
export const ExitFullscreen = createIcon(Icons.mdiFullscreenExit);
|
export const Dismiss = createIcon(mdiClose);
|
||||||
|
export const File = createIcon(mdiFileOutline);
|
||||||
export const Announcement = createIcon(Icons.mdiBullhorn);
|
export const Download = createIcon(mdiArrowDown);
|
||||||
export const Publish = createIcon(Icons.mdiCloudUploadOutline);
|
export const ImageDownload = createIcon(mdiImage);
|
||||||
export const Published = createIcon(Icons.mdiCloudCheckOutline);
|
|
||||||
|
|
||||||
export const Copy = createIcon(Icons.mdiContentCopy);
|
|
||||||
|
|
||||||
export const Select = createIcon(Icons.mdiCheckboxMultipleMarkedCircleOutline);
|
|
||||||
export const NotebookEdit = createIcon(Icons.mdiBookEditOutline);
|
|
||||||
export const DeleteForver = createIcon(Icons.mdiDeleteForeverOutline);
|
|
||||||
|
|
||||||
export const Monographs = createIcon(Icons.mdiTextBoxMultipleOutline);
|
|
||||||
|
|
||||||
export const Rocket = createIcon(Icons.mdiRocketLaunchOutline);
|
|
||||||
export const Share = createIcon(Icons.mdiShareVariantOutline);
|
|
||||||
export const Password = createIcon(Icons.mdiFormTextboxPassword);
|
|
||||||
export const Destruct = createIcon(Icons.mdiBomb);
|
|
||||||
|
|
||||||
export const CompactView = createIcon(Icons.mdiViewHeadline);
|
|
||||||
export const DetailedView = createIcon(Icons.mdiViewSequentialOutline);
|
|
||||||
|
|
||||||
export const MailCheck = createIcon(Icons.mdiEmailCheckOutline);
|
|
||||||
|
|
||||||
export const Discord = createIcon(Icons.mdiDiscord);
|
|
||||||
export const Twitter = createIcon(Icons.mdiTwitter);
|
|
||||||
export const Reddit = createIcon(Icons.mdiReddit);
|
|
||||||
|
|
||||||
export const Dismiss = createIcon(Icons.mdiClose);
|
|
||||||
|
|
||||||
export const File = createIcon(Icons.mdiFileOutline);
|
|
||||||
export const Download = createIcon(Icons.mdiArrowDown);
|
|
||||||
export const ImageDownload = createIcon(Icons.mdiImage);
|
|
||||||
|
|||||||
21
apps/web/src/components/loader/index.js
Normal file
21
apps/web/src/components/loader/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Flex, Text } from "rebass";
|
||||||
|
import { Loading } from "../icons";
|
||||||
|
|
||||||
|
export default function Loader({ title, text }) {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
flex={1}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Loading rotate />
|
||||||
|
<Text variant="subtitle" mt={4}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text variant="body" mt={2}>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import React, { useEffect, useMemo } from "react";
|
|||||||
import { Flex, Box, Text, Button } from "rebass";
|
import { Flex, Box, Text, Button } from "rebass";
|
||||||
import { useIsUserPremium } from "../../hooks/use-is-user-premium";
|
import { useIsUserPremium } from "../../hooks/use-is-user-premium";
|
||||||
import useMobile from "../../utils/use-mobile";
|
import useMobile from "../../utils/use-mobile";
|
||||||
import Animated from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
|
|
||||||
function Menu(props) {
|
function Menu(props) {
|
||||||
const { menuItems, data, closeMenu, id, style, sx, state } = props;
|
const { menuItems, data, closeMenu, id, style, sx, state } = props;
|
||||||
@@ -193,7 +193,7 @@ function MobileMenuContainer({ style, id, state, title, children }) {
|
|||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
sx={{ position: "relative" }}
|
sx={{ position: "relative" }}
|
||||||
>
|
>
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
width="100%"
|
width="100%"
|
||||||
bg="background"
|
bg="background"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -221,7 +221,7 @@ function MobileMenuContainer({ style, id, state, title, children }) {
|
|||||||
</Text>
|
</Text>
|
||||||
{children}
|
{children}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
import React, { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { Box, Flex } from "rebass";
|
import { Box, Flex } from "rebass";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import {
|
||||||
import * as Icon from "../icons";
|
Note,
|
||||||
import { useStore as useUserStore } from "../../stores/user-store";
|
Notebook,
|
||||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
StarOutline,
|
||||||
import Animated from "../animated";
|
Monographs,
|
||||||
|
Tag,
|
||||||
|
Trash,
|
||||||
|
Settings,
|
||||||
|
Notebook2,
|
||||||
|
Tag2,
|
||||||
|
Topic,
|
||||||
|
DarkMode,
|
||||||
|
LightMode,
|
||||||
|
Sync,
|
||||||
|
Login,
|
||||||
|
Circle,
|
||||||
|
} from "../icons";
|
||||||
|
import { AnimatedFlex } from "../animated";
|
||||||
import NavigationItem from "./navigation-item";
|
import NavigationItem from "./navigation-item";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
import useMobile from "../../utils/use-mobile";
|
import useMobile from "../../utils/use-mobile";
|
||||||
import { useLocation } from "wouter";
|
|
||||||
import { showRenameColorDialog } from "../../common/dialog-controller";
|
import { showRenameColorDialog } from "../../common/dialog-controller";
|
||||||
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
|
import { useStore as useUserStore } from "../../stores/user-store";
|
||||||
|
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||||
|
import useLocation from "../../hooks/use-location";
|
||||||
|
|
||||||
function shouldSelectNavItem(route, pin) {
|
function shouldSelectNavItem(route, pin) {
|
||||||
if (pin.type === "notebook") {
|
if (pin.type === "notebook") {
|
||||||
@@ -23,31 +39,31 @@ function shouldSelectNavItem(route, pin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ title: "Notes", path: "/", icon: Icon.Note },
|
{ title: "Notes", path: "/notes", icon: Note },
|
||||||
{
|
{
|
||||||
title: "Notebooks",
|
title: "Notebooks",
|
||||||
path: "/notebooks",
|
path: "/notebooks",
|
||||||
icon: Icon.Notebook,
|
icon: Notebook,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Favorites",
|
title: "Favorites",
|
||||||
path: "/favorites",
|
path: "/favorites",
|
||||||
icon: Icon.StarOutline,
|
icon: StarOutline,
|
||||||
},
|
},
|
||||||
{ title: "Tags", path: "/tags", icon: Icon.Tag },
|
{ title: "Tags", path: "/tags", icon: Tag },
|
||||||
{
|
{
|
||||||
title: "Monographs",
|
title: "Monographs",
|
||||||
path: "/monographs",
|
path: "/monographs",
|
||||||
icon: Icon.Monographs,
|
icon: Monographs,
|
||||||
},
|
},
|
||||||
{ title: "Trash", path: "/trash", icon: Icon.Trash },
|
{ title: "Trash", path: "/trash", icon: Trash },
|
||||||
];
|
];
|
||||||
|
|
||||||
const bottomRoutes = [
|
const bottomRoutes = [
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
icon: Icon.Settings,
|
icon: Settings,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -77,7 +93,7 @@ function NavigationMenu(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
id="navigationmenu"
|
id="navigationmenu"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@@ -141,7 +157,7 @@ function NavigationMenu(props) {
|
|||||||
<NavigationItem
|
<NavigationItem
|
||||||
key={color.id}
|
key={color.id}
|
||||||
title={db.colors.alias(color.id)}
|
title={db.colors.alias(color.id)}
|
||||||
icon={Icon.Circle}
|
icon={Circle}
|
||||||
selected={location === `/colors/${color.id}`}
|
selected={location === `/colors/${color.id}`}
|
||||||
color={color.title.toLowerCase()}
|
color={color.title.toLowerCase()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -181,10 +197,10 @@ function NavigationMenu(props) {
|
|||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
pin.type === "notebook"
|
pin.type === "notebook"
|
||||||
? Icon.Notebook2
|
? Notebook2
|
||||||
: pin.type === "tag"
|
: pin.type === "tag"
|
||||||
? Icon.Tag2
|
? Tag2
|
||||||
: Icon.Topic
|
: Topic
|
||||||
}
|
}
|
||||||
isShortcut
|
isShortcut
|
||||||
selected={shouldSelectNavItem(location, pin)}
|
selected={shouldSelectNavItem(location, pin)}
|
||||||
@@ -204,13 +220,13 @@ function NavigationMenu(props) {
|
|||||||
{theme === "light" ? (
|
{theme === "light" ? (
|
||||||
<NavigationItem
|
<NavigationItem
|
||||||
title="Dark mode"
|
title="Dark mode"
|
||||||
icon={Icon.DarkMode}
|
icon={DarkMode}
|
||||||
onClick={toggleNightMode}
|
onClick={toggleNightMode}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<NavigationItem
|
<NavigationItem
|
||||||
title="Light mode"
|
title="Light mode"
|
||||||
icon={Icon.LightMode}
|
icon={LightMode}
|
||||||
onClick={toggleNightMode}
|
onClick={toggleNightMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -219,14 +235,14 @@ function NavigationMenu(props) {
|
|||||||
<NavigationItem
|
<NavigationItem
|
||||||
title="Sync"
|
title="Sync"
|
||||||
isLoading={isSyncing}
|
isLoading={isSyncing}
|
||||||
icon={Icon.Sync}
|
icon={Sync}
|
||||||
onClick={sync}
|
onClick={sync}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<NavigationItem
|
<NavigationItem
|
||||||
title="Login"
|
title="Login"
|
||||||
icon={Icon.Login}
|
icon={Login}
|
||||||
onClick={() => navigate("/login")}
|
onClick={() => navigate("/login")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -242,7 +258,7 @@ function NavigationMenu(props) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default NavigationMenu;
|
export default NavigationMenu;
|
||||||
|
|||||||
190
apps/web/src/components/navigation-menu/index.lite.js
Normal file
190
apps/web/src/components/navigation-menu/index.lite.js
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { Flex } from "rebass";
|
||||||
|
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||||
|
import { Button, Text } from "rebass";
|
||||||
|
import useTablet from "../../utils/use-tablet";
|
||||||
|
import {
|
||||||
|
Note,
|
||||||
|
Notebook,
|
||||||
|
StarOutline,
|
||||||
|
Monographs,
|
||||||
|
Tag,
|
||||||
|
Trash,
|
||||||
|
Settings,
|
||||||
|
DarkMode,
|
||||||
|
LightMode,
|
||||||
|
Sync,
|
||||||
|
Login,
|
||||||
|
} from "../icons";
|
||||||
|
import useLocation from "../../hooks/use-location";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ title: "Notes", path: "/", icon: Note },
|
||||||
|
{
|
||||||
|
title: "Notebooks",
|
||||||
|
path: "/notebooks",
|
||||||
|
icon: Notebook,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Favorites",
|
||||||
|
path: "/favorites",
|
||||||
|
icon: StarOutline,
|
||||||
|
},
|
||||||
|
{ title: "Tags", path: "/tags", icon: Tag },
|
||||||
|
{
|
||||||
|
title: "Monographs",
|
||||||
|
path: "/monographs",
|
||||||
|
icon: Monographs,
|
||||||
|
},
|
||||||
|
{ title: "Trash", path: "/trash", icon: Trash },
|
||||||
|
];
|
||||||
|
|
||||||
|
const bottomRoutes = [
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
path: "/settings",
|
||||||
|
icon: Settings,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const NAVIGATION_MENU_WIDTH = "10em";
|
||||||
|
const NAVIGATION_MENU_TABLET_WIDTH = "4em";
|
||||||
|
|
||||||
|
function NavigationMenu() {
|
||||||
|
const isLoggedIn = false;
|
||||||
|
const theme = useThemeStore((store) => store.theme);
|
||||||
|
const toggleNightMode = useThemeStore((store) => store.toggleNightMode);
|
||||||
|
const [location] = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
id="navigationmenu"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="space-between"
|
||||||
|
flex={1}
|
||||||
|
sx={{
|
||||||
|
borderRight: "1px solid",
|
||||||
|
borderRightColor: "border",
|
||||||
|
minWidth: [
|
||||||
|
NAVIGATION_MENU_WIDTH,
|
||||||
|
NAVIGATION_MENU_TABLET_WIDTH,
|
||||||
|
NAVIGATION_MENU_WIDTH,
|
||||||
|
],
|
||||||
|
maxWidth: [
|
||||||
|
NAVIGATION_MENU_WIDTH,
|
||||||
|
NAVIGATION_MENU_TABLET_WIDTH,
|
||||||
|
NAVIGATION_MENU_WIDTH,
|
||||||
|
],
|
||||||
|
zIndex: 1,
|
||||||
|
height: "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
bg={"bgSecondary"}
|
||||||
|
px={0}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
sx={{
|
||||||
|
overflow: "scroll",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
"::-webkit-scrollbar": { width: 0, height: 0 },
|
||||||
|
msOverflowStyle: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{routes.map((item) => (
|
||||||
|
<NavigationItem
|
||||||
|
key={item.path}
|
||||||
|
title={item.title}
|
||||||
|
icon={item.icon}
|
||||||
|
selected={
|
||||||
|
item.path === "/"
|
||||||
|
? location === item.path
|
||||||
|
: location.startsWith(item.path)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
{theme === "light" ? (
|
||||||
|
<NavigationItem
|
||||||
|
title="Dark mode"
|
||||||
|
icon={DarkMode}
|
||||||
|
onClick={toggleNightMode}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NavigationItem
|
||||||
|
title="Light mode"
|
||||||
|
icon={LightMode}
|
||||||
|
onClick={toggleNightMode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<>
|
||||||
|
<NavigationItem title="Sync" icon={Sync} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<NavigationItem title="Login" icon={Login} />
|
||||||
|
)}
|
||||||
|
{bottomRoutes.map((item) => (
|
||||||
|
<NavigationItem
|
||||||
|
key={item.path}
|
||||||
|
title={item.title}
|
||||||
|
icon={item.icon}
|
||||||
|
selected={location.startsWith(item.path)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default NavigationMenu;
|
||||||
|
|
||||||
|
function NavigationItem(props) {
|
||||||
|
const { icon: Icon, color, title, isLoading } = props;
|
||||||
|
const isTablet = useTablet();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
data-test-id={`navitem-${title.toLowerCase()}`}
|
||||||
|
variant="icon"
|
||||||
|
bg={props.selected ? "border" : "transparent"}
|
||||||
|
p={2}
|
||||||
|
mx={2}
|
||||||
|
mt={[1, 2, 1]}
|
||||||
|
sx={{
|
||||||
|
borderRadius: "default",
|
||||||
|
position: "relative",
|
||||||
|
":first-of-type": { mt: 2 },
|
||||||
|
":last-of-type": { mb: 2 },
|
||||||
|
}}
|
||||||
|
label={title}
|
||||||
|
title={title}
|
||||||
|
onClick={() => {
|
||||||
|
props.onClick();
|
||||||
|
}}
|
||||||
|
display="flex"
|
||||||
|
justifyContent={["flex-start", "center", "flex-start"]}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
size={isTablet ? 18 : 15}
|
||||||
|
color={color || (props.selected ? "primary" : "icon")}
|
||||||
|
rotate={isLoading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
display={["block", "none", "block"]}
|
||||||
|
variant="body"
|
||||||
|
fontSize="subtitle"
|
||||||
|
sx={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
fontWeight: props.selected ? "bold" : "normal",
|
||||||
|
}}
|
||||||
|
ml={1}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Fav } from "../../assets/fav.svg";
|
import Fav from "../../assets/fav.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
|
|
||||||
function FavoritesPlaceholder() {
|
function FavoritesPlaceholder() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Button, Flex, Text } from "rebass";
|
import { Button, Flex, Image, Text } from "rebass";
|
||||||
import { useStore } from "../../stores/theme-store";
|
import { useStore } from "../../stores/theme-store";
|
||||||
import { changeSvgTheme } from "../../utils/css";
|
import { changeSvgTheme } from "../../utils/css";
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ function Placeholder(props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changeSvgTheme(accent);
|
changeSvgTheme(accent);
|
||||||
}, [accent]);
|
}, [accent]);
|
||||||
const { image: Image, text, callToAction } = props;
|
const { image: PlaceholderImage, text, callToAction } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -17,7 +17,7 @@ function Placeholder(props) {
|
|||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
sx={{ position: "relative" }}
|
sx={{ position: "relative" }}
|
||||||
>
|
>
|
||||||
<Image width={"100%"} height={"100px"} />
|
<Image src={PlaceholderImage} width={"100%"} height={"100px"} />
|
||||||
<Text
|
<Text
|
||||||
variant="body"
|
variant="body"
|
||||||
mt={2}
|
mt={2}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Monographs } from "../../assets/monographs.svg";
|
import Monographs from "../../assets/monographs.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
|
|
||||||
function MonographsPlaceholder() {
|
function MonographsPlaceholder() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Notebook } from "../../assets/notebook.svg";
|
import Notebook from "../../assets/notebook.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
import * as Icon from "../icons";
|
import * as Icon from "../icons";
|
||||||
import { hashNavigate } from "../../navigation";
|
import { hashNavigate } from "../../navigation";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Note } from "../../assets/note.svg";
|
import Note from "../../assets/note.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
import * as Icon from "../icons";
|
import * as Icon from "../icons";
|
||||||
import { hashNavigate } from "../../navigation";
|
import { hashNavigate } from "../../navigation";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Search } from "../../assets/search.svg";
|
import Search from "../../assets/search.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
|
|
||||||
function SearchPlaceholder({ text }) {
|
function SearchPlaceholder({ text }) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Tag } from "../../assets/tag.svg";
|
import Tag from "../../assets/tag.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
|
|
||||||
function TagsPlaceholder() {
|
function TagsPlaceholder() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Notebook } from "../../assets/notebook.svg";
|
import Notebook from "../../assets/notebook.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
import * as Icon from "../icons";
|
import * as Icon from "../icons";
|
||||||
import { hashNavigate } from "../../navigation";
|
import { hashNavigate } from "../../navigation";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ReactComponent as Trash } from "../../assets/trash.svg";
|
import Trash from "../../assets/trash.svg";
|
||||||
import Placeholder from "./index";
|
import Placeholder from "./index";
|
||||||
|
|
||||||
function TrashPlaceholder() {
|
function TrashPlaceholder() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useAnimation } from "framer-motion";
|
import { useAnimation } from "framer-motion";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Flex } from "rebass";
|
import { Flex } from "rebass";
|
||||||
import Animated from "../animated";
|
import { AnimatedBox } from "../animated";
|
||||||
|
|
||||||
function ProgressBar(props) {
|
function ProgressBar(props) {
|
||||||
const { width, progress, duration = 1, onLoadingEnd, sx } = props;
|
const { width, progress, duration = 1, onLoadingEnd, sx } = props;
|
||||||
@@ -12,7 +12,7 @@ function ProgressBar(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex overflow="hidden" width={width} sx={sx}>
|
<Flex overflow="hidden" width={width} sx={sx}>
|
||||||
<Animated.Box
|
<AnimatedBox
|
||||||
height={5}
|
height={5}
|
||||||
initial={{ width: "0%" }}
|
initial={{ width: "0%" }}
|
||||||
animate={animation}
|
animate={animation}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useStore } from "../../stores/editor-store";
|
|||||||
import { AppEventManager, AppEvents, COLORS } from "../../common";
|
import { AppEventManager, AppEvents, COLORS } from "../../common";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
import Animated from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
import Toggle from "./toggle";
|
import Toggle from "./toggle";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import IconTag from "../icon-tag";
|
import IconTag from "../icon-tag";
|
||||||
@@ -77,7 +77,7 @@ function Properties({ noteId }) {
|
|||||||
if (isFocusMode || !sessionId) return null;
|
if (isFocusMode || !sessionId) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Animated.Flex
|
<AnimatedFlex
|
||||||
animate={{
|
animate={{
|
||||||
x: arePropertiesVisible ? 0 : 800,
|
x: arePropertiesVisible ? 0 : 800,
|
||||||
}}
|
}}
|
||||||
@@ -325,7 +325,7 @@ function Properties({ noteId }) {
|
|||||||
})}
|
})}
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</Animated.Flex>
|
</AnimatedFlex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Box, Button, Flex, Text } from "rebass";
|
import { Box, Button, Flex, Text } from "rebass";
|
||||||
import EditorFooter from "../editor/footer";
|
import EditorFooter from "../editor/footer";
|
||||||
import * as Icon from "../icons";
|
import { Circle, Sync, Loading, Update } from "../icons";
|
||||||
import { useStore as useUserStore } from "../../stores/user-store";
|
import { useStore as useUserStore } from "../../stores/user-store";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
import TimeAgo from "timeago-react";
|
import TimeAgo from "timeago-react";
|
||||||
@@ -43,7 +43,7 @@ function StatusBar() {
|
|||||||
display="flex"
|
display="flex"
|
||||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
>
|
>
|
||||||
<Icon.Circle
|
<Circle
|
||||||
size={7}
|
size={7}
|
||||||
color={user.isEmailConfirmed ? "success" : "warn"}
|
color={user.isEmailConfirmed ? "success" : "warn"}
|
||||||
/>
|
/>
|
||||||
@@ -58,7 +58,7 @@ function StatusBar() {
|
|||||||
onClick={sync}
|
onClick={sync}
|
||||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
>
|
>
|
||||||
<Icon.Sync size={10} rotate={isSyncing} />
|
<Sync size={10} rotate={isSyncing} />
|
||||||
<Text variant="subBody" ml={1}>
|
<Text variant="subBody" ml={1}>
|
||||||
{"Synced "}
|
{"Synced "}
|
||||||
{lastSynced ? (
|
{lastSynced ? (
|
||||||
@@ -76,7 +76,7 @@ function StatusBar() {
|
|||||||
onClick={() => navigate("/login")}
|
onClick={() => navigate("/login")}
|
||||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
>
|
>
|
||||||
<Icon.Circle size={7} color="error" />
|
<Circle size={7} color="error" />
|
||||||
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
||||||
Not logged in
|
Not logged in
|
||||||
</Text>
|
</Text>
|
||||||
@@ -84,7 +84,7 @@ function StatusBar() {
|
|||||||
)}
|
)}
|
||||||
{processingStatuses?.map(({ key, status, progress }) => (
|
{processingStatuses?.map(({ key, status, progress }) => (
|
||||||
<Flex key={key} ml={1} alignItems="center" justifyContent="center">
|
<Flex key={key} ml={1} alignItems="center" justifyContent="center">
|
||||||
<Icon.Loading size={12} />
|
<Loading size={12} />
|
||||||
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
||||||
{progress ? `${progress}% ${status}` : status}
|
{progress ? `${progress}% ${status}` : status}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -106,7 +106,7 @@ function StatusBar() {
|
|||||||
}}
|
}}
|
||||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
>
|
>
|
||||||
<Icon.Update
|
<Update
|
||||||
rotate={
|
rotate={
|
||||||
updateStatus.type !== "updated" &&
|
updateStatus.type !== "updated" &&
|
||||||
updateStatus.type !== "completed" &&
|
updateStatus.type !== "completed" &&
|
||||||
|
|||||||
27
apps/web/src/components/status-bar/index.lite.js
Normal file
27
apps/web/src/components/status-bar/index.lite.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Box, Button, Text } from "rebass";
|
||||||
|
import { Circle } from "../icons";
|
||||||
|
|
||||||
|
function StatusBar() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="bgSecondary"
|
||||||
|
display={["none", "flex"]}
|
||||||
|
sx={{ borderTop: "1px solid", borderTopColor: "border" }}
|
||||||
|
justifyContent="space-between"
|
||||||
|
px={2}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="statusitem"
|
||||||
|
display="flex"
|
||||||
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
|
>
|
||||||
|
<Circle size={7} color="error" />
|
||||||
|
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
||||||
|
Not logged in
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatusBar;
|
||||||
16
apps/web/src/hooks/use-database.js
Normal file
16
apps/web/src/hooks/use-database.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { initializeDatabase } from "../common/db";
|
||||||
|
|
||||||
|
export default function useDatabase() {
|
||||||
|
const [isAppLoaded, setIsAppLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
await import("../app.css");
|
||||||
|
await initializeDatabase();
|
||||||
|
setIsAppLoaded(true);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [isAppLoaded];
|
||||||
|
}
|
||||||
18
apps/web/src/hooks/use-location.js
Normal file
18
apps/web/src/hooks/use-location.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const { useState, useEffect } = require("react");
|
||||||
|
const { getCurrentPath, NavigationEvents } = require("../navigation");
|
||||||
|
|
||||||
|
export default function useLocation() {
|
||||||
|
const [location, setLocation] = useState(getCurrentPath());
|
||||||
|
useEffect(() => {
|
||||||
|
const navigateEvent = NavigationEvents.subscribe(
|
||||||
|
"onNavigate",
|
||||||
|
(_, location) => {
|
||||||
|
setLocation(location);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
navigateEvent.unsubscribe();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return [location];
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
|
|
||||||
export default function useSlider({ initialIndex, onSliding, onChange }) {
|
export default function useSlider(
|
||||||
const ref = useRef();
|
sliderId,
|
||||||
|
{ initialIndex, onSliding, onChange }
|
||||||
|
) {
|
||||||
|
const ref = useRef(document.getElementById(sliderId));
|
||||||
const slides = useMemo(() => [], []);
|
const slides = useMemo(() => [], []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,5 +52,5 @@ export default function useSlider({ initialIndex, onSliding, onChange }) {
|
|||||||
[ref, slides]
|
[ref, slides]
|
||||||
);
|
);
|
||||||
|
|
||||||
return [ref, slideToIndex];
|
return [slideToIndex];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
html,
|
|
||||||
body {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
font-size: 18px;
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
margin: 0 !important;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
||||||
Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeUp {
|
|
||||||
0% {
|
|
||||||
transform: translateY(500px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* svg {
|
|
||||||
width: 1.5rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.ReactModal__Overlay {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 200ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactModal__Overlay--after-open {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactModal__Overlay--before-close {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel.carousel-slider {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,46 @@
|
|||||||
import "./commands";
|
import { render } from "react-dom";
|
||||||
import React from "react";
|
import { getCurrentPath } from "./navigation";
|
||||||
import { initializeDatabase } from "./common/db";
|
|
||||||
import "./index.css";
|
|
||||||
import * as serviceWorker from "./serviceWorkerRegistration";
|
import * as serviceWorker from "./serviceWorkerRegistration";
|
||||||
import { loadTrackerScript } from "./utils/analytics";
|
if (process.env.REACT_APP_PLATFORM === "desktop") require("./commands");
|
||||||
import Config from "./utils/config";
|
|
||||||
import { isTesting } from "./utils/platform";
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
const ROUTES = {
|
||||||
loadTrackerScript();
|
"/account/recovery": {
|
||||||
console.log = () => {};
|
component: () => import("./views/recovery"),
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
"/account/verified": {
|
||||||
|
component: () => import("./views/email-confirmed"),
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
"/signup": {
|
||||||
|
component: () => import("./views/auth"),
|
||||||
|
props: { type: "signup" },
|
||||||
|
},
|
||||||
|
"/login": {
|
||||||
|
component: () => import("./views/auth"),
|
||||||
|
props: { type: "login" },
|
||||||
|
},
|
||||||
|
"/recover": {
|
||||||
|
component: () => import("./views/auth"),
|
||||||
|
props: { type: "recover" },
|
||||||
|
},
|
||||||
|
default: { component: () => import("./app"), props: {} },
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRoute() {
|
||||||
|
const path = getCurrentPath();
|
||||||
|
return ROUTES[path] || ROUTES.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOMEPAGE_ROUTE = { 1: "/notebooks", 2: "/favorites", 3: "/tags" };
|
const route = getRoute();
|
||||||
|
route.component().then(({ default: Component }) => {
|
||||||
async function checkRedirects(db) {
|
render(
|
||||||
const isLoggedIn = !!(await db.user.getUser());
|
<Component {...route.props} />,
|
||||||
if (window.location.pathname === "/") {
|
document.getElementById("root"),
|
||||||
const skipInitiation = Config.get("skipInitiation", false);
|
() => {
|
||||||
const homepage = Config.get("homepage", 0);
|
|
||||||
if (!isTesting() && !isLoggedIn && !skipInitiation)
|
|
||||||
window.location.replace("/signup");
|
|
||||||
else if (homepage) {
|
|
||||||
const route = HOMEPAGE_ROUTE[homepage];
|
|
||||||
window.location.replace(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeDatabase().then(async (db) => {
|
|
||||||
await checkRedirects(db);
|
|
||||||
|
|
||||||
import("react-dom").then(({ render }) => {
|
|
||||||
import("./App").then(({ default: App }) => {
|
|
||||||
render(<App />, document.getElementById("root"), async () => {
|
|
||||||
document.getElementById("splash").remove();
|
document.getElementById("splash").remove();
|
||||||
import("react-modal").then(({ default: Modal }) => {
|
}
|
||||||
Modal.setAppElement("#root");
|
);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ async function hashStream(reader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function readEncrypted(filename, key, cipherData) {
|
async function readEncrypted(filename, key, cipherData) {
|
||||||
console.log("Reading encrypted file", filename);
|
|
||||||
const fileHandle = await streamablefs.readFile(filename);
|
const fileHandle = await streamablefs.readFile(filename);
|
||||||
if (!fileHandle) {
|
if (!fileHandle) {
|
||||||
console.error(`File not found. Filename: ${filename}`);
|
console.error(`File not found. Filename: ${filename}`);
|
||||||
@@ -159,8 +158,6 @@ async function readEncrypted(filename, key, cipherData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadFile(filename, requestOptions) {
|
async function uploadFile(filename, requestOptions) {
|
||||||
console.log("Request to upload file", filename, requestOptions);
|
|
||||||
|
|
||||||
const fileHandle = await streamablefs.readFile(filename);
|
const fileHandle = await streamablefs.readFile(filename);
|
||||||
if (!fileHandle)
|
if (!fileHandle)
|
||||||
throw new Error(`File stream not found. Filename: ${filename}`);
|
throw new Error(`File stream not found. Filename: ${filename}`);
|
||||||
@@ -261,7 +258,6 @@ function reportProgress(ev, { type, hash }) {
|
|||||||
|
|
||||||
async function downloadFile(filename, requestOptions) {
|
async function downloadFile(filename, requestOptions) {
|
||||||
const { url, headers, cancellationToken } = requestOptions;
|
const { url, headers, cancellationToken } = requestOptions;
|
||||||
console.log("Request to download file", filename, url, headers);
|
|
||||||
if (await streamablefs.exists(filename)) return true;
|
if (await streamablefs.exists(filename)) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -273,7 +269,6 @@ async function downloadFile(filename, requestOptions) {
|
|||||||
reportProgress(ev, { type: "download", hash: filename }),
|
reportProgress(ev, { type: "download", hash: filename }),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("File downloaded", filename, url, response);
|
|
||||||
if (!isSuccessStatusCode(response.status)) return false;
|
if (!isSuccessStatusCode(response.status)) return false;
|
||||||
const distributor = new ChunkDistributor(ENCRYPTED_CHUNK_SIZE);
|
const distributor = new ChunkDistributor(ENCRYPTED_CHUNK_SIZE);
|
||||||
distributor.fill(new Uint8Array(response.data));
|
distributor.fill(new Uint8Array(response.data));
|
||||||
@@ -352,7 +347,6 @@ function cancellable(operation) {
|
|||||||
return {
|
return {
|
||||||
execute: () => operation(filename, requestOptions),
|
execute: () => operation(filename, requestOptions),
|
||||||
cancel: (message) => {
|
cancel: (message) => {
|
||||||
console.log("Canceled", message);
|
|
||||||
source.cancel(message);
|
source.cancel(message);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import EventManager from "notes-core/utils/eventmanager";
|
import EventManager from "notes-core/utils/eventmanager";
|
||||||
// import { useCallback } from "react";
|
import Config from "../utils/config";
|
||||||
|
|
||||||
export function navigate(url, replaceOrQuery, replace) {
|
export function navigate(url, replaceOrQuery, replace) {
|
||||||
if (typeof url !== "string") {
|
if (typeof url !== "string") {
|
||||||
@@ -81,3 +81,13 @@ export function getCurrentHash() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const NavigationEvents = new EventManager();
|
export const NavigationEvents = new EventManager();
|
||||||
|
|
||||||
|
const HOMEPAGE_ROUTE = {
|
||||||
|
0: "/notes",
|
||||||
|
1: "/notebooks",
|
||||||
|
2: "/favorites",
|
||||||
|
3: "/tags",
|
||||||
|
};
|
||||||
|
export function getHomeRoute() {
|
||||||
|
return HOMEPAGE_ROUTE[Config.get("homepage", 0)];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import AccountRecovery from "../views/recovery";
|
|
||||||
import EmailConfirmed from "../views/emailconfirmed";
|
|
||||||
import Auth from "../views/auth";
|
|
||||||
|
|
||||||
const rootroutes = {
|
|
||||||
"/account/recovery": () => ({ component: <AccountRecovery /> }),
|
|
||||||
"/account/verified": () => ({ component: <EmailConfirmed /> }),
|
|
||||||
"/signup": () => ({ component: <Auth type="signup" /> }),
|
|
||||||
"/login": () => ({ component: <Auth type="login" /> }),
|
|
||||||
"/recover": () => ({ component: <Auth type="recover" /> }),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default rootroutes;
|
|
||||||
@@ -15,7 +15,7 @@ import Monographs from "../views/monographs";
|
|||||||
import { showToast } from "../utils/toast";
|
import { showToast } from "../utils/toast";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
"/": () => ({
|
"/notes": () => ({
|
||||||
key: "home",
|
key: "home",
|
||||||
type: "notes",
|
type: "notes",
|
||||||
title: "Notes",
|
title: "Notes",
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ class UserStore extends BaseStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
EV.subscribe(EVENTS.databaseSyncRequested, async () => {
|
EV.subscribe(EVENTS.databaseSyncRequested, async () => {
|
||||||
console.log("Sync requested.");
|
|
||||||
await appStore.sync(false);
|
await appStore.sync(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import makeMatcher from "wouter/matcher";
|
import makeMatcher from "wouter/matcher";
|
||||||
import { navigate } from "../navigation";
|
import { navigate, getHomeRoute } from "../navigation";
|
||||||
import { store as selectionStore } from "../stores/selection-store";
|
|
||||||
|
|
||||||
export default function useRoutes(routes, options) {
|
export default function useRoutes(routes, options) {
|
||||||
const [location] = useLocation();
|
const [location] = useLocation();
|
||||||
const matcher = makeMatcher();
|
const matcher = makeMatcher();
|
||||||
|
|
||||||
// TODO move this to an extension function
|
if (location === "/") navigate(getHomeRoute());
|
||||||
if (selectionStore.get().isSelectionMode)
|
|
||||||
selectionStore.toggleSelectionMode(false);
|
options?.hooks?.beforeNavigate(location);
|
||||||
|
|
||||||
for (var key in routes) {
|
for (var key in routes) {
|
||||||
const [match, params] = matcher(key, location);
|
const [match, params] = matcher(key, location);
|
||||||
if (match) {
|
if (match) {
|
||||||
const result = routes[key](params);
|
const result = routes[key](params);
|
||||||
if (!result) break;
|
if (!result) break;
|
||||||
return result;
|
return [result, location];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!options) return;
|
if (!options) return [];
|
||||||
const { fallbackRoute } = options;
|
const { fallbackRoute } = options;
|
||||||
if (fallbackRoute) {
|
if (fallbackRoute) {
|
||||||
navigate(fallbackRoute);
|
navigate(fallbackRoute);
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Box, Button, Flex, Text } from "rebass";
|
import { Box, Button, Flex, Text } from "rebass";
|
||||||
import ThemeProvider from "../components/theme-provider";
|
import ThemeProvider from "../components/theme-provider";
|
||||||
import * as Icon from "../components/icons";
|
import { CheckCircle, Loading, ArrowRight, Error } from "../components/icons";
|
||||||
import Field from "../components/field";
|
import Field from "../components/field";
|
||||||
import { getQueryParams, navigate, useQueryParams } from "../navigation";
|
import { getQueryParams, navigate, useQueryParams } from "../navigation";
|
||||||
import { store as userstore } from "../stores/user-store";
|
import { store as userstore } from "../stores/user-store";
|
||||||
import { db } from "../common/db";
|
import { db } from "../common/db";
|
||||||
import Config from "../utils/config";
|
import Config from "../utils/config";
|
||||||
|
import useDatabase from "../hooks/use-database";
|
||||||
|
import Loader from "../components/loader";
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
@@ -90,7 +92,6 @@ const authTypes = {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
onSubmit: async (form, onError) => {
|
onSubmit: async (form, onError) => {
|
||||||
console.log(form);
|
|
||||||
if (form.password !== form.confirmPassword) {
|
if (form.password !== form.confirmPassword) {
|
||||||
onError("Passwords do not match.");
|
onError("Passwords do not match.");
|
||||||
return;
|
return;
|
||||||
@@ -162,6 +163,7 @@ function Auth(props) {
|
|||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
const [success, setSuccess] = useState();
|
const [success, setSuccess] = useState();
|
||||||
const [featureIndex, setFeatureIndex] = useState(0);
|
const [featureIndex, setFeatureIndex] = useState(0);
|
||||||
|
const [isAppLoaded] = useDatabase();
|
||||||
|
|
||||||
const data = authTypes[type];
|
const data = authTypes[type];
|
||||||
const feature = features[featureIndex];
|
const feature = features[featureIndex];
|
||||||
@@ -188,7 +190,7 @@ function Auth(props) {
|
|||||||
setSuccess();
|
setSuccess();
|
||||||
if (authTypes[type].resetOnNavigate) {
|
if (authTypes[type].resetOnNavigate) {
|
||||||
const form = document.getElementById("authForm");
|
const form = document.getElementById("authForm");
|
||||||
form.reset();
|
form?.reset();
|
||||||
}
|
}
|
||||||
}, [type]);
|
}, [type]);
|
||||||
|
|
||||||
@@ -211,7 +213,7 @@ function Auth(props) {
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.ArrowRight size={16} />
|
<ArrowRight size={16} />
|
||||||
<Text variant="body" ml={1}>
|
<Text variant="body" ml={1}>
|
||||||
Go to app
|
Go to app
|
||||||
</Text>
|
</Text>
|
||||||
@@ -262,7 +264,7 @@ function Auth(props) {
|
|||||||
mt={4}
|
mt={4}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Icon.ArrowRight size={18} color="static" />
|
<ArrowRight size={18} color="static" />
|
||||||
<Text variant="title" ml={1} color="static">
|
<Text variant="title" ml={1} color="static">
|
||||||
{feature.linkText}
|
{feature.linkText}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -285,6 +287,7 @@ function Auth(props) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex justifyContent="center" flex={1} flexShrink={0}>
|
<Flex justifyContent="center" flex={1} flexShrink={0}>
|
||||||
|
{isAppLoaded ? (
|
||||||
<Flex
|
<Flex
|
||||||
as="form"
|
as="form"
|
||||||
id="authForm"
|
id="authForm"
|
||||||
@@ -312,11 +315,19 @@ function Auth(props) {
|
|||||||
<Text variant="heading" textAlign="center" fontWeight="heading">
|
<Text variant="heading" textAlign="center" fontWeight="heading">
|
||||||
{data.title}
|
{data.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="body" mt={1} textAlign="center" color="fontTertiary">
|
<Text
|
||||||
|
variant="body"
|
||||||
|
mt={1}
|
||||||
|
textAlign="center"
|
||||||
|
color="fontTertiary"
|
||||||
|
>
|
||||||
{data.subtitle.text}{" "}
|
{data.subtitle.text}{" "}
|
||||||
{data.subtitle.action && (
|
{data.subtitle.action && (
|
||||||
<Text
|
<Text
|
||||||
sx={{ ":hover": { color: "dimPrimary" }, cursor: "pointer" }}
|
sx={{
|
||||||
|
":hover": { color: "dimPrimary" },
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
as="b"
|
as="b"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={data.subtitle.action.onClick}
|
onClick={data.subtitle.action.onClick}
|
||||||
@@ -391,7 +402,7 @@ function Auth(props) {
|
|||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Icon.Loading color="static" size={16} sx={{ mr: 1 }} />{" "}
|
<Loading color="static" size={16} sx={{ mr: 1 }} />{" "}
|
||||||
{data.primaryAction.loadingText}
|
{data.primaryAction.loadingText}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -409,8 +420,13 @@ function Auth(props) {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<Flex bg="errorBg" p={1} mt={2} sx={{ borderRadius: "default" }}>
|
<Flex
|
||||||
<Icon.Error size={15} color="error" />
|
bg="errorBg"
|
||||||
|
p={1}
|
||||||
|
mt={2}
|
||||||
|
sx={{ borderRadius: "default" }}
|
||||||
|
>
|
||||||
|
<Error size={15} color="error" />
|
||||||
<Text variant="error" ml={1}>
|
<Text variant="error" ml={1}>
|
||||||
{error}
|
{error}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -418,7 +434,7 @@ function Auth(props) {
|
|||||||
)}
|
)}
|
||||||
{success && (
|
{success && (
|
||||||
<Flex bg="shade" p={1} mt={2} sx={{ borderRadius: "default" }}>
|
<Flex bg="shade" p={1} mt={2} sx={{ borderRadius: "default" }}>
|
||||||
<Icon.CheckCircle size={15} color="primary" />
|
<CheckCircle size={15} color="primary" />
|
||||||
<Text variant="error" color="primary" ml={1}>
|
<Text variant="error" color="primary" ml={1}>
|
||||||
{success}
|
{success}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -430,6 +446,12 @@ function Auth(props) {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Loader
|
||||||
|
title="Did you know?"
|
||||||
|
text="Your password never leaves your device. Ever."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "../app.css";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Flex, Text } from "rebass";
|
import { Flex, Text } from "rebass";
|
||||||
import ThemeProvider from "../components/theme-provider";
|
import ThemeProvider from "../components/theme-provider";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useCallback } from "react";
|
|||||||
import { showRecoveryKeyDialog } from "../common/dialog-controller";
|
import { showRecoveryKeyDialog } from "../common/dialog-controller";
|
||||||
import { createBackup } from "../common";
|
import { createBackup } from "../common";
|
||||||
import Logo from "../assets/logo.svg";
|
import Logo from "../assets/logo.svg";
|
||||||
|
import useDatabase from "../hooks/use-database";
|
||||||
|
|
||||||
function navigate(path) {
|
function navigate(path) {
|
||||||
window.location.href = path;
|
window.location.href = path;
|
||||||
@@ -42,7 +43,9 @@ function useRecovery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useAuthenticateUser({ code, userId, performAction }) {
|
function useAuthenticateUser({ code, userId, performAction }) {
|
||||||
|
const [isAppLoaded] = useDatabase();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isAppLoaded) return;
|
||||||
performAction({
|
performAction({
|
||||||
message: "Authenticating. Please wait...",
|
message: "Authenticating. Please wait...",
|
||||||
error: "Failed to authenticate. Please try again.",
|
error: "Failed to authenticate. Please try again.",
|
||||||
@@ -63,7 +66,7 @@ function useAuthenticateUser({ code, userId, performAction }) {
|
|||||||
}
|
}
|
||||||
await db.user.fetchUser(true);
|
await db.user.fetchUser(true);
|
||||||
}
|
}
|
||||||
}, [code, userId, performAction]);
|
}, [code, userId, performAction, isAppLoaded]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const steps = [RecoveryKeyStep, BackupDataStep, NewPasswordStep, FinalStep];
|
const steps = [RecoveryKeyStep, BackupDataStep, NewPasswordStep, FinalStep];
|
||||||
|
|||||||
Reference in New Issue
Block a user