Compare commits

...

31 Commits

Author SHA1 Message Date
Radio Rambo
286b2b39be doc: more clear wording in create a note in notebook doc (#798)
Also checked with Grammarly
2022-08-11 10:47:56 +05:00
Radio Rambo
6eb383c75a doc: minor corrections and changes in documentation (#789)
* Corrections & minor rewrite of documentation

* corrections and changes in documentation
2022-08-10 10:39:25 +05:00
thecodrr
f6a93061fd ci: build script not called on deploy 2022-07-30 08:39:46 +05:00
thecodrr
96246a1e33 chore(vericrypt): bump version to 1.1.0 2022-07-30 08:35:34 +05:00
thecodrr
1f38f67871 feat(vericrypt): add support & instructions for new sync API 2022-07-30 08:35:34 +05:00
Patryk R
203d7ad881 chore: fix punctuation in README.md & ROADMAP.md (#746) 2022-07-28 06:23:26 +05:00
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
64 changed files with 24892 additions and 93450 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

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

View File

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

View File

@@ -1,2 +1,5 @@
1. 🥳 Added OneNote support! 🥳
2. 🥳 Added Zoho Notebook support! 🥳
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.2.1",
"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.2.3",
"@notesnook/importer": "^1.3.3",
"colord": "^2.9.2",
"file-saver": "^2.0.5",
"hash-wasm": "^4.9.0",

View File

@@ -2,7 +2,7 @@ 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 { useEffect, useState } from "react";
import { StepSeperator } from "./components/StepSeperator";
import { ProviderSelector } from "./components/ProviderSelector";
import { FileProviderHandler } from "./components/FileProviderHandler";
@@ -12,11 +12,23 @@ 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" }}>
@@ -27,7 +39,12 @@ function App() {
alignItems: "center",
}}
>
<ProviderSelector onProviderChanged={setSelectedProvider} />
<ProviderSelector
onProviderChanged={(provider) => {
setSelectedProvider(provider);
setProviderResult(undefined);
}}
/>
{selectedProvider ? (
<>

View File

@@ -49,7 +49,7 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
useEffect(() => {
(async () => {
if (!provider || !files.length) return;
if (!files.length) return;
setProgress({ total: 0, done: 0 });
let result: ProviderResult = { notes: [], errors: [] };
@@ -71,7 +71,11 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
setProgress({ total: 0, done: 0 });
onTransformFinished(result);
})();
}, [provider, files, onTransformFinished]);
// 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" }}>

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,14 +12,13 @@ export function ImportErrors(props: ImportErrorsProps) {
sx={{ bg: "errorBg", borderRadius: "default", mt: 2 }}
color="#E53935"
>
<Flex sx={{ flexDirection: "column", px: 2, pb: 2 }}>
<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}{" "}
{error.file ? `(${error.file.name})` : ""}
{index + 1}. {error.message}
<br />
</Text>
))}

View File

@@ -10,11 +10,15 @@ type ProviderSelectorProps = {
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,7 +31,11 @@ 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;
@@ -42,7 +50,7 @@ export function ProviderSelector(props: ProviderSelectorProps) {
))}
</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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/vericrypt",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"devDependencies": {
"@testing-library/jest-dom": "^5.11.4",
@@ -20,6 +20,7 @@
"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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -26,7 +26,8 @@ export function ErrorsList(props: ErrorsListProps) {
onClick={() =>
window.open(
"https://github.com/streetwriters/notesnook/issues/new",
"_blank"
"_blank",
"noopener"
)
}
>

View File

