Compare commits

...

39 Commits

Author SHA1 Message Date
thecodrr
725fe41db2 chore: announce Notesnook open sourcing plan 2022-07-23 16:06:09 +05:00
thecodrr
6f6fc52c47 chore(crypto): bump version to 1.0.1 2022-07-19 20:29:01 +05:00
thecodrr
2e74f807d4 chore: fix libsodium-wrappers version to 0.7.9 2022-07-19 20:28:14 +05:00
thecodrr
eb02332f37 chore: fix issue templates 2022-06-10 08:13:35 +05:00
thecodrr
829c27d456 chore: add new issue forms 2022-06-10 08:06:46 +05:00
thecodrr
b08abcf943 chore(importer-web): bump version to 1.3.3 2022-06-08 07:54:12 +05:00
thecodrr
cddd6f3f5b fix(importer): resolve tracker script promise correctly 2022-06-08 07:53:53 +05:00
thecodrr
8f90bbef59 chore: bump @notesnook/importer to v1.3.3 2022-06-07 23:17:40 +05:00
thecodrr
dc621a2483 feat(importer): add basic telemetry 2022-06-07 23:13:58 +05:00
thecodrr
d5f9fb10d4 fix(importer): use only light mode 2022-06-07 23:10:48 +05:00
thecodrr
85efebbaca fix(importer): include .zip file in SN supported ext 2022-06-07 22:32:56 +05:00
thecodrr
2fcd9d26e6 fix(importer): better timestamp extraction from SN backup 2022-06-07 22:28:11 +05:00
thecodrr
61bd7552ae fix(importer): convert SN timestamps to ms (fixes #607) 2022-06-07 22:04:41 +05:00
Irshad Ali
8128f827a3 fix: Apple app store badge broken (#587)
* Add files via upload

* Apple app store broken icon fixed
2022-05-30 03:34:06 +05:00
thecodrr
1b3b760b53 fix(importer-web): optimize for mobile screens 2022-05-17 09:26:09 +05:00
thecodrr
ffd7501e80 ci: fix env-cmd not found error 2022-05-17 09:14:27 +05:00
thecodrr
9f5d61250b ci: use turbo to build all packages 2022-05-17 09:04:11 +05:00
thecodrr
a71b5a96f2 chore: regenerate package-lock.json 2022-05-17 05:54:37 +05:00
thecodrr
81bb053513 ci: run npm i in root dir 2022-05-13 19:26:22 +05:00
thecodrr
63796d5896 chore(importer-web): bump verion to 1.3.0 2022-05-13 19:00:03 +05:00
thecodrr
19a1ea2236 chore(importer-web): update importer core to 1.3.0 2022-05-13 19:00:03 +05:00
thecodrr
c72ce1ee69 chore(importer): bump version to 1.3.0 2022-05-13 19:00:03 +05:00
thecodrr
ba88f28ae7 fix(importer): remove all @joplin/lib usage 2022-05-13 19:00:03 +05:00
thecodrr
80186fd1cd feat(importer): add joplin importer 2022-05-13 19:00:03 +05:00
thecodrr
ec07cab365 feat(importer): update standardnotes types 2022-05-13 19:00:03 +05:00
thecodrr
ed8271cf9d chore(tinymce-plugins): bump version to 1.5.18 2022-04-01 22:06:44 +05:00
thecodrr
da7723e7e0 fix: make inline code toggleable (fixes #444) 2022-04-01 22:02:00 +05:00
thecodrr
43e817e259 fix(importer): use correct redirect uri for onenote 2022-03-27 16:34:21 +05:00
thecodrr
eb50ebeb81 chore(importer): update packages 2022-03-27 16:26:23 +05:00
thecodrr
16b702294a chore(onenote): bump version to 1.0.1 2022-03-27 16:10:35 +05:00
thecodrr
9bd68e8c5a fix(onenote): set redirectUri via authconfig 2022-03-27 16:10:16 +05:00
thecodrr
f1b2deb893 chore(importer-web): bump version to 1.2.0 2022-03-26 22:39:03 +05:00
thecodrr
64eae3044d chore(importer): bump version to 1.2.1 2022-03-26 22:36:22 +05:00
thecodrr
f1be7a57a1 chore(importer): bump version to 1.2.0 2022-03-26 22:30:38 +05:00
Abdullah Atta
80dd83552a feat: add Zoho Notebook support in Importer (#382)
* feat: add zoho notebook importer

* fix: checklists in todo notes are not converted

* chore: use @notesnook/znel from npm
2022-03-26 22:27:20 +05:00
thecodrr
b4b7f9c519 chore: fix tests and update snjs 2022-03-26 22:14:56 +05:00
Abdullah Atta
5d0e556e1b feat: add support for OneNote in Importer (#296)
* chore: initialize onenote provider package

* add basic support for onenote

* feat: add progress

* refactor: cleanup and finalize onenote api

* feat: add onenote support in importer-web

* refactor: use a single import result component

* fix(onenote): avoid circular references

* test: add onenote tests

* chore: publish @notesnook/onenote to npm

* chore: use @notesnook/onenote from npm
2022-03-26 22:09:01 +05:00
thecodrr
ecd1640c1d fix: use custom curl parser 2022-03-26 15:15:55 +05:00
Abdullah Atta
085b7c4eb1 feat: add vericrypt utility for Notesnook encryption verification (#311)
* feat: add basic version of vericrypt utility

* feat: implement decryption logic & refresh ui

* fix: reinstall favicon

* chore: change icon

* chore: vericrypt -> notesnook vericrypt

* vericrypt: finalize ui & ux

* fix: minor adjustments to the instructions

* ci: add vericrypt deployment github action

Co-authored-by: ammarahm-ed <ammarahmed6506@gmail.com>
2022-03-26 14:15:51 +05:00
181 changed files with 65141 additions and 74739 deletions

View File

@@ -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
View 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
View 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!

View File

@@ -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.

View 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.

View File

@@ -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

View File

@@ -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
View 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 }}

View File

@@ -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.

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -0,0 +1,7 @@
{
"associatedApplications": [
{
"applicationId": "4952c7cf-9c02-4fb7-b867-b87727bb52d8"
}
]
}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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",

View File

@@ -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={() =>

View File

@@ -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} />}

View 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>
);
}

View File

@@ -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",

View File

@@ -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,

View File

@@ -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.

View File

@@ -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",

View File

@@ -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(),

View 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 };
}

View 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;
}
}

View 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
View 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
View File

38562
apps/vericrypt/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View 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"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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
View 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;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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 ? " + " : ""}
</>
))}
</>
);
}

View 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 &amp; 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>
);
}

View 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>
);
}

View 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>
);
}

View 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" /> &amp; 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" /> &amp; 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 &amp; 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) &amp;{" "}
{encryptedData.attachments.length} attachments.
</Text>
</Flex>
)}
</StepContainer>
);
}

View 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>
);
}

View 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>
);
}

View File

View 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
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View 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),
};
}
}

View 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;

View 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),
};
}
}

View 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",
};
}
}

View 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",
};
}
}

View 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;`,
},
};
}
}

View 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(),
};
}
}

View 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",
},
};
}
}

View 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;

View 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",
},
};
}
}

View 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" };
}
}

View 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;
}

View 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];
}

View 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}`;
}

View File

@@ -0,0 +1 @@
export const appVersion = process.env.REACT_APP_VERSION;

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,7 @@
"turbo": "^1.0.19"
},
"workspaces": [
"packages/*",
"apps/*"
"packages/*"
],
"turbo": {
"baseBranch": "origin/main",

View File

@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

Some files were not shown because too many files have changed in this diff Show More