mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
web: fix service worker to allow Notesnook to behave as a PWA again (#2905)
* web: make service worker work again * web: only enable sourcemap during dev
This commit is contained in:
@@ -1,47 +0,0 @@
|
|||||||
const { execSync } = require("child_process");
|
|
||||||
const { cpus, networkInterfaces } = require("os");
|
|
||||||
const { version } = require("./package.json");
|
|
||||||
const ip = require("ip");
|
|
||||||
|
|
||||||
const NUM_CPUS = cpus().length;
|
|
||||||
const IS_CI = process.env.CI;
|
|
||||||
const gitHash = execSync("git rev-parse --short HEAD").toString().trim();
|
|
||||||
const APP_VERSION = version.replaceAll(".", "");
|
|
||||||
console.log("App version:", APP_VERSION);
|
|
||||||
console.log("Ip address:", ip.address());
|
|
||||||
module.exports = {
|
|
||||||
beta: {
|
|
||||||
REACT_APP_BETA: true
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
TEST_ALL: true
|
|
||||||
},
|
|
||||||
all: {
|
|
||||||
UV_THREADPOOL_SIZE: IS_CI ? NUM_CPUS : 2,
|
|
||||||
GENERATE_SOURCEMAP: process.env.NODE_ENV === "development",
|
|
||||||
// INLINE_RUNTIME_CHUNK: false,
|
|
||||||
// DISABLE_ESLINT_PLUGIN: true,
|
|
||||||
REACT_APP_GIT_HASH: gitHash,
|
|
||||||
REACT_APP_VERSION: APP_VERSION
|
|
||||||
},
|
|
||||||
dev: {
|
|
||||||
REACT_APP_LOCALHOST: ip.address()
|
|
||||||
},
|
|
||||||
web: {
|
|
||||||
REACT_APP_PLATFORM: "web"
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
PWDEBUG: 1,
|
|
||||||
DEBUG: "pw:api"
|
|
||||||
},
|
|
||||||
silent: {
|
|
||||||
REACT_APP_TEST: true
|
|
||||||
// DISABLE_ESLINT_PLUGIN: "true"
|
|
||||||
// FAST_REFRESH: "false",
|
|
||||||
// BROWSER: "none"
|
|
||||||
},
|
|
||||||
desktop: {
|
|
||||||
// BROWSER: "none",
|
|
||||||
REACT_APP_PLATFORM: "desktop"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
1083
apps/web/package-lock.json
generated
1083
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -82,8 +82,8 @@
|
|||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"env-cmd": "^10.1.0",
|
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"find-process": "^1.4.4",
|
"find-process": "^1.4.4",
|
||||||
"happy-dom": "^8.9.0",
|
"happy-dom": "^8.9.0",
|
||||||
@@ -111,13 +111,13 @@
|
|||||||
"@types/react-dom@>17": "17.0.2"
|
"@types/react-dom@>17": "17.0.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "env-cmd -e all,dev,web vite",
|
"start": "cross-env PLATFORM=web vite",
|
||||||
"start:desktop": "env-cmd -e all,desktop vite",
|
"start:desktop": "cross-env PLATFORM=desktop vite",
|
||||||
"start:test": "serve -s build/ -p 3000",
|
"start:test": "serve -s build/ -p 3000",
|
||||||
"build": "env-cmd -e all,web vite build",
|
"build": "cross-env PLATFORM=web vite build",
|
||||||
"build:test": "env-cmd -e all,dev,web,silent vite build",
|
"build:test": "cross-env PLATFORM=web TEST=true vite build",
|
||||||
"build:beta": "env-cmd -e all,web,beta vite build",
|
"build:beta": "cross-env PLATFORM=web BETA=true vite build",
|
||||||
"build:desktop": "env-cmd -e all,desktop vite build",
|
"build:desktop": "cross-env PLATFORM=desktop vite build",
|
||||||
"test": "playwright test -u"
|
"test": "playwright test -u"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
showOnboardingDialog
|
showOnboardingDialog
|
||||||
} from "./common/dialog-controller";
|
} from "./common/dialog-controller";
|
||||||
import useSystemTheme from "./hooks/use-system-theme";
|
import useSystemTheme from "./hooks/use-system-theme";
|
||||||
import { isTesting } from "./utils/platform";
|
|
||||||
import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
|
import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
|
||||||
import { showToast } from "./utils/toast";
|
import { showToast } from "./utils/toast";
|
||||||
import { interruptedOnboarding } from "./dialogs/onboarding-dialog";
|
import { interruptedOnboarding } from "./dialogs/onboarding-dialog";
|
||||||
@@ -199,7 +199,7 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialogAnnouncements.length || isTesting()) return;
|
if (!dialogAnnouncements.length || IS_TESTING) return;
|
||||||
(async () => {
|
(async () => {
|
||||||
await showAnnouncementDialog(dialogAnnouncements[0]);
|
await showAnnouncementDialog(dialogAnnouncements[0]);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -111,11 +111,7 @@ function SuspenseLoader<TComponent extends React.JSXElementConstructor<any>>({
|
|||||||
props: any
|
props: any
|
||||||
) => React.ReactComponentElement<any, any>;
|
) => React.ReactComponentElement<any, any>;
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense fallback={IS_DESKTOP_APP ? null : fallback}>
|
||||||
fallback={
|
|
||||||
import.meta.env.REACT_APP_PLATFORM === "desktop" ? null : fallback
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
|||||||
FS,
|
FS,
|
||||||
new Compressor()
|
new Compressor()
|
||||||
);
|
);
|
||||||
// if (isTesting()) {
|
// if (IS_TESTING) {
|
||||||
|
|
||||||
// } else {
|
// } else {
|
||||||
// db.host({
|
// db.host({
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import Config from "../utils/config";
|
|||||||
import { hashNavigate, getCurrentHash } from "../navigation";
|
import { hashNavigate, getCurrentHash } from "../navigation";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { sanitizeFilename } from "@notesnook/common";
|
import { sanitizeFilename } from "@notesnook/common";
|
||||||
import { isDesktop, isTesting } from "../utils/platform";
|
|
||||||
import { store as userstore } from "../stores/user-store";
|
import { store as userstore } from "../stores/user-store";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { showToast } from "../utils/toast";
|
import { showToast } from "../utils/toast";
|
||||||
@@ -66,7 +66,7 @@ export const CREATE_BUTTON_MAP = {
|
|||||||
|
|
||||||
export async function introduceFeatures() {
|
export async function introduceFeatures() {
|
||||||
const hash = getCurrentHash().replace("#", "");
|
const hash = getCurrentHash().replace("#", "");
|
||||||
if (!!hash || isTesting()) return;
|
if (!!hash || IS_TESTING) return;
|
||||||
const features = [];
|
const features = [];
|
||||||
for (let feature of features) {
|
for (let feature of features) {
|
||||||
if (!Config.get(`feature:${feature}`)) {
|
if (!Config.get(`feature:${feature}`)) {
|
||||||
@@ -95,7 +95,7 @@ export async function createBackup() {
|
|||||||
const filename = sanitizeFilename(`notesnook-backup-${getFormattedDate()}`);
|
const filename = sanitizeFilename(`notesnook-backup-${getFormattedDate()}`);
|
||||||
|
|
||||||
const ext = "nnbackup";
|
const ext = "nnbackup";
|
||||||
if (isDesktop()) {
|
if (IS_DESKTOP_APP) {
|
||||||
const directory = Config.get(
|
const directory = Config.get(
|
||||||
"backupStorageLocation",
|
"backupStorageLocation",
|
||||||
PATHS.backupsDirectory
|
PATHS.backupsDirectory
|
||||||
@@ -189,7 +189,7 @@ export function totalSubscriptionConsumed(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function showUpgradeReminderDialogs() {
|
export async function showUpgradeReminderDialogs() {
|
||||||
if (isTesting()) return;
|
if (IS_TESTING) return;
|
||||||
|
|
||||||
const user = userstore.get().user;
|
const user = userstore.get().user;
|
||||||
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
|
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { Backup, User, Email, Warn, Icon } from "../components/icons";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { showBuyDialog, showRecoveryKeyDialog } from "./dialog-controller";
|
import { showBuyDialog, showRecoveryKeyDialog } from "./dialog-controller";
|
||||||
import { hardNavigate, hashNavigate } from "../navigation";
|
import { hardNavigate, hashNavigate } from "../navigation";
|
||||||
import { isDesktop, isTesting } from "../utils/platform";
|
|
||||||
import { isUserPremium } from "../hooks/use-is-user-premium";
|
import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||||
import { showToast } from "../utils/toast";
|
import { showToast } from "../utils/toast";
|
||||||
import { TaskScheduler } from "../utils/task-scheduler";
|
import { TaskScheduler } from "../utils/task-scheduler";
|
||||||
@@ -176,9 +176,9 @@ function isIgnored(key: keyof typeof NoticesData) {
|
|||||||
|
|
||||||
var openedToast: { hide: () => void } | null = null;
|
var openedToast: { hide: () => void } | null = null;
|
||||||
async function saveBackup() {
|
async function saveBackup() {
|
||||||
if (isDesktop()) {
|
if (IS_DESKTOP_APP) {
|
||||||
await createBackup();
|
await createBackup();
|
||||||
} else if (isUserPremium() && !isTesting()) {
|
} else if (isUserPremium() && !IS_TESTING) {
|
||||||
if (openedToast !== null) return;
|
if (openedToast !== null) return;
|
||||||
openedToast = showToast(
|
openedToast = showToast(
|
||||||
"success",
|
"success",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ type TipTapProps = {
|
|||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SAVE_INTERVAL = import.meta.env.REACT_APP_TEST ? 100 : 300;
|
const SAVE_INTERVAL = IS_TESTING ? 100 : 300;
|
||||||
|
|
||||||
function save(
|
function save(
|
||||||
sessionId: number,
|
sessionId: number,
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import { useStore as useUserStore } from "../../stores/user-store";
|
|||||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||||
import useLocation from "../../hooks/use-location";
|
import useLocation from "../../hooks/use-location";
|
||||||
import { FlexScrollContainer } from "../scroll-container";
|
import { FlexScrollContainer } from "../scroll-container";
|
||||||
import { isDesktop } from "../../utils/platform";
|
|
||||||
|
|
||||||
type Route = {
|
type Route = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -192,7 +191,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
|||||||
))}
|
))}
|
||||||
{colors.map((color, index) => (
|
{colors.map((color, index) => (
|
||||||
<NavigationItem
|
<NavigationItem
|
||||||
animate={!isDesktop()}
|
animate={!IS_DESKTOP_APP}
|
||||||
index={index}
|
index={index}
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
key={color.id}
|
key={color.id}
|
||||||
@@ -221,7 +220,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
|||||||
/>
|
/>
|
||||||
{shortcuts.map((item, index) => (
|
{shortcuts.map((item, index) => (
|
||||||
<NavigationItem
|
<NavigationItem
|
||||||
animate={!isDesktop()}
|
animate={!IS_DESKTOP_APP}
|
||||||
index={colors.length - 1 + index}
|
index={colors.length - 1 + index}
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|||||||
@@ -31,20 +31,17 @@ import {
|
|||||||
PricingInfo
|
PricingInfo
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
// const isDev = false; // import.meta.env.NODE_ENV === "development";
|
// const isDev = false; // import.meta.env.DEV;
|
||||||
// const VENDOR_ID = isDev ? 1506 : 128190;
|
// const VENDOR_ID = isDev ? 1506 : 128190;
|
||||||
const PADDLE_ORIGIN =
|
const PADDLE_ORIGIN = import.meta.env.DEV
|
||||||
import.meta.env.NODE_ENV === "development"
|
? "https://sandbox-buy.paddle.com"
|
||||||
? "https://sandbox-buy.paddle.com"
|
: "https://buy.paddle.com";
|
||||||
: "https://buy.paddle.com";
|
const CHECKOUT_CREATE_ORIGIN = import.meta.env.DEV
|
||||||
const CHECKOUT_CREATE_ORIGIN =
|
? "https://sandbox-create-checkout.paddle.com"
|
||||||
import.meta.env.NODE_ENV === "development"
|
: "https://create-checkout.paddle.com";
|
||||||
? "https://sandbox-create-checkout.paddle.com"
|
const CHECKOUT_SERVICE_ORIGIN = import.meta.env.DEV
|
||||||
: "https://create-checkout.paddle.com";
|
? "https://sandbox-checkout-service.paddle.com"
|
||||||
const CHECKOUT_SERVICE_ORIGIN =
|
: "https://checkout-service.paddle.com";
|
||||||
import.meta.env.NODE_ENV === "development"
|
|
||||||
? "https://sandbox-checkout-service.paddle.com"
|
|
||||||
: "https://checkout-service.paddle.com";
|
|
||||||
|
|
||||||
const SUBSCRIBED_EVENTS: PaddleEvents[] = [
|
const SUBSCRIBED_EVENTS: PaddleEvents[] = [
|
||||||
PaddleEvents["Checkout.Loaded"],
|
PaddleEvents["Checkout.Loaded"],
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { isTesting } from "../../utils/platform";
|
|
||||||
import { Period, Plan } from "./types";
|
import { Period, Plan } from "./types";
|
||||||
|
|
||||||
type PlanMetadata = {
|
type PlanMetadata = {
|
||||||
@@ -32,7 +32,7 @@ export const DEFAULT_PLANS: Plan[] = [
|
|||||||
country: "PK",
|
country: "PK",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
discount: 0,
|
discount: 0,
|
||||||
id: import.meta.env.NODE_ENV === "development" ? "9822" : "648884",
|
id: import.meta.env.DEV ? "9822" : "648884",
|
||||||
price: { gross: 4.49, net: 0, tax: 0 }
|
price: { gross: 4.49, net: 0, tax: 0 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -40,7 +40,7 @@ export const DEFAULT_PLANS: Plan[] = [
|
|||||||
country: "PK",
|
country: "PK",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
discount: 0,
|
discount: 0,
|
||||||
id: import.meta.env.NODE_ENV === "development" ? "50305" : "658759",
|
id: import.meta.env.DEV ? "50305" : "658759",
|
||||||
price: { gross: 49.99, net: 0, tax: 0 }
|
price: { gross: 49.99, net: 0, tax: 0 }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -52,8 +52,7 @@ export const PLAN_METADATA: Record<Period, PlanMetadata> = {
|
|||||||
|
|
||||||
let CACHED_PLANS: Plan[];
|
let CACHED_PLANS: Plan[];
|
||||||
export async function getPlans(): Promise<Plan[] | null> {
|
export async function getPlans(): Promise<Plan[] | null> {
|
||||||
if (isTesting() || import.meta.env.NODE_ENV === "development")
|
if (IS_TESTING || import.meta.env.DEV) return DEFAULT_PLANS;
|
||||||
return DEFAULT_PLANS;
|
|
||||||
if (CACHED_PLANS) return CACHED_PLANS;
|
if (CACHED_PLANS) return CACHED_PLANS;
|
||||||
|
|
||||||
const url = `https://notesnook.com/api/v1/prices/products/web`;
|
const url = `https://notesnook.com/api/v1/prices/products/web`;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import Dialog from "../components/dialog";
|
|||||||
import { getHomeRoute, hardNavigate } from "../navigation";
|
import { getHomeRoute, hardNavigate } from "../navigation";
|
||||||
import { appVersion } from "../utils/version";
|
import { appVersion } from "../utils/version";
|
||||||
import Config from "../utils/config";
|
import Config from "../utils/config";
|
||||||
import { isTesting } from "../utils/platform";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { ArrowRight, Checkmark, Icon, Warn } from "../components/icons";
|
import { ArrowRight, Checkmark, Icon, Warn } from "../components/icons";
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ const features: Record<FeatureKeys, Feature> = {
|
|||||||
appVersion.isBeta || Config.has((k) => k.endsWith(":highlights"));
|
appVersion.isBeta || Config.has((k) => k.endsWith(":highlights"));
|
||||||
if (!hasShownAny) Config.set(key, true);
|
if (!hasShownAny) Config.set(key, true);
|
||||||
|
|
||||||
return hasShownAny && !isTesting() && !hasShownBefore;
|
return hasShownAny && !IS_TESTING && !hasShownBefore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { createBackup } from "../common";
|
|||||||
import { db } from "../common/db";
|
import { db } from "../common/db";
|
||||||
import { Perform } from "../common/dialog-controller";
|
import { Perform } from "../common/dialog-controller";
|
||||||
import { TaskManager } from "../common/task-manager";
|
import { TaskManager } from "../common/task-manager";
|
||||||
import { isDesktop } from "../utils/platform";
|
|
||||||
import Dialog from "../components/dialog";
|
import Dialog from "../components/dialog";
|
||||||
|
|
||||||
type MigrationProgressEvent = {
|
type MigrationProgressEvent = {
|
||||||
@@ -74,7 +74,7 @@ export default function MigrationDialog(props: MigrationDialogProps) {
|
|||||||
}, [props]);
|
}, [props]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDesktop()) {
|
if (IS_DESKTOP_APP) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await startMigration();
|
await startMigration();
|
||||||
})();
|
})();
|
||||||
@@ -115,7 +115,7 @@ export default function MigrationDialog(props: MigrationDialogProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop() || isProcessing) return null;
|
if (IS_DESKTOP_APP || isProcessing) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { SettingsGroup } from "./types";
|
import { SettingsGroup } from "./types";
|
||||||
import { useStore as useSettingStore } from "../../stores/setting-store";
|
import { useStore as useSettingStore } from "../../stores/setting-store";
|
||||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||||
import { isDesktop } from "../../utils/platform";
|
|
||||||
import { AccentColors } from "./components/accent-colors";
|
import { AccentColors } from "./components/accent-colors";
|
||||||
|
|
||||||
export const AppearanceSettings: SettingsGroup[] = [
|
export const AppearanceSettings: SettingsGroup[] = [
|
||||||
@@ -72,7 +72,7 @@ export const AppearanceSettings: SettingsGroup[] = [
|
|||||||
key: "zoom-factor",
|
key: "zoom-factor",
|
||||||
title: "Zoom factor",
|
title: "Zoom factor",
|
||||||
description: "Zoom in or out the app content.",
|
description: "Zoom in or out the app content.",
|
||||||
isHidden: () => !isDesktop(),
|
isHidden: () => !IS_DESKTOP_APP,
|
||||||
onStateChange: (listener) =>
|
onStateChange: (listener) =>
|
||||||
useThemeStore.subscribe(
|
useThemeStore.subscribe(
|
||||||
(s) => [s.theme, s.followSystemTheme],
|
(s) => [s.theme, s.followSystemTheme],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { isUserPremium } from "../../hooks/use-is-user-premium";
|
|||||||
import { createBackup, importBackup, verifyAccount } from "../../common";
|
import { createBackup, importBackup, verifyAccount } from "../../common";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
import { exportNotes } from "../../common/export";
|
import { exportNotes } from "../../common/export";
|
||||||
import { isDesktop, isTesting } from "../../utils/platform";
|
|
||||||
import { desktop } from "../../common/desktop-bridge";
|
import { desktop } from "../../common/desktop-bridge";
|
||||||
import { PATHS } from "@notesnook/desktop";
|
import { PATHS } from "@notesnook/desktop";
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export const BackupExportSettings: SettingsGroup[] = [
|
|||||||
key: "restore-backup",
|
key: "restore-backup",
|
||||||
title: "Restore backup",
|
title: "Restore backup",
|
||||||
description: "Restore a backup file from your disk drive.",
|
description: "Restore a backup file from your disk drive.",
|
||||||
isHidden: () => !useUserStore.getState().isLoggedIn && !isTesting(),
|
isHidden: () => !useUserStore.getState().isLoggedIn && !IS_TESTING,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
@@ -71,8 +71,8 @@ export const BackupExportSettings: SettingsGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "auto-backup",
|
key: "auto-backup",
|
||||||
title: isDesktop() ? "Automatic backups" : "Backup reminders",
|
title: IS_DESKTOP_APP ? "Automatic backups" : "Backup reminders",
|
||||||
description: isDesktop()
|
description: IS_DESKTOP_APP
|
||||||
? "Backup all your data automatically at a set interval."
|
? "Backup all your data automatically at a set interval."
|
||||||
: "You will be shown regular reminders to backup your data.",
|
: "You will be shown regular reminders to backup your data.",
|
||||||
isHidden: () => !isUserPremium(),
|
isHidden: () => !isUserPremium(),
|
||||||
@@ -119,7 +119,7 @@ export const BackupExportSettings: SettingsGroup[] = [
|
|||||||
key: "backup-directory",
|
key: "backup-directory",
|
||||||
title: "Backups directory",
|
title: "Backups directory",
|
||||||
description: "Select directory to store all backup files.",
|
description: "Select directory to store all backup files.",
|
||||||
isHidden: () => !isDesktop(),
|
isHidden: () => !IS_DESKTOP_APP,
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { useStore as useSettingStore } from "../../stores/setting-store";
|
|||||||
import { getFonts } from "@notesnook/editor";
|
import { getFonts } from "@notesnook/editor";
|
||||||
import { useSpellChecker } from "../../hooks/use-spell-checker";
|
import { useSpellChecker } from "../../hooks/use-spell-checker";
|
||||||
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
|
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
|
||||||
import { isDesktop } from "../../utils/platform";
|
|
||||||
import { CustomizeToolbar } from "./components/customize-toolbar";
|
import { CustomizeToolbar } from "./components/customize-toolbar";
|
||||||
|
|
||||||
export const EditorSettings: SettingsGroup[] = [
|
export const EditorSettings: SettingsGroup[] = [
|
||||||
@@ -118,7 +118,7 @@ symbols (e.g. 202305261253)`,
|
|||||||
key: "spell-check",
|
key: "spell-check",
|
||||||
section: "editor",
|
section: "editor",
|
||||||
header: "Spell check",
|
header: "Spell check",
|
||||||
isHidden: () => !isDesktop(),
|
isHidden: () => !IS_DESKTOP_APP,
|
||||||
onRender: () => {
|
onRender: () => {
|
||||||
useSpellChecker.getState().refresh();
|
useSpellChecker.getState().refresh();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import { SyncSettings } from "./sync-settings";
|
|||||||
import { BehaviourSettings } from "./behaviour-settings";
|
import { BehaviourSettings } from "./behaviour-settings";
|
||||||
import { DesktopIntegrationSettings } from "./desktop-integration-settings";
|
import { DesktopIntegrationSettings } from "./desktop-integration-settings";
|
||||||
import { NotificationsSettings } from "./notifications-settings";
|
import { NotificationsSettings } from "./notifications-settings";
|
||||||
import { isDesktop } from "../../utils/platform";
|
|
||||||
import { BackupExportSettings } from "./backup-export-settings";
|
import { BackupExportSettings } from "./backup-export-settings";
|
||||||
import { ImporterSettings } from "./importer-settings";
|
import { ImporterSettings } from "./importer-settings";
|
||||||
import { VaultSettings } from "./vault-settings";
|
import { VaultSettings } from "./vault-settings";
|
||||||
@@ -105,7 +105,7 @@ const sectionGroups: SectionGroup[] = [
|
|||||||
key: "desktop",
|
key: "desktop",
|
||||||
title: "Desktop integration",
|
title: "Desktop integration",
|
||||||
icon: Desktop,
|
icon: Desktop,
|
||||||
isHidden: () => !isDesktop()
|
isHidden: () => !IS_DESKTOP_APP
|
||||||
},
|
},
|
||||||
{ key: "notifications", title: "Notifications", icon: Notification }
|
{ key: "notifications", title: "Notifications", icon: Notification }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ What data is collected & when?`,
|
|||||||
"Prevent Notesnook app from being captured by any screen capturing software like TeamViewer & AnyDesk.",
|
"Prevent Notesnook app from being captured by any screen capturing software like TeamViewer & AnyDesk.",
|
||||||
onStateChange: (listener) =>
|
onStateChange: (listener) =>
|
||||||
useSettingStore.subscribe((s) => s.privacyMode, listener),
|
useSettingStore.subscribe((s) => s.privacyMode, listener),
|
||||||
isHidden: () => !isDesktop() || getPlatform() === "linux",
|
isHidden: () => !IS_DESKTOP_APP || getPlatform() === "linux",
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: "toggle",
|
||||||
|
|||||||
9
apps/web/src/global.d.ts
vendored
9
apps/web/src/global.d.ts
vendored
@@ -16,11 +16,20 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
|
||||||
import "vite/client";
|
import "vite/client";
|
||||||
import "vite-plugin-svgr/client";
|
import "vite-plugin-svgr/client";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
var PUBLIC_URL: string;
|
||||||
|
var APP_VERSION: string;
|
||||||
|
var GIT_HASH: string;
|
||||||
|
var IS_DESKTOP_APP: boolean;
|
||||||
|
var IS_TESTING: boolean;
|
||||||
|
var PLATFORM: "web" | "desktop";
|
||||||
|
var IS_BETA: boolean;
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
os?: () => NodeJS.Platform | "mas";
|
os?: () => NodeJS.Platform | "mas";
|
||||||
NativeNNCrypto?: new () => import("@notesnook/crypto").NNCrypto;
|
NativeNNCrypto?: new () => import("@notesnook/crypto").NNCrypto;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { isDesktop } from "../utils/platform";
|
|
||||||
import { checkForUpdate } from "../utils/updater";
|
import { checkForUpdate } from "../utils/updater";
|
||||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export function useAutoUpdater() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateNotAvailable() {
|
function updateNotAvailable() {
|
||||||
if (isDesktop()) changeStatus({ type: "updated" });
|
if (IS_DESKTOP_APP) changeStatus({ type: "updated" });
|
||||||
else changeStatus();
|
else changeStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import "allotment/dist/style.css";
|
|||||||
import "../utils/analytics";
|
import "../utils/analytics";
|
||||||
import "../app.css";
|
import "../app.css";
|
||||||
|
|
||||||
if (import.meta.env.NODE_ENV === "production") {
|
// if (import.meta.env.PROD) {
|
||||||
console.log = () => {};
|
// console.log = () => {};
|
||||||
}
|
// }
|
||||||
|
|
||||||
const memory = {
|
const memory = {
|
||||||
isDatabaseLoaded: false
|
isDatabaseLoaded: false
|
||||||
@@ -45,4 +45,4 @@ export async function loadDatabase(persistence: "db" | "memory" = "db") {
|
|||||||
|
|
||||||
await initializeDatabase(persistence);
|
await initializeDatabase(persistence);
|
||||||
memory.isDatabaseLoaded = true;
|
memory.isDatabaseLoaded = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
useStore as useUserStore,
|
useStore as useUserStore,
|
||||||
store as userstore
|
store as userstore
|
||||||
} from "../stores/user-store";
|
} from "../stores/user-store";
|
||||||
import { isTesting } from "../utils/platform";
|
|
||||||
|
|
||||||
export function useIsUserPremium() {
|
export function useIsUserPremium() {
|
||||||
const user = useUserStore((store) => store.user);
|
const user = useUserStore((store) => store.user);
|
||||||
@@ -30,7 +29,7 @@ export function useIsUserPremium() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isUserPremium(user?: User) {
|
export function isUserPremium(user?: User) {
|
||||||
if (isTesting()) return true;
|
if (IS_TESTING) return true;
|
||||||
if (!user) user = userstore.get().user;
|
if (!user) user = userstore.get().user;
|
||||||
|
|
||||||
const subStatus = user?.subscription?.type;
|
const subStatus = user?.subscription?.type;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { AppEventManager, AppEvents } from "./common/app-events";
|
|||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
import { getCurrentHash, getCurrentPath, makeURL } from "./navigation";
|
||||||
import Config from "./utils/config";
|
import Config from "./utils/config";
|
||||||
import { isTesting } from "./utils/platform";
|
|
||||||
import { initalizeLogger, logger } from "./utils/logger";
|
import { initalizeLogger, logger } from "./utils/logger";
|
||||||
import { AuthProps } from "./views/auth";
|
import { AuthProps } from "./views/auth";
|
||||||
import { loadDatabase } from "./hooks/use-database";
|
import { loadDatabase } from "./hooks/use-database";
|
||||||
@@ -125,7 +125,7 @@ function fallbackRoute(): RouteWithPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function redirectToRegistration(path: Routes): RouteWithPath<AuthProps> | null {
|
function redirectToRegistration(path: Routes): RouteWithPath<AuthProps> | null {
|
||||||
if (!isTesting() && !shouldSkipInitiation() && !routes[path]) {
|
if (!IS_TESTING && !shouldSkipInitiation() && !routes[path]) {
|
||||||
window.history.replaceState({}, "", makeURL("/signup", getCurrentHash()));
|
window.history.replaceState({}, "", makeURL("/signup", getCurrentHash()));
|
||||||
return { route: routes["/signup"], path: "/signup" };
|
return { route: routes["/signup"], path: "/signup" };
|
||||||
}
|
}
|
||||||
@@ -156,8 +156,7 @@ async function renderApp() {
|
|||||||
} = getRoute();
|
} = getRoute();
|
||||||
|
|
||||||
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
||||||
if (import.meta.env.REACT_APP_PLATFORM === "desktop")
|
if (IS_DESKTOP_APP) await loadDatabase("db");
|
||||||
await loadDatabase("db");
|
|
||||||
|
|
||||||
logger.measure("app render");
|
logger.measure("app render");
|
||||||
|
|
||||||
@@ -174,7 +173,7 @@ async function renderApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initializeServiceWorker() {
|
async function initializeServiceWorker() {
|
||||||
if (import.meta.env.REACT_APP_PLATFORM !== "desktop") {
|
if (!IS_DESKTOP_APP) {
|
||||||
logger.info("Initializing service worker...");
|
logger.info("Initializing service worker...");
|
||||||
const serviceWorker = await import("./service-worker-registration");
|
const serviceWorker = await import("./service-worker-registration");
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { INNCrypto } from "@notesnook/crypto/dist/src/interfaces";
|
import { INNCrypto } from "@notesnook/crypto/dist/src/interfaces";
|
||||||
import CryptoWorker from "@notesnook/crypto-worker/dist/src/worker.js?worker";
|
import CryptoWorker from "@notesnook/crypto-worker/dist/src/worker.js?worker";
|
||||||
import { isDesktop } from "../utils/platform";
|
|
||||||
|
|
||||||
async function loadNNCrypto() {
|
async function loadNNCrypto() {
|
||||||
const hasWorker = "Worker" in window || "Worker" in global;
|
const hasWorker = "Worker" in window || "Worker" in global;
|
||||||
if (isDesktop() && window.NativeNNCrypto) {
|
if (IS_DESKTOP_APP && window.NativeNNCrypto) {
|
||||||
return window.NativeNNCrypto;
|
return window.NativeNNCrypto;
|
||||||
} else if (hasWorker) {
|
} else if (hasWorker) {
|
||||||
const { NNCryptoWorker } = await import("@notesnook/crypto-worker");
|
const { NNCryptoWorker } = await import("@notesnook/crypto-worker");
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// To learn more about the benefits of this model and instructions on how to
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
// opt-in, read https://cra.link/PWA
|
// opt-in, read https://cra.link/PWA
|
||||||
|
|
||||||
|
type ServiceWorkerRegistrationConfig = {
|
||||||
|
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||||
|
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
const isLocalhost = Boolean(
|
||||||
window.location.hostname === "localhost" ||
|
window.location.hostname === "localhost" ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
@@ -39,13 +45,10 @@ const isLocalhost = Boolean(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config: ServiceWorkerRegistrationConfig) {
|
||||||
if (
|
if (import.meta.env.PROD && "serviceWorker" in navigator) {
|
||||||
import.meta.env.NODE_ENV === "production" &&
|
|
||||||
"serviceWorker" in navigator
|
|
||||||
) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(import.meta.env.PUBLIC_URL, window.location.href);
|
const publicUrl = new URL(PUBLIC_URL, window.location.href);
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
@@ -54,7 +57,7 @@ export function register(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
const swUrl = `${import.meta.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
@@ -76,7 +79,10 @@ export function register(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
function registerValidSW(
|
||||||
|
swUrl: string,
|
||||||
|
config: ServiceWorkerRegistrationConfig
|
||||||
|
) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
@@ -120,7 +126,10 @@ function registerValidSW(swUrl, config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
function checkValidServiceWorker(
|
||||||
|
swUrl: string,
|
||||||
|
config: ServiceWorkerRegistrationConfig
|
||||||
|
) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { "Service-Worker": "script" }
|
headers: { "Service-Worker": "script" }
|
||||||
@@ -16,14 +16,8 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable no-var */
|
||||||
/* eslint-disable no-restricted-globals */
|
/// <reference lib="webworker" />
|
||||||
// This service worker can be customized!
|
|
||||||
// See https://developers.google.com/web/tools/workbox/modules
|
|
||||||
// for the list of available Workbox modules, or add any other
|
|
||||||
// code you'd like.
|
|
||||||
// You can also remove this file if you'd prefer not to use a
|
|
||||||
// service worker, and the Workbox build step will be skipped.
|
|
||||||
|
|
||||||
import { clientsClaim } from "workbox-core";
|
import { clientsClaim } from "workbox-core";
|
||||||
import { ExpirationPlugin } from "workbox-expiration";
|
import { ExpirationPlugin } from "workbox-expiration";
|
||||||
@@ -31,20 +25,17 @@ import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
|
|||||||
import { registerRoute } from "workbox-routing";
|
import { registerRoute } from "workbox-routing";
|
||||||
import { StaleWhileRevalidate } from "workbox-strategies";
|
import { StaleWhileRevalidate } from "workbox-strategies";
|
||||||
|
|
||||||
|
declare var self: ServiceWorkerGlobalScope & typeof globalThis;
|
||||||
|
|
||||||
clientsClaim();
|
clientsClaim();
|
||||||
|
|
||||||
// Precache all of the assets generated by your build process.
|
const precacheRoutes = self.__WB_MANIFEST;
|
||||||
// Their URLs are injected into the manifest variable below.
|
|
||||||
// This variable must be present somewhere in your service worker file,
|
|
||||||
// even if you decide not to use precaching. See https://cra.link/PWA
|
|
||||||
/**
|
|
||||||
* @type {import("workbox-precaching/_types").PrecacheEntry[]}
|
|
||||||
*/
|
|
||||||
var precacheRoutes = self.__WB_MANIFEST;
|
|
||||||
const filters = [/KaTeX/i, /hack/i, /code-lang-/i];
|
const filters = [/KaTeX/i, /hack/i, /code-lang-/i];
|
||||||
precacheAndRoute(
|
precacheAndRoute(
|
||||||
precacheRoutes.filter((route) => {
|
precacheRoutes.filter((route) => {
|
||||||
return filters.every((filter) => !filter.test(route.url));
|
return filters.every(
|
||||||
|
(filter) => !filter.test(typeof route === "string" ? route : route.url)
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -70,7 +61,7 @@ registerRoute(
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
createHandlerBoundToURL(import.meta.env.PUBLIC_URL + "/index.html")
|
createHandlerBoundToURL(PUBLIC_URL + "/index.html")
|
||||||
);
|
);
|
||||||
|
|
||||||
// An example runtime caching route for requests that aren't handled by the
|
// An example runtime caching route for requests that aren't handled by the
|
||||||
@@ -101,12 +92,11 @@ self.addEventListener("message", (event) => {
|
|||||||
break;
|
break;
|
||||||
case "GET_VERSION":
|
case "GET_VERSION":
|
||||||
{
|
{
|
||||||
const VERSION = import.meta.env.REACT_APP_VERSION;
|
if (!event.source) return;
|
||||||
const HASH = import.meta.env.REACT_APP_GIT_HASH;
|
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
type: data.type,
|
type: data.type,
|
||||||
version: VERSION,
|
version: APP_VERSION,
|
||||||
hash: HASH
|
hash: GIT_HASH
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -25,7 +25,6 @@ import { isUserPremium } from "../hooks/use-is-user-premium";
|
|||||||
import { SUBSCRIPTION_STATUS } from "../common/constants";
|
import { SUBSCRIPTION_STATUS } from "../common/constants";
|
||||||
import { appVersion } from "../utils/version";
|
import { appVersion } from "../utils/version";
|
||||||
import { findItemAndDelete } from "@notesnook/core/utils/array";
|
import { findItemAndDelete } from "@notesnook/core/utils/array";
|
||||||
import { isTesting } from "../utils/platform";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends {BaseStore<AnnouncementStore>}
|
* @extends {BaseStore<AnnouncementStore>}
|
||||||
@@ -35,7 +34,7 @@ class AnnouncementStore extends BaseStore {
|
|||||||
dialogAnnouncements = [];
|
dialogAnnouncements = [];
|
||||||
|
|
||||||
refresh = async () => {
|
refresh = async () => {
|
||||||
if (isTesting()) return;
|
if (IS_TESTING) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const inlineAnnouncements = [];
|
const inlineAnnouncements = [];
|
||||||
@@ -77,7 +76,7 @@ export { useStore, store };
|
|||||||
|
|
||||||
export const allowedPlatforms = [
|
export const allowedPlatforms = [
|
||||||
"all",
|
"all",
|
||||||
import.meta.env.REACT_APP_PLATFORM,
|
PLATFORM,
|
||||||
...(window.os ? [window.os()] : [])
|
...(window.os ? [window.os()] : [])
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { showReminderPreviewDialog } from "../common/dialog-controller";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import Config from "../utils/config";
|
import Config from "../utils/config";
|
||||||
import { store as notestore } from "./note-store";
|
import { store as notestore } from "./note-store";
|
||||||
import { isDesktop, isTesting } from "../utils/platform";
|
|
||||||
import { desktop } from "../common/desktop-bridge";
|
import { desktop } from "../common/desktop-bridge";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +57,7 @@ async function resetReminders(reminders) {
|
|||||||
await TaskScheduler.stopAllWithPrefix("reminder:");
|
await TaskScheduler.stopAllWithPrefix("reminder:");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isTesting() &&
|
!IS_TESTING &&
|
||||||
(!("Notification" in window) || Notification.permission !== "granted")
|
(!("Notification" in window) || Notification.permission !== "granted")
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -93,12 +93,12 @@ function scheduleReminder(id, reminder, cron) {
|
|||||||
return TaskScheduler.register(`reminder:${id}`, cron, async () => {
|
return TaskScheduler.register(`reminder:${id}`, cron, async () => {
|
||||||
if (!Config.get("reminderNotifications", true)) return;
|
if (!Config.get("reminderNotifications", true)) return;
|
||||||
|
|
||||||
if (isTesting()) {
|
if (IS_TESTING) {
|
||||||
window.confirm("Reminder activated!");
|
window.confirm("Reminder activated!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop()) {
|
if (IS_DESKTOP_APP) {
|
||||||
const tag = await desktop?.integration.showNotification.query({
|
const tag = await desktop?.integration.showNotification.query({
|
||||||
title: reminder.title,
|
title: reminder.title,
|
||||||
body: reminder.description,
|
body: reminder.description,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import CompressorWorker from "./compressor.worker.ts?worker";
|
import CompressorWorker from "./compressor.worker.ts?worker";
|
||||||
import type { Compressor as CompressorWorkerType } from "./compressor.worker";
|
import type { Compressor as CompressorWorkerType } from "./compressor.worker";
|
||||||
import { wrap, Remote } from "comlink";
|
import { wrap, Remote } from "comlink";
|
||||||
import { isDesktop } from "./platform";
|
|
||||||
import { desktop } from "../common/desktop-bridge";
|
import { desktop } from "../common/desktop-bridge";
|
||||||
|
|
||||||
export class Compressor {
|
export class Compressor {
|
||||||
@@ -28,21 +28,21 @@ export class Compressor {
|
|||||||
private compressor!: Remote<CompressorWorkerType>;
|
private compressor!: Remote<CompressorWorkerType>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!isDesktop()) {
|
if (!IS_DESKTOP_APP) {
|
||||||
this.worker = new CompressorWorker();
|
this.worker = new CompressorWorker();
|
||||||
this.compressor = wrap<CompressorWorkerType>(this.worker);
|
this.compressor = wrap<CompressorWorkerType>(this.worker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async compress(data: string) {
|
async compress(data: string) {
|
||||||
if (isDesktop())
|
if (IS_DESKTOP_APP)
|
||||||
return await desktop?.compress.gzip.query({ data, level: 6 });
|
return await desktop?.compress.gzip.query({ data, level: 6 });
|
||||||
|
|
||||||
return await this.compressor.gzip({ data, level: 6 });
|
return await this.compressor.gzip({ data, level: 6 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async decompress(data: string) {
|
async decompress(data: string) {
|
||||||
if (isDesktop()) return await desktop?.compress.gunzip.query(data);
|
if (IS_DESKTOP_APP) return await desktop?.compress.gunzip.query(data);
|
||||||
|
|
||||||
return await this.compressor.gunzip({ data });
|
return await this.compressor.gunzip({ data });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,10 +122,6 @@ export function getDownloadLink(platform: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDesktop() {
|
|
||||||
return "os" in window || import.meta.env.REACT_APP_PLATFORM === "desktop";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isMac() {
|
export function isMac() {
|
||||||
return (
|
return (
|
||||||
getPlatform() === "macOS" || getPlatform() === "darwin" || isMacStoreApp()
|
getPlatform() === "macOS" || getPlatform() === "darwin" || isMacStoreApp()
|
||||||
@@ -135,7 +131,3 @@ export function isMac() {
|
|||||||
export function isMacStoreApp() {
|
export function isMacStoreApp() {
|
||||||
return window.os && window.os() === "mas";
|
return window.os && window.os() === "mas";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTesting() {
|
|
||||||
return !!import.meta.env.REACT_APP_TEST;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import Config from "./config";
|
|||||||
|
|
||||||
export function isTelemetryEnabled() {
|
export function isTelemetryEnabled() {
|
||||||
// telemetry is always disabled in DEBUG/TEST mode
|
// telemetry is always disabled in DEBUG/TEST mode
|
||||||
if (import.meta.env.NODE_ENV !== "production") return false;
|
if (import.meta.env.DEV) return false;
|
||||||
|
|
||||||
return Config.get("telemetry", false);
|
return Config.get("telemetry", false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||||
import { desktop } from "../common/desktop-bridge";
|
import { desktop } from "../common/desktop-bridge";
|
||||||
import { isDesktop } from "./platform";
|
|
||||||
import { appVersion, getServiceWorkerVersion } from "./version";
|
import { appVersion, getServiceWorkerVersion } from "./version";
|
||||||
|
|
||||||
export async function checkForUpdate() {
|
export async function checkForUpdate() {
|
||||||
if (isDesktop()) await desktop?.updater.check.query();
|
if (IS_DESKTOP_APP) await desktop?.updater.check.query();
|
||||||
else {
|
else {
|
||||||
AppEventManager.publish(AppEvents.checkingForUpdate);
|
AppEventManager.publish(AppEvents.checkingForUpdate);
|
||||||
|
|
||||||
@@ -35,7 +34,11 @@ export async function checkForUpdate() {
|
|||||||
const workerVersion = await getServiceWorkerVersion(
|
const workerVersion = await getServiceWorkerVersion(
|
||||||
registration.waiting
|
registration.waiting
|
||||||
);
|
);
|
||||||
if (!workerVersion || workerVersion.numerical <= appVersion.numerical) {
|
if (
|
||||||
|
!workerVersion ||
|
||||||
|
workerVersion.numerical <= appVersion.numerical ||
|
||||||
|
workerVersion.hash === appVersion.hash
|
||||||
|
) {
|
||||||
registration.waiting.postMessage({ type: "SKIP_WAITING" });
|
registration.waiting.postMessage({ type: "SKIP_WAITING" });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -52,17 +55,21 @@ export async function checkForUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadUpdate() {
|
export async function downloadUpdate() {
|
||||||
if (isDesktop()) await desktop?.updater.download.query();
|
if (IS_DESKTOP_APP) await desktop?.updater.download.query();
|
||||||
else {
|
else {
|
||||||
console.log("Force updating");
|
console.log("Force updating");
|
||||||
if (!("serviceWorker" in navigator)) return;
|
try {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
if (!("serviceWorker" in navigator)) return;
|
||||||
await registration.update();
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
await registration.update();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installUpdate() {
|
export async function installUpdate() {
|
||||||
if (isDesktop()) await desktop?.updater.install.query();
|
if (IS_DESKTOP_APP) await desktop?.updater.install.query();
|
||||||
else {
|
else {
|
||||||
const registrations =
|
const registrations =
|
||||||
(await navigator.serviceWorker?.getRegistrations()) || [];
|
(await navigator.serviceWorker?.getRegistrations()) || [];
|
||||||
|
|||||||
@@ -20,15 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
export type Platforms = "web" | "desktop";
|
export type Platforms = "web" | "desktop";
|
||||||
export type AppVersion = typeof appVersion;
|
export type AppVersion = typeof appVersion;
|
||||||
export const appVersion = {
|
export const appVersion = {
|
||||||
formatted: format(
|
formatted: format(APP_VERSION, GIT_HASH, PLATFORM, IS_BETA),
|
||||||
import.meta.env.REACT_APP_VERSION,
|
clean: formatVersion(APP_VERSION),
|
||||||
import.meta.env.REACT_APP_GIT_HASH,
|
numerical: parseInt(APP_VERSION || "0"),
|
||||||
import.meta.env.REACT_APP_PLATFORM as Platforms,
|
hash: GIT_HASH,
|
||||||
import.meta.env.REACT_APP_BETA === "true"
|
isBeta: IS_BETA
|
||||||
),
|
|
||||||
clean: formatVersion(import.meta.env.REACT_APP_VERSION),
|
|
||||||
numerical: parseInt(import.meta.env.REACT_APP_VERSION || "0"),
|
|
||||||
isBeta: import.meta.env.REACT_APP_BETA === "true"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function format(
|
function format(
|
||||||
@@ -59,11 +55,12 @@ export function getServiceWorkerVersion(
|
|||||||
if (type !== "GET_VERSION") return;
|
if (type !== "GET_VERSION") return;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
const { version } = ev.data;
|
const { version, hash } = ev.data;
|
||||||
resolve({
|
resolve({
|
||||||
formatted: formatVersion(version),
|
formatted: formatVersion(version),
|
||||||
numerical: parseInt(version),
|
numerical: parseInt(version),
|
||||||
clean: formatVersion(version),
|
clean: formatVersion(version),
|
||||||
|
hash,
|
||||||
isBeta: appVersion.isBeta
|
isBeta: appVersion.isBeta
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import useDatabase from "../hooks/use-database";
|
|||||||
import Loader from "../components/loader";
|
import Loader from "../components/loader";
|
||||||
import { showToast } from "../utils/toast";
|
import { showToast } from "../utils/toast";
|
||||||
import AuthContainer from "../components/auth-container";
|
import AuthContainer from "../components/auth-container";
|
||||||
import { isTesting } from "../utils/platform";
|
|
||||||
import { useTimer } from "../hooks/use-timer";
|
import { useTimer } from "../hooks/use-timer";
|
||||||
import { AuthenticatorType } from "../dialogs/mfa/types";
|
import { AuthenticatorType } from "../dialogs/mfa/types";
|
||||||
import {
|
import {
|
||||||
@@ -521,7 +521,7 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
|
|||||||
|
|
||||||
const url = await db.user?.recoverAccount(form.email.toLowerCase());
|
const url = await db.user?.recoverAccount(form.email.toLowerCase());
|
||||||
console.log(url);
|
console.log(url);
|
||||||
if (isTesting()) {
|
if (IS_TESTING) {
|
||||||
window.open(url, "_self");
|
window.open(url, "_self");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,128 +21,23 @@ import { defineConfig } from "vite";
|
|||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import svgrPlugin from "vite-plugin-svgr";
|
import svgrPlugin from "vite-plugin-svgr";
|
||||||
import envCompatible from "vite-plugin-env-compatible";
|
import envCompatible from "vite-plugin-env-compatible";
|
||||||
import { VitePWA, ManifestOptions } from "vite-plugin-pwa";
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
import autoprefixer from "autoprefixer";
|
import autoprefixer from "autoprefixer";
|
||||||
|
import { WEB_MANIFEST } from "./web-manifest";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import { version } from "./package.json";
|
||||||
|
|
||||||
const WEB_MANIFEST: Partial<ManifestOptions> = {
|
const gitHash = (() => {
|
||||||
name: "Notesnook",
|
try {
|
||||||
description:
|
return execSync("git rev-parse --short HEAD").toString().trim();
|
||||||
"A fully open source & end-to-end encrypted note taking alternative to Evernote.",
|
} catch (e) {
|
||||||
short_name: "Notesnook",
|
return process.env.GIT_HASH || "gitless";
|
||||||
shortcuts: [
|
}
|
||||||
{
|
})();
|
||||||
name: "New note",
|
const appVersion = version.replaceAll(".", "");
|
||||||
url: "/#/notes/create",
|
|
||||||
description: "Create a new note",
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: "/android-chrome-192x192.png",
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "New notebook",
|
|
||||||
url: "/#/notebooks/create",
|
|
||||||
description: "Create a new notebook",
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: "/android-chrome-192x192.png",
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: "favicon-16x16.png",
|
|
||||||
sizes: "16x16",
|
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "favicon-32x32.png",
|
|
||||||
sizes: "32x32",
|
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "favicon-72x72.png",
|
|
||||||
sizes: "72x72",
|
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/android-chrome-192x192.png",
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/android-chrome-512x512.png",
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
screenshots: [
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-1.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-2.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-3.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-4.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-5.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-6.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/screenshots/screenshot-7.jpg",
|
|
||||||
sizes: "1080x1920",
|
|
||||||
type: "image/jpeg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
related_applications: [
|
|
||||||
{
|
|
||||||
platform: "play",
|
|
||||||
url: "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook",
|
|
||||||
id: "com.streetwriters.notesnook"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
platform: "itunes",
|
|
||||||
url: "https://apps.apple.com/us/app/notesnook-private-notes-app/id1544027013"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
prefer_related_applications: true,
|
|
||||||
orientation: "any",
|
|
||||||
start_url: ".",
|
|
||||||
theme_color: "#01c352",
|
|
||||||
background_color: "#ffffff",
|
|
||||||
display: "standalone",
|
|
||||||
categories: ["productivity", "lifestyle", "education", "books"]
|
|
||||||
};
|
|
||||||
|
|
||||||
const isTesting =
|
const isTesting =
|
||||||
process.env.REACT_APP_TEST === "true" ||
|
process.env.TEST === "true" || process.env.NODE_ENV === "development";
|
||||||
process.env.NODE_ENV === "development";
|
const isDesktop = process.env.PLATFORM === "desktop";
|
||||||
const isDesktop = process.env.REACT_APP_PLATFORM === "desktop";
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
envPrefix: "REACT_APP_",
|
envPrefix: "REACT_APP_",
|
||||||
@@ -152,14 +47,23 @@ export default defineConfig({
|
|||||||
minify: "esbuild",
|
minify: "esbuild",
|
||||||
cssMinify: true,
|
cssMinify: true,
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
sourcemap: process.env.GENERATE_SOURCEMAP === "true",
|
sourcemap: isTesting,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
assetFileNames: "assets/[name]-[hash:12][extname]",
|
assetFileNames: "assets/[name]-[hash:12][extname]",
|
||||||
chunkFileNames: "[name]-[hash:12].js"
|
chunkFileNames: "assets/[name]-[hash:12].js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
define: {
|
||||||
|
GIT_HASH: `"${gitHash}"`,
|
||||||
|
APP_VERSION: `"${appVersion}"`,
|
||||||
|
PUBLIC_URL: `"${process.env.PUBLIC_URL || ""}"`,
|
||||||
|
IS_DESKTOP_APP: isDesktop,
|
||||||
|
PLATFORM: `"${process.env.PLATFORM}"`,
|
||||||
|
IS_TESTING: process.env.TEST === "true",
|
||||||
|
IS_BETA: process.env.BETA === "true"
|
||||||
|
},
|
||||||
logLevel: process.env.NODE_ENV === "production" ? "warn" : "info",
|
logLevel: process.env.NODE_ENV === "production" ? "warn" : "info",
|
||||||
resolve: {
|
resolve: {
|
||||||
dedupe: ["react", "react-dom", "@mdi/js", "@mdi/react", "@emotion/react"],
|
dedupe: ["react", "react-dom", "@mdi/js", "@mdi/react", "@emotion/react"],
|
||||||
@@ -194,7 +98,7 @@ export default defineConfig({
|
|||||||
manifest: WEB_MANIFEST,
|
manifest: WEB_MANIFEST,
|
||||||
injectRegister: null,
|
injectRegister: null,
|
||||||
srcDir: "src",
|
srcDir: "src",
|
||||||
filename: "service-worker.js"
|
filename: "service-worker.ts"
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
react({
|
react({
|
||||||
|
|||||||
135
apps/web/web-manifest.ts
Normal file
135
apps/web/web-manifest.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ManifestOptions } from "vite-plugin-pwa";
|
||||||
|
|
||||||
|
export const WEB_MANIFEST: Partial<ManifestOptions> = {
|
||||||
|
name: "Notesnook",
|
||||||
|
description:
|
||||||
|
"A fully open source & end-to-end encrypted note taking alternative to Evernote.",
|
||||||
|
short_name: "Notesnook",
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
name: "New note",
|
||||||
|
url: "/#/notes/create",
|
||||||
|
description: "Create a new note",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/android-chrome-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New notebook",
|
||||||
|
url: "/#/notebooks/create",
|
||||||
|
description: "Create a new notebook",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/android-chrome-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "favicon-16x16.png",
|
||||||
|
sizes: "16x16",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "favicon-32x32.png",
|
||||||
|
sizes: "32x32",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "favicon-72x72.png",
|
||||||
|
sizes: "72x72",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/android-chrome-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/android-chrome-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
screenshots: [
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-1.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-2.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-3.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-4.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-5.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-6.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/screenshots/screenshot-7.jpg",
|
||||||
|
sizes: "1080x1920",
|
||||||
|
type: "image/jpeg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
related_applications: [
|
||||||
|
{
|
||||||
|
platform: "play",
|
||||||
|
url: "https://play.google.com/store/apps/details?id=com.streetwriters.notesnook",
|
||||||
|
id: "com.streetwriters.notesnook"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
platform: "itunes",
|
||||||
|
url: "https://apps.apple.com/us/app/notesnook-private-notes-app/id1544027013"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
prefer_related_applications: true,
|
||||||
|
orientation: "any",
|
||||||
|
start_url: ".",
|
||||||
|
theme_color: "#01c352",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
display: "standalone",
|
||||||
|
categories: ["productivity", "lifestyle", "education", "books"]
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user