@@ -84,8 +84,8 @@ const steps = {
<>
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.
the 'user' object, scroll to end using <Code text="Page down" /> key in
key-value pair list and then search again.
</>,
`At the top of the list, you'll see an entry with key "user". Expand the value.`,
<Flex sx={{ flexDirection: "column" }}>

View File

@@ -1,10 +1,19 @@
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 DevtoolsRequestsWS from "../assets/screenshots/devtools_requests_ws.png";
import DevtoolsRequestsWSMessages from "../assets/screenshots/devtools_requests_ws_messages.png";
import DevtoolsRequestsWSMessagesSelect from "../assets/screenshots/devtools_requests_ws_messages_select.png";
import DevtoolsRequestsWSMessagesCopy from "../assets/screenshots/devtools_requests_ws_messages_copy.png";
import DevtoolsRequestsFilterFirefox from "../assets/screenshots/firefox/network_tab_firefox.png";
import DevtoolsRequestsCopyFirefox from "../assets/screenshots/firefox/copy_curl_request.png";
// Firefox does not support extracting raw WebSocket response data yet.
// Once it does we can start using these.
// import FirefoxDevtoolsRequestsWS from "../assets/screenshots/firefox/devtools_requests_ws.png";
// import FirefoxDevtoolsRequestsFilter from "../assets/screenshots/firefox/devtools_requests_filter.png";
// import FirefoxDevtoolsRequestsWSResponse from "../assets/screenshots/firefox/devtools_requests_ws_response.png";
// import FirefoxDevtoolsRequestsWSResponseSizeColumn from "../assets/screenshots/firefox/devtools_requests_ws_response_sizecolumn.png";
// import FirefoxDevtoolsRequestsWSMessagesSelect from "../assets/screenshots/firefox/devtools_requests_ws_messages_select.png";
// import FirefoxDevtoolsRequestsWSMessagesCopy from "../assets/screenshots/firefox/devtools_requests_ws_messages_copy.png";
import { Accordion } from "./Accordion";
import { getCombo } from "../utils/keycombos";
@@ -14,7 +23,7 @@ import { Code } from "./Code";
import { useState, useEffect } from "react";
import { ErrorsList } from "./ErrorsList";
import { getSourceUrl } from "../utils/links";
import { parseCURL } from "../utils/curlparser";
import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack";
type PasteEncryptedDataProps = {
onEncryptedDataPasted: (data?: SyncRequestBody) => void;
@@ -37,16 +46,7 @@ export type SyncRequestBody = {
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 SAMPLE_CURL = `Paste raw base64 encoded data here.`;
const steps = {
chromium: [
@@ -58,6 +58,13 @@ const steps = {
<>
Switch to the <Code text="Network" /> tab.
</>,
<Flex sx={{ flexDirection: "column" }}>
<Text as="p">
Make sure you have <Code text="WS" /> toggled instead of{" "}
<Code text="Fetch/XHR" />
</Text>
<Image src={DevtoolsRequestsWS} width={400} sx={{ mt: 1 }} />
</Flex>,
<Flex sx={{ flexDirection: "column" }}>
<Text as="p">
In the filter input, type <Code text="sync" /> to filter out sync
@@ -66,63 +73,134 @@ const steps = {
<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.
<b>Refresh the page by pressing F5</b> (this is done so we can capture the
websocket request that happens on startup).
</>,
<>
In the requests table in <Code text="Network" /> tab, you'll now see 3 or
more new requests popup.
Wait until a request starting with <Code text="sync?access_token=" /> pops
up. It'll probably be the only one popping up and might take a bit so
don't panic if you don't see anything.
</>,
<>Left-click on this request to open its details.</>,
<Flex sx={{ flexDirection: "column" }}>
<Text as="p">
Switch to the <Code text="Messages" /> tab.
</Text>
<Image src={DevtoolsRequestsWSMessages} width={400} sx={{ mt: 1 }} />
</Flex>,
<>
At this point, you will a list of Binary messages. None of these will
appear really useful. This is normal.
</>,
<>
We are interested in only the <Code text="sync" /> request. Right click on
this request to open the context menu.
Now try editing one of your notes and syncing. (Make sure to keep the{" "}
<Code text="Network" /> tab open.)
</>,
<Flex sx={{ flexDirection: "column" }}>
<Text as="p">
From the context menu, go to <Code text="Copy" /> &amp; click on{" "}
<Code text="Copy as cURL" />.
You will see a bunch of new <Code text="Binary Messages" /> appear in
the list. Find the one with the largest Length (in KBs). Also make sure
the output type is set to <Code text="Base64" /> as in the screenshot
below.
</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 }} />
<Image
src={DevtoolsRequestsWSMessagesSelect}
width={700}
sx={{ mt: 1, maxWidth: "95%" }}
/>
</Flex>,
<Flex sx={{ flexDirection: "column" }}>
<Text as="p">Click on the copy button to copy the payload.</Text>
<Image src={DevtoolsRequestsWSMessagesCopy} width={200} sx={{ mt: 1 }} />
</Flex>,
<> Paste it below to see the decrypted data.</>,
],
firefox: [],
// firefox: [
// "Focus the Notesnook tab in your browser.",
// <>
// Press <KeyCombo combo={getCombo("firefox", "developerTools")} /> to open
// Developer Tools.
// </>,
// <>
// Switch to the <Code text="Network" /> tab.
// </>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// Make sure you have <Code text="WS" /> toggled instead of{" "}
// <Code text="Fetch/XHR" />
// </Text>
// <Image src={FirefoxDevtoolsRequestsWS} width={400} sx={{ mt: 1 }} />
// </Flex>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// In the filter input, type <Code text="sync" /> to filter out sync
// requests.
// </Text>
// <Image src={FirefoxDevtoolsRequestsFilter} width={400} sx={{ mt: 1 }} />
// </Flex>,
// <>
// <b>Refresh the page by pressing F5</b> (this is done so we can capture the
// websocket request that happens on startup).
// </>,
// <>
// Wait until a request starting with <Code text="sync?access_token=" /> pops
// up. It'll probably be the only one popping up and might take a bit so
// don't panic if you don't see anything.
// </>,
// <>Left-click on this request to open its details.</>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// Switch to the <Code text="Response" /> tab.
// </Text>
// <Image
// src={FirefoxDevtoolsRequestsWSResponse}
// width={400}
// sx={{ mt: 1 }}
// />
// </Flex>,
// <>
// At this point, you will a list of Binary messages. None of these will
// appear really useful. This is normal.
// </>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// To help in finding the right request, <Code text="Right click" /> on the
// table header &amp; enable the <Code text="Size" /> column.
// </Text>
// <Image
// src={FirefoxDevtoolsRequestsWSResponseSizeColumn}
// width={300}
// sx={{ mt: 1 }}
// />
// </Flex>,
// <>
// Now try editing one of your notes and syncing. (Make sure to keep the{" "}
// <Code text="Network" /> tab open.)
// </>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// You will see a bunch of new items appear in the table. Find the one with
// the largest Length (in KBs).
// </Text>
// <Image
// src={FirefoxDevtoolsRequestsWSMessagesSelect}
// width={700}
// sx={{ mt: 1, maxWidth: "95%" }}
// />
// </Flex>,
// <Flex sx={{ flexDirection: "column" }}>
// <Text as="p">
// Right-click on the item &amp; click on <Code text="Copy Message" />
// </Text>
// <Image
// src={FirefoxDevtoolsRequestsWSMessagesCopy}
// width={400}
// sx={{ mt: 1 }}
// />
// </Flex>,
// <> Paste it below to see the decrypted data.</>,
// ],
};
const isChromium = Platform.name === "Chrome";
@@ -161,23 +239,37 @@ export function PasteEncryptedData(props: PasteEncryptedDataProps) {
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}
{isChromium ? (
<>
<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>
<Text as="ol" variant="body" sx={{ mb: 2 }}>
{instructions?.map((item) => (
<Text as="li" sx={{ mt: 1 }}>
{item}
</Text>
))}
</Text>
</>
) : (
<Flex sx={{ bg: "errorBg", p: 1 }}>
<Text as="p" variant="body" sx={{ color: "error" }}>
Currently Firefox does not support pasting raw data from the
WebSocket response view.{" "}
<b>Please use a Chromium-based browser</b> for grabbing the
necessary data.
</Text>
</Flex>
)}
</Accordion>
<Textarea
variant="forms.clean"
placeholder={SAMPLE_CURL}
spellCheck={false}
sx={{
mt: 2,
fontSize: "body",
@@ -188,28 +280,67 @@ export function PasteEncryptedData(props: PasteEncryptedDataProps) {
onChange={(e) => {
try {
setError(undefined);
const curlRequest = e.target.value;
if (!curlRequest) return setEncryptedData(undefined);
const parsed = parseCURL(curlRequest);
const base64Data = e.target.value;
if (parsed.url !== "https://api.notesnook.com/sync") {
throw new Error(
"Invalid cURL request. URL must be https://api.notesnook.com/sync."
);
const protocol = new MessagePackHubProtocol();
const messages = protocol
.parseMessages(toArrayBuffer(base64Data), {
log: console.log,
})
.filter((m) => m.type === 1);
if (!messages.length) {
setError("Invalid message.");
return setEncryptedData(undefined);
}
if (!parsed.data)
throw new Error("Invalid cURL request. No data found.");
const syncData: SyncRequestBody = {
attachments: [],
content: [],
notebooks: [],
notes: [],
};
for (const message of messages) {
if (message.type === 1) {
const { items, types } = message.arguments[0] as {
items?: string[];
types?: string[];
};
if (!items || !types) continue;
for (let i = 0; i < types.length; ++i) {
const itemType = types[i];
const item = JSON.parse(items[i]) as EncryptedSyncItem;
switch (itemType) {
case "note":
syncData.notes.push(item);
break;
case "notebook":
syncData.notebooks.push(item);
break;
case "content":
syncData.content.push(item);
break;
case "attachment":
syncData.attachments.push(item);
break;
default:
continue;
}
}
}
}
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."
"The pasted data does not contain any notes. Please select another payload."
);
}
setEncryptedData(syncData);
} catch (e) {
console.error(e);
const error = e as Error;
setError(error.message);
}
@@ -239,3 +370,13 @@ export function PasteEncryptedData(props: PasteEncryptedDataProps) {
</StepContainer>
);
}
function toArrayBuffer(payload: string) {
var binary_string = window.atob(payload);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}

View File

@@ -1,169 +0,0 @@
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

@@ -14,6 +14,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"downlevelIteration": true,
"jsx": "react-jsx"
},
"exclude": ["node_modules"],

BIN
badges/app-store-badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -1,21 +1,23 @@
---
icon: pencil
order: 990
label: Creating a note
label: Create a note
---
# Creating a note
You are in a notes app, the first thing you want to do is make a note.
# Create a note
Once you have successfully installed [Notesnook](https://notesnook.com/downloads/), the first thing you may want to do is create a note.
To create a note on
+++ Desktop/Web
1. Click on the `+` button on top right corner or click anywhere inside the editor to focus it.
2. Start writing and your note will be created.
3. As you write, note will be saved automatically whenever you stop for a second.
1. Click on the green colored `+` button in the top right corner of the `Notes` section.
2. Start writing in the editor and your note will be created automatically with your current date and time as note title. Alternatively, you can name your note title yourself.
3. Notes will be saved automatically whenever you stop writing for a time interval of more than one second.
+++ Mobile/Tablet
1. On mobile, just like desktop, regardless of what screen you have opened, you can always swipe from right to left and open the editor.
2. You can also press the `+` button on bottom right corner to open the editor when you are in `Notes`, a `Notebook` or `Tag`.
3. Start writing in editor and your note will be created.
4. As you write, note will be saved automatically whenever you stop for a second.
1. On mobile, regardless of what section of the app you have opened, you can always swipe from right to left and open the editor.
2. Alternatively, you can also press the `+` button in the bottom right corner to open the editor when you are in `Notes`, a `Notebook`, or `Tag` section.
3. Start writing in the editor and your note will be created automatically with your current date and time as note title. Alternatively, you can name your note title yourself.
4. Notes will be saved automatically whenever you stop writing for a time interval of more than one second.
+++
And that is how you create your first private note on Notesnook!
Congratulations, You have successfully created your first private note on Notesnook!

View File

@@ -11,28 +11,28 @@ We are currently working on writing detailed docs for our app. The current versi
!!!
Notesnook is a simple and cross-platform **private notes app** that keeps all your notes organized and synced on your phone, tablet and computer.
Notesnook is a simple and cross-platform **private notes app** that keeps all your notes organized and synced between your phone, tablet and computer.
<img src="https://github.com/streetwriters/notesnook/raw/main/banners/crossplatform.jpg" alt="Notesnook - Take private notes"/>
We use best-in-class encryption algorithms **XChaCha20-Poly1305 & Argon2** that are stronger & safer than AES256, impenetrable & future-proof. Your notes are encrypted on your device. All your notes are 100% private. **No one but you can read your notes (not even us).**
We use best-in-class encryption algorithms **XChaCha20-Poly1305 & Argon2** that are stronger & safer than AES256, impenetrable & future-proof. Your notes are always encrypted on your device first and then stored on servers. All your notes are 100% private. **No one but only you can read your notes (not even us).**
Take notes, capture ideas, create to-do lists, keep a journal, write your assignments and manage projects, we have got you covered everywhere.
## Features
Privacy isn't the only thing that Notesnook has to offer:
Privacy isn't the only thing that Notesnook offers:
1. Cross platform - Notesnook has apps on all platforms and works even on your browser.
2. End to end encrypted private note syncing to unlimted devices
3. Organize your notes with notebooks, tags and colors
4. Encrypt/lock your notes in a private vault locally
5. Full rich text editor with markdown support
6. Focus mode (distraction-free mode)
7. Export notes in HTML, Markdown, PDF & Text formats
8. Automatic backups
9. Dark mode and custom accents
10. Notesnook Monograph - Share your notes on public url with one click in a safe and secure way
1. Cross platform - Notesnook supports apps on all platforms and works even on your browser.
2. End to end encrypted private note syncing between unlimited devices.
3. Organize your notes with notebooks, tags and colors.
4. Encrypt/lock your notes in a private vault locally.
5. Full rich text editor with markdown support.
6. Focus mode (distraction-free mode).
7. Export notes in HTML, Markdown, PDF & other Text formats.
8. Automatic backups.
9. Dark mode and custom accents.
10. Notesnook Monograph - Share your notes on public url with just one click in a safe and secure way.
Notesnook is specifically designed for each platform to give you the best private note taking experience.
@@ -42,8 +42,8 @@ Notesnook is available to download on Android, iOS, Windows, MacOS, and Linux. P
## Support
Technical support questions are best asked on our [Discord](https://discord.com/invite/5davZnhw3V). We are available there 24/7 to respond to your queries. You can also chat with us there and other members who are using notesnook and share your experience.
For technical questions, doubts or support, join our [Discord](https://discord.com/invite/5davZnhw3V). We are available 24/7 to respond to your queries. You can also join to communicate our ever growing community members and share your experiences on Discord.
If you find a bug or would like to submit a feature request, please create an [Issue](https://github.com/streetwriters/notesnook/issues) and we will investigate right away.
If you found any bug or like to submit a feature request, please create an [Issue](https://github.com/streetwriters/notesnook/issues) and we will review right away.
Do you have a general inquiry? Please feel free to contact us at support@notesnook.com.
If you have any other general inquiry, feel free to contact us at support@notesnook.com.

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

View File

@@ -10,3 +10,4 @@ Supported providers:
6. Standard Notes
7. OneNote
8. Zoho Notebook
9. Joplin

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,20 @@
{
"name": "@notesnook/importer",
"private": false,
"version": "1.2.3",
"version": "1.3.3",
"main": "dist/index.js",
"description": "Convert notes from various note taking apps into a simple & standard format.",
"scripts": {
"test": "tap --ts --no-check-coverage --no-coverage",
"build": "tsc"
"build": "tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@notesnook/enex": "^1.0.3",
"@notesnook/onenote": "^1.0.2",
"@notesnook/znel": "^1.0.0",
"@standardnotes/common": "^1.8.0",
"@standardnotes/features": "^1.25.0",
"@standardnotes/common": "^1.19.8",
"@standardnotes/features": "^1.42.1",
"fflate": "^0.7.3",
"highlight.js": "^11.4.0",
"node-html-parser": "github:thecodrr/node-html-parser",

View File

@@ -0,0 +1,83 @@
import { ModelType } from "./types";
// Taken from https://github.com/laurent22/joplin/blob/6f1a806e5c7159a544de5d997b189e0e83a9d8ea/packages/lib/models/BaseItem.ts#L477-L523
export async function unserialize(content: string) {
const lines = content.split("\n");
let output: any = {};
let state = "readingProps";
const body: string[] = [];
for (let i = lines.length - 1; i >= 0; i--) {
let line = lines[i];
if (state == "readingProps") {
line = line.trim();
if (line == "") {
state = "readingBody";
continue;
}
const p = line.indexOf(":");
if (p < 0)
throw new Error(`Invalid property format: ${line}: ${content}`);
const key = line.substr(0, p).trim();
const value = line.substr(p + 1).trim();
output[key] = value;
} else if (state == "readingBody") {
body.splice(0, 0, line);
}
}
if (!output.type_)
throw new Error(`Missing required property: type_: ${content}`);
output.type_ = Number(output.type_);
if (body.length) {
const title = body.splice(0, 2);
output.title = title[0];
}
if (output.type_ === ModelType.Note) output.body = body.join("\n");
for (const n in output) {
if (!output.hasOwnProperty(n)) continue;
output[n] = await unserialize_format(output.type_, n, output[n]);
}
return output;
}
// Taken from https://github.com/laurent22/joplin/blob/dev/packages/lib/models/BaseItem.ts#L325-L351
// slightly modified to remove all database related code
function unserialize_format(type: ModelType, propName: string, propValue: any) {
if (propName[propName.length - 1] == "_") return propValue; // Private property
if (["title_diff", "body_diff"].indexOf(propName) >= 0) {
if (!propValue) return "";
propValue = JSON.parse(propValue);
} else if (["longitude", "latitude", "altitude"].indexOf(propName) >= 0) {
const places = propName === "altitude" ? 4 : 8;
propValue = Number(propValue).toFixed(places);
} else {
if (
[
"created_time",
"updated_time",
"user_created_time",
"user_updated_time",
].indexOf(propName) >= 0
) {
propValue = !propValue ? "0" : new Date(propValue).getTime();
}
}
if (propName === "body") return propValue;
return typeof propValue === "string"
? propValue
.replace(/\\n/g, "\n")
.replace(/\\r/g, "\r")
.replace(/\\\n/g, "\\n")
.replace(/\\\r/g, "\\r")
: propValue;
}

View File

@@ -0,0 +1,181 @@
import { ContentType, Note, Notebook } from "../../models/note";
import { File } from "../../utils/file";
import {
IFileProvider,
iterate,
ProviderResult,
ProviderSettings,
} from "../provider";
import { parse } from "node-html-parser";
import {
NoteEntity,
FolderEntity,
TagEntity,
NoteTagEntity,
ResourceEntity,
} from "./types";
import showdown from "showdown";
import { Attachment, attachmentToHTML } from "../../models/attachment";
import { HTMLRootElement } from "node-html-parser/dist/nodes/html";
import { IHasher } from "../../utils/hasher";
import { unserialize } from "./helpers";
import { ModelType } from "./types";
const converter = new showdown.Converter();
type JoplinData = {
notes: NoteEntity[];
noteTags: NoteTagEntity[];
folders: FolderEntity[];
tags: TagEntity[];
resources: ResourceEntity[];
};
export class Joplin implements IFileProvider {
type: "file" = "file";
public supportedExtensions = [".jex", ".md"];
public validExtensions = [...this.supportedExtensions];
public version = "1.0.0";
public name = "Joplin";
async process(
files: File[],
settings: ProviderSettings
): Promise<ProviderResult> {
const data: JoplinData = {
folders: [],
noteTags: [],
notes: [],
resources: [],
tags: [],
};
const notes: Note[] = [];
await iterate(this, files, async (file) => {
const item = await unserialize(file.text);
switch (item.type_) {
case ModelType.Note:
data.notes.push(item);
break;
case ModelType.NoteTag:
data.noteTags.push(item);
break;
case ModelType.Resource:
data.resources.push(item);
break;
case ModelType.Tag:
data.tags.push(item);
break;
case ModelType.Folder:
data.folders.push(item);
break;
}
return true;
});
for (const note of data.notes) {
if (!note.id || !note.body) continue;
const tags = this.resolveTags(note.id, data.tags, data.noteTags);
const parentFolder = data.folders.find((a) => a.id === note.parent_id);
const notebooks: Notebook[] = [];
const html = converter.makeHtml(note.body);
const document = parse(html);
const title =
note.title ||
document.querySelector("h1,h2")?.textContent ||
"Untitled note";
const attachments = await this.resolveResources(
data.resources,
document,
files,
settings.hasher
);
if (parentFolder) {
const notebook = this.resolveFolders(data.folders, parentFolder);
if (notebook) notebooks.push(notebook);
}
notes.push({
title,
dateCreated: note.user_created_time || note.created_time,
dateEdited: note.user_updated_time || note.updated_time,
tags,
attachments,
notebooks,
content: {
data: document.outerHTML,
type: ContentType.HTML,
},
});
}
return { errors: [], notes };
}
private resolveTags(
noteId: string,
tags: TagEntity[],
noteTags: NoteTagEntity[]
) {
return tags
.filter((t) => {
return noteTags.some(
(noteTag) => noteTag.tag_id === t.id && noteTag.note_id === noteId
);
})
.map((tag) => tag.title!);
}
private async resolveResources(
resources: ResourceEntity[],
document: HTMLRootElement,
files: File[],
hasher: IHasher
) {
const attachments: Attachment[] = [];
for (let resource of resources) {
if (!resource.id) continue;
const element = document.querySelector(
`[src=":/${resource.id}"],[href=":/${resource.id}"]`
);
if (!element) continue;
const resourceFile = files.find((f) =>
f.path?.includes(`resources/${resource.id!}`)
);
if (!resourceFile) continue;
const dataHash = await hasher.hash(resourceFile.bytes);
const attachment: Attachment = {
data: resourceFile?.bytes,
size: resourceFile?.bytes.length,
hash: dataHash,
filename: resource.title || resource.filename || dataHash,
hashType: hasher.type,
mime: resource.mime || "application/octet-stream",
};
attachments.push(attachment);
element.replaceWith(attachmentToHTML(attachment));
}
return attachments;
}
private resolveFolders(
folders: FolderEntity[],
parentFolder: FolderEntity
): Notebook | null {
let folder: FolderEntity | undefined = parentFolder;
let path: string[] = [parentFolder.title!];
while (folder?.parent_id) {
folder = folders.find((f) => f.id === folder?.parent_id);
if (!folder) break;
path.push(folder.title!);
}
const topMost = path.pop();
if (!topMost) return null;
return {
notebook: topMost,
topic: path.reverse().join(".") || "All notes",
};
}
}

View File

@@ -0,0 +1,125 @@
/**
* We cannot use @joplin/lib directly because Typescript throws errors
* during compilation. Until those errors are fixed upstream, we'll have to
* use extracted types.
*/
// Extracted from https://github.com/laurent22/joplin/blob/2acd55eb810d9d6f6e4b2e1c6adff9ce72dde956/packages/lib/BaseModel.ts
export enum ModelType {
Note = 1,
Folder = 2,
Setting = 3,
Resource = 4,
Tag = 5,
NoteTag = 6,
Search = 7,
Alarm = 8,
MasterKey = 9,
ItemChange = 10,
NoteResource = 11,
ResourceLocalState = 12,
Revision = 13,
Migration = 14,
SmartFilter = 15,
Command = 16,
}
/**
* All the below types have been extracted from: https://github.com/laurent22/joplin/blob/2acd55eb810d9d6f6e4b2e1c6adff9ce72dde956/packages/lib/services/database/types.ts
*/
export interface FolderEntity {
id?: string | null;
title?: string;
created_time?: number;
updated_time?: number;
user_created_time?: number;
user_updated_time?: number;
encryption_cipher_text?: string;
encryption_applied?: number;
parent_id?: string;
is_shared?: number;
share_id?: string;
master_key_id?: string;
icon?: string;
type_?: number;
}
export interface NoteTagEntity {
id?: string | null;
note_id?: string;
tag_id?: string;
created_time?: number;
updated_time?: number;
user_created_time?: number;
user_updated_time?: number;
encryption_cipher_text?: string;
encryption_applied?: number;
is_shared?: number;
type_?: number;
}
export interface NoteEntity {
id?: string | null;
parent_id?: string;
title?: string;
body?: string;
created_time?: number;
updated_time?: number;
is_conflict?: number;
latitude?: number;
longitude?: number;
altitude?: number;
author?: string;
source_url?: string;
is_todo?: number;
todo_due?: number;
todo_completed?: number;
source?: string;
source_application?: string;
application_data?: string;
order?: number;
user_created_time?: number;
user_updated_time?: number;
encryption_cipher_text?: string;
encryption_applied?: number;
markup_language?: number;
is_shared?: number;
share_id?: string;
conflict_original_id?: string;
master_key_id?: string;
type_?: number;
}
export interface ResourceEntity {
id?: string | null;
title?: string;
mime?: string;
filename?: string;
created_time?: number;
updated_time?: number;
user_created_time?: number;
user_updated_time?: number;
file_extension?: string;
encryption_cipher_text?: string;
encryption_applied?: number;
encryption_blob_encrypted?: number;
size?: number;
is_shared?: number;
share_id?: string;
master_key_id?: string;
type_?: number;
}
export interface TagEntity {
id?: string | null;
title?: string;
created_time?: number;
updated_time?: number;
user_created_time?: number;
user_updated_time?: number;
encryption_cipher_text?: string;
encryption_applied?: number;
is_shared?: number;
parent_id?: string;
type_?: number;
}

View File

@@ -6,6 +6,7 @@ import { GoogleKeep } from "./keep";
import { Simplenote } from "./simplenote";
import { ZohoNotebook } from "./zohonotebook";
import { OneNote } from "./onenote";
import { Joplin } from "./joplin";
const providerMap = {
evernote: Evernote,
@@ -16,18 +17,10 @@ const providerMap = {
onenote: OneNote,
standardnotes: StandardNotes,
zohonotebook: ZohoNotebook,
joplin: Joplin,
};
type ProvidersMap = {
evernote: Evernote;
md: Markdown;
html: HTML;
keep: GoogleKeep;
simplenote: Simplenote;
onenote: OneNote;
standardnotes: StandardNotes;
zohonotebook: ZohoNotebook;
};
type ProvidersMap = typeof providerMap;
export type Providers = keyof ProvidersMap;
@@ -38,8 +31,8 @@ export class ProviderFactory {
static getProvider<TProvider extends Providers>(
provider: TProvider
): ProvidersMap[TProvider] {
const Provider = <unknown>new providerMap[provider]();
return <ProvidersMap[TProvider]>Provider;
): InstanceType<ProvidersMap[TProvider]> {
const Provider = new providerMap[provider]();
return Provider as InstanceType<ProvidersMap[TProvider]>;
}
}

View File

@@ -38,7 +38,7 @@ const defaultEditorDescription = (item: SNNote): EditorDescription => {
export class StandardNotes implements IFileProvider {
public type: "file" = "file";
public supportedExtensions = [".txt"];
public supportedExtensions = [".txt", ".zip"];
public validExtensions = [...this.supportedExtensions];
public version = "1.0.0";
public name = "Standard Notes";
@@ -81,10 +81,11 @@ export class StandardNotes implements IFileProvider {
});
for (let item of snnotes) {
const { createdAt, updatedAt } = this.getTimestamps(item);
let note: Note = {
title: item.content.title,
dateCreated: item.created_at_timestamp,
dateEdited: item.updated_at_timestamp || item.created_at_timestamp,
dateCreated: createdAt,
dateEdited: updatedAt,
pinned: <boolean>item.content.appData[DefaultAppDomain]?.pinned,
tags: this.getTags(item, tags),
content: this.parseContent(item, components),
@@ -224,4 +225,23 @@ export class StandardNotes implements IFileProvider {
};
}
}
private getTimestamps(note: SNNote) {
let createdAt =
typeof note.created_at === "string"
? new Date(note.created_at).getTime()
: note.created_at_timestamp
? note.created_at_timestamp / 1000
: undefined;
let updatedAt =
typeof note.updated_at === "string"
? new Date(note.updated_at).getTime()
: note.updated_at_timestamp
? note.updated_at_timestamp / 1000
: undefined;
if (updatedAt === 0) updatedAt = undefined;
if (createdAt === 0) createdAt = undefined;
return { createdAt, updatedAt };
}
}

View File

@@ -1,18 +1,30 @@
import type { AppDataField, BackupFile } from "@standardnotes/snjs";
import { ComponentArea, EditorFeatureDescription, NoteType } from "@standardnotes/features";
import {
AppDataField,
BackupFile,
BackupFileDecryptedContextualPayload,
ItemContent,
} from "@standardnotes/snjs";
import {
ComponentArea,
EditorFeatureDescription,
NoteType,
} from "@standardnotes/features";
import { ContentType } from "@standardnotes/common";
import {RawPayload, NoteContent, TagContent, ComponentContent} from "@standardnotes/snjs";
import { NoteContent, TagContent, ComponentContent } from "@standardnotes/snjs";
enum ProtocolVersion {
V001 = "001",
V002 = "002",
V003 = "003",
V004 = "004"
V004 = "004",
}
const ComponentDataDomain = 'org.standardnotes.sn.components';
const ComponentDataDomain = "org.standardnotes.sn.components";
const DefaultAppDomain = "org.standardnotes.sn";
type SNBackupItem<TContentType extends ContentType, TContent> = RawPayload & {
type SNBackupItem<
TContentType extends ContentType,
TContent extends ItemContent
> = BackupFileDecryptedContextualPayload<TContent> & {
content_type: TContentType;
content: TContent & {
appData: {
@@ -20,14 +32,14 @@ type SNBackupItem<TContentType extends ContentType, TContent> = RawPayload & {
[ComponentDataDomain]?: Record<string, any | null | undefined>;
};
};
created_at?: string;
updated_at?: string;
};
export type SNNote = SNBackupItem<ContentType.Note, NoteContent>;
export type SNComponent = SNBackupItem<ContentType.Component, ComponentContent>;
export type SNTag = SNBackupItem<ContentType.Tag, TagContent>;
export type SNBackup = {
items: RawPayload[];
} & BackupFile;
export type SNBackup = BackupFile;
export type EditorDescription = Pick<
EditorFeatureDescription,
"note_type" | "file_type"
@@ -41,7 +53,10 @@ export type Spreadsheet = {
};
export {
DefaultAppDomain, ComponentDataDomain, ProtocolVersion,
DefaultAppDomain,
ComponentDataDomain,
ProtocolVersion,
ContentType,
ComponentArea, NoteType
ComponentArea,
NoteType,
};

View File

@@ -25,17 +25,24 @@ type Unpacker = (file: IFile) => IFile[];
const unpackers: Record<string, Unpacker> = {
".zip": unzip,
".jex": untar,
".znote": untar,
};
function isArchive(file: IFile) {
return file.name.endsWith(".zip") || file.name.endsWith(".znote");
for (let unpacker in unpackers) {
if (file.name.endsWith(unpacker)) return true;
}
return false;
}
export function unpack(files: IFile[], root?: string): File[] {
const extracted: File[] = [];
for (let file of files) {
if (file.path && root) file.path = path.join(root, file.path);
extracted.push(new File(file));
if (isArchive(file)) {
try {
const root = path.basename(file.name, "");
@@ -45,9 +52,6 @@ export function unpack(files: IFile[], root?: string): File[] {
console.error(e);
continue;
}
} else {
if (file.path && root) file.path = path.join(root, file.path);
extracted.push(new File(file));
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@
"@azure/msal-node-extensions": "^1.0.0-alpha.15",
"@microsoft/microsoft-graph-client": "^3.0.1",
"dotenv": "^16.0.0",
"isomorphic-fetch": "^3.0.0"
"isomorphic-fetch": "^3.0.0",
"node-html-parser": "github:thecodrr/node-html-parser"
}
}

View File

@@ -5,7 +5,8 @@
"description": "All the custom made or modified plugins for TinyMCE used in Notesnook & other apps maintained by Streetwriters team.",
"main": "index.js",
"scripts": {
"test": "node scripts/testHarness.js"
"test": "node scripts/testHarness.js",
"build": "echo \"Noop.\""
},
"keywords": [
"tinymce",

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/znel"
},
"dependencies": {
"base64-arraybuffer": "^1.0.2",
"node-html-parser": "github:thecodrr/node-html-parser",
"spark-md5": "^3.0.2"
}
}