Compare commits
84 Commits
v1.7.4
...
1.8.9-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed8271cf9d | ||
|
|
da7723e7e0 | ||
|
|
43e817e259 | ||
|
|
eb50ebeb81 | ||
|
|
16b702294a | ||
|
|
9bd68e8c5a | ||
|
|
f1b2deb893 | ||
|
|
64eae3044d | ||
|
|
f1be7a57a1 | ||
|
|
80dd83552a | ||
|
|
b4b7f9c519 | ||
|
|
5d0e556e1b | ||
|
|
ecd1640c1d | ||
|
|
085b7c4eb1 | ||
|
|
9838ad715e | ||
|
|
f370f90a2b | ||
|
|
02b2a9fb17 | ||
|
|
17d953e59a | ||
|
|
c47436927e | ||
|
|
8f89414f89 | ||
|
|
557e1de9f0 | ||
|
|
6cc065a0f7 | ||
|
|
cba87d8477 | ||
|
|
26203bf3da | ||
|
|
e9d43f3026 | ||
|
|
57df03e46c | ||
|
|
b673519898 | ||
|
|
5356e9d4ed | ||
|
|
3d1b9de51b | ||
|
|
c1741b1924 | ||
|
|
4a17164bd9 | ||
|
|
c26dbefe26 | ||
|
|
f532d6f01b | ||
|
|
351f56dc0d | ||
|
|
feb74ae910 | ||
|
|
4b483d200d | ||
|
|
5858cae55b | ||
|
|
e7491d1c03 | ||
|
|
e740d56a84 | ||
|
|
01c78c654d | ||
|
|
db67114a30 | ||
|
|
7483286833 | ||
|
|
9606517f38 | ||
|
|
1d86624ce3 | ||
|
|
bdc2af9597 | ||
|
|
41d77b6424 | ||
|
|
3bda97b4f5 | ||
|
|
5d5b1e9eac | ||
|
|
f46ef8347f | ||
|
|
c613e67218 | ||
|
|
a9ff91d51e | ||
|
|
52f51c4087 | ||
|
|
f0ec25c296 | ||
|
|
d2ae716993 | ||
|
|
8d432d4810 | ||
|
|
e2bd1cd1f1 | ||
|
|
7286c60961 | ||
|
|
8a9cd91d54 | ||
|
|
462ae4d8ab | ||
|
|
45e7d5e46b | ||
|
|
9426e937a5 | ||
|
|
8ec4102395 | ||
|
|
4832d243da | ||
|
|
9504f278c3 | ||
|
|
8cefa1defc | ||
|
|
b4e58139e3 | ||
|
|
73f60704dd | ||
|
|
470e3040fc | ||
|
|
73d9a0f371 | ||
|
|
7a01898b2b | ||
|
|
41f5a4eecf | ||
|
|
08744fd0ee | ||
|
|
4788060c5a | ||
|
|
7a5e49bcbe | ||
|
|
536744f74a | ||
|
|
b1c33ebf28 | ||
|
|
323cd373e5 | ||
|
|
6c5cfb1505 | ||
|
|
b628e6071e | ||
|
|
8a2c1c0b2a | ||
|
|
77734aebba | ||
|
|
31711987c3 | ||
|
|
e193afb97d | ||
|
|
02b8fc4eb5 |
32
.github/workflows/enex-tests.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: notesnook/enex tests
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/enex/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd packages/enex
|
||||
npm i
|
||||
npm run test
|
||||
5
.github/workflows/importer-deploy.yml
vendored
@@ -27,9 +27,10 @@ jobs:
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd apps/importer
|
||||
npm i
|
||||
npx turbo run build
|
||||
mv ./apps/importer/build/ ./apps/importer/public_html/
|
||||
npm run build
|
||||
mv ./build/ ./public_html/
|
||||
|
||||
- uses: blbecker/rsync-deploy@v1.0
|
||||
with:
|
||||
|
||||
32
.github/workflows/importer-tests.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: notesnook/importer tests
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/importer/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd packages/importer
|
||||
npm i
|
||||
npm run test
|
||||
41
.github/workflows/vericrypt-deploy.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Vericrypt Deploy
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/vericrypt/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd apps/vericrypt
|
||||
npm i
|
||||
npm run build
|
||||
mv ./build/ ./public_html/
|
||||
|
||||
- uses: blbecker/rsync-deploy@v1.0
|
||||
with:
|
||||
source_dir: ./apps/vericrypt/public_html/
|
||||
destination_path: ${{ secrets.REMOTE_VERICRYPT_PATH }}
|
||||
destination_host: ${{ secrets.REMOTE_HOST }}
|
||||
deployment_user: ${{ secrets.REMOTE_USER }}
|
||||
deployment_key: ${{ secrets.REMOTE_KEY }}
|
||||
@@ -1,9 +1,15 @@
|
||||
const { version } = require("./package.json");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const APP_VERSION = version;
|
||||
const CHANGELOG = fs.readFileSync(path.join(__dirname, "changelog.txt"), {
|
||||
encoding: "utf8",
|
||||
});
|
||||
module.exports = {
|
||||
all: {
|
||||
REACT_APP_VERSION: APP_VERSION,
|
||||
REACT_APP_CHANGELOG: CHANGELOG,
|
||||
},
|
||||
release: {
|
||||
GENERATE_SOURCEMAP: false,
|
||||
|
||||
2
apps/importer/changelog.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
1. 🥳 Added OneNote support! 🥳
|
||||
2. 🥳 Added Zoho Notebook support! 🥳
|
||||
11870
apps/importer/package-lock.json
generated
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@notesnook/importer-web",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.1",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@types/color": "^3.0.2",
|
||||
"@types/file-saver": "^2.0.4",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^12.0.0",
|
||||
@@ -20,8 +19,8 @@
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@notesnook/importer": "file:../../packages/importer",
|
||||
"color": "^4.1.0",
|
||||
"@notesnook/importer": "^1.2.3",
|
||||
"colord": "^2.9.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"hash-wasm": "^4.9.0",
|
||||
"interweave": "^12.9.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"associatedApplications": [
|
||||
{
|
||||
"applicationId": "4952c7cf-9c02-4fb7-b867-b87727bb52d8"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,19 +3,19 @@ import { ThemeProvider } from "@theme-ui/theme-provider";
|
||||
import "./App.css";
|
||||
import { ThemeFactory } from "./theme";
|
||||
import { useState } from "react";
|
||||
import { Providers } from "@notesnook/importer/dist/src/providers/providerfactory";
|
||||
import { StepSeperator } from "./components/StepSeperator";
|
||||
import { ProviderSelector } from "./components/ProviderSelector";
|
||||
import { FileSelector } from "./components/FileSelector";
|
||||
import { FileProviderHandler } from "./components/FileProviderHandler";
|
||||
import { ImportResult } from "./components/ImportResult";
|
||||
import { Hero } from "./components/Hero";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { IProvider } from "@notesnook/importer";
|
||||
import { NetworkProviderHandler } from "./components/NetworkProviderHandler";
|
||||
import { ProviderResult } from "@notesnook/importer/dist/src/providers/provider";
|
||||
|
||||
function App() {
|
||||
const [selectedProvider, setSelectedProvider] = useState<
|
||||
Providers | undefined
|
||||
>();
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [selectedProvider, setSelectedProvider] = useState<IProvider>();
|
||||
const [providerResult, setProviderResult] = useState<ProviderResult>();
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={ThemeFactory.construct()}>
|
||||
@@ -29,21 +29,28 @@ function App() {
|
||||
>
|
||||
<ProviderSelector onProviderChanged={setSelectedProvider} />
|
||||
|
||||
{selectedProvider && (
|
||||
{selectedProvider ? (
|
||||
<>
|
||||
<StepSeperator />
|
||||
<FileSelector
|
||||
provider={selectedProvider}
|
||||
onFilesChanged={setSelectedFiles}
|
||||
/>
|
||||
{selectedProvider.type === "file" ? (
|
||||
<FileProviderHandler
|
||||
provider={selectedProvider}
|
||||
onTransformFinished={setProviderResult}
|
||||
/>
|
||||
) : selectedProvider.type === "network" ? (
|
||||
<NetworkProviderHandler
|
||||
provider={selectedProvider}
|
||||
onTransformFinished={setProviderResult}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{selectedFiles.length > 0 && selectedProvider && (
|
||||
) : null}
|
||||
{providerResult ? (
|
||||
<>
|
||||
<StepSeperator />
|
||||
<ImportResult files={selectedFiles} provider={selectedProvider} />
|
||||
<ImportResult result={providerResult} />{" "}
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex>
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
import { Flex, Input, Text } from "@theme-ui/components";
|
||||
import { ProviderFactory } from "@notesnook/importer";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { IProvider } from "@notesnook/importer/dist/src/providers/provider";
|
||||
import { Providers } from "@notesnook/importer/dist/src/providers/providerfactory";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import { Accordion } from "./Accordion";
|
||||
import { IFileProvider, transform } from "@notesnook/importer";
|
||||
import { xxhash64 } from "hash-wasm";
|
||||
import {
|
||||
ProviderResult,
|
||||
ProviderSettings,
|
||||
} from "@notesnook/importer/dist/src/providers/provider";
|
||||
import { IFile } from "@notesnook/importer/dist/src/utils/file";
|
||||
|
||||
type FileSelectorProps = {
|
||||
provider?: Providers;
|
||||
onFilesChanged: (files: File[]) => void;
|
||||
type FileProviderHandlerProps = {
|
||||
provider: IFileProvider;
|
||||
onTransformFinished: (result: ProviderResult) => void;
|
||||
};
|
||||
|
||||
export function FileSelector(props: FileSelectorProps) {
|
||||
const { provider: providerName, onFilesChanged } = props;
|
||||
type Progress = {
|
||||
total: number;
|
||||
done: number;
|
||||
};
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
clientType: "browser",
|
||||
hasher: { type: "xxh64", hash: (data) => xxhash64(data) },
|
||||
};
|
||||
|
||||
export function FileProviderHandler(props: FileProviderHandlerProps) {
|
||||
const { provider, onTransformFinished } = props;
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [provider, setProvider] = useState<IProvider | undefined>();
|
||||
const [progress, setProgress] = useState<Progress>({ total: 0, done: 0 });
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
setFiles((files) => {
|
||||
@@ -23,22 +37,41 @@ export function FileSelector(props: FileSelectorProps) {
|
||||
return newFiles;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
accept: provider?.supportedExtensions?.concat(".zip"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onFilesChanged(files);
|
||||
}, [files, onFilesChanged]);
|
||||
setFiles([]);
|
||||
}, [provider]);
|
||||
|
||||
useEffect(() => {
|
||||
setFiles([]);
|
||||
setProvider(undefined);
|
||||
(async () => {
|
||||
if (!provider || !files.length) return;
|
||||
|
||||
if (!providerName) return;
|
||||
setProvider(ProviderFactory.getProvider(providerName));
|
||||
}, [providerName]);
|
||||
setProgress({ total: 0, done: 0 });
|
||||
let result: ProviderResult = { notes: [], errors: [] };
|
||||
for (let file of files) {
|
||||
const providerFile: IFile = {
|
||||
name: file.name,
|
||||
data: await file.arrayBuffer(),
|
||||
modifiedAt: file.lastModified,
|
||||
};
|
||||
const transformResult = await transform(
|
||||
provider,
|
||||
[providerFile],
|
||||
settings
|
||||
);
|
||||
result.notes.push(...transformResult.notes);
|
||||
result.errors.push(...transformResult.errors);
|
||||
setProgress((p) => ({ total: files.length, done: p.done + 1 }));
|
||||
}
|
||||
setProgress({ total: 0, done: 0 });
|
||||
onTransformFinished(result);
|
||||
})();
|
||||
}, [provider, files, onTransformFinished]);
|
||||
|
||||
return (
|
||||
<StepContainer sx={{ flexDirection: "column", alignItems: "stretch" }}>
|
||||
@@ -84,6 +117,7 @@ export function FileSelector(props: FileSelectorProps) {
|
||||
>
|
||||
{files.map((file, index) => (
|
||||
<Flex
|
||||
key={file.name}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
p: 2,
|
||||
@@ -108,6 +142,11 @@ export function FileSelector(props: FileSelectorProps) {
|
||||
</Flex>
|
||||
</Accordion>
|
||||
) : null}
|
||||
{progress.total > 0 ? (
|
||||
<Text variant="body">
|
||||
Processing files ({progress.done}/{progress.total})
|
||||
</Text>
|
||||
) : null}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,8 @@ export function Hero() {
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
my: 150,
|
||||
mt: 150,
|
||||
mb: 100,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
@@ -42,6 +43,21 @@ export function Hero() {
|
||||
Notesnook Web
|
||||
</Link>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
flexDirection: "column",
|
||||
width: 400,
|
||||
mt: 4,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text variant="subtitle">What's new in v{appVersion}</Text>
|
||||
<Text variant="body" sx={{ whiteSpace: "pre-wrap" }}>
|
||||
{process.env.REACT_APP_CHANGELOG}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@ export function ImportErrors(props: ImportErrorsProps) {
|
||||
color="#E53935"
|
||||
>
|
||||
<Flex sx={{ flexDirection: "column", px: 2, pb: 2 }}>
|
||||
<Text variant="body" sx={{ color: "error" }}>
|
||||
{props.errors.map((error, index) => (
|
||||
<>
|
||||
{index + 1}. {error.message} ({error.file.name})
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</Text>
|
||||
{props.errors.map((error, index) => (
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{ color: "error", my: 1, fontFamily: "monospace" }}
|
||||
>
|
||||
{index + 1}. {error.message}{" "}
|
||||
{error.file ? `(${error.file.name})` : ""}
|
||||
<br />
|
||||
</Text>
|
||||
))}
|
||||
<Button
|
||||
sx={{ bg: "error", color: "static", alignSelf: "start", mt: 2 }}
|
||||
onClick={() =>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
import { pack, transform } from "@notesnook/importer";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
ProviderResult,
|
||||
ProviderSettings,
|
||||
} from "@notesnook/importer/dist/src/providers/provider";
|
||||
import { Providers } from "@notesnook/importer/dist/src/providers/providerfactory";
|
||||
import { IFile } from "@notesnook/importer/dist/src/utils/file";
|
||||
import { xxhash64 } from "hash-wasm";
|
||||
import { pack } from "@notesnook/importer";
|
||||
import { useCallback, useState } from "react";
|
||||
import { ProviderResult } from "@notesnook/importer/dist/src/providers/provider";
|
||||
import { Note } from "@notesnook/importer/dist/src/models/note";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import { NotePreview } from "./NotePreview";
|
||||
@@ -16,51 +10,13 @@ import { ImportHelp } from "./ImportHelp";
|
||||
import { saveAs } from "file-saver";
|
||||
import { NotesList } from "./NotesList";
|
||||
|
||||
type ResultProps = {
|
||||
files: File[];
|
||||
provider: Providers | undefined;
|
||||
type ImportResultProps = {
|
||||
result: ProviderResult;
|
||||
};
|
||||
|
||||
type Progress = {
|
||||
total: number;
|
||||
done: number;
|
||||
};
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
hasher: { type: "xxh64", hash: (data) => xxhash64(data) },
|
||||
};
|
||||
|
||||
export function ImportResult(props: ResultProps) {
|
||||
const { provider, files } = props;
|
||||
|
||||
const [progress, setProgress] = useState<Progress>({ total: 0, done: 0 });
|
||||
const [result, setResult] = useState<ProviderResult | undefined>();
|
||||
export function ImportResult(props: ImportResultProps) {
|
||||
const { result } = props;
|
||||
const [selectedNote, setSelectedNote] = useState<Note | undefined>();
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!provider) return;
|
||||
|
||||
setProgress({ total: 0, done: 0 });
|
||||
let result: ProviderResult = { notes: [], errors: [] };
|
||||
for (let file of files) {
|
||||
const providerFile: IFile = {
|
||||
name: file.name,
|
||||
data: await file.arrayBuffer(),
|
||||
modifiedAt: file.lastModified,
|
||||
};
|
||||
const transformResult = await transform(
|
||||
[providerFile],
|
||||
provider,
|
||||
settings
|
||||
);
|
||||
result.notes.push(...transformResult.notes);
|
||||
result.errors.push(...transformResult.errors);
|
||||
setProgress((p) => ({ total: files.length, done: p.done + 1 }));
|
||||
}
|
||||
setProgress({ total: 0, done: 0 });
|
||||
setResult(result);
|
||||
})();
|
||||
}, [provider, files]);
|
||||
|
||||
const downloadAsZip = useCallback(() => {
|
||||
if (!result) return;
|
||||
@@ -79,16 +35,11 @@ export function ImportResult(props: ResultProps) {
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Your notes are ready for download</Text>
|
||||
{progress.total > 0 ? (
|
||||
<Text variant="body">
|
||||
Processing files ({progress.done}/{progress.total})
|
||||
</Text>
|
||||
) : result ? (
|
||||
{result ? (
|
||||
<>
|
||||
<Text variant="body" sx={{ color: "fontTertiary" }}></Text>
|
||||
<NotesList
|
||||
notes={result?.notes}
|
||||
filesLength={files.length}
|
||||
onNoteSelected={(note) => setSelectedNote(note)}
|
||||
/>
|
||||
{result.errors.length > 0 && <ImportErrors errors={result.errors} />}
|
||||
|
||||
88
apps/importer/src/components/NetworkProviderHandler.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Text, Button } from "@theme-ui/components";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import { INetworkProvider } from "@notesnook/importer";
|
||||
import {
|
||||
ProviderResult,
|
||||
ProviderSettings,
|
||||
} from "@notesnook/importer/dist/src/providers/provider";
|
||||
import { xxhash64 } from "hash-wasm";
|
||||
import { OneNote } from "@notesnook/importer/dist/src/providers/onenote";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
type NetworkProviderHandlerProps = {
|
||||
provider: INetworkProvider<unknown>;
|
||||
onTransformFinished: (result: ProviderResult) => void;
|
||||
};
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
clientType: "browser",
|
||||
hasher: { type: "xxh64", hash: (data) => xxhash64(data) },
|
||||
};
|
||||
|
||||
export function NetworkProviderHandler(props: NetworkProviderHandlerProps) {
|
||||
const { provider, onTransformFinished } = props;
|
||||
const [progress, setProgress] = useState<string | null>();
|
||||
|
||||
const startImport = useCallback(() => {
|
||||
(async () => {
|
||||
if (!provider) return;
|
||||
|
||||
setProgress(null);
|
||||
let result: ProviderResult = { notes: [], errors: [] };
|
||||
if (provider instanceof OneNote) {
|
||||
result = await provider.process({
|
||||
...settings,
|
||||
clientId: "4952c7cf-9c02-4fb7-b867-b87727bb52d8",
|
||||
redirectUri:
|
||||
process.env.NODE_ENV === "development"
|
||||
? "http://localhost:3000"
|
||||
: "https://importer.notesnook.com",
|
||||
report: setProgress,
|
||||
});
|
||||
}
|
||||
|
||||
setProgress(null);
|
||||
onTransformFinished(result);
|
||||
})();
|
||||
}, [onTransformFinished, provider]);
|
||||
|
||||
return (
|
||||
<StepContainer
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
}}
|
||||
>
|
||||
{progress ? (
|
||||
<>
|
||||
<Text variant="title">Importing your notes from {provider.name}</Text>
|
||||
<Text
|
||||
as="pre"
|
||||
variant="body"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
my: 4,
|
||||
bg: "bgSecondary",
|
||||
p: 4,
|
||||
fontFamily: "monospace",
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
>
|
||||
{progress}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text variant="title">Connect your {provider.name} account</Text>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={startImport}
|
||||
sx={{ my: 4, alignSelf: "center" }}
|
||||
>
|
||||
Start importing
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
@@ -100,6 +100,7 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
<Flex my={1}>
|
||||
{notebooks?.map((notebook) => (
|
||||
<Flex
|
||||
key={notebook.notebook + notebook.topic}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
bg: "bgSecondary",
|
||||
@@ -119,6 +120,7 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
))}
|
||||
{tags?.map((tag) => (
|
||||
<Text
|
||||
key={tag}
|
||||
variant="subBody"
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
|
||||
@@ -4,17 +4,14 @@ import { Accordion } from "./Accordion";
|
||||
|
||||
type NotesListProps = {
|
||||
notes: Note[];
|
||||
filesLength: number;
|
||||
onNoteSelected: (note: Note) => void;
|
||||
};
|
||||
export function NotesList(props: NotesListProps) {
|
||||
const { notes, filesLength, onNoteSelected } = props;
|
||||
const { notes, onNoteSelected } = props;
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
title={`${notes.length} notes found in ${filesLength} ${
|
||||
filesLength > 1 ? "files" : "file"
|
||||
}`}
|
||||
title={`${notes.length} notes found`}
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
@@ -30,6 +27,7 @@ export function NotesList(props: NotesListProps) {
|
||||
>
|
||||
{notes.map((note, index) => (
|
||||
<Flex
|
||||
key={note.title + note.dateCreated}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
p: 2,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Flex, Select, Text } from "@theme-ui/components";
|
||||
import { ProviderFactory } from "@notesnook/importer";
|
||||
import { IProvider, ProviderFactory } from "@notesnook/importer";
|
||||
import { Providers } from "@notesnook/importer/dist/src/providers/providerfactory";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
|
||||
type ProviderSelectorProps = {
|
||||
onProviderChanged: (provider: Providers) => void;
|
||||
onProviderChanged: (provider: IProvider) => void;
|
||||
};
|
||||
|
||||
export function ProviderSelector(props: ProviderSelectorProps) {
|
||||
@@ -31,12 +31,12 @@ export function ProviderSelector(props: ProviderSelectorProps) {
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const providerName: Providers = e.target.value as Providers;
|
||||
props.onProviderChanged(providerName);
|
||||
props.onProviderChanged(ProviderFactory.getProvider(providerName));
|
||||
}}
|
||||
>
|
||||
<option value=""></option>
|
||||
{ProviderFactory.getAvailableProviders().map((provider) => (
|
||||
<option value={provider}>
|
||||
<option key={provider} value={provider}>
|
||||
{ProviderFactory.getProvider(provider as Providers).name}
|
||||
</option>
|
||||
))}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import Color from "color";
|
||||
import { colord } from "colord";
|
||||
import { StaticColors } from "./static";
|
||||
|
||||
export class DarkColorScheme {
|
||||
static construct(accent: Color) {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
primary: accent.hex().toString(),
|
||||
placeholder: Color("#ffffff").alpha(0.6).hex().toString(),
|
||||
primary: colord(accent).toHex(),
|
||||
placeholder: colord("#ffffff")
|
||||
.alpha(0.6)
|
||||
.toHex(),
|
||||
background: "#1f1f1f",
|
||||
bgTransparent: "#1f1f1f99",
|
||||
accent: "#000",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Color from "color";
|
||||
import { DarkColorScheme } from "./dark";
|
||||
import { LightColorScheme } from "./light";
|
||||
|
||||
@@ -9,7 +8,7 @@ const colorSchemes = {
|
||||
|
||||
class ColorSchemeFactory {
|
||||
static construct<TTheme extends keyof typeof colorSchemes>(theme: TTheme) {
|
||||
return colorSchemes[theme].construct(Color("#008837"));
|
||||
return colorSchemes[theme].construct("#008837");
|
||||
}
|
||||
}
|
||||
export default ColorSchemeFactory;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Color from "color";
|
||||
import { colord } from "colord";
|
||||
import { StaticColors } from "./static";
|
||||
|
||||
export class LightColorScheme {
|
||||
static construct(accent: Color) {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
primary: accent.hex().toString(),
|
||||
primary: colord(accent).toHex(),
|
||||
background: "white",
|
||||
bgTransparent: "#ffffff99",
|
||||
accent: "white",
|
||||
@@ -20,7 +20,9 @@ export class LightColorScheme {
|
||||
secondary: "white",
|
||||
icon: "#3b3b3b",
|
||||
disabled: "#9b9b9b",
|
||||
placeholder: Color("#000000").alpha(0.6).hex().toString(),
|
||||
placeholder: colord("#000000")
|
||||
.alpha(0.6)
|
||||
.toHex(),
|
||||
...StaticColors.construct(accent),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import type Color from "color";
|
||||
import { colord } from "colord";
|
||||
|
||||
export class StaticColors {
|
||||
static construct(accent: Color) {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
shade: accent.alpha(0.1).rgb().toString(),
|
||||
textSelection: accent.alpha(0.2).rgb().toString(),
|
||||
dimPrimary: accent.alpha(0.7).rgb().toString(),
|
||||
shade: colord(accent)
|
||||
.alpha(0.1)
|
||||
.toRgbString(),
|
||||
textSelection: colord(accent)
|
||||
.alpha(0.2)
|
||||
.toRgbString(),
|
||||
dimPrimary: colord(accent)
|
||||
.alpha(0.7)
|
||||
.toRgbString(),
|
||||
transparent: "transparent",
|
||||
static: "white",
|
||||
error: "#E53935",
|
||||
|
||||
13
apps/vericrypt/.env-cmdrc.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { version } = require("./package.json");
|
||||
|
||||
const APP_VERSION = version;
|
||||
module.exports = {
|
||||
all: {
|
||||
REACT_APP_VERSION: APP_VERSION,
|
||||
},
|
||||
release: {
|
||||
GENERATE_SOURCEMAP: false,
|
||||
INLINE_RUNTIME_CHUNK: false,
|
||||
DISABLE_ESLINT_PLUGIN: true,
|
||||
},
|
||||
};
|
||||
23
apps/vericrypt/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
0
apps/vericrypt/README.md
Normal file
38562
apps/vericrypt/package-lock.json
generated
Normal file
56
apps/vericrypt/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@notesnook/vericrypt",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@types/file-saver": "^2.0.4",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/platform": "^1.3.4",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-dropzone": "^5.1.0",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/theme-ui": "^0.6.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "^1.0.0",
|
||||
"clipboard-polyfill": "^3.0.3",
|
||||
"colord": "^2.9.2",
|
||||
"platform": "^1.3.6",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"theme-ui": "^0.12.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "env-cmd -e all react-scripts start",
|
||||
"build": "env-cmd -e all,release react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
apps/vericrypt/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
38
apps/vericrypt/public/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="An open source utility to verify Notesnook data encryption in a safe & reliable way."
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Notesnook Vericrypt</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apps/vericrypt/public/logo192.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
3
apps/vericrypt/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
71
apps/vericrypt/src/App.css
Normal file
@@ -0,0 +1,71 @@
|
||||
/* 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+ */
|
||||
}
|
||||
142
apps/vericrypt/src/App.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Image, Flex, Text } from "@theme-ui/components";
|
||||
import { ThemeProvider } from "@theme-ui/theme-provider";
|
||||
import "./App.css";
|
||||
import { MdVpnKey, MdCheck } from "react-icons/md";
|
||||
import { BiPlus } from "react-icons/all";
|
||||
import { ThemeFactory } from "./theme";
|
||||
import { Hero } from "./components/Hero";
|
||||
import { LoginToNotesnook } from "./components/Step1";
|
||||
import { GetAccountSalt } from "./components/Step2";
|
||||
import { EnterAccountPassword } from "./components/Step3";
|
||||
import { PasteEncryptedData, SyncRequestBody } from "./components/Step4";
|
||||
import { StepSeperator } from "./components/StepSeperator";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { useState } from "react";
|
||||
import { NNCrypto } from "@notesnook/crypto";
|
||||
import { Code } from "./components/Code";
|
||||
import { Accordion } from "./components/Accordion";
|
||||
import { DecryptedResult } from "./components/DecryptedResult";
|
||||
import Map from "./assets/images/map.svg";
|
||||
|
||||
const instructions = [
|
||||
"Go to Notesnook",
|
||||
"Open Settings",
|
||||
<>
|
||||
Click on <Code text="Backup data recovery key" />
|
||||
</>,
|
||||
"Enter your account password for verification",
|
||||
"Confirm that your generated encryption key matches",
|
||||
];
|
||||
|
||||
function App() {
|
||||
const [password, setPassword] = useState<string>();
|
||||
const [salt, setSalt] = useState<string>();
|
||||
const [key, setKey] = useState<string>();
|
||||
const [data, setData] = useState<SyncRequestBody | undefined>();
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={ThemeFactory.construct()}>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Image src={Map} sx={{ position: "absolute", opacity: 0.1 }} />
|
||||
<Hero />
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<LoginToNotesnook />
|
||||
<StepSeperator />
|
||||
<GetAccountSalt onSaltSubmitted={setSalt} />
|
||||
|
||||
{salt && (
|
||||
<>
|
||||
<StepSeperator icon={BiPlus} />
|
||||
<EnterAccountPassword onPasswordSubmitted={setPassword} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{salt && password && (
|
||||
<>
|
||||
<StepSeperator
|
||||
icon={MdVpnKey}
|
||||
tooltip="Click to see derived encryption key"
|
||||
onShowPopup={async () => {
|
||||
if (!password) return false;
|
||||
const { key } = await new NNCrypto().exportKey(
|
||||
password,
|
||||
salt
|
||||
);
|
||||
setKey(key);
|
||||
return true;
|
||||
}}
|
||||
popup={{
|
||||
title: "Your data encryption key",
|
||||
body: key ? (
|
||||
<>
|
||||
<Text
|
||||
as="code"
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
mt: 2,
|
||||
bg: "bgSecondary",
|
||||
p: 1,
|
||||
borderRadius: 5,
|
||||
}}
|
||||
>
|
||||
{key}
|
||||
</Text>
|
||||
<Accordion
|
||||
title="How to verify your encryption key?"
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ mx: 2 }}>
|
||||
Now that you have derived your encryption key, you'll
|
||||
want to verify it to make sure we didn't just make it
|
||||
up. To do so, follow these steps:
|
||||
<Text as="ol" variant="body" sx={{ mb: 2 }}>
|
||||
{instructions?.map((item) => (
|
||||
<Text as="li" sx={{ mt: 1 }}>
|
||||
{item}
|
||||
</Text>
|
||||
))}
|
||||
</Text>
|
||||
</Text>
|
||||
</Accordion>
|
||||
</>
|
||||
) : (
|
||||
undefined
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<PasteEncryptedData onEncryptedDataPasted={setData} />
|
||||
</>
|
||||
)}
|
||||
{password && salt && data && (
|
||||
<>
|
||||
<StepSeperator icon={MdCheck} />
|
||||
<DecryptedResult
|
||||
password={password}
|
||||
salt={salt}
|
||||
data={data}
|
||||
onRestartProcess={() => {
|
||||
setSalt(undefined);
|
||||
setPassword(undefined);
|
||||
setKey(undefined);
|
||||
setData(undefined);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-600.woff
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-600.woff2
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-700.woff
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-700.woff2
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-italic.woff
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-italic.woff2
Normal file
BIN
apps/vericrypt/src/assets/fonts/open-sans-v20-latin-regular.woff
Normal file
1
apps/vericrypt/src/assets/images/map.svg
Normal file
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 25 KiB |
BIN
apps/vericrypt/src/assets/screenshots/devtools_requests_copy.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
BIN
apps/vericrypt/src/assets/screenshots/devtools_salt.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
apps/vericrypt/src/assets/screenshots/devtools_select_db.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 22 KiB |
43
apps/vericrypt/src/components/Accordion.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Flex, FlexProps, Text } from "@theme-ui/components";
|
||||
import { PropsWithChildren, useState } from "react";
|
||||
import { IoChevronDown, IoChevronUp } from "react-icons/io5";
|
||||
|
||||
type AccordionProps = FlexProps & {
|
||||
title: string;
|
||||
color?: string;
|
||||
};
|
||||
export function Accordion({
|
||||
title,
|
||||
children,
|
||||
sx,
|
||||
color = "icon",
|
||||
...restProps
|
||||
}: PropsWithChildren<AccordionProps>) {
|
||||
const [isContentHidden, setIsContentHidden] = useState<boolean>(true);
|
||||
return (
|
||||
<Flex sx={{ flexDirection: "column", ...sx }} {...restProps}>
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
p: 2,
|
||||
cursor: "pointer",
|
||||
color: color,
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsContentHidden((state) => !state);
|
||||
}}
|
||||
>
|
||||
<Text variant="subtitle" sx={{ color }}>
|
||||
{title}
|
||||
</Text>
|
||||
{isContentHidden ? (
|
||||
<IoChevronDown size={16} color={color} />
|
||||
) : (
|
||||
<IoChevronUp size={16} color={color} />
|
||||
)}
|
||||
</Flex>
|
||||
{!isContentHidden && children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
26
apps/vericrypt/src/components/Code.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Text, Link } from "@theme-ui/components";
|
||||
|
||||
type CodeProps = { text: string; href?: string };
|
||||
export function Code(props: CodeProps) {
|
||||
return (
|
||||
<Text
|
||||
as="code"
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
px: 1,
|
||||
borderRadius: 5,
|
||||
fontFamily: "monospace",
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
cursor: props.href ? "pointer" : "unset",
|
||||
}}
|
||||
>
|
||||
{props.href ? (
|
||||
<Link href={props.href} color="primary">
|
||||
{props.text}
|
||||
</Link>
|
||||
) : (
|
||||
props.text
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
166
apps/vericrypt/src/components/DecryptedResult.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { Flex, Button, Text, Link } from "@theme-ui/components";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import { SyncRequestBody } from "./Step4";
|
||||
import { NNCrypto } from "@notesnook/crypto";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FcDataEncryption } from "react-icons/fc";
|
||||
import { Code } from "./Code";
|
||||
import { getSourceUrl } from "../utils/links";
|
||||
import * as clipboard from "clipboard-polyfill/text";
|
||||
import { Accordion } from "./Accordion";
|
||||
import { ErrorsList } from "./ErrorsList";
|
||||
|
||||
type DecryptedResultProps = {
|
||||
password: string;
|
||||
salt: string;
|
||||
data: SyncRequestBody;
|
||||
onRestartProcess: () => void;
|
||||
};
|
||||
|
||||
export function DecryptedResult(props: DecryptedResultProps) {
|
||||
const [isDecrypting, setIsDecrypting] = useState(true);
|
||||
const [decryptedData, setDecryptedData] = useState<string>();
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
(async function() {
|
||||
try {
|
||||
const data: any = {
|
||||
notes: [],
|
||||
notebooks: [],
|
||||
content: [],
|
||||
attachments: [],
|
||||
};
|
||||
const crypto = new NNCrypto();
|
||||
const key = await crypto.exportKey(props.password, props.salt);
|
||||
for (let arrayKey in data) {
|
||||
const array = data[arrayKey];
|
||||
for (let encryptedItem of (props.data as any)[arrayKey]) {
|
||||
const { data } = await crypto.decrypt(key, encryptedItem, "text");
|
||||
array.push(JSON.parse(data as string));
|
||||
}
|
||||
}
|
||||
setDecryptedData(JSON.stringify(data, undefined, " "));
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
setError(error.message);
|
||||
} finally {
|
||||
setIsDecrypting(false);
|
||||
}
|
||||
})();
|
||||
}, [props]);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<StepContainer
|
||||
as="form"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Decryption failed</Text>
|
||||
<ErrorsList errors={[error]} />
|
||||
</StepContainer>
|
||||
);
|
||||
|
||||
if (isDecrypting)
|
||||
return (
|
||||
<StepContainer
|
||||
as="form"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<FcDataEncryption size={50} />
|
||||
<Text variant="title" sx={{ mt: 2 }}>
|
||||
Decrypting your data...
|
||||
</Text>
|
||||
</StepContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
<StepContainer
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
border: "2px solid var(--theme-ui-colors-primary)",
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text variant="title">Your data has been decrypted</Text>
|
||||
<Code
|
||||
text="src/components/DecryptedResult.tsx"
|
||||
href={getSourceUrl("src/components/DecryptedResult.tsx")}
|
||||
/>
|
||||
</Flex>
|
||||
<Text variant="body">
|
||||
This is your data in it's raw decrypted format. Feel free to scroll
|
||||
through and see what it contains.
|
||||
</Text>
|
||||
<Text
|
||||
as="pre"
|
||||
variant="body"
|
||||
sx={{
|
||||
maxHeight: 400,
|
||||
overflowY: "auto",
|
||||
fontFamily: "monospace",
|
||||
whiteSpace: "pre-wrap",
|
||||
color: "icon",
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
{decryptedData}
|
||||
</Text>
|
||||
<Accordion
|
||||
title="What happens now?"
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text as="p" variant="body" sx={{ mx: 2 }}>
|
||||
Congratulations! You successfully verified Notesnook's data encryption
|
||||
claims.
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mx: 2, mt: 2 }}>
|
||||
Of course, this is just one part (a very crucial one) of proving that
|
||||
you can trust Notesnook with your data. If you have any other
|
||||
preservations, let us know by reaching out to us at{" "}
|
||||
<Link href="mailto:support@streetwriters.co">
|
||||
support@streetwriters.co
|
||||
</Link>{" "}
|
||||
or{" "}
|
||||
<Link href="https://discord.gg/">joining our Discord community</Link>.
|
||||
We'll do our best to alleviate all your worries.
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mx: 2, mt: 2, fontWeight: "bold" }}>
|
||||
What about open sourcing Notesnook?
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mx: 2, my: 2 }}>
|
||||
Open sourcing is another part of garnering our users' trust. We have{" "}
|
||||
<Link href="https://notesnook.com/roadmap">plans</Link> to begin open
|
||||
sourcing in May but open sourcing will not make this tool obsolete.
|
||||
Verifying the integrity of encrypted data at any point in time is very
|
||||
important even if the software is open source.
|
||||
</Text>
|
||||
</Accordion>
|
||||
<Flex sx={{ alignSelf: "center", mt: 4 }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
sx={{ mr: 2 }}
|
||||
onClick={async () => {
|
||||
if (!decryptedData) return;
|
||||
await clipboard.writeText(decryptedData);
|
||||
}}
|
||||
>
|
||||
Copy data as JSON
|
||||
</Button>
|
||||
<Button variant="primary" onClick={props.onRestartProcess}>
|
||||
Start again
|
||||
</Button>
|
||||
</Flex>
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
38
apps/vericrypt/src/components/ErrorsList.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
type ErrorsListProps = {
|
||||
errors: string[];
|
||||
};
|
||||
|
||||
export function ErrorsList(props: ErrorsListProps) {
|
||||
return (
|
||||
<Accordion
|
||||
title={`${props.errors.length} errors occured`}
|
||||
sx={{ bg: "errorBg", borderRadius: "default", mt: 2 }}
|
||||
color="#E53935"
|
||||
>
|
||||
<Flex sx={{ flexDirection: "column", px: 2, pb: 2 }}>
|
||||
<Text variant="body" sx={{ color: "error" }}>
|
||||
{props.errors.map((error, index) => (
|
||||
<>
|
||||
{error}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</Text>
|
||||
<Button
|
||||
sx={{ bg: "error", color: "static", alignSelf: "start", mt: 2 }}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
"https://github.com/streetwriters/notesnook/issues/new",
|
||||
"_blank"
|
||||
)
|
||||
}
|
||||
>
|
||||
Send us a bug report
|
||||
</Button>
|
||||
</Flex>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
23
apps/vericrypt/src/components/Footer.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { appVersion } from "../utils/version";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
mt: 100,
|
||||
pt: 50,
|
||||
pb: 50,
|
||||
borderTop: "1px solid var(--theme-ui-colors-border)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text variant="body">Notesnook Vericrypt (v{appVersion})</Text>
|
||||
<Text variant="subBody">
|
||||
© {new Date().getFullYear()} Streetwriters LLC
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
109
apps/vericrypt/src/components/Hero.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Flex, Box, Link, Text } from "@theme-ui/components";
|
||||
import { appVersion } from "../utils/version";
|
||||
import {
|
||||
MdOutlineShield,
|
||||
MdVpnKey,
|
||||
MdOutlineStorage,
|
||||
MdOutlineEnhancedEncryption,
|
||||
} from "react-icons/md";
|
||||
import { getAppUrl } from "../utils/links";
|
||||
|
||||
const Algorithms = [
|
||||
{
|
||||
key: "argon2",
|
||||
title: "Encryption key derivation (PKDF)",
|
||||
name: "Argon2",
|
||||
icon: MdVpnKey,
|
||||
},
|
||||
{
|
||||
key: "aes-gcm",
|
||||
title: "Encryption key storage",
|
||||
name: "AES-GCM 256",
|
||||
icon: MdOutlineStorage,
|
||||
},
|
||||
{
|
||||
key: "xchacha",
|
||||
title: "Data encryption",
|
||||
name: "XChaXCha-Poly1305-IETF",
|
||||
icon: MdOutlineEnhancedEncryption,
|
||||
},
|
||||
];
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
mt: 150,
|
||||
mb: 100,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
variant="heading"
|
||||
sx={{ textAlign: "center", display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<MdOutlineShield /> Notesnook Vericrypt
|
||||
</Text>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "title",
|
||||
textAlign: "center",
|
||||
color: "fontTertiary",
|
||||
}}
|
||||
>
|
||||
An open source utility to verify Notesnook data encryption claims in a
|
||||
provable way.
|
||||
</Text>
|
||||
<Flex sx={{ mt: 2, zIndex: 10 }}>
|
||||
<Text variant="body" sx={{ px: 1 }}>
|
||||
v{appVersion}
|
||||
</Text>
|
||||
<Link
|
||||
href={getAppUrl("vericrypt")}
|
||||
variant="text.body"
|
||||
sx={{ px: 1, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
>
|
||||
See source code
|
||||
</Link>
|
||||
</Flex>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 100,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
rowGap: 2,
|
||||
}}
|
||||
>
|
||||
{Algorithms.map((alg) => (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
mr: 4,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
bg: "black",
|
||||
width: 250,
|
||||
}}
|
||||
key={alg.key}
|
||||
>
|
||||
<alg.icon color={"var(--theme-ui-colors-fontSecondary)"} />
|
||||
<Text variant="subtitle" sx={{ color: "fontSecondary" }}>
|
||||
{alg.title}
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
fontSize: "subtitle",
|
||||
color: "fontSecondary",
|
||||
}}
|
||||
>
|
||||
{alg.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
15
apps/vericrypt/src/components/KeyCombo.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Code } from "./Code";
|
||||
|
||||
type KeyComboProps = { combo: string[] };
|
||||
export function KeyCombo(props: KeyComboProps) {
|
||||
return (
|
||||
<>
|
||||
{props.combo.map((key, index) => (
|
||||
<>
|
||||
<Code text={key} />
|
||||
{index < props.combo.length - 1 ? " + " : ""}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
67
apps/vericrypt/src/components/Step1.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import { Code } from "./Code";
|
||||
import { getPackageUrl } from "../utils/links";
|
||||
|
||||
export function LoginToNotesnook() {
|
||||
return (
|
||||
<StepContainer
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("step_2")?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
onSubmitCapture={() => false}
|
||||
as="form"
|
||||
sx={{ flexDirection: "column" }}
|
||||
>
|
||||
<Text variant="title">Welcome to Vericrypt</Text>
|
||||
<Text as="p" variant="body" sx={{ mt: 1 }}>
|
||||
Trust is a huge problem in closed source end-to-end encrypted
|
||||
applications. How can you be sure that the app is actually encrypting
|
||||
your data?
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mt: 1 }}>
|
||||
The only way to earn a user's trust is by allowing them to see how the
|
||||
underlying encryption actually works. To do this, we at Notesnook have
|
||||
taken several steps.
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mt: 1 }}>
|
||||
The first step has been the{" "}
|
||||
<b>
|
||||
open sourcing of our{" "}
|
||||
<Code text="@notesnook/crypto" href={getPackageUrl("crypto")} />{" "}
|
||||
module
|
||||
</b>{" "}
|
||||
which is responsible for all cryptographic needs of the app.
|
||||
</Text>
|
||||
<Text as="p" variant="body" sx={{ mt: 1 }}>
|
||||
The second step is this tool which is also open source and uses{" "}
|
||||
<Code text="@notesnook/crypto" href={getPackageUrl("crypto")} />{" "}
|
||||
underneath. Vericrypt will allow you to verify all encryption claims
|
||||
made by Notesnook in a practical & provable way right inside your
|
||||
browser.
|
||||
</Text>
|
||||
<Text
|
||||
as="p"
|
||||
variant="body"
|
||||
sx={{ mt: 1, bg: "bgSecondary", p: 2, borderRadius: 5 }}
|
||||
>
|
||||
When you use this tool, you'll be guided each step of the way to
|
||||
extract/insert raw data from raw sources.{" "}
|
||||
<b>The whole process happens completely in your browser offline</b> and
|
||||
you can even disconnect your internet to make sure we aren't just saying
|
||||
that.
|
||||
</Text>
|
||||
<Button
|
||||
sx={{ alignSelf: "center", mt: 2 }}
|
||||
onClick={() => window.open("https://app.notesnook.com/login", "_blank")}
|
||||
>
|
||||
Login to Notesnook
|
||||
</Button>
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
195
apps/vericrypt/src/components/Step2.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import { Flex, Text, Image, Input } from "@theme-ui/components";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import DevtoolsApplicationTab from "../assets/screenshots/devtools_application_tab.png";
|
||||
import DevtoolsSelectDB from "../assets/screenshots/devtools_select_db.png";
|
||||
import DevtoolsSalt from "../assets/screenshots/devtools_salt.png";
|
||||
|
||||
import DevtoolsFirefoxStorageTab from "../assets/screenshots/firefox/firefox_storage_tab.png";
|
||||
import DevtoolsSelectDBFirefox from "../assets/screenshots//firefox/firefox_keyvalue_pairs.png";
|
||||
import DevtoolsSaltFirefox from "../assets/screenshots/firefox/firefox_user_object.png";
|
||||
|
||||
import { Accordion } from "./Accordion";
|
||||
import { getCombo } from "../utils/keycombos";
|
||||
import Platform from "platform";
|
||||
import { KeyCombo } from "./KeyCombo";
|
||||
import { Code } from "./Code";
|
||||
import { useState } from "react";
|
||||
import { getSourceUrl } from "../utils/links";
|
||||
|
||||
type GetAccountSaltProps = {
|
||||
onSaltSubmitted: (salt: string) => void;
|
||||
};
|
||||
|
||||
const steps = {
|
||||
chromium: [
|
||||
"Focus the Notesnook tab in your browser.",
|
||||
<>
|
||||
Press <KeyCombo combo={getCombo("chromium", "developerTools")} /> to open
|
||||
Developer Tools.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
Switch to the <Code text="Application" /> tab.
|
||||
</Text>
|
||||
<Image src={DevtoolsApplicationTab} width={300} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
From the side menu, expand <Code text="IndexedDB" /> which is under the
|
||||
Storage heading.
|
||||
</>,
|
||||
`You'll see a number of databases . Expand the one that starts with "Notesnook".`,
|
||||
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
Click on <Code text="keyvaluepairs" />.
|
||||
</Text>
|
||||
<Image src={DevtoolsSelectDB} width={200} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
On the right panel, you'll see an input with the placeholder "Start from
|
||||
key" at the top. Type <Code text="user" /> in that input.
|
||||
</>,
|
||||
`At the top of the list, you'll see an entry with key "user". Expand the value.`,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
You'll see a key named <Code text="salt" />. Copy the value against it.
|
||||
</Text>
|
||||
<Image src={DevtoolsSalt} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
],
|
||||
firefox: [
|
||||
"Focus the Notesnook tab in your browser.",
|
||||
<>
|
||||
Press <KeyCombo combo={getCombo("firefox", "developerTools")} /> to open
|
||||
Developer Tools.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
Switch to the <Code text="Storage" /> tab.
|
||||
</Text>
|
||||
<Image src={DevtoolsFirefoxStorageTab} width={300} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
From the side menu, expand <Code text="IndexedDB" /> which is under the
|
||||
Storage heading.
|
||||
</>,
|
||||
`You'll see a number of databases . Expand the one that starts with "Notesnook".`,
|
||||
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
Click on <Code text="keyvaluepairs" />.
|
||||
</Text>
|
||||
<Image src={DevtoolsSelectDBFirefox} width={200} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
On the right panel, you'll see an input with the placeholder "Start from
|
||||
key" at the top. Type <Code text="user" /> in that input. If you don't see
|
||||
the 'user' object, scroll down in key-value pair list and the search
|
||||
again.
|
||||
</>,
|
||||
`At the top of the list, you'll see an entry with key "user". Expand the value.`,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
You'll see a key named <Code text="salt" />. Right click and copy the
|
||||
value against it.
|
||||
</Text>
|
||||
<Image src={DevtoolsSaltFirefox} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
],
|
||||
};
|
||||
|
||||
const isChromium = Platform.name === "Chrome";
|
||||
const isFirefox = Platform.name === "Firefox";
|
||||
const instructions = isChromium
|
||||
? steps.chromium
|
||||
: isFirefox
|
||||
? steps.firefox
|
||||
: null;
|
||||
|
||||
console.log(Platform.name);
|
||||
export function GetAccountSalt(props: GetAccountSaltProps) {
|
||||
const [isSaltValid, setIsSaltValid] = useState<boolean>();
|
||||
|
||||
return (
|
||||
<StepContainer
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (isSaltValid) {
|
||||
document.getElementById("step_3")?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
onSubmitCapture={() => false}
|
||||
inputMode="text"
|
||||
id="step_2"
|
||||
as="form"
|
||||
sx={{ flexDirection: "column" }}
|
||||
>
|
||||
<Flex sx={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text variant="title">Account salt</Text>
|
||||
<Code
|
||||
text="src/components/Step2.tsx"
|
||||
href={getSourceUrl("src/components/Step2.tsx")}
|
||||
/>
|
||||
</Flex>
|
||||
<Accordion
|
||||
title="How to get your account salt?"
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ mx: 2 }}>
|
||||
We'll be extracting your account's salt right from Notesnook's local
|
||||
database that lives in your web browser. So put on your seat belt and
|
||||
let's get some salt!
|
||||
</Text>
|
||||
<Text as="ol" variant="body" sx={{ mb: 2 }}>
|
||||
{instructions?.map((item) => (
|
||||
<Text as="li" sx={{ mt: 1 }}>
|
||||
{item}
|
||||
</Text>
|
||||
))}
|
||||
</Text>
|
||||
</Accordion>
|
||||
<Input
|
||||
variant="forms.clean"
|
||||
id="salt"
|
||||
name="salt"
|
||||
type="text"
|
||||
placeholder="Enter your account salt"
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontSize: "subheading",
|
||||
fontFamily: "monospace",
|
||||
textAlign: "center",
|
||||
color:
|
||||
isSaltValid === true
|
||||
? "primary"
|
||||
: isSaltValid === false
|
||||
? "error"
|
||||
: "text",
|
||||
}}
|
||||
spellCheck={false}
|
||||
onChange={(e) => {
|
||||
setIsSaltValid(undefined);
|
||||
try {
|
||||
const value = e.target.value;
|
||||
const isValid = Buffer.from(value, "base64").length === 16;
|
||||
if (!isValid) setIsSaltValid(false);
|
||||
else {
|
||||
setIsSaltValid(true);
|
||||
props.onSaltSubmitted(value);
|
||||
}
|
||||
} catch (e) {
|
||||
setIsSaltValid(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
71
apps/vericrypt/src/components/Step3.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Flex, Text, Input } from "@theme-ui/components";
|
||||
import { getSourceUrl } from "../utils/links";
|
||||
import { Code } from "./Code";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
|
||||
type EnterAccountPasswordProps = {
|
||||
onPasswordSubmitted: (password: string) => void;
|
||||
};
|
||||
|
||||
export function EnterAccountPassword(props: EnterAccountPasswordProps) {
|
||||
return (
|
||||
<StepContainer
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("step_4")?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
onSubmitCapture={() => false}
|
||||
inputMode="text"
|
||||
id="step_3"
|
||||
as="form"
|
||||
sx={{ flexDirection: "column" }}
|
||||
>
|
||||
<Flex sx={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text variant="title">Account password</Text>
|
||||
<Code
|
||||
text="src/components/Step3.tsx"
|
||||
href={getSourceUrl("src/components/Step3.tsx")}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
mt: 2,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Text variant="subtitle">
|
||||
Does my account password ever gets sent to the server?
|
||||
</Text>
|
||||
<Text as="p" variant="body">
|
||||
Never. Your password never ever leaves the device. Everything is
|
||||
happening locally. For good measure, you can even disconnect your
|
||||
Internet. This is the most fundamental part of zero knowledge data
|
||||
encryption.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Input
|
||||
variant="forms.clean"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Enter your account password"
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontSize: "subheading",
|
||||
fontFamily: "monospace",
|
||||
textAlign: "center",
|
||||
}}
|
||||
onChange={(e) => {
|
||||
props.onPasswordSubmitted(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
241
apps/vericrypt/src/components/Step4.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
import { Flex, Text, Image, Textarea } from "@theme-ui/components";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
import DevtoolsRequestsFilter from "../assets/screenshots/devtools_requests_filter.png";
|
||||
import DevtoolsRequestsCopy from "../assets/screenshots/devtools_requests_copy.png";
|
||||
|
||||
import DevtoolsRequestsFilterFirefox from "../assets/screenshots/firefox/network_tab_firefox.png";
|
||||
import DevtoolsRequestsCopyFirefox from "../assets/screenshots/firefox/copy_curl_request.png";
|
||||
|
||||
import { Accordion } from "./Accordion";
|
||||
import { getCombo } from "../utils/keycombos";
|
||||
import Platform from "platform";
|
||||
import { KeyCombo } from "./KeyCombo";
|
||||
import { Code } from "./Code";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ErrorsList } from "./ErrorsList";
|
||||
import { getSourceUrl } from "../utils/links";
|
||||
import { parseCURL } from "../utils/curlparser";
|
||||
|
||||
type PasteEncryptedDataProps = {
|
||||
onEncryptedDataPasted: (data?: SyncRequestBody) => void;
|
||||
};
|
||||
|
||||
type EncryptedSyncItem = {
|
||||
alg: string;
|
||||
cipher: string;
|
||||
format: "base64";
|
||||
id: string;
|
||||
iv: string;
|
||||
length: number;
|
||||
salt: string;
|
||||
v: number;
|
||||
};
|
||||
export type SyncRequestBody = {
|
||||
notes: EncryptedSyncItem[];
|
||||
content: EncryptedSyncItem[];
|
||||
notebooks: EncryptedSyncItem[];
|
||||
attachments: EncryptedSyncItem[];
|
||||
};
|
||||
|
||||
const SAMPLE_CURL = `Paste your cURL request here.
|
||||
For example:
|
||||
curl 'https://api.notesnook.com/sync'
|
||||
-H 'authority: api.notesnook.com'
|
||||
-H 'authorization: Bearer 84374D98...'
|
||||
-H ...
|
||||
-H ...
|
||||
-H 'content-type: application/json'
|
||||
--data-raw '{"notes":[{...}], "notebooks": [{...}], "content": [{...}], "lastSynced":1443215713231}'
|
||||
--compressed`;
|
||||
|
||||
const steps = {
|
||||
chromium: [
|
||||
"Focus the Notesnook tab in your browser.",
|
||||
<>
|
||||
Press <KeyCombo combo={getCombo("chromium", "developerTools")} /> to open
|
||||
Developer Tools.
|
||||
</>,
|
||||
<>
|
||||
Switch to the <Code text="Network" /> tab.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
In the filter input, type <Code text="sync" /> to filter out sync
|
||||
requests.
|
||||
</Text>
|
||||
<Image src={DevtoolsRequestsFilter} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
At this point, you probably won't see anything useful. Try editing one of
|
||||
your notes and syncing. Make sure to keep the <Code text="Network" /> tab
|
||||
open.
|
||||
</>,
|
||||
<>
|
||||
In the requests table in <Code text="Network" /> tab, you'll now see 3 or
|
||||
more new requests popup.
|
||||
</>,
|
||||
<>
|
||||
We are interested in only the <Code text="sync" /> request. Right click on
|
||||
this request to open the context menu.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
From the context menu, go to <Code text="Copy" /> & click on{" "}
|
||||
<Code text="Copy as cURL" />.
|
||||
</Text>
|
||||
<Image src={DevtoolsRequestsCopy} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
],
|
||||
firefox: [
|
||||
"Focus the Notesnook tab in your browser.",
|
||||
<>
|
||||
Press <KeyCombo combo={getCombo("chromium", "developerTools")} /> to open
|
||||
Developer Tools.
|
||||
</>,
|
||||
<>
|
||||
Switch to the <Code text="Network" /> tab.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
In the filter input, type <Code text="sync" /> to filter out sync
|
||||
requests.
|
||||
</Text>
|
||||
<Image src={DevtoolsRequestsFilterFirefox} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
<>
|
||||
At this point, you probably won't see anything useful. Try editing one of
|
||||
your notes and syncing. Make sure to keep the <Code text="Network" /> tab
|
||||
open.
|
||||
</>,
|
||||
<>
|
||||
In the requests table in <Code text="Network" /> tab, you'll now see 3 or
|
||||
more new requests popup.
|
||||
</>,
|
||||
<>
|
||||
We are interested in only the <Code text="sync" /> request. Right click on
|
||||
this request to open the context menu.
|
||||
</>,
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text as="p">
|
||||
From the context menu, go to <Code text="Copy" /> & click on{" "}
|
||||
<Code text="Copy as cURL" />.
|
||||
</Text>
|
||||
<Image src={DevtoolsRequestsCopyFirefox} width={400} sx={{ mt: 1 }} />
|
||||
</Flex>,
|
||||
],
|
||||
};
|
||||
|
||||
const isChromium = Platform.name === "Chrome";
|
||||
const isFirefox = Platform.name === "Firefox";
|
||||
const instructions = isChromium
|
||||
? steps.chromium
|
||||
: isFirefox
|
||||
? steps.firefox
|
||||
: null;
|
||||
|
||||
export function PasteEncryptedData(props: PasteEncryptedDataProps) {
|
||||
const { onEncryptedDataPasted } = props;
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [encryptedData, setEncryptedData] = useState<
|
||||
SyncRequestBody | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
onEncryptedDataPasted(encryptedData);
|
||||
}, [encryptedData, onEncryptedDataPasted]);
|
||||
|
||||
return (
|
||||
<StepContainer id="step_4" as="form" sx={{ flexDirection: "column" }}>
|
||||
<Flex sx={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text variant="title">Paste raw encrypted data</Text>
|
||||
<Code
|
||||
text="src/components/Step4.tsx"
|
||||
href={getSourceUrl("src/components/Step4.tsx")}
|
||||
/>
|
||||
</Flex>
|
||||
<Accordion
|
||||
title="How to get raw encrypted data?"
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ mx: 2 }}>
|
||||
To make this whole process verifiable & trustworthy, we need to
|
||||
extract the raw data that Notesnook sends to its servers during sync.
|
||||
That way you can be sure that the data leaving your device is actually
|
||||
encrypted or not.
|
||||
</Text>
|
||||
<Text as="ol" variant="body" sx={{ mb: 2 }}>
|
||||
{instructions?.map((item) => (
|
||||
<Text as="li" sx={{ mt: 1 }}>
|
||||
{item}
|
||||
</Text>
|
||||
))}
|
||||
</Text>
|
||||
</Accordion>
|
||||
<Textarea
|
||||
variant="forms.clean"
|
||||
placeholder={SAMPLE_CURL}
|
||||
sx={{
|
||||
mt: 2,
|
||||
fontSize: "body",
|
||||
fontFamily: "monospace",
|
||||
whiteSpace: "pre-wrap",
|
||||
height: 280,
|
||||
}}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setError(undefined);
|
||||
const curlRequest = e.target.value;
|
||||
if (!curlRequest) return setEncryptedData(undefined);
|
||||
const parsed = parseCURL(curlRequest);
|
||||
|
||||
if (parsed.url !== "https://api.notesnook.com/sync") {
|
||||
throw new Error(
|
||||
"Invalid cURL request. URL must be https://api.notesnook.com/sync."
|
||||
);
|
||||
}
|
||||
|
||||
if (!parsed.data)
|
||||
throw new Error("Invalid cURL request. No data found.");
|
||||
|
||||
const syncData: SyncRequestBody = JSON.parse(parsed.data);
|
||||
if (syncData.notes.length <= 0 && syncData.content.length <= 0) {
|
||||
throw new Error(
|
||||
"Empty cURL request. Make sure the request body contains at least 1 note or 1 content."
|
||||
);
|
||||
}
|
||||
|
||||
setEncryptedData(syncData);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
setError(error.message);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{error ? <ErrorsList errors={[error]} /> : null}
|
||||
{encryptedData && (
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "shade",
|
||||
border: "2px solid var(--theme-ui-colors-primary)",
|
||||
mt: 2,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Text variant="subtitle">Parsing complete</Text>
|
||||
<Text as="p" variant="body">
|
||||
We found {encryptedData.notes.length} note(s),{" "}
|
||||
{encryptedData.content.length} content(s),{" "}
|
||||
{encryptedData.notebooks.length} notebook(s) &{" "}
|
||||
{encryptedData.attachments.length} attachments.
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
23
apps/vericrypt/src/components/StepContainer.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Flex, FlexProps } from "@theme-ui/components";
|
||||
|
||||
export function StepContainer({
|
||||
children,
|
||||
sx,
|
||||
...restProps
|
||||
}: React.PropsWithChildren<FlexProps>) {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: "40%",
|
||||
boxShadow: "0px 0px 20px 0px #00000011",
|
||||
p: 4,
|
||||
borderRadius: "default",
|
||||
bg: "background",
|
||||
...sx,
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
98
apps/vericrypt/src/components/StepSeperator.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { useState } from "react";
|
||||
import { IconType } from "react-icons";
|
||||
import { MdClose } from "react-icons/md";
|
||||
import { ThemeProvider } from "theme-ui";
|
||||
import { ThemeFactory } from "../theme";
|
||||
|
||||
type StepSeperatorProps = {
|
||||
icon?: IconType;
|
||||
onShowPopup?: () => Promise<boolean>;
|
||||
tooltip?: string;
|
||||
popup?: { title: string; body?: JSX.Element };
|
||||
};
|
||||
|
||||
export function StepSeperator(props: StepSeperatorProps) {
|
||||
const [showPopup, setShowPopup] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
height: 200,
|
||||
width: 2,
|
||||
bg: "bgSecondary",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
tabIndex={1}
|
||||
>
|
||||
{props.icon && (
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "background",
|
||||
p: 2,
|
||||
boxShadow: "0px 0px 10px 0px #00000011",
|
||||
borderRadius: 50,
|
||||
transition: "transform 100ms ease-out",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
}}
|
||||
title={props.tooltip}
|
||||
onClick={async () => {
|
||||
if (showPopup) return setShowPopup(false);
|
||||
if (!showPopup && props.onShowPopup)
|
||||
setShowPopup(await props.onShowPopup());
|
||||
}}
|
||||
>
|
||||
<props.icon size={20} />
|
||||
</Flex>
|
||||
)}
|
||||
{showPopup && props.popup && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 60,
|
||||
p: 2,
|
||||
width: 400,
|
||||
}}
|
||||
>
|
||||
<ThemeProvider theme={ThemeFactory.construct("dark")}>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "background",
|
||||
borderRadius: "default",
|
||||
boxShadow: "0px 0px 10px 0px #00000011",
|
||||
p: 2,
|
||||
flexDirection: "column",
|
||||
color: "icon",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{ justifyContent: "space-between", alignItems: "center" }}
|
||||
>
|
||||
<Text variant="title">{props.popup.title}</Text>
|
||||
<MdClose
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setShowPopup(false)}
|
||||
/>
|
||||
</Flex>
|
||||
{props.popup.body}
|
||||
</Flex>
|
||||
</ThemeProvider>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
0
apps/vericrypt/src/index.css
Normal file
11
apps/vericrypt/src/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
1
apps/vericrypt/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
28
apps/vericrypt/src/theme/colorscheme/dark.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { colord } from "colord";
|
||||
import { StaticColors } from "./static";
|
||||
|
||||
export class DarkColorScheme {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
primary: colord(accent).toHex(),
|
||||
placeholder: colord("#ffffff")
|
||||
.alpha(0.6)
|
||||
.toHex(),
|
||||
background: "#1f1f1f",
|
||||
bgTransparent: "#1f1f1f99",
|
||||
accent: "#000",
|
||||
bgSecondary: "#2b2b2b",
|
||||
bgSecondaryText: "#A1A1A1",
|
||||
border: "#2b2b2b",
|
||||
hover: "#3b3b3b",
|
||||
fontSecondary: "#000",
|
||||
fontTertiary: "#A1A1A1",
|
||||
text: "#d3d3d3",
|
||||
overlay: "rgba(53, 53, 53, 0.5)",
|
||||
secondary: "black",
|
||||
icon: "#dbdbdb",
|
||||
disabled: "#5b5b5b",
|
||||
...StaticColors.construct(accent),
|
||||
};
|
||||
}
|
||||
}
|
||||
14
apps/vericrypt/src/theme/colorscheme/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DarkColorScheme } from "./dark";
|
||||
import { LightColorScheme } from "./light";
|
||||
|
||||
const colorSchemes = {
|
||||
dark: DarkColorScheme,
|
||||
light: LightColorScheme,
|
||||
};
|
||||
|
||||
class ColorSchemeFactory {
|
||||
static construct<TTheme extends keyof typeof colorSchemes>(theme: TTheme) {
|
||||
return colorSchemes[theme].construct(theme === "light" ? "#000" : "#fff");
|
||||
}
|
||||
}
|
||||
export default ColorSchemeFactory;
|
||||
29
apps/vericrypt/src/theme/colorscheme/light.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { colord } from "colord";
|
||||
import { StaticColors } from "./static";
|
||||
|
||||
export class LightColorScheme {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
primary: colord(accent).toHex(),
|
||||
background: "white",
|
||||
bgTransparent: "#ffffff99",
|
||||
accent: "white",
|
||||
bgSecondary: "#f7f7f7",
|
||||
bgSecondaryText: "#5E5E5E",
|
||||
border: "#e7e7e7",
|
||||
hover: "#f0f0f0",
|
||||
active: "#ababab",
|
||||
fontSecondary: "white",
|
||||
fontTertiary: "#656565",
|
||||
text: "#202124",
|
||||
overlay: "rgba(0, 0, 0, 0.1)",
|
||||
secondary: "white",
|
||||
icon: "#3b3b3b",
|
||||
disabled: "#9b9b9b",
|
||||
placeholder: colord("#000000")
|
||||
.alpha(0.6)
|
||||
.toHex(),
|
||||
...StaticColors.construct(accent),
|
||||
};
|
||||
}
|
||||
}
|
||||
31
apps/vericrypt/src/theme/colorscheme/static.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { colord } from "colord";
|
||||
|
||||
export class StaticColors {
|
||||
static construct(accent: string) {
|
||||
return {
|
||||
shade: colord(accent)
|
||||
.alpha(0.1)
|
||||
.toRgbString(),
|
||||
textSelection: colord(accent)
|
||||
.alpha(0.2)
|
||||
.toRgbString(),
|
||||
dimPrimary: colord(accent)
|
||||
.alpha(0.7)
|
||||
.toRgbString(),
|
||||
transparent: "transparent",
|
||||
static: "white",
|
||||
error: "#E53935",
|
||||
errorBg: "#E5393520",
|
||||
success: "#4F8A10",
|
||||
warn: "#FF5722",
|
||||
favorite: "#ffd700",
|
||||
red: "#f44336",
|
||||
orange: "#FF9800",
|
||||
yellow: "#f0c800",
|
||||
green: "#4CAF50",
|
||||
blue: "#2196F3",
|
||||
purple: "#9568ED",
|
||||
gray: "#9E9E9E",
|
||||
};
|
||||
}
|
||||
}
|
||||
13
apps/vericrypt/src/theme/font/fontsize.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export class FontSizeFactory {
|
||||
static construct() {
|
||||
return {
|
||||
heading: "48px",
|
||||
subheading: "24px",
|
||||
title: "16px",
|
||||
subtitle: "14px",
|
||||
body: "14px",
|
||||
input: "14px",
|
||||
subBody: "12px",
|
||||
};
|
||||
}
|
||||
}
|
||||
19
apps/vericrypt/src/theme/font/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { FontSizeFactory } from "./fontsize";
|
||||
|
||||
export class FontFactory {
|
||||
static construct() {
|
||||
return {
|
||||
fontSizes: FontSizeFactory.construct(),
|
||||
fontWeights: {
|
||||
normal: 400,
|
||||
body: 400,
|
||||
heading: 600,
|
||||
bold: 600,
|
||||
},
|
||||
fonts: {
|
||||
body: `Open Sans,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Oxygen-Sans,Ubuntu,Cantarell,sans-serif;`,
|
||||
heading: `Open Sans,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Oxygen-Sans,Ubuntu,Cantarell,sans-serif;`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
27
apps/vericrypt/src/theme/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import ColorSchemeFactory from "./colorscheme";
|
||||
import VariantsFactory from "./variants";
|
||||
import { FontFactory } from "./font";
|
||||
import { Theme } from "theme-ui";
|
||||
|
||||
export class ThemeFactory {
|
||||
static construct(theme: "dark" | "light" = "light"): Theme {
|
||||
return {
|
||||
breakpoints: ["480px", "1000px", "1000px"],
|
||||
space: [0, 5, 10, 15, 20, 25, 30, 35],
|
||||
sizes: { full: "100%", half: "50%" },
|
||||
radii: { none: 0, default: 10 },
|
||||
// config: {
|
||||
// initialColorModeName: "light",
|
||||
// },
|
||||
// rawColors: {
|
||||
// modes: {
|
||||
// dark: ColorSchemeFactory.construct("dark"),
|
||||
// light: ColorSchemeFactory.construct("light"),
|
||||
// },
|
||||
// },
|
||||
rawColors: ColorSchemeFactory.construct(theme),
|
||||
...FontFactory.construct(),
|
||||
...new VariantsFactory(),
|
||||
};
|
||||
}
|
||||
}
|
||||
76
apps/vericrypt/src/theme/variants/button.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
class ButtonFactory {
|
||||
constructor() {
|
||||
return {
|
||||
default: new Default(),
|
||||
primary: new Primary(),
|
||||
secondary: new Secondary(),
|
||||
tertiary: new Tertiary(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default ButtonFactory;
|
||||
|
||||
class Default {
|
||||
constructor() {
|
||||
return {
|
||||
bg: "transparent",
|
||||
fontFamily: "body",
|
||||
fontWeight: "body",
|
||||
fontSize: "body",
|
||||
borderRadius: "5px",
|
||||
cursor: "pointer",
|
||||
p: 2,
|
||||
px: 4,
|
||||
transition: "filter 200ms ease-in, box-shadow 200ms ease-out",
|
||||
":hover:not(:disabled)": {
|
||||
filter: "brightness(90%)",
|
||||
},
|
||||
":active": {
|
||||
filter: "brightness(98%)",
|
||||
},
|
||||
outline: "none",
|
||||
":focus-visible:not(:active)": {
|
||||
boxShadow: "0px 0px 0px 2px var(--text)",
|
||||
},
|
||||
":disabled": {
|
||||
opacity: 0.5,
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Primary {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "buttons.default",
|
||||
color: "static",
|
||||
bg: "primary",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Secondary {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "buttons.default",
|
||||
color: "text",
|
||||
bg: "border",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Tertiary {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "buttons.default",
|
||||
color: "text",
|
||||
bg: "transparent",
|
||||
border: "2px solid",
|
||||
borderColor: "border",
|
||||
":hover": {
|
||||
borderColor: "primary",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
14
apps/vericrypt/src/theme/variants/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import ButtonFactory from "./button";
|
||||
import InputFactory from "./input";
|
||||
import TextFactory from "./text";
|
||||
|
||||
class VariantFactory {
|
||||
constructor() {
|
||||
return {
|
||||
buttons: new ButtonFactory(),
|
||||
forms: new InputFactory(),
|
||||
text: new TextFactory(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default VariantFactory;
|
||||
57
apps/vericrypt/src/theme/variants/input.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
class InputFactory {
|
||||
constructor() {
|
||||
return {
|
||||
input: new Default(),
|
||||
error: new Error(),
|
||||
clean: new Clean(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default InputFactory;
|
||||
|
||||
class Default {
|
||||
constructor() {
|
||||
return {
|
||||
borderRadius: 5,
|
||||
border: "none",
|
||||
// borderColor: "border",
|
||||
boxShadow: "0px 0px 0px 1px var(--theme-ui-colors-border) inset",
|
||||
fontFamily: "body",
|
||||
fontWeight: "body",
|
||||
fontSize: "input",
|
||||
color: "text",
|
||||
outline: "none",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 1.5px var(--theme-ui-colors-primary) inset",
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "0px 0px 0px 1px var(--theme-ui-colors-dimPrimary) inset",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Clean {
|
||||
constructor() {
|
||||
return {
|
||||
outline: "none",
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Error {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "forms.input",
|
||||
boxShadow: "0px 0px 0px 1px var(--error) inset",
|
||||
outline: "none",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 1.5px var(--error) inset",
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "0px 0px 0px 1px var(--error) inset",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
85
apps/vericrypt/src/theme/variants/text.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
class TextFactory {
|
||||
constructor() {
|
||||
return {
|
||||
default: new Default(),
|
||||
heading: new Heading(),
|
||||
subheading: new Subheading(),
|
||||
title: new Title(),
|
||||
subtitle: new Subtitle(),
|
||||
body: new Body(),
|
||||
subBody: new SubBody(),
|
||||
error: new Error(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default TextFactory;
|
||||
|
||||
class Default {
|
||||
constructor() {
|
||||
return {
|
||||
color: "text",
|
||||
fontFamily: "body",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Heading {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "text.default",
|
||||
fontFamily: "heading",
|
||||
fontWeight: "bold",
|
||||
fontSize: "heading",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Subheading {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "text.heading",
|
||||
fontSize: "subheading",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Title {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "text.heading",
|
||||
fontSize: "title",
|
||||
fontWeight: "bold",
|
||||
};
|
||||
}
|
||||
}
|
||||
class Subtitle {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "text.heading",
|
||||
fontSize: "subtitle",
|
||||
fontWeight: "bold",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Body {
|
||||
constructor() {
|
||||
return { variant: "text.default", fontSize: "body" };
|
||||
}
|
||||
}
|
||||
|
||||
class SubBody {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "text.default",
|
||||
fontSize: "subBody",
|
||||
color: "fontTertiary",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Error {
|
||||
constructor() {
|
||||
return { variant: "text.default", fontSize: "subBody", color: "error" };
|
||||
}
|
||||
}
|
||||
169
apps/vericrypt/src/utils/curlparser.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
const LITERAL_NAMES = {
|
||||
CURL: "curl",
|
||||
INCLUDE_HEADERS: "include-headers",
|
||||
SILENT: "run-silent",
|
||||
USE_GET: "use-get",
|
||||
COMPRESSED: "compressed",
|
||||
STATES: {
|
||||
HEADER: "header-state",
|
||||
CUSTOM_METHOD: "custom-method-state",
|
||||
DATA: "data-state",
|
||||
USER: "user-state",
|
||||
},
|
||||
STRING: "string",
|
||||
OTHERS: "others",
|
||||
};
|
||||
|
||||
const literals = [
|
||||
{
|
||||
regex: /^\bcurl\b/,
|
||||
name: LITERAL_NAMES.CURL,
|
||||
},
|
||||
{
|
||||
regex: /^(-H|--header)/,
|
||||
name: LITERAL_NAMES.STATES.HEADER,
|
||||
},
|
||||
{
|
||||
regex: /^-X/,
|
||||
name: LITERAL_NAMES.STATES.CUSTOM_METHOD,
|
||||
},
|
||||
{
|
||||
regex: /^(-u|--user)/,
|
||||
name: LITERAL_NAMES.STATES.USER,
|
||||
},
|
||||
{
|
||||
regex: /^(-d|--data-raw|--data-ascii|--data)/,
|
||||
name: LITERAL_NAMES.STATES.DATA,
|
||||
},
|
||||
{
|
||||
regex: /^--compressed/,
|
||||
name: LITERAL_NAMES.COMPRESSED,
|
||||
},
|
||||
{
|
||||
regex: /^(-i|--include)/,
|
||||
name: LITERAL_NAMES.INCLUDE_HEADERS,
|
||||
},
|
||||
{
|
||||
regex: /^(-s|--silent)/,
|
||||
name: LITERAL_NAMES.SILENT,
|
||||
},
|
||||
{
|
||||
regex: /^(-G|--get)/,
|
||||
name: LITERAL_NAMES.USE_GET,
|
||||
},
|
||||
{
|
||||
regex: /^'[^']*'/,
|
||||
name: LITERAL_NAMES.STRING,
|
||||
},
|
||||
{
|
||||
regex: /^"[^"]*"/,
|
||||
name: LITERAL_NAMES.STRING,
|
||||
},
|
||||
{
|
||||
regex: /^[^\n\r\s]*/,
|
||||
name: LITERAL_NAMES.STRING,
|
||||
},
|
||||
{
|
||||
regex: /^.*/,
|
||||
name: LITERAL_NAMES.OTHERS,
|
||||
},
|
||||
];
|
||||
|
||||
const trimValue = (val: string) =>
|
||||
val
|
||||
.trim()
|
||||
.replace(/^('|")\s*/, "")
|
||||
.replace(/\s*('|")$/, "");
|
||||
|
||||
const isURL = (val: string) =>
|
||||
/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w\-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/.test(
|
||||
val
|
||||
);
|
||||
|
||||
type Token = { name: string; value: RegExpMatchArray | null };
|
||||
type ParsedCurlRequest = {
|
||||
url: string;
|
||||
method: string;
|
||||
headers: Record<string, string>;
|
||||
data?: string;
|
||||
};
|
||||
|
||||
export function parseCURL(curlCmd: string) {
|
||||
let cmd = curlCmd.trim();
|
||||
const tokens: Token[] = [];
|
||||
const result: ParsedCurlRequest = {
|
||||
url: "",
|
||||
method: "GET",
|
||||
headers: {} as Record<string, string>,
|
||||
data: "",
|
||||
};
|
||||
while (cmd) {
|
||||
for (const literal of literals) {
|
||||
if (literal.regex.test(cmd)) {
|
||||
tokens.push({
|
||||
name: literal.name,
|
||||
value: cmd.match(literal.regex),
|
||||
});
|
||||
|
||||
cmd = cmd.replace(literal.regex, "").trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens.length) {
|
||||
if (tokens.shift()?.name !== "curl") {
|
||||
throw new Error("Not a curl command.");
|
||||
}
|
||||
|
||||
let state = "";
|
||||
tokens.forEach((token) => {
|
||||
if (!token.value) return;
|
||||
|
||||
if (!state) {
|
||||
switch (token.name) {
|
||||
case LITERAL_NAMES.STATES.HEADER:
|
||||
state = LITERAL_NAMES.STATES.HEADER;
|
||||
break;
|
||||
case LITERAL_NAMES.STATES.DATA:
|
||||
state = LITERAL_NAMES.STATES.DATA;
|
||||
break;
|
||||
case LITERAL_NAMES.STATES.CUSTOM_METHOD:
|
||||
state = LITERAL_NAMES.STATES.CUSTOM_METHOD;
|
||||
break;
|
||||
case LITERAL_NAMES.STATES.USER:
|
||||
state = LITERAL_NAMES.STATES.USER;
|
||||
break;
|
||||
case LITERAL_NAMES.STRING: {
|
||||
const val = trimValue(token.value[0]);
|
||||
if (isURL(val)) {
|
||||
result.url = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (state) {
|
||||
case LITERAL_NAMES.STATES.HEADER: {
|
||||
const pair = trimValue(token.value[0]).split(/\s*:\s*/);
|
||||
result.headers[pair[0]] = pair[1];
|
||||
break;
|
||||
}
|
||||
case LITERAL_NAMES.STATES.DATA: {
|
||||
console.log(token);
|
||||
const pair = token.value[0];
|
||||
result.data = trimValue(pair).trim();
|
||||
break;
|
||||
}
|
||||
case LITERAL_NAMES.STATES.CUSTOM_METHOD: {
|
||||
const pair = token.value[0];
|
||||
result.method = trimValue(pair).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
state = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
26
apps/vericrypt/src/utils/keycombos.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import Platform from "platform";
|
||||
|
||||
const isMac = Platform.os
|
||||
?.toString()
|
||||
.toLowerCase()
|
||||
.includes("mac");
|
||||
|
||||
const combos = {
|
||||
macos: {
|
||||
chromium: { developerTools: ["Cmd", "Opt", "J"] },
|
||||
firefox: { developerTools: ["Command", "Option", "K"] },
|
||||
},
|
||||
others: {
|
||||
chromium: { developerTools: ["Control", "Shift", "J"] },
|
||||
firefox: { developerTools: ["Control", "Shift", "K"] },
|
||||
},
|
||||
};
|
||||
|
||||
type KeyboardTypes = keyof typeof combos;
|
||||
type Browsers = keyof typeof combos[KeyboardTypes];
|
||||
type ComboIds = keyof typeof combos[KeyboardTypes][Browsers];
|
||||
|
||||
export function getCombo(browser: Browsers, id: ComboIds): string[] {
|
||||
const keyboardType: KeyboardTypes = isMac ? "macos" : "others";
|
||||
return combos[keyboardType][browser][id];
|
||||
}
|
||||
17
apps/vericrypt/src/utils/links.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
type Packages = "crypto" | "importer" | "enex";
|
||||
type Apps = "vericrypt" | "importer";
|
||||
|
||||
export function getSourceUrl(path: string) {
|
||||
const baseUrl = `https://github.com/streetwriters/notesnook/tree/main/apps/vericrypt`;
|
||||
return `${baseUrl}/${path}`;
|
||||
}
|
||||
|
||||
export function getPackageUrl(packageId: Packages) {
|
||||
const baseUrl = `https://github.com/streetwriters/notesnook/tree/main/packages`;
|
||||
return `${baseUrl}/${packageId}`;
|
||||
}
|
||||
|
||||
export function getAppUrl(appId: Apps) {
|
||||
const baseUrl = `https://github.com/streetwriters/notesnook/tree/main/apps`;
|
||||
return `${baseUrl}/${appId}`;
|
||||
}
|
||||
1
apps/vericrypt/src/utils/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const appVersion = process.env.REACT_APP_VERSION;
|
||||
21
apps/vericrypt/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -46,13 +46,14 @@ function createTrackerScript(document) {
|
||||
|
||||
(function() {
|
||||
const output = buildDocs();
|
||||
console.log(output)
|
||||
if (!output.includes("0 errors")) {
|
||||
console.error(output);
|
||||
return;
|
||||
}
|
||||
console.log("Docs generated!");
|
||||
|
||||
const outputPath = /Output: (.*$)/gm.exec(output)[1];
|
||||
const outputPath = /OUTPUT: (.*$)/gm.exec(output)[1];
|
||||
|
||||
console.log(`Documentation output path:`, outputPath);
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ If you face any problem while importing your data from other note apps, please r
|
||||
|
||||
Notesnook importer is your one stop location to import all your notes from any notes app. We have added support for most of the popular note apps and common export formats such as markdown, html and text files.
|
||||
|
||||
<img src="../static/notesnook_importer.png" alt="Notesnook importer"/>
|
||||
<img src="/static/notesnook_importer.png" alt="Notesnook importer"/>
|
||||
|
||||
## Try it out
|
||||
|
||||
You can try out the importer by going to [https://importer.notesnook.com](https://importer.notesnook.com) to import your notes from any notes app.
|
||||
|
||||
## Supported note apps and formats
|
||||
@@ -32,5 +33,5 @@ You can try out the importer by going to [https://importer.notesnook.com](https:
|
||||
**Don't see your notes app?** No worries, create an issue on [github](https://github.com/streetwriters/notesnook/issues)
|
||||
|
||||
## Is it safe to import?
|
||||
Not a single byte of your data from other apps is sent to our servers. Everything is processed 100% on the client side inside this browser.
|
||||
|
||||
Not a single byte of your data from other apps is sent to our servers. Everything is processed 100% on the client side inside this browser.
|
||||
|
||||
@@ -10,24 +10,20 @@ The following steps will help you import your notes from Bear notes easily.
|
||||
1. Open Bear Notes app on Desktop.
|
||||
|
||||
2. Click on `File` and select `Backup Notes`
|
||||
<img style="margin-bottom:15px;" src="../static/bear_import_step_1.png" alt="Click on `File` and select `Backup Notes`"/>
|
||||
<img style="margin-bottom:15px;" src="/static/bear_import_step_1.png" alt="Click on `File` and select `Backup Notes`"/>
|
||||
|
||||
2. Select the location where you want to store the backup file. It will be a `.bearbk` file.
|
||||
<img style="margin-bottom:15px;" src="../static/bear_import_step_2.png" alt="Select the location where you want to store the backup file. It will be a `.bearbk` file."/>
|
||||
3. Select the location where you want to store the backup file. It will be a `.bearbk` file.
|
||||
<img style="margin-bottom:15px;" src="/static/bear_import_step_2.png" alt="Select the location where you want to store the backup file. It will be a `.bearbk` file."/>
|
||||
|
||||
4. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Bear Notes` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/bearnotes_import_step_3.png" alt="Go to https://importer.notesnook.com and select `Bear Notes` from list of apps."/>
|
||||
<img style="margin-bottom:15px;" src="/static/bearnotes_import_step_3.png" alt="Go to https://importer.notesnook.com and select `Bear Notes` from list of apps."/>
|
||||
|
||||
5. Drop the `.bearbk` backup file you exported earlier from Bear Notes in the box or click anywhere to open system file picker to select the file
|
||||
<img style="margin-bottom:15px;" src="../static/bear_import_step_4.png" alt="Drop the `.bearbk` backup file you exported earlier from Bear Notes in the box or click anywhere to open system file picker to select the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/bear_import_step_4.png" alt="Drop the `.bearbk` backup file you exported earlier from Bear Notes in the box or click anywhere to open system file picker to select the file."/>
|
||||
|
||||
6. Click on `Import 1 file` button to start importing.
|
||||
|
||||
7. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,26 +10,22 @@ The following steps will help you import your notes from EverNote easily.
|
||||
1. Open EverNote app on Desktop.
|
||||
|
||||
2. Go to `Notebooks` from the side menu
|
||||
<img style="margin-bottom:15px;" src="../static/evernote_import_step_1.png" alt="Go to `Notebooks` from the side menu"/>
|
||||
<img style="margin-bottom:15px;" src="/static/evernote_import_step_1.png" alt="Go to `Notebooks` from the side menu"/>
|
||||
|
||||
2. Click on the `three-dot` button on each notebook and click on `Export Notebook`
|
||||
<img style="margin-bottom:15px;" src="../static/evernote_import_step_2.png" alt="Click on the `three-dot` button on each notebook and click on `Export Notebook`"/>
|
||||
3. Click on the `three-dot` button on each notebook and click on `Export Notebook`
|
||||
<img style="margin-bottom:15px;" src="/static/evernote_import_step_2.png" alt="Click on the `three-dot` button on each notebook and click on `Export Notebook`"/>
|
||||
|
||||
4. Choose `.enex` format and click on `Export` and save it to desired location.
|
||||
<img style="margin-bottom:15px;" src="../static/evernote_import_step_3.png" alt="Choose `.enex` format and click on `Export`"/>
|
||||
<img style="margin-bottom:15px;" src="/static/evernote_import_step_3.png" alt="Choose `.enex` format and click on `Export`"/>
|
||||
|
||||
5. Repeat this for all your Notebooks.
|
||||
|
||||
6. Drop the `.enex` backup files you exported earlier from EverNote in the box or click anywhere to open system file picker to select the file
|
||||
<img style="margin-bottom:15px;" src="../static/evernote_import_step_4.png" alt="Drop the `.enex` backup files you exported earlier from EverNote in the box or click anywhere to open system file picker to select the file"/>
|
||||
<img style="margin-bottom:15px;" src="/static/evernote_import_step_4.png" alt="Drop the `.enex` backup files you exported earlier from EverNote in the box or click anywhere to open system file picker to select the file"/>
|
||||
|
||||
6. Click on `Import N files` button to start importing.
|
||||
7. Click on `Import N files` button to start importing.
|
||||
|
||||
7. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
8. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,24 +10,23 @@ The following steps will help you import your notes from Google Keep easily.
|
||||
1. Go to [Google Takeout](https://takeout.google.com/settings/takeout) and Login to your account.
|
||||
|
||||
2. On Google Takeout Page, first click on `Deselect all` then scroll down and select `Keep` and click `Next Step`.
|
||||
<img style="margin-bottom:15px;" src="../static/googlekeep_import_step_1.png" alt="On Google Takeout Page, first click on `Deselect all` then scroll down and select `Keep` and click `Next Step`."/>
|
||||
<img style="margin-bottom:15px;" src="/static/googlekeep_import_step_1.png" alt="On Google Takeout Page, first click on `Deselect all` then scroll down and select `Keep` and click `Next Step`."/>
|
||||
|
||||
3. Click on create export
|
||||
<img style="margin-bottom:15px;" src="../static/googlekeep_import_step_2.png" alt="Click on create export"/>
|
||||
<img style="margin-bottom:15px;" src="/static/googlekeep_import_step_2.png" alt="Click on create export"/>
|
||||
|
||||
4. Download your exported file.
|
||||
<img style="margin-bottom:15px;" src="../static/googlekeep_import_step_3.png" alt="Download your exported file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/googlekeep_import_step_3.png" alt="Download your exported file."/>
|
||||
|
||||
5. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Google Keep` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/googlekeep_import_step_4.png" alt="Go to [Notesnook Importer](https://importer.notesnook.com) and select `Google Keep` from list of apps."/>
|
||||
<img style="margin-bottom:15px;" src="/static/googlekeep_import_step_4.png" alt="Go to [Notesnook Importer](https://importer.notesnook.com) and select `Google Keep` from list of apps."/>
|
||||
|
||||
6. Drop the .zip backup file(s) you exported earlier from Google Takeout in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="../static/googlekeep_import_step_5.png" alt="Drop the .zip backup file(s) you exported earlier from Google Takeout in the box or click anywhere to open system file picker to select the backup."/>
|
||||
<img style="margin-bottom:15px;" src="/static/googlekeep_import_step_5.png" alt="Drop the .zip backup file(s) you exported earlier from Google Takeout in the box or click anywhere to open system file picker to select the backup."/>
|
||||
|
||||
7. Click on `Import N files` button to start importing.
|
||||
|
||||
8. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
label: Html files
|
||||
order: 800
|
||||
---
|
||||
|
||||
# How do I import notes from html files?
|
||||
|
||||
To import html files to notesnook go to Notesnook Importer on your PC/Laptop.
|
||||
|
||||
1. From the list of formats to import, select "Html file".
|
||||
<img style="margin-bottom:15px;" src="../static/html_import_step_1.png" alt="From the list of formats to import, select Html file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/html_import_step_1.png" alt="From the list of formats to import, select Html file."/>
|
||||
|
||||
2. Drop your text files or click anywhere inside the box to browse and select your html files.
|
||||
<img style="margin-bottom:15px;" src="../static/html_import_step_2.png" alt="Drop your text files or click anywhere inside the box to browse and select your html files."/>
|
||||
<img style="margin-bottom:15px;" src="/static/html_import_step_2.png" alt="Drop your text files or click anywhere inside the box to browse and select your html files."/>
|
||||
|
||||
3. Click on import files button on bottom right corner to start importing files
|
||||
4. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
@@ -2,27 +2,24 @@
|
||||
label: Joplin
|
||||
order: -100
|
||||
---
|
||||
|
||||
# How do I import notes from Joplin notes app?
|
||||
|
||||
The following steps will help you import your notes from Joplin easily.
|
||||
|
||||
1. Open Joplin app on Desktop.
|
||||
2. Click on `File` and select `Export All -> JEX - Joplin Export File` and save it in desired location.
|
||||
<img style="margin-bottom:15px;" src="../static/joplin_import_step_1.png" alt="Click on `File` and select `Export All -> JEX - Joplin Export File`"/>
|
||||
<img style="margin-bottom:15px;" src="/static/joplin_import_step_1.png" alt="Click on `File` and select `Export All -> JEX - Joplin Export File`"/>
|
||||
|
||||
4) Go to [Notesnook Importer](https://importer.notesnook.com) and select `Joplin` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="/static/joplin_import_step_2.png" alt=" Go to Notesnook Importer https://importer.notesnook.com and select `Joplin` from list of apps."/>
|
||||
|
||||
4. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Joplin` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/joplin_import_step_2.png" alt=" Go to Notesnook Importer https://importer.notesnook.com and select `Joplin` from list of apps."/>
|
||||
5) Drop the .jex backup file you exported earlier from Joplin in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="/static/joplin_import_step_3.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
|
||||
5. Drop the .jex backup file you exported earlier from Joplin in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="../static/joplin_import_step_3.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
6) Click on `Import 1 file` button to start importing.
|
||||
|
||||
6. Click on `Import 1 file` button to start importing.
|
||||
|
||||
7. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
7) Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
label: Markdown files
|
||||
order: 700
|
||||
---
|
||||
|
||||
# How do I import notes from markdown files?
|
||||
|
||||
To import markdown files to notesnook go to Notesnook Importer on your PC/Laptop.
|
||||
|
||||
1. From the list of formats to import, select "Markdown file".
|
||||
<img style="margin-bottom:15px;" src="../static/markdown_import_step_1.png" alt="From the list of formats to import, select Markdown file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/markdown_import_step_1.png" alt="From the list of formats to import, select Markdown file."/>
|
||||
|
||||
2. Drop your markdown files or click anywhere inside the box to browse and select your markdown files.
|
||||
<img style="margin-bottom:15px;" src="../static/markdown_import_step_2.png" alt="Drop your markdown files or click anywhere inside the box to browse and select your markdown files."/>
|
||||
<img style="margin-bottom:15px;" src="/static/markdown_import_step_2.png" alt="Drop your markdown files or click anywhere inside the box to browse and select your markdown files."/>
|
||||
|
||||
3. Click on import files button on bottom right corner to start importing files
|
||||
4. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
@@ -8,22 +8,18 @@ order: 600
|
||||
The following steps will help you import your notes from OneNote easily.
|
||||
|
||||
1. Go to [Notesnook Importer](https://importer.notesnook.com) and select `OneNote` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/onenote_import_step_1.png" alt="Go to Notesnook Importer]https://importer.notesnook.com and select `OneNote` from list of apps."/>
|
||||
<img style="margin-bottom:15px;" src="/static/onenote_import_step_1.png" alt="Go to Notesnook Importer]https://importer.notesnook.com and select `OneNote` from list of apps."/>
|
||||
|
||||
2. Click on `Sign in with Microsoft`
|
||||
<img style="margin-bottom:15px;" src="../static/onenote_import_step_2.png" alt="Click on `Sign in with Microsoft`"/>
|
||||
<img style="margin-bottom:15px;" src="/static/onenote_import_step_2.png" alt="Click on `Sign in with Microsoft`"/>
|
||||
|
||||
3. Login to your account
|
||||
<img style="margin-bottom:15px;" src="../static/onenote_import_step_3.png" alt="Login to your account"/>
|
||||
<img style="margin-bottom:15px;" src="/static/onenote_import_step_3.png" alt="Login to your account"/>
|
||||
|
||||
4. Please wait while all your notes are imported.
|
||||
<img style="margin-bottom:15px;" src="../static/onenote_import_step_4.png" alt="Please wait while all your notes are imported."/>
|
||||
<img style="margin-bottom:15px;" src="/static/onenote_import_step_4.png" alt="Please wait while all your notes are imported."/>
|
||||
|
||||
4. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
5. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
label: Plain text files
|
||||
order: 900
|
||||
---
|
||||
|
||||
# How do I import notes from plain text files?
|
||||
|
||||
To import plain text files to notesnook go to Notesnook Importer on your PC/Laptop.
|
||||
|
||||
1. From the list of formats to import, select "Plain Text".
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_1.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_1.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
|
||||
2. Drop your text files or click anywhere inside the box to browse and select your text files.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_2.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_2.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
|
||||
3. Click on import files button on bottom right corner to start importing files
|
||||
4. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="From the list of formats to import, select Plain Text."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
@@ -2,28 +2,29 @@
|
||||
label: Simplenote
|
||||
order: 100
|
||||
---
|
||||
|
||||
# How to import notes from Simplenote notes app?
|
||||
|
||||
The following steps will help you import your notes from Simplenote easily.
|
||||
|
||||
1. Open Simplenote app on Desktop or Login to [https://app.simplenote.com](https://app.simplenote.com).
|
||||
2. Open sidebar and click on Settings.
|
||||
<img style="margin-bottom:15px;" src="../static/simplenote_import_step_1.png" alt="Open sidebar and click on Settings."/>
|
||||
<img style="margin-bottom:15px;" src="/static/simplenote_import_step_1.png" alt="Open sidebar and click on Settings."/>
|
||||
|
||||
3. Go to `Tools` tab in Settings
|
||||
<img style="margin-bottom:15px;" src="../static/simplenote_import_step_2.png" alt="Go to Tools tab"/>
|
||||
<img style="margin-bottom:15px;" src="/static/simplenote_import_step_2.png" alt="Go to Tools tab"/>
|
||||
|
||||
4. Click on `Export notes` to download your notes as a .zip file.
|
||||
|
||||
5. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Simplenote` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/simplenote_import_step_3.png" alt="Go to [Notesnook Importer](https://importer.notesnook.com) and select `Simplenote` from list of apps."/>
|
||||
<img style="margin-bottom:15px;" src="/static/simplenote_import_step_3.png" alt="Go to [Notesnook Importer](https://importer.notesnook.com) and select `Simplenote` from list of apps."/>
|
||||
|
||||
6. Drop the .zip backup file you exported earlier from Simplenote in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="../static/simplenote_import_step_4.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
<img style="margin-bottom:15px;" src="/static/simplenote_import_step_4.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
|
||||
7. Click on `Import 1 file` button to start importing.
|
||||
|
||||
8. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
@@ -2,31 +2,29 @@
|
||||
label: Standard Notes
|
||||
order: 300
|
||||
---
|
||||
|
||||
# How do I import notes from Standard Notes?
|
||||
|
||||
The following steps will help you import your notes from Standard notes easily.
|
||||
|
||||
1. Open Standard Notes app on Desktop or visit [https://app.standardnotes.org](https://app.standardnotes.org) and Login to your account.
|
||||
2. Click on `Account` on bottom left corner
|
||||
<img style="margin-bottom:15px;" src="../static/snnotes_import_step_1.png" alt="Click on `Account` on bottom left corner"/>
|
||||
<img style="margin-bottom:15px;" src="/static/snnotes_import_step_1.png" alt="Click on `Account` on bottom left corner"/>
|
||||
|
||||
2. Scroll down to `Data backups` section and choose backup type `Decrypted`.
|
||||
<img style="margin-bottom:15px;" src="../static/snnotes_import_step_2.png" alt="Scroll down to `Data backups` section and choose backup type Decrypted."/>
|
||||
3. Scroll down to `Data backups` section and choose backup type `Decrypted`.
|
||||
<img style="margin-bottom:15px;" src="/static/snnotes_import_step_2.png" alt="Scroll down to `Data backups` section and choose backup type Decrypted."/>
|
||||
|
||||
3. Click on `Download backup` to download your backup file. It will be .zip file.
|
||||
4. Click on `Download backup` to download your backup file. It will be .zip file.
|
||||
|
||||
4. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Standard Notes` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="../static/snnotes_import_step_3.png" alt="Go to https://importer.notesnook.com and select Standard Notes from list of apps."/>
|
||||
5. Go to [Notesnook Importer](https://importer.notesnook.com) and select `Standard Notes` from list of apps.
|
||||
<img style="margin-bottom:15px;" src="/static/snnotes_import_step_3.png" alt="Go to https://importer.notesnook.com and select Standard Notes from list of apps."/>
|
||||
|
||||
5. Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="../static/snnotes_import_step_4.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
6. Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup.
|
||||
<img style="margin-bottom:15px;" src="/static/snnotes_import_step_4.png" alt="Drop the .zip backup file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup."/>
|
||||
|
||||
6. Click on `Import 1 file` button to start importing.
|
||||
7. Click on `Import 1 file` button to start importing.
|
||||
|
||||
7. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="../static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
8. Click on `Download .nnbackup` to download the file.
|
||||
<img style="margin-bottom:15px;" src="/static/plain_text_import_step_3.png" alt="Click on `Download .nnbackup` to download the file."/>
|
||||
|
||||
After you have downloaded the `.nnbackup` file, go to Notesnook app and [restore backup](../backup-and-restore-notes-in-notesnook.md) file to get all your notes.
|
||||
|
||||
|
||||
|
||||
|
||||