Compare commits
39 Commits
1.8.3-andr
...
2.1.4-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
725fe41db2 | ||
|
|
6f6fc52c47 | ||
|
|
2e74f807d4 | ||
|
|
eb02332f37 | ||
|
|
829c27d456 | ||
|
|
b08abcf943 | ||
|
|
cddd6f3f5b | ||
|
|
8f90bbef59 | ||
|
|
dc621a2483 | ||
|
|
d5f9fb10d4 | ||
|
|
85efebbaca | ||
|
|
2fcd9d26e6 | ||
|
|
61bd7552ae | ||
|
|
8128f827a3 | ||
|
|
1b3b760b53 | ||
|
|
ffd7501e80 | ||
|
|
9f5d61250b | ||
|
|
a71b5a96f2 | ||
|
|
81bb053513 | ||
|
|
63796d5896 | ||
|
|
19a1ea2236 | ||
|
|
c72ce1ee69 | ||
|
|
ba88f28ae7 | ||
|
|
80186fd1cd | ||
|
|
ec07cab365 | ||
|
|
ed8271cf9d | ||
|
|
da7723e7e0 | ||
|
|
43e817e259 | ||
|
|
eb50ebeb81 | ||
|
|
16b702294a | ||
|
|
9bd68e8c5a | ||
|
|
f1b2deb893 | ||
|
|
64eae3044d | ||
|
|
f1be7a57a1 | ||
|
|
80dd83552a | ||
|
|
b4b7f9c519 | ||
|
|
5d0e556e1b | ||
|
|
ecd1640c1d | ||
|
|
085b7c4eb1 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
56
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Bug Report
|
||||
description: Are you facing a bug or a crash in Notesnook?
|
||||
labels: ["Type: Bug", "Status: Pending"]
|
||||
assignees:
|
||||
- thecodrr
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Tell us what you were trying to do & what happened
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: If we can reproduce it, we can fix it
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Notesnook are you using?
|
||||
placeholder: v1.8.10 etc
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform/OS
|
||||
description: What platform are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
- iOS
|
||||
- Android
|
||||
- Chrome/Chromium
|
||||
- Firefox
|
||||
- Safari
|
||||
- Other browser
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Notesnook Discord Community
|
||||
url: https://discord.gg/6mHHyncHJE
|
||||
about: Reach out to us directly & discuss your issues, suggestions & other feedback!
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature you are missing in Notesnook
|
||||
labels: ["Type: Feature Request", "Status: Pending"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to suggest this idea!
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem are you facing?
|
||||
description: Also tell us how this idea will fix the problem & what's the use case.
|
||||
placeholder: e.g. I'm always frustrated when...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What's the solution you'd like to see in Notesnook?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: What alternatives have you considered?
|
||||
- type: textarea
|
||||
id: general
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
3
.github/workflows/enex-tests.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd packages/enex
|
||||
npm i
|
||||
npx turbo run build
|
||||
cd packages/enex
|
||||
npm run test
|
||||
|
||||
3
.github/workflows/importer-tests.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd packages/importer
|
||||
npm i
|
||||
npx turbo run build
|
||||
cd packages/importer
|
||||
npm run test
|
||||
|
||||
40
.github/workflows/vericrypt-deploy.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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
|
||||
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 }}
|
||||
60
README.md
@@ -1,50 +1,15 @@
|
||||
<p align="center">
|
||||
|
||||
<img style="align:center;" src="https://app.notesnook.com/apple-touch-icon.png" alt="Notesnook Logo" width="100" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Notesnook</h1>
|
||||
<h2 align="center">Take private notes, capture ideas, make lists & sync them anywhere</h2>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/5davZnhw3V"><img alt="Chat with us on Discord" src="https://img.shields.io/discord/796015620436787241?label=Chat%20with%20us&logo=discord&logoColor=white"/></a>
|
||||
<a href="https://GitHub.com/streetwriters/notesnook/issues/"><img src="https://img.shields.io/github/issues/streetwriters/notesnook.svg"/></a>
|
||||
<a href="https://twitter.com/notesnook"><img alt="Follow us on Twitter @notesnook" src="https://img.shields.io/twitter/follow/notesnook?label=Follow%20us%20%40notesnook&logo=twitter&style=social"></a>
|
||||
<a href="https://notesnook.com/">Website</a> | <a href="https://notesnook.com/roadmap">Roadmap</a> | <a href="https://notesnook.com/downloads">Downloads</a> | <a href="https://twitter.com/@notesnook">Twitter</a> | <a href="https://discord.gg/5davZnhw3V">Discord</a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="https://github.com/streetwriters/notesnook/raw/main/banners/crossplatform.jpg" alt="Notesnook - Take private notes" width="75%"/></p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.streetwriters.notesnook">
|
||||
<img alt="Get it on Google Play" src="https://github.com/streetwriters/notesnook/raw/main/badges/google.png" height="50px">
|
||||
</a>
|
||||
<a href="https://apps.apple.com/pk/app/notesnook-take-private-notes/id1544027013">
|
||||
<img alt="Download on the App Store" src="https://notesnook.com/static//apple.09f3b62d.png" height="50px">
|
||||
</a>
|
||||
<a href="https://app.notesnook.com/">
|
||||
<img alt="Try Notesnook on the Web" src="https://github.com/streetwriters/notesnook/raw/main/badges/web.png" height="50px" style="margin-left: 15px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 😎 Features
|
||||
|
||||
Privacy isn't the only thing we have:
|
||||
|
||||
1. Cross platform - Notesnook has apps on all platforms
|
||||
2. End to end encrypted sync to unlimted devices
|
||||
3. Organize your notes using:
|
||||
1. Notebooks
|
||||
2. Topics
|
||||
3. Tags
|
||||
4. Colors
|
||||
4. Encrypt/lock your notes locally
|
||||
5. Full rich text editor with markdown support
|
||||
6. Focus mode (distraction-free mode)
|
||||
7. Export notes as HTML, MD, PDF & TXT
|
||||
8. Automatic backups
|
||||
9. Dark mode and custom accents
|
||||
|
||||
**_Note: You can checkout a preview of all these features on [the website](https://notesnook.com/)._**
|
||||
> ## Announcement:
|
||||
>
|
||||
> ### We will be open sourcing Notesnook in July-August, 2022. Follow our [Twitter](https://twitter.com/@notesnook) for updates.
|
||||
|
||||
## Support
|
||||
|
||||
@@ -59,7 +24,7 @@ We take all queries, issues, bug reports that you might have. Feel free to ask.
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
This is just the beginning; we are not stopping here. **Read what we have planned for Notesnook in [our roadmap](https://github.com/streetwriters/notesnook/blob/main/ROADMAP.md).**
|
||||
This is just the beginning; we are not stopping here. **Read what we have planned for Notesnook in [our roadmap](https://notesnook.com/roadmap).**
|
||||
|
||||
## ❓ FAQs
|
||||
|
||||
@@ -78,13 +43,11 @@ At Notesnook we believe in `simple privacy`. Simple privacy means giving you ful
|
||||
|
||||
### If you are so privacy focused, why did you not make Notesnook open source?
|
||||
|
||||
Notesnook is developed & maintained by a small team (only 3 people). The effort required to make it "open source-able" (writing good docs, managing pull requests etc) is currently out of our scope. We want to make a good product and ship it to you as fast as possible.
|
||||
|
||||
However, we have plans to open source the core parts of Notesnook (like the auth, encryption, storage etc.) that'll be reusable and auditable by the public.
|
||||
Notesnook is going open source in July-August 2022 (subject to change).
|
||||
|
||||
### Can I self host Notesnook?
|
||||
|
||||
Nope. Not right now. We might make the sync server available for self-hosting in the future (after certain procedures) but as of now, no.
|
||||
Nope. Not right now. We have plans to make the sync server available for self-hosting in the future (after certain procedures) after the client apps are open sourced.
|
||||
|
||||
### What happens to my notes if Notesnook is abandoned?
|
||||
|
||||
@@ -116,7 +79,7 @@ Anyone offering privacy for free is scamming you to sell your data. Why? Because
|
||||
`$4.49/mo` is worth it because:
|
||||
|
||||
1. It is cheaper than all other premium note taking apps (like Evernote, Standard Notes).
|
||||
2. It is the minimum viable price you can spend to ensure your notes' privacy. (If you self host, the cheapest VPS will cost you around $5).
|
||||
2. It is the minimum viable price you can spend to ensure your notes' privacy. (If you self host, the cheapest VPS will cost you around \$5).
|
||||
3. Your subscription is evidence that privacy is a global issue and should be addressed. That you care.
|
||||
|
||||
If you still think $4.49/mo is too much, you are welcome to spend >$4.49/mo on some other privacy invasive note taking app. :)
|
||||
@@ -145,8 +108,3 @@ Social proof helps other people know what they are getting. If you have tried ou
|
||||
2. Posting on Facebook, Reddit or any other social media.
|
||||
|
||||
We take all social proofs and publish them on the website for the public to see.
|
||||
|
||||
---
|
||||
|
||||
<sup>1</sup>Google Play and the Google Play logo are trademarks of Google LLC.<br/>
|
||||
<sup>2</sup>Apple logo® is a trademark of Apple Inc.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
1. 🥳 Support for Standard Notes is here! 🥳
|
||||
2. Fixed issue with Evernote provider where images got cut off.
|
||||
3. Fixes issue where some attachments were lost during Evernote .enex import.
|
||||
1. 🥳 Added Joplin support! 🥳
|
||||
2. Optimize for mobile screens
|
||||
3. Add very basic telemetry (only sends the provider used)
|
||||
4. Fixed timestamp issues with Standard Notes provider
|
||||
5. Fixed issue where importer would always open in dark mode
|
||||
26864
apps/importer/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/importer-web",
|
||||
"version": "1.1.2",
|
||||
"version": "1.3.3",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
@@ -19,7 +19,7 @@
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@notesnook/importer": "^1.1.3",
|
||||
"@notesnook/importer": "^1.3.3",
|
||||
"colord": "^2.9.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"hash-wasm": "^4.9.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"associatedApplications": [
|
||||
{
|
||||
"applicationId": "4952c7cf-9c02-4fb7-b867-b87727bb52d8"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,20 +2,32 @@ import { Flex } from "@theme-ui/components";
|
||||
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 { useEffect, useState } from "react";
|
||||
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";
|
||||
import { trackEvent } from "./utils/analytics";
|
||||
|
||||
function App() {
|
||||
const [selectedProvider, setSelectedProvider] = useState<
|
||||
Providers | undefined
|
||||
>();
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [selectedProvider, setSelectedProvider] = useState<IProvider>();
|
||||
const [providerResult, setProviderResult] = useState<ProviderResult>();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProvider && providerResult) {
|
||||
(async () => {
|
||||
await trackEvent(
|
||||
{ name: selectedProvider.name, type: "event" },
|
||||
selectedProvider.name
|
||||
);
|
||||
})();
|
||||
}
|
||||
}, [providerResult, selectedProvider]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={ThemeFactory.construct()}>
|
||||
@@ -27,23 +39,35 @@ function App() {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ProviderSelector onProviderChanged={setSelectedProvider} />
|
||||
<ProviderSelector
|
||||
onProviderChanged={(provider) => {
|
||||
setSelectedProvider(provider);
|
||||
setProviderResult(undefined);
|
||||
}}
|
||||
/>
|
||||
|
||||
{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,45 @@ 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 (!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);
|
||||
})();
|
||||
|
||||
// Disabled because we do not want to cause an update when provider changes
|
||||
// which only happens when the user selects a different provider.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [files, onTransformFinished]);
|
||||
|
||||
return (
|
||||
<StepContainer sx={{ flexDirection: "column", alignItems: "stretch" }}>
|
||||
@@ -84,6 +121,7 @@ export function FileSelector(props: FileSelectorProps) {
|
||||
>
|
||||
{files.map((file, index) => (
|
||||
<Flex
|
||||
key={file.name}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
p: 2,
|
||||
@@ -108,6 +146,11 @@ export function FileSelector(props: FileSelectorProps) {
|
||||
</Flex>
|
||||
</Accordion>
|
||||
) : null}
|
||||
{progress.total > 0 ? (
|
||||
<Text variant="body">
|
||||
Processing files ({progress.done}/{progress.total})
|
||||
</Text>
|
||||
) : null}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,9 @@ export function Footer() {
|
||||
}}
|
||||
>
|
||||
<Text variant="body">Notesnook Importer (v{appVersion})</Text>
|
||||
<Text variant="subBody">© 2021 Streetwriters LLC</Text>
|
||||
<Text variant="subBody">
|
||||
© {new Date().getFullYear()} Streetwriters LLC
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Flex, Link, Text } from "@theme-ui/components";
|
||||
import { Checkbox, Flex, Label, Link, Text } from "@theme-ui/components";
|
||||
import { useTelemetry } from "../utils/analytics";
|
||||
import { appVersion } from "../utils/version";
|
||||
|
||||
export function Hero() {
|
||||
const { isEnabled, setIsEnabled } = useTelemetry();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
mt: 150,
|
||||
mb: 100,
|
||||
mt: [50, 150],
|
||||
mb: [50, 100],
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
@@ -20,34 +23,52 @@ export function Hero() {
|
||||
fontSize: "title",
|
||||
textAlign: "center",
|
||||
color: "fontTertiary",
|
||||
mt: [2, 0],
|
||||
}}
|
||||
>
|
||||
Import your notes from any notes app into Notesnook.
|
||||
</Text>
|
||||
<Flex sx={{ mt: 2 }}>
|
||||
<Text variant="body" sx={{ px: 1 }}>
|
||||
<Flex sx={{ mt: 2, alignItems: "center" }}>
|
||||
<Text variant="body" sx={{ px: 2 }}>
|
||||
v{appVersion}
|
||||
</Text>
|
||||
<Link
|
||||
href="https://github.com/streetwriters/notesnook"
|
||||
variant="text.body"
|
||||
sx={{ px: 1, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
sx={{ px: 2, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
<Link
|
||||
href="https://app.notesnook.com/"
|
||||
variant="text.body"
|
||||
sx={{ px: 1, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
sx={{ px: 2, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
>
|
||||
Notesnook Web
|
||||
</Link>
|
||||
<Label
|
||||
variant="text.body"
|
||||
sx={{
|
||||
width: "auto",
|
||||
alignItems: "center",
|
||||
px: 2,
|
||||
borderLeft: "1px solid var(--theme-ui-colors-border)",
|
||||
}}
|
||||
title={`Currently the only thing sent is which provider you used after a successful import. This data is completely anonymous, of course.`}
|
||||
>
|
||||
<Checkbox
|
||||
sx={{ mr: 1 }}
|
||||
checked={isEnabled}
|
||||
onChange={() => setIsEnabled((s) => !s)}
|
||||
/>
|
||||
Telemetry {isEnabled ? "enabled" : "disabled"}
|
||||
</Label>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
flexDirection: "column",
|
||||
width: 400,
|
||||
width: ["90%", 400],
|
||||
mt: 4,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { Accordion } from "./Accordion";
|
||||
import { TransformError } from "@notesnook/importer/dist/src/utils/transformerror";
|
||||
|
||||
type ImportErrorsProps = {
|
||||
errors: TransformError[];
|
||||
errors: Error[];
|
||||
};
|
||||
|
||||
export function ImportErrors(props: ImportErrorsProps) {
|
||||
@@ -13,15 +12,16 @@ export function ImportErrors(props: ImportErrorsProps) {
|
||||
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) => (
|
||||
<>
|
||||
{index + 1}. {error.message} ({error.file.name})
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</Text>
|
||||
<Flex sx={{ flexDirection: "column", px: 2, pb: 2, overflowX: "auto" }}>
|
||||
{props.errors.map((error, index) => (
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{ color: "error", my: 1, fontFamily: "monospace" }}
|
||||
>
|
||||
{index + 1}. {error.message}
|
||||
<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,20 +1,24 @@
|
||||
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) {
|
||||
return (
|
||||
<StepContainer sx={{ flexDirection: "column" }}>
|
||||
<Flex sx={{ justifyContent: "space-between" }}>
|
||||
<Text variant="title">Choose a provider to import from</Text>
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: ["stretch", "space-between"],
|
||||
flexDirection: ["column", "row"],
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Select a notes app to import from</Text>
|
||||
<Select
|
||||
sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
px: 2,
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
@@ -27,22 +31,26 @@ export function ProviderSelector(props: ProviderSelectorProps) {
|
||||
},
|
||||
fontFamily: "body",
|
||||
fontSize: "body",
|
||||
width: 150,
|
||||
width: ["100%", 150],
|
||||
mt: [2, 0],
|
||||
p: [2, 0],
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
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>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Text variant="body" sx={{ color: "fontTertiary" }}>
|
||||
<Text variant="body" sx={{ color: "fontTertiary", mt: [2, 0] }}>
|
||||
Can't find your notes app in the list?{" "}
|
||||
<a href="https://github.com/streetwriters/notesnook/issues/new">
|
||||
Send us a request.
|
||||
|
||||
@@ -8,7 +8,7 @@ export function StepContainer({
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: "40%",
|
||||
width: ["90%", "40%"],
|
||||
boxShadow: "0px 0px 20px 0px #00000011",
|
||||
p: 4,
|
||||
borderRadius: "default",
|
||||
|
||||
@@ -10,12 +10,7 @@ export class ThemeFactory {
|
||||
space: [0, 5, 10, 15, 20, 25, 30, 35],
|
||||
sizes: { full: "100%", half: "50%" },
|
||||
radii: { none: 0, default: 10 },
|
||||
colors: {
|
||||
modes: {
|
||||
dark: ColorSchemeFactory.construct("dark"),
|
||||
light: ColorSchemeFactory.construct("light"),
|
||||
},
|
||||
},
|
||||
colors: ColorSchemeFactory.construct("light"),
|
||||
rawColors: ColorSchemeFactory.construct("light"),
|
||||
...FontFactory.construct(),
|
||||
...new VariantsFactory(),
|
||||
|
||||
75
apps/importer/src/utils/analytics.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Config from "./config";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
umami?: {
|
||||
trackEvent: (
|
||||
value: string,
|
||||
type: string,
|
||||
url?: string,
|
||||
websiteId?: string
|
||||
) => void;
|
||||
trackView: (url: string, referrer?: string, websiteId?: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function loadTrackerScript() {
|
||||
if (Config.get("telemetry") === "false") return Promise.resolve(false);
|
||||
if (!!document.getElementById("analytics")) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.id = "analytics";
|
||||
script.src = "https://analytics.streetwriters.co/umami.js";
|
||||
script.async = true;
|
||||
script.dataset.websiteId = "b84b000d-9fcb-48e3-bbc0-0adad1a960c0";
|
||||
|
||||
if (process.env.REACT_APP_PLATFORM !== "desktop")
|
||||
script.dataset.domains = "importer.notesnook.com";
|
||||
script.dataset.autoTrack = "false";
|
||||
script.dataset.doNotTrack = "true";
|
||||
script.dataset.hostUrl = "https://analytics.streetwriters.co";
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
script.onload = () => resolve(true);
|
||||
script.onerror = (e) => reject(e);
|
||||
|
||||
var firstScriptElement = document.getElementsByTagName("script")[0];
|
||||
firstScriptElement.parentNode?.insertBefore(script, firstScriptElement);
|
||||
});
|
||||
}
|
||||
|
||||
type TrackerEvent = {
|
||||
name: string;
|
||||
// description: string;
|
||||
type?: "event" | "view";
|
||||
};
|
||||
|
||||
export async function trackEvent(event: TrackerEvent, eventMessage?: string) {
|
||||
if (
|
||||
!(await loadTrackerScript()) ||
|
||||
!Config.get("telemetry", true) ||
|
||||
!window.umami
|
||||
)
|
||||
return;
|
||||
|
||||
if (event.type === "view") trackVisit(event.name);
|
||||
else if (eventMessage) window.umami.trackEvent(eventMessage, event.name);
|
||||
}
|
||||
|
||||
function trackVisit(url: string = "/") {
|
||||
window.umami?.trackView(url, window.document.referrer);
|
||||
}
|
||||
|
||||
export function useTelemetry() {
|
||||
const [isEnabled, setIsEnabled] = useState(Config.get("telemetry", true));
|
||||
|
||||
useEffect(() => {
|
||||
Config.set("telemetry", isEnabled);
|
||||
}, [isEnabled]);
|
||||
|
||||
return { isEnabled, setIsEnabled };
|
||||
}
|
||||
27
apps/importer/src/utils/config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
function set(key: string, value: any) {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function get<T>(key: string, def?: T): T | undefined {
|
||||
const value = window.localStorage.getItem(key);
|
||||
if (!value) return def;
|
||||
|
||||
return tryParse(value);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
const Config = { set, get, clear };
|
||||
export default Config;
|
||||
|
||||
function tryParse(val: any) {
|
||||
if (val === "undefined" || val === "null") return;
|
||||
|
||||
try {
|
||||
return JSON.parse(val);
|
||||
} catch (e) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
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"]
|
||||
}
|
||||
BIN
badges/app-store-badge.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
@@ -8,6 +8,10 @@
|
||||
"name": "🚀 @notesnook/importer-web",
|
||||
"path": "apps/importer"
|
||||
},
|
||||
{
|
||||
"name": "🚀 @notesnook/vericrypt",
|
||||
"path": "apps/vericrypt"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/crypto",
|
||||
"path": "packages/crypto"
|
||||
@@ -20,9 +24,17 @@
|
||||
"name": "📦 @notesnook/importer",
|
||||
"path": "packages/importer"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/onenote",
|
||||
"path": "packages/onenote"
|
||||
},
|
||||
{
|
||||
"name": "📦 @streetwriters/tinymce-plugins",
|
||||
"path": "packages/tinymce-plugins"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/znel",
|
||||
"path": "packages/znel"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
||||
48719
package-lock.json
generated
@@ -22,8 +22,7 @@
|
||||
"turbo": "^1.0.19"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"apps/*"
|
||||
"packages/*"
|
||||
],
|
||||
"turbo": {
|
||||
"baseBranch": "origin/main",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/crypto",
|
||||
"private": false,
|
||||
"description": "An easy to use wrapper around libsodium exposing essential cryptographic methods. Currently only Web is supported.",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
@@ -12,10 +12,10 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"libsodium-wrappers": "^0.7.9"
|
||||
"libsodium-wrappers": "0.7.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/libsodium-wrappers": "^0.7.9"
|
||||
"@types/libsodium-wrappers": "0.7.9"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
8501
packages/enex/package-lock.json
generated
@@ -18,14 +18,14 @@
|
||||
},
|
||||
"author": "Abdullah Atta <abdullahatta@streetwriters.co>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2",
|
||||
"node-html-parser": "github:thecodrr/node-html-parser",
|
||||
"spark-md5": "^3.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/streetwriters/notesnook.git",
|
||||
"directory": "packages/enex"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2",
|
||||
"node-html-parser": "github:thecodrr/node-html-parser",
|
||||
"spark-md5": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||