Compare commits
263 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6a93061fd | ||
|
|
96246a1e33 | ||
|
|
1f38f67871 | ||
|
|
203d7ad881 | ||
|
|
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 | ||
|
|
9838ad715e | ||
|
|
f370f90a2b | ||
|
|
02b2a9fb17 | ||
|
|
17d953e59a | ||
|
|
c47436927e | ||
|
|
8f89414f89 | ||
|
|
557e1de9f0 | ||
|
|
6cc065a0f7 | ||
|
|
cba87d8477 | ||
|
|
26203bf3da | ||
|
|
e9d43f3026 | ||
|
|
57df03e46c | ||
|
|
b673519898 | ||
|
|
5356e9d4ed | ||
|
|
3d1b9de51b | ||
|
|
c1741b1924 | ||
|
|
4a17164bd9 | ||
|
|
c26dbefe26 | ||
|
|
f532d6f01b | ||
|
|
351f56dc0d | ||
|
|
feb74ae910 | ||
|
|
4b483d200d | ||
|
|
5858cae55b | ||
|
|
e7491d1c03 | ||
|
|
e740d56a84 | ||
|
|
01c78c654d | ||
|
|
db67114a30 | ||
|
|
7483286833 | ||
|
|
9606517f38 | ||
|
|
1d86624ce3 | ||
|
|
bdc2af9597 | ||
|
|
41d77b6424 | ||
|
|
3bda97b4f5 | ||
|
|
5d5b1e9eac | ||
|
|
f46ef8347f | ||
|
|
c613e67218 | ||
|
|
a9ff91d51e | ||
|
|
52f51c4087 | ||
|
|
f0ec25c296 | ||
|
|
d2ae716993 | ||
|
|
8d432d4810 | ||
|
|
e2bd1cd1f1 | ||
|
|
7286c60961 | ||
|
|
8a9cd91d54 | ||
|
|
462ae4d8ab | ||
|
|
45e7d5e46b | ||
|
|
9426e937a5 | ||
|
|
8ec4102395 | ||
|
|
4832d243da | ||
|
|
9504f278c3 | ||
|
|
8cefa1defc | ||
|
|
b4e58139e3 | ||
|
|
73f60704dd | ||
|
|
470e3040fc | ||
|
|
73d9a0f371 | ||
|
|
7a01898b2b | ||
|
|
41f5a4eecf | ||
|
|
08744fd0ee | ||
|
|
4788060c5a | ||
|
|
7a5e49bcbe | ||
|
|
536744f74a | ||
|
|
b1c33ebf28 | ||
|
|
323cd373e5 | ||
|
|
6c5cfb1505 | ||
|
|
b628e6071e | ||
|
|
8a2c1c0b2a | ||
|
|
77734aebba | ||
|
|
31711987c3 | ||
|
|
e193afb97d | ||
|
|
02b8fc4eb5 | ||
|
|
fd68f02f60 | ||
|
|
6089c58177 | ||
|
|
cfdfbb36cb | ||
|
|
38791a3760 | ||
|
|
e1426d4c73 | ||
|
|
71e45c23b5 | ||
|
|
673e0287bc | ||
|
|
bda62c761a | ||
|
|
412b40d64f | ||
|
|
975bc3e64d | ||
|
|
2832af2753 | ||
|
|
258e9f0a21 | ||
|
|
8a9de63ebc | ||
|
|
49ec85effd | ||
|
|
87e70b2aef | ||
|
|
c6f5276619 | ||
|
|
640f03af97 | ||
|
|
92c2538fbc | ||
|
|
867edbd878 | ||
|
|
6e2bcdd832 | ||
|
|
25e12564b6 | ||
|
|
cf0a12bdb9 | ||
|
|
9dce7c737e | ||
|
|
0bdb135b7b | ||
|
|
d9e3798dd1 | ||
|
|
4d9c108144 | ||
|
|
ba19214307 | ||
|
|
1aee142b59 | ||
|
|
eedf201a61 | ||
|
|
b7acb75686 | ||
|
|
ee8924c7c5 | ||
|
|
6461fac76e | ||
|
|
3c31fcbe6e | ||
|
|
858c92aad5 | ||
|
|
c03e672623 | ||
|
|
ae8720d69a | ||
|
|
6db218fc06 | ||
|
|
77e28d25e3 | ||
|
|
309ca061a2 | ||
|
|
5268485715 | ||
|
|
ee1517e800 | ||
|
|
5770972e88 | ||
|
|
dc24e629c5 | ||
|
|
9dd125656b | ||
|
|
3f548e1086 | ||
|
|
919710c069 | ||
|
|
9b4b28741f | ||
|
|
7d078e9e40 | ||
|
|
bf194ff7d2 | ||
|
|
2e02b57068 | ||
|
|
5696de8a24 | ||
|
|
39b7057bb7 | ||
|
|
78f6d18386 | ||
|
|
f1c4d5a78d | ||
|
|
13ab6ca9f7 | ||
|
|
6920147a17 | ||
|
|
1ba1c52165 | ||
|
|
b7bc1741d6 | ||
|
|
6b4ca9f2ae | ||
|
|
6616e87e1d | ||
|
|
0eb0e4fceb | ||
|
|
481a31ab27 | ||
|
|
255cb89523 | ||
|
|
bda96a7d82 | ||
|
|
8ccc7136e2 | ||
|
|
468498f087 | ||
|
|
1a57ef5177 | ||
|
|
e3b428a3fb | ||
|
|
65e1ced04c | ||
|
|
95d1f17c40 | ||
|
|
1fe3b77ba1 | ||
|
|
0250a86b12 | ||
|
|
bf64300c68 | ||
|
|
6280ba31c3 | ||
|
|
ad4d3c8889 | ||
|
|
2a96b9c615 | ||
|
|
5dcc8b3b12 | ||
|
|
429c6f0e69 | ||
|
|
b25f4f3145 | ||
|
|
7d33160817 | ||
|
|
a73a1b36c8 | ||
|
|
b9860eb21e | ||
|
|
b471e44f04 | ||
|
|
79f5b30ce4 | ||
|
|
f858b6ace3 | ||
|
|
c1a700029f | ||
|
|
34168716fe | ||
|
|
164710b56b | ||
|
|
ec5db3f8ea | ||
|
|
a9f1d12e37 | ||
|
|
8091d5f906 | ||
|
|
a240e9cdee | ||
|
|
d5b8b06981 | ||
|
|
7820b33bce | ||
|
|
494eda997c | ||
|
|
a7425e7636 | ||
|
|
af999ffe8a | ||
|
|
86ffc8c321 | ||
|
|
217de2dd17 | ||
|
|
428abf73da | ||
|
|
441d081e66 | ||
|
|
72a938e33a | ||
|
|
c36b6aef35 | ||
|
|
540b6fe8f0 | ||
|
|
1243f76dd1 | ||
|
|
00e751b6ef | ||
|
|
718be83e60 | ||
|
|
d6e199a77e | ||
|
|
ec82bf32f8 | ||
|
|
cb1ccc19e5 | ||
|
|
28bd768793 | ||
|
|
553938e2ab | ||
|
|
f9895f2749 | ||
|
|
11e164f53b | ||
|
|
299d6ac117 | ||
|
|
651d4bf342 | ||
|
|
7f442bb719 | ||
|
|
fd79177d4f | ||
|
|
4b01ea7da7 | ||
|
|
3fdaab2b0b | ||
|
|
dbb4ea616a | ||
|
|
a1b33a97bd | ||
|
|
29d165ef77 | ||
|
|
600972cd29 | ||
|
|
6a91986669 | ||
|
|
3956e21da8 | ||
|
|
c98b0a4818 | ||
|
|
544df95078 | ||
|
|
3d423e6718 | ||
|
|
39b4792984 | ||
|
|
14fc8e805a | ||
|
|
5eadb6bf79 | ||
|
|
25b7a94d26 | ||
|
|
7efbe2e37b | ||
|
|
54d66b999b | ||
|
|
47b113cfad | ||
|
|
67290524e2 | ||
|
|
43db5763dd | ||
|
|
d8ed840e23 | ||
|
|
c90665587c | ||
|
|
0a52991b03 | ||
|
|
6dfa0cbbf4 | ||
|
|
58da719a30 | ||
|
|
033ab2276e | ||
|
|
2345058c7b | ||
|
|
3abbbfda03 | ||
|
|
ca90a4c5c0 | ||
|
|
aa96181609 | ||
|
|
9b740c8d17 | ||
|
|
c70f820020 |
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
10
.changeset/config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
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.
|
||||
8
.github/workflows/docs-deploy.yml
vendored
@@ -1,12 +1,14 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
name: Docs Deploy
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
@@ -22,7 +24,7 @@ jobs:
|
||||
- uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.x
|
||||
|
||||
|
||||
- name: Use Node.js 15.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
@@ -34,7 +36,7 @@ jobs:
|
||||
- uses: blbecker/rsync-deploy@v1.0
|
||||
with:
|
||||
source_dir: ./docs/public_html
|
||||
destination_path: ${{ secrets.REMOTE_PATH }}
|
||||
destination_path: ${{ secrets.REMOTE_DOCS_PATH }}
|
||||
destination_host: ${{ secrets.REMOTE_HOST }}
|
||||
deployment_user: ${{ secrets.REMOTE_USER }}
|
||||
deployment_key: ${{ secrets.REMOTE_KEY }}
|
||||
|
||||
33
.github/workflows/enex-tests.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: notesnook/enex tests
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/enex/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
npm i
|
||||
npx turbo run build
|
||||
cd packages/enex
|
||||
npm run test
|
||||
41
.github/workflows/importer-deploy.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Importer Deploy
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/importer/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd apps/importer
|
||||
npm i
|
||||
npm run build
|
||||
mv ./build/ ./public_html/
|
||||
|
||||
- uses: blbecker/rsync-deploy@v1.0
|
||||
with:
|
||||
source_dir: ./apps/importer/public_html/
|
||||
destination_path: ${{ secrets.REMOTE_IMPORTER_PATH }}
|
||||
destination_host: ${{ secrets.REMOTE_HOST }}
|
||||
deployment_user: ${{ secrets.REMOTE_USER }}
|
||||
deployment_key: ${{ secrets.REMOTE_KEY }}
|
||||
33
.github/workflows/importer-tests.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: notesnook/importer tests
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/importer/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
npm i
|
||||
npx turbo run build
|
||||
cd packages/importer
|
||||
npm run test
|
||||
41
.github/workflows/vericrypt-deploy.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Vericrypt Deploy
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push events or manually
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/vericrypt/**"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: |
|
||||
cd apps/vericrypt
|
||||
npm i
|
||||
npm run build
|
||||
mv ./build/ ./public_html/
|
||||
|
||||
- uses: blbecker/rsync-deploy@v1.0
|
||||
with:
|
||||
source_dir: ./apps/vericrypt/public_html/
|
||||
destination_path: ${{ secrets.REMOTE_VERICRYPT_PATH }}
|
||||
destination_host: ${{ secrets.REMOTE_HOST }}
|
||||
deployment_user: ${{ secrets.REMOTE_USER }}
|
||||
deployment_key: ${{ secrets.REMOTE_KEY }}
|
||||
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.turbo
|
||||
dist
|
||||
node_modules
|
||||
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
72
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
|
||||
|
||||
@@ -55,11 +20,11 @@ You can reach out to us via:
|
||||
3. [Twitter](https://twitter.com/notesnook)
|
||||
4. [Create an issue](https://github.com/streetwriters/notesnook/issues/new)
|
||||
|
||||
We take all queries, issues, bug reports that you might have. Feel free to ask.
|
||||
We take all queries, issues and 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
|
||||
|
||||
@@ -69,8 +34,8 @@ There are [many](https://en.wikipedia.org/wiki/Comparison_of_note-taking_softwar
|
||||
|
||||
These are the 4 problems that Notesnook solves:
|
||||
|
||||
1. No compromise on security and user privacy
|
||||
2. No sacrifice on important note taking features (e.g. note locking, PDF exports, organize via notebooks, topics & tags)
|
||||
1. No compromise on security and user privacy.
|
||||
2. No sacrifice on important note taking features (e.g. note locking, PDF exports, organize via notebooks, topics & tags).
|
||||
3. Provides a simple, easy to use user interface.
|
||||
4. Works exactly the same on all platforms.
|
||||
|
||||
@@ -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,10 +79,10 @@ 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. :)
|
||||
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 :)
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -135,18 +98,13 @@ Bug reports are given utmost priority. Report it and we'll fix it ASAP.
|
||||
|
||||
### Join our Discord server & help out in the community
|
||||
|
||||
You can join [our Discord server](https://discord.gg/5davZnhw3V) and help other Notesnook users. We also post announcements, host polls, ask for early feedback there.
|
||||
You can join [our Discord server](https://discord.gg/5davZnhw3V) and help other Notesnook users. We also post announcements, host polls and ask for early feedback there.
|
||||
|
||||
### Help us out by giving social proof
|
||||
|
||||
Social proof helps other people know what they are getting. If you have tried out Notesnook and loved it, you can give social proof:
|
||||
|
||||
1. By tweeting about @notesnook on Twitter
|
||||
1. By tweeting about @notesnook on Twitter.
|
||||
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.
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
- [ ] **Two-Factor-Authentication** via Auth app & SMS.
|
||||
- [ ] **Stylus & Drawing Support** to take notes by drawing on the screen. We will be adding a dedicated editor where you can draw and take notes.
|
||||
- [ ] **Note Publishing** on a public url that is viewable by anyone with a shareable link. The note can be encrypted if required before publishing so only people with the password can view it.
|
||||
- [ ] **Links:** A dedicated links screen where user can list all links and import browser bookmarks
|
||||
- [ ] **Links** - A dedicated links screen where user can list all links and import browser bookmarks.
|
||||
- [ ] **Widgets on Android & iOS** - Initially we are working on adding a Quick Note widget to add notes without opening app.
|
||||
|
||||
19
apps/importer/.env-cmdrc.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { version } = require("./package.json");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const APP_VERSION = version;
|
||||
const CHANGELOG = fs.readFileSync(path.join(__dirname, "changelog.txt"), {
|
||||
encoding: "utf8",
|
||||
});
|
||||
module.exports = {
|
||||
all: {
|
||||
REACT_APP_VERSION: APP_VERSION,
|
||||
REACT_APP_CHANGELOG: CHANGELOG,
|
||||
},
|
||||
release: {
|
||||
GENERATE_SOURCEMAP: false,
|
||||
INLINE_RUNTIME_CHUNK: false,
|
||||
DISABLE_ESLINT_PLUGIN: true,
|
||||
},
|
||||
};
|
||||
23
apps/importer/.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/importer/README.md
Normal file
5
apps/importer/changelog.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
42088
apps/importer/package-lock.json
generated
Normal file
59
apps/importer/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@notesnook/importer-web",
|
||||
"version": "1.3.3",
|
||||
"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/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/importer": "^1.3.3",
|
||||
"colord": "^2.9.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"hash-wasm": "^4.9.0",
|
||||
"interweave": "^12.9.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^11.4.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-modal": "^3.14.4",
|
||||
"react-select": "^5.2.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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"associatedApplications": [
|
||||
{
|
||||
"applicationId": "4952c7cf-9c02-4fb7-b867-b87727bb52d8"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
apps/importer/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 32 KiB |
38
apps/importer/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="Import your notes from any notes app into Notesnook."
|
||||
/>
|
||||
<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 Importer</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/importer/public/logo192.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
3
apps/importer/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
71
apps/importer/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+ */
|
||||
}
|
||||
78
apps/importer/src/App.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import { ThemeProvider } from "@theme-ui/theme-provider";
|
||||
import "./App.css";
|
||||
import { ThemeFactory } from "./theme";
|
||||
import { useEffect, useState } from "react";
|
||||
import { StepSeperator } from "./components/StepSeperator";
|
||||
import { ProviderSelector } from "./components/ProviderSelector";
|
||||
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<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()}>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Hero />
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ProviderSelector
|
||||
onProviderChanged={(provider) => {
|
||||
setSelectedProvider(provider);
|
||||
setProviderResult(undefined);
|
||||
}}
|
||||
/>
|
||||
|
||||
{selectedProvider ? (
|
||||
<>
|
||||
<StepSeperator />
|
||||
{selectedProvider.type === "file" ? (
|
||||
<FileProviderHandler
|
||||
provider={selectedProvider}
|
||||
onTransformFinished={setProviderResult}
|
||||
/>
|
||||
) : selectedProvider.type === "network" ? (
|
||||
<NetworkProviderHandler
|
||||
provider={selectedProvider}
|
||||
onTransformFinished={setProviderResult}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
{providerResult ? (
|
||||
<>
|
||||
<StepSeperator />
|
||||
<ImportResult result={providerResult} />{" "}
|
||||
</>
|
||||
) : null}
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-600.woff
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-600.woff2
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-700.woff
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-700.woff2
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-italic.woff
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-italic.woff2
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-regular.woff
Normal file
BIN
apps/importer/src/assets/fonts/open-sans-v20-latin-regular.woff2
Normal file
42
apps/importer/src/components/Accordion.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
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,
|
||||
...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",
|
||||
}}
|
||||
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>
|
||||
);
|
||||
}
|
||||
156
apps/importer/src/components/FileProviderHandler.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
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 FileProviderHandlerProps = {
|
||||
provider: IFileProvider;
|
||||
onTransformFinished: (result: ProviderResult) => void;
|
||||
};
|
||||
|
||||
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 [progress, setProgress] = useState<Progress>({ total: 0, done: 0 });
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
setFiles((files) => {
|
||||
const newFiles = [...acceptedFiles, ...files];
|
||||
return newFiles;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
accept: provider?.supportedExtensions?.concat(".zip"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setFiles([]);
|
||||
}, [provider]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!files.length) 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(
|
||||
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" }}>
|
||||
<Text variant="title">Select {provider?.name} files</Text>
|
||||
|
||||
<Flex
|
||||
{...getRootProps()}
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200,
|
||||
border: "2px dashed var(--theme-ui-colors-border)",
|
||||
borderRadius: "default",
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
<Input {...getInputProps()} />
|
||||
<Text variant="body" sx={{ textAlign: "center" }}>
|
||||
{isDragActive
|
||||
? "Drop the files here"
|
||||
: "Drag & drop files here, or click to select files"}
|
||||
<br />
|
||||
<Text variant="subBody">
|
||||
Only {provider?.supportedExtensions.join(", ")} files are supported.
|
||||
You can also select .zip files containing{" "}
|
||||
{provider?.supportedExtensions.join(", ")} files.
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
{files.length > 0 ? (
|
||||
<Accordion
|
||||
title={`${files.length} ${
|
||||
files.length > 1 ? "files" : "file"
|
||||
} selected`}
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{ flexDirection: "column", overflowY: "auto", maxHeight: 400 }}
|
||||
>
|
||||
{files.map((file, index) => (
|
||||
<Flex
|
||||
key={file.name}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
p: 2,
|
||||
bg: index % 2 ? "transparent" : "bgSecondary",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
bg: "hover",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setFiles((files) => {
|
||||
const _files = files.slice();
|
||||
_files.splice(index, 1);
|
||||
return _files;
|
||||
});
|
||||
}}
|
||||
title="Click to remove"
|
||||
>
|
||||
<Text variant="body">{file.name}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Accordion>
|
||||
) : null}
|
||||
{progress.total > 0 ? (
|
||||
<Text variant="body">
|
||||
Processing files ({progress.done}/{progress.total})
|
||||
</Text>
|
||||
) : null}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
23
apps/importer/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 Importer (v{appVersion})</Text>
|
||||
<Text variant="subBody">
|
||||
© {new Date().getFullYear()} Streetwriters LLC
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
84
apps/importer/src/components/Hero.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
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: [50, 150],
|
||||
mb: [50, 100],
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text variant="heading" sx={{ textAlign: "center" }}>
|
||||
Notesnook Importer
|
||||
</Text>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "title",
|
||||
textAlign: "center",
|
||||
color: "fontTertiary",
|
||||
mt: [2, 0],
|
||||
}}
|
||||
>
|
||||
Import your notes from any notes app into Notesnook.
|
||||
</Text>
|
||||
<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: 2, borderLeft: "1px solid var(--theme-ui-colors-border)" }}
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
<Link
|
||||
href="https://app.notesnook.com/"
|
||||
variant="text.body"
|
||||
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: ["90%", 400],
|
||||
mt: 4,
|
||||
p: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Text variant="subtitle">What's new in v{appVersion}</Text>
|
||||
<Text variant="body" sx={{ whiteSpace: "pre-wrap" }}>
|
||||
{process.env.REACT_APP_CHANGELOG}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
39
apps/importer/src/components/ImportErrors.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
type ImportErrorsProps = {
|
||||
errors: Error[];
|
||||
};
|
||||
|
||||
export function ImportErrors(props: ImportErrorsProps) {
|
||||
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, 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={() =>
|
||||
window.open(
|
||||
"https://github.com/streetwriters/notesnook/issues/new",
|
||||
"_blank"
|
||||
)
|
||||
}
|
||||
>
|
||||
Send us a bug report
|
||||
</Button>
|
||||
</Flex>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
47
apps/importer/src/components/ImportHelp.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
type ImportHelpProps = {
|
||||
onDownload: () => void;
|
||||
};
|
||||
|
||||
export function ImportHelp(props: ImportHelpProps) {
|
||||
return (
|
||||
<Accordion
|
||||
title="How to import your notes into Notesnook?"
|
||||
sx={{ bg: "bgSecondary", mt: 2, borderRadius: "default" }}
|
||||
>
|
||||
<Text
|
||||
as="ol"
|
||||
variant="body"
|
||||
sx={{
|
||||
lineHeight: "24px",
|
||||
paddingInlineStart: 30,
|
||||
pb: 2,
|
||||
}}
|
||||
>
|
||||
<Text as="li">
|
||||
<Button variant="primary" sx={{ py: 0 }} onClick={props.onDownload}>
|
||||
Download the ZIP file
|
||||
</Button>{" "}
|
||||
containing your notes
|
||||
</Text>
|
||||
<Text as="li">
|
||||
<a href="https://app.notesnook.com/">Open Notesnook</a> and make sure
|
||||
you are logged in.
|
||||
</Text>
|
||||
<Text as="li">
|
||||
Go to <b>Settings</b>
|
||||
</Text>
|
||||
<Text as="li">
|
||||
Expand the section titled <b>Notesnook Importer</b>
|
||||
</Text>
|
||||
<Text as="li">
|
||||
Click on <b>Import from ZIP file</b>
|
||||
</Text>
|
||||
<Text as="li">Select the downloaded ZIP file</Text>
|
||||
<Text as="li">Your notes should appear in the app.</Text>
|
||||
</Text>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
64
apps/importer/src/components/ImportResult.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
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";
|
||||
import { ImportErrors } from "./ImportErrors";
|
||||
import { ImportHelp } from "./ImportHelp";
|
||||
import { saveAs } from "file-saver";
|
||||
import { NotesList } from "./NotesList";
|
||||
|
||||
type ImportResultProps = {
|
||||
result: ProviderResult;
|
||||
};
|
||||
|
||||
export function ImportResult(props: ImportResultProps) {
|
||||
const { result } = props;
|
||||
const [selectedNote, setSelectedNote] = useState<Note | undefined>();
|
||||
|
||||
const downloadAsZip = useCallback(() => {
|
||||
if (!result) return;
|
||||
const packed = pack(result.notes);
|
||||
saveAs(
|
||||
new Blob([packed], { type: "application/zip" }),
|
||||
`notesnook-importer.zip`
|
||||
);
|
||||
}, [result]);
|
||||
|
||||
return (
|
||||
<StepContainer
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Your notes are ready for download</Text>
|
||||
{result ? (
|
||||
<>
|
||||
<Text variant="body" sx={{ color: "fontTertiary" }}></Text>
|
||||
<NotesList
|
||||
notes={result?.notes}
|
||||
onNoteSelected={(note) => setSelectedNote(note)}
|
||||
/>
|
||||
{result.errors.length > 0 && <ImportErrors errors={result.errors} />}
|
||||
<ImportHelp onDownload={downloadAsZip} />
|
||||
<Button
|
||||
variant="primary"
|
||||
sx={{ alignSelf: "center", mt: 2 }}
|
||||
onClick={downloadAsZip}
|
||||
>
|
||||
Download ZIP file
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{selectedNote && (
|
||||
<NotePreview
|
||||
note={selectedNote}
|
||||
onClose={() => setSelectedNote(undefined)}
|
||||
/>
|
||||
)}
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
152
apps/importer/src/components/NotePreview.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { IoStarOutline } from "react-icons/io5";
|
||||
import { GrPin } from "react-icons/gr";
|
||||
import { MdCircle, MdClose, MdOutlineBook } from "react-icons/md";
|
||||
import { Markup } from "interweave";
|
||||
import Modal from "react-modal";
|
||||
import { Note } from "@notesnook/importer/dist/src/models/note";
|
||||
Modal.setAppElement("#root");
|
||||
|
||||
type NotePreviewProps = {
|
||||
note: Note;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function NotePreview(props: NotePreviewProps) {
|
||||
const {
|
||||
title,
|
||||
content,
|
||||
dateEdited,
|
||||
dateCreated,
|
||||
favorite,
|
||||
color,
|
||||
pinned,
|
||||
notebooks,
|
||||
tags,
|
||||
attachments,
|
||||
} = props.note;
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
contentLabel={title}
|
||||
style={{
|
||||
content: {
|
||||
borderRadius: 10,
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
boxShadow: "0px 0px 20px 0px #00000011",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Flex sx={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Text variant="subBody">
|
||||
This preview is not an exact representation of how the note will
|
||||
appear in Notesnook.
|
||||
</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
sx={{
|
||||
bg: "transparent",
|
||||
display: "flex",
|
||||
alignSelf: "end",
|
||||
p: 0,
|
||||
m: 0,
|
||||
alignItems: "center",
|
||||
}}
|
||||
onClick={props.onClose}
|
||||
>
|
||||
<MdClose size={24} />
|
||||
</Button>
|
||||
</Flex>
|
||||
<Text as="h1" variant="subheading">
|
||||
{title}
|
||||
</Text>
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
{color && (
|
||||
<MdCircle color={color} size={14} style={{ marginRight: 5 }} />
|
||||
)}
|
||||
{favorite && <IoStarOutline style={{ marginRight: 5 }} />}
|
||||
{pinned && <GrPin size={12} style={{ marginRight: 5 }} />}
|
||||
{dateCreated && (
|
||||
<>
|
||||
<Text variant="subBody">
|
||||
Created on: {formatDate(dateCreated)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{dateEdited && (
|
||||
<>
|
||||
<MdCircle
|
||||
color="#5b5b5b"
|
||||
size={7}
|
||||
style={{ marginLeft: 3, marginRight: 3 }}
|
||||
/>
|
||||
<Text variant="subBody">
|
||||
Last modified: {formatDate(dateEdited)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{attachments && (
|
||||
<>
|
||||
<MdCircle
|
||||
color="#5b5b5b"
|
||||
size={7}
|
||||
style={{ marginLeft: 3, marginRight: 3 }}
|
||||
/>
|
||||
<Text variant="subBody">{attachments.length} attachments</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex my={1}>
|
||||
{notebooks?.map((notebook) => (
|
||||
<Flex
|
||||
key={notebook.notebook + notebook.topic}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
bg: "bgSecondary",
|
||||
mr: 1,
|
||||
p: 1,
|
||||
py: "2px",
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
borderRadius: "default",
|
||||
color: "fontTertiary",
|
||||
}}
|
||||
>
|
||||
<MdOutlineBook size={11} />
|
||||
<Text variant="subBody" sx={{ ml: "2px" }}>
|
||||
{notebook.notebook} > {notebook.topic}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
{tags?.map((tag) => (
|
||||
<Text
|
||||
key={tag}
|
||||
variant="subBody"
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
mr: 1,
|
||||
p: 1,
|
||||
py: "2px",
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
borderRadius: "default",
|
||||
color: "fontTertiary",
|
||||
}}
|
||||
>
|
||||
#{tag}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
<Text variant="body">
|
||||
<Markup content={content?.data} />
|
||||
</Text>
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDate(date: number) {
|
||||
return new Date(date).toLocaleString(undefined, {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
});
|
||||
}
|
||||
60
apps/importer/src/components/NotesList.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { Note } from "@notesnook/importer/dist/src/models/note";
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
type NotesListProps = {
|
||||
notes: Note[];
|
||||
onNoteSelected: (note: Note) => void;
|
||||
};
|
||||
export function NotesList(props: NotesListProps) {
|
||||
const { notes, onNoteSelected } = props;
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
title={`${notes.length} notes found`}
|
||||
sx={{
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
mt: 2,
|
||||
borderRadius: "default",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
overflowY: "auto",
|
||||
maxHeight: 300,
|
||||
}}
|
||||
>
|
||||
{notes.map((note, index) => (
|
||||
<Flex
|
||||
key={note.title + note.dateCreated}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
p: 2,
|
||||
bg: index % 2 ? "transparent" : "bgSecondary",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
bg: "hover",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
onNoteSelected(note);
|
||||
}}
|
||||
title="Click to preview"
|
||||
>
|
||||
<Text variant="body">{note.title}</Text>
|
||||
{note.dateEdited && (
|
||||
<Text variant="subBody">
|
||||
Last modified on:{" "}
|
||||
{new Date(note.dateEdited).toLocaleString(undefined, {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
61
apps/importer/src/components/ProviderSelector.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Flex, Select, Text } from "@theme-ui/components";
|
||||
import { IProvider, ProviderFactory } from "@notesnook/importer";
|
||||
import { Providers } from "@notesnook/importer/dist/src/providers/providerfactory";
|
||||
import { StepContainer } from "./StepContainer";
|
||||
|
||||
type ProviderSelectorProps = {
|
||||
onProviderChanged: (provider: IProvider) => void;
|
||||
};
|
||||
|
||||
export function ProviderSelector(props: ProviderSelectorProps) {
|
||||
return (
|
||||
<StepContainer sx={{ flexDirection: "column" }}>
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: ["stretch", "space-between"],
|
||||
flexDirection: ["column", "row"],
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Select a notes app to import from</Text>
|
||||
<Select
|
||||
sx={{
|
||||
m: 0,
|
||||
px: 2,
|
||||
border: "1px solid var(--theme-ui-colors-border)",
|
||||
outline: "none",
|
||||
":hover": {
|
||||
borderColor: "hover",
|
||||
},
|
||||
":active": {
|
||||
borderColor: "active",
|
||||
},
|
||||
fontFamily: "body",
|
||||
fontSize: "body",
|
||||
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(ProviderFactory.getProvider(providerName));
|
||||
}}
|
||||
>
|
||||
<option value=""></option>
|
||||
{ProviderFactory.getAvailableProviders().map((provider) => (
|
||||
<option key={provider} value={provider}>
|
||||
{ProviderFactory.getProvider(provider as Providers).name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<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.
|
||||
</a>
|
||||
</Text>
|
||||
</StepContainer>
|
||||
);
|
||||
}
|
||||
22
apps/importer/src/components/StepContainer.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Flex, FlexProps } from "@theme-ui/components";
|
||||
|
||||
export function StepContainer({
|
||||
children,
|
||||
sx,
|
||||
...restProps
|
||||
}: React.PropsWithChildren<FlexProps>) {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: ["90%", "40%"],
|
||||
boxShadow: "0px 0px 20px 0px #00000011",
|
||||
p: 4,
|
||||
borderRadius: "default",
|
||||
...sx,
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
5
apps/importer/src/components/StepSeperator.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Box } from "@theme-ui/components";
|
||||
|
||||
export function StepSeperator() {
|
||||
return <Box sx={{ height: 150, width: 2, bg: "bgSecondary" }} />;
|
||||
}
|
||||
0
apps/importer/src/index.css
Normal file
11
apps/importer/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/importer/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
28
apps/importer/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/importer/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("#008837");
|
||||
}
|
||||
}
|
||||
export default ColorSchemeFactory;
|
||||
29
apps/importer/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/importer/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/importer/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: "12px",
|
||||
subBody: "12px",
|
||||
};
|
||||
}
|
||||
}
|
||||
19
apps/importer/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;`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
19
apps/importer/src/theme/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import ColorSchemeFactory from "./colorscheme";
|
||||
import VariantsFactory from "./variants";
|
||||
import { FontFactory } from "./font";
|
||||
import { Theme } from "theme-ui";
|
||||
|
||||
export class ThemeFactory {
|
||||
static construct(): 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 },
|
||||
colors: ColorSchemeFactory.construct("light"),
|
||||
rawColors: ColorSchemeFactory.construct("light"),
|
||||
...FontFactory.construct(),
|
||||
...new VariantsFactory(),
|
||||
};
|
||||
}
|
||||
}
|
||||
76
apps/importer/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: 1,
|
||||
px: 2,
|
||||
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/importer/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;
|
||||
64
apps/importer/src/theme/variants/input.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
class InputFactory {
|
||||
constructor() {
|
||||
return {
|
||||
input: new Default(),
|
||||
error: new Error(),
|
||||
clean: new Clean(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default InputFactory;
|
||||
|
||||
class Default {
|
||||
constructor() {
|
||||
return {
|
||||
borderRadius: "default",
|
||||
border: "none",
|
||||
// borderColor: "border",
|
||||
boxShadow: "0px 0px 0px 1px var(--border) inset",
|
||||
fontFamily: "body",
|
||||
fontWeight: "body",
|
||||
fontSize: "input",
|
||||
color: "text",
|
||||
outline: "none",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 1.5px var(--primary) inset",
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "0px 0px 0px 1px var(--dimPrimary) inset",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Clean {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "forms.input",
|
||||
outline: "none",
|
||||
boxShadow: "none",
|
||||
":focus": {
|
||||
boxShadow: "none",
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "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/importer/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" };
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
1
apps/importer/src/utils/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const appVersion = process.env.REACT_APP_VERSION;
|
||||
21
apps/importer/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"]
|
||||
}
|
||||
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
38378
apps/vericrypt/package-lock.json
generated
Normal file
57
apps/vericrypt/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@notesnook/vericrypt",
|
||||
"version": "1.1.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": {
|
||||
"@microsoft/signalr-protocol-msgpack": "^6.0.7",
|
||||
"@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 |