├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── bug_report.md
├── SECURITY.md
├── SUPPORT.md
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── public
├── favicon.ico
├── icons
│ ├── avatar.png
│ ├── logo-180x180.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── dbk
│ │ ├── drabkirn-logo-72x72.png
│ │ ├── drabkirn-logo-96x96.png
│ │ ├── drabkirn-logo-128x128.png
│ │ ├── drabkirn-logo-144x144.png
│ │ ├── drabkirn-logo-152x152.png
│ │ ├── drabkirn-logo-192x192.png
│ │ ├── drabkirn-logo-384x384.png
│ │ └── drabkirn-logo-512x512.png
├── robots.txt
├── images
│ ├── og_image.png
│ └── illustrations
│ │ ├── privacy.svg
│ │ ├── factory.svg
│ │ ├── lockin.svg
│ │ ├── storage.svg
│ │ └── notebook.svg
├── fonts
│ ├── fontawesome
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
│ ├── material-icons
│ │ ├── MaterialIcons-Regular.eot
│ │ ├── MaterialIcons-Regular.ttf
│ │ ├── MaterialIcons-Regular.woff
│ │ └── MaterialIcons-Regular.woff2
│ ├── rubik-mono-one
│ │ ├── rubik-mono-one-v8-latin-regular.eot
│ │ ├── rubik-mono-one-v8-latin-regular.ttf
│ │ ├── rubik-mono-one-v8-latin-regular.woff
│ │ └── rubik-mono-one-v8-latin-regular.woff2
│ └── source-code-pro
│ │ ├── source-code-pro-v11-latin-regular.eot
│ │ ├── source-code-pro-v11-latin-regular.ttf
│ │ ├── source-code-pro-v11-latin-regular.woff
│ │ └── source-code-pro-v11-latin-regular.woff2
├── manifest.json
├── css
│ └── fonts.min.css
└── index.html
├── drabkirn-logo-120x120.png
├── src
├── components
│ ├── Shared
│ │ ├── history.js
│ │ ├── generateUUID.js
│ │ ├── Highlight.js
│ │ ├── Footer.js
│ │ ├── FloatingIcon.js
│ │ ├── Loading.js
│ │ ├── keyboardShortcuts.js
│ │ ├── Navbar.js
│ │ └── defaults.js
│ ├── Assets
│ │ └── scss
│ │ │ ├── _media.scss
│ │ │ ├── _config.scss
│ │ │ └── App.scss
│ ├── App.js
│ ├── Login
│ │ └── Login.js
│ ├── Notebook
│ │ ├── New.js
│ │ ├── Show.js
│ │ └── Edit.js
│ ├── Dash
│ │ └── Dash.js
│ └── Home
│ │ └── Home.js
├── store
│ ├── reducers
│ │ ├── rootReducer.js
│ │ ├── tagsReducer.js
│ │ └── notesReducer.js
│ └── actions
│ │ ├── tagsAction.js
│ │ └── notesAction.js
├── index.js
└── serviceWorker.js
├── .gitignore
├── config-overrides.js
├── package.json
├── README.md
├── CODE_OF_CONDUCT.md
└── CONTRIBUTING.md
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/icons/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/avatar.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/drabkirn-logo-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/drabkirn-logo-120x120.png
--------------------------------------------------------------------------------
/public/images/og_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/images/og_image.png
--------------------------------------------------------------------------------
/public/icons/logo-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/logo-180x180.png
--------------------------------------------------------------------------------
/public/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/fonts/fontawesome/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/fontawesome/FontAwesome.otf
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-72x72.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-96x96.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-128x128.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-144x144.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-152x152.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-192x192.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-384x384.png
--------------------------------------------------------------------------------
/public/icons/dbk/drabkirn-logo-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/icons/dbk/drabkirn-logo-512x512.png
--------------------------------------------------------------------------------
/public/fonts/fontawesome/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/fontawesome/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/fontawesome/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/fontawesome/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/fontawesome/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/fontawesome/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/fontawesome/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/fontawesome/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/material-icons/MaterialIcons-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/material-icons/MaterialIcons-Regular.eot
--------------------------------------------------------------------------------
/public/fonts/material-icons/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/material-icons/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/public/fonts/material-icons/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/material-icons/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/public/fonts/material-icons/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/material-icons/MaterialIcons-Regular.woff2
--------------------------------------------------------------------------------
/src/components/Shared/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history';
2 |
3 | export default createBrowserHistory({
4 | basename: process.env.PUBLIC_URL
5 | });
--------------------------------------------------------------------------------
/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.eot
--------------------------------------------------------------------------------
/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.ttf
--------------------------------------------------------------------------------
/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff
--------------------------------------------------------------------------------
/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff2
--------------------------------------------------------------------------------
/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.eot
--------------------------------------------------------------------------------
/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.ttf
--------------------------------------------------------------------------------
/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.woff
--------------------------------------------------------------------------------
/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drabkirn/notga/HEAD/public/fonts/source-code-pro/source-code-pro-v11-latin-regular.woff2
--------------------------------------------------------------------------------
/src/store/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import notesReducer from './notesReducer';
2 | import tagsReducer from './tagsReducer';
3 |
4 | import { combineReducers } from 'redux';
5 |
6 | const rootReducer = combineReducers({
7 | notes: notesReducer,
8 | tags: tagsReducer
9 | });
10 |
11 | export default rootReducer;
--------------------------------------------------------------------------------
/src/components/Shared/generateUUID.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const generateUUID = () => {
3 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
4 | .replace(/[xy]/g, (c) => {
5 | const r = Math.random() * 16 | 0;
6 | const v = c === 'x' ? r : (r & 0x3 | 0x8);
7 | return v.toString(16);
8 | });
9 | };
10 |
11 | export default generateUUID;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/store/reducers/tagsReducer.js:
--------------------------------------------------------------------------------
1 | let initState = {
2 | isFetching: true,
3 | tagsData: null,
4 | err: null
5 | };
6 |
7 | const tagsReducer = (state = initState, action) => {
8 | switch (action.type){
9 | case 'TAGS_FILE_FETCH_SUCCESS':
10 | return {
11 | ...state,
12 | tagsData: action.payload.data,
13 | isFetching: false
14 | };
15 | case 'TAGS_FILE_POST_SUCCESS':
16 | return {
17 | ...state,
18 | tagsData: action.payload.data,
19 | isFetching: false
20 | };
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 |
27 | export default tagsReducer;
--------------------------------------------------------------------------------
/src/store/reducers/notesReducer.js:
--------------------------------------------------------------------------------
1 | let initState = {
2 | isFetching: true,
3 | notesData: null,
4 | err: null
5 | };
6 |
7 | const notesReducer = (state = initState, action) => {
8 | switch (action.type){
9 | case 'NOTEBOOK_FILE_FETCH_SUCCESS':
10 | return {
11 | ...state,
12 | notesData: action.payload.data,
13 | isFetching: false
14 | };
15 | case 'NOTEBOOK_FILE_POST_SUCCESS':
16 | return {
17 | ...state,
18 | notesData: action.payload.data,
19 | isFetching: false
20 | };
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 |
27 | export default notesReducer;
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Enhancement
3 | about: For new features, functions, and improvements.
4 | title: ''
5 | labels: enhancement
6 | assignees: cdadityang
7 |
8 | ---
9 |
10 |
11 |
12 | ### What is the current behavior?*
13 |
14 |
15 |
16 | ### What is the expected behavior?
17 |
18 |
19 |
20 | ### What is the motivation/use case for changing the behavior?
21 |
22 |
23 | ### Other information:
24 | *Example: Detailed explanation, related issues, screnshoots, suggestions on how to improve, links for us to have context*
--------------------------------------------------------------------------------
/src/components/Shared/Highlight.js:
--------------------------------------------------------------------------------
1 | import React, { createRef, useEffect } from 'react';
2 |
3 | import 'highlight.js/styles/github.css';
4 |
5 | import hljs from 'highlight.js/lib/index';
6 |
7 | function Highlight(props) {
8 | const nodeRef = createRef();
9 |
10 | useEffect(() => {
11 | initHighlightJS();
12 | }, [props.content]);
13 |
14 | const initHighlightJS = () => {
15 | if(nodeRef) {
16 | const nodes = nodeRef.current.querySelectorAll('pre');
17 | nodes.forEach((node) => {
18 | hljs.highlightBlock(node);
19 | });
20 | }
21 | };
22 |
23 | return(
24 |
25 | );
26 | }
27 |
28 | export default Highlight;
--------------------------------------------------------------------------------
/src/components/Assets/scss/_media.scss:
--------------------------------------------------------------------------------
1 | @media only screen and (max-width: 992px) {
2 | // Navbar
3 | nav.custom-nav {
4 | .brand-logo {
5 | margin-left: 0;
6 | }
7 | }
8 | }
9 |
10 | @media only screen and (max-width: 600px) {
11 | // Defaults
12 | h1 {
13 | font-size: 2.5rem;
14 | }
15 |
16 | h2 {
17 | font-size: 2.2rem;
18 | }
19 |
20 | h3 {
21 | font-size: 1.8rem;
22 | }
23 |
24 |
25 | // Helpers
26 | .flex-center-vh {
27 | display: block;
28 | }
29 |
30 |
31 | // New/Edit
32 | .form-tag {
33 | & &-title-field {
34 | input {
35 | font-size: 1.3rem;
36 | }
37 | }
38 | }
39 |
40 |
41 | // Footer
42 | footer {
43 | & .footer-left {
44 | text-align: center;
45 | }
46 |
47 | & .footer-right {
48 | justify-content: center;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Our security policy
2 |
3 | Safety and security are critical and of the utmost priority to us. If you have discovered a security vulnerability in our codebase, we would appreciate your help in disclosing it to us in a responsible manner.
4 |
5 | ## Reporting security issues:
6 | We request you **NOT** do disclose any security-related information using GitHub issues, instead, send us an email at [drabkirn@cdadityang.xyz](mailto:drabkirn@cdadityang.xyz), and we'll get back to you as soon as possible(ASAP) and keep updating you throughout the patching process.
7 |
8 | We're committed to working for open-source and free software, so we'll not be able to reward you for reporting security vulnerabilities. However, If you choose, after patching the security issue submitted by you, we'll credit you publicly on our website for your efforts.
9 |
10 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖
--------------------------------------------------------------------------------
/src/components/Assets/scss/_config.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $white: #FFFFFF;
3 | $oxford_blue: #04052E;
4 | $rich_black: #02010A;
5 | $apple_red: #F40000;
6 | $forest_green: #248232;
7 | $honey_dew: #D7F3E5;
8 | $tea_green: #D3F9B5;
9 |
10 | $available-colors-list: (
11 | "white": $white,
12 | "oxford-blue": $oxford_blue,
13 | "rich-black": $rich_black,
14 | "apple-red": $apple_red,
15 | "forest-green": $forest_green,
16 | "honey-dew": $honey_dew,
17 | "tea-green": $tea_green
18 | );
19 |
20 | $font-amounts: (
21 | "1-1": 1.1,
22 | "1-2": 1.2
23 | );
24 |
25 | $margin-amounts: (
26 | "1rem": 1,
27 | "2rem": 2,
28 | "3rem": 3,
29 | "5rem": 5,
30 | "10rem": 10
31 | );
32 |
33 | // Set text color based on BG
34 | @function set-text-color($color) {
35 | @if(lightness($color) >= 50) {
36 | @return $rich_black;
37 | } @else {
38 | @return $white;
39 | }
40 | }
41 |
42 | // Set BG and text color
43 | @mixin set-background($color) {
44 | background-color: $color !important;
45 | color: set-text-color($color) !important;
46 | }
--------------------------------------------------------------------------------
/.github/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Our Support
2 |
3 | We're committed to providing the best support to our community. You've come to a great place.
4 |
5 | ## Documentation
6 | Please take the time to read our [documentation](https://go.cdadityang.xyz/docs) for installation and other guides. Also, more detailed information is available in our project's [README file](https://github.com/drabkirn/notga/blob/master/README.md).
7 |
8 | ## Bugs
9 | If you've found a bug and before submitting that bug, Check that [our issue database](https://github.com/drabkirn/notga/issues)
10 | doesn't already include your problem or suggestion. If no one has reported the problem, you can [Open a new issue here](https://github.com/drabkirn/notga/issues/new/choose)
11 |
12 | ## Security Vulnerabilities
13 | We have a dedicated section for security-related support, [check that here](https://github.com/drabkirn/notga/blob/master/.github/SECURITY.md).
14 |
15 | Thank you for checking out Drabkirn. You can always reach us at [drabkirn@cdadityang.xyz](mailto:drabkirn@cdadityang.xyz) for any information, and we'll respond as soon as possible.
16 |
17 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖
--------------------------------------------------------------------------------
/src/components/Shared/Footer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | function Footer() {
4 | useEffect(() => {
5 | setTimeout(() => {
6 | const bodyTag = document.querySelector('body');
7 | const bodyHeight = bodyTag.clientHeight;
8 | const windowHeight = window.innerHeight;
9 | if(bodyHeight <= windowHeight) {
10 | const footerTag = document.querySelector('footer');
11 | const footerHeight = footerTag.clientHeight;
12 |
13 | const finalMarginTop = windowHeight - bodyHeight + footerHeight;
14 |
15 | footerTag.style.marginTop = finalMarginTop + "px";
16 | }
17 | }, 2000);
18 | }, []);
19 |
20 | return(
21 |
22 |
23 |
24 |
Copyrights ©, 2019-2020 - Drabkirn
25 |
26 |
27 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Footer;
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### What kind of change does this PR introduce?
2 | *Example: Bug fix, feature, docs update, ...*
3 |
4 | - [ ] Bug fix (non-breaking change which fixes an issue)
5 | - [ ] New feature (non-breaking change which adds functionality)
6 | - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected)
7 | - [ ] Documentation update
8 | - [ ] Others
9 |
10 |
11 | ### What is the current behavior?
12 | *Note: You can also link to an open issue here. If appropriate, provide us some screenshots.*
13 |
14 |
15 | ### What is the new behavior?
16 | *Note: Use this only if this is a feature. If appropriate, provide us some screenshots.*
17 |
18 |
19 | ### Other information:
20 | *Example: Provide us some more information that we need to know*
21 |
22 |
23 | ### Please check if the PR fulfills these requirements:
24 |
25 |
26 | - [ ] My code follows the code style and contribution guidelines of this project.
27 | - [ ] My change requires a change to the documentation.
28 | - [ ] I have updated the documentation accordingly.
29 | - [ ] I have commented my code, particularly in hard-to-understand areas
30 | - [ ] My changes generate no new warnings
--------------------------------------------------------------------------------
/src/components/Shared/FloatingIcon.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | function FloatingIcon() {
5 | useEffect(() => {
6 | setTimeout(() => {
7 | const fixedActionBtn = document.querySelector('.fixed-action-btn');
8 |
9 | if(fixedActionBtn) {
10 | document.addEventListener("scroll", () => {
11 | const bodyTag = document.querySelector('body');
12 |
13 | const bodyHeight = bodyTag.clientHeight;
14 | const windowWidth = window.innerWidth;
15 | const windowHeight = window.innerHeight;
16 |
17 | const scrollingDifference = bodyHeight - windowHeight;
18 | const scrollTopPosition = document.scrollingElement.scrollTop;
19 |
20 | if(scrollTopPosition >= scrollingDifference) {
21 | if(windowWidth <= 600) {
22 | fixedActionBtn.style.bottom = "130px";
23 | } else {
24 | fixedActionBtn.style.bottom = "70px";
25 | }
26 | } else {
27 | fixedActionBtn.style.bottom = "23px";
28 | }
29 | });
30 | }
31 | }, 2000);
32 | }, []);
33 |
34 | return(
35 |
36 |
37 | add
38 |
39 |
40 | );
41 | };
42 |
43 | export default FloatingIcon;
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report for code that is not working as expected.
4 | title: ''
5 | labels: bug
6 | assignees: cdadityang
7 |
8 | ---
9 |
10 |
11 |
12 | Your bug may already be reported!
13 | Please search on the [issue track](../) before creating a new one. Delete this line to submit this issue still.
14 |
15 |
16 | ### What is the current behavior?*
17 |
18 |
19 |
20 | ### Please provide the steps to reproduce and if possible a minimal demo of the problem
21 |
22 |
23 | 1.
24 | 2.
25 | 3.
26 | 4.
27 |
28 |
29 | ### What is the expected behavior?
30 |
31 |
32 |
33 | ### What is the motivation/use case for changing the behavior?
34 |
35 |
36 |
37 | ### Please tell us about your environment:
38 | - Version: 2.0.0-beta.X
39 | - Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
40 | - Language: [all | Ruby 2.6.3 | ES6/7 | ES5]
41 |
42 |
43 | ### Other information:
44 | *Example: Detailed explanation, stack traces, screenshots, related issues, suggestions on how to fix, links for us to have context*
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpack: function (config, env) {
3 | return config;
4 | },
5 | jest: function (config) {
6 | return config;
7 | },
8 | // configFunction is the original react-scripts function that creates the
9 | // Webpack Dev Server config based on the settings for proxy/allowedHost.
10 | // react-scripts injects this into your function (so you can use it to
11 | // create the standard config to start from), and needs to receive back a
12 | // function that takes the same arguments as the original react-scripts
13 | // function so that it can be used as a replacement for the original one.
14 | devServer: function (configFunction) {
15 | return function(proxy, allowedHost) {
16 | const config = configFunction(proxy, allowedHost);
17 | // Edit config here - example: set your own certificates.
18 | //
19 | // const fs = require('fs');
20 | // config.https = {
21 | // key: fs.readFileSync(process.env.REACT_HTTPS_KEY, 'utf8'),
22 | // cert: fs.readFileSync(process.env.REACT_HTTPS_CERT, 'utf8'),
23 | // ca: fs.readFileSync(process.env.REACT_HTTPS_CA, 'utf8'),
24 | // passphrase: process.env.REACT_HTTPS_PASS
25 | // };
26 | config.headers = {
27 | "Access-Control-Allow-Origin": "*",
28 | "Access-Control-Allow-Methods": "GET,PUT,POST,DELETE",
29 | "Access-Control-Allow-Headers": "Content-Type"
30 | }
31 |
32 | return config;
33 | };
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Notga",
3 | "name": "Drabkirn Notga",
4 | "description": "Take private, E2E notes on the Go.",
5 | "icons": [
6 | {
7 | "src": "favicon.ico",
8 | "sizes": "64x64 32x32 24x24 16x16",
9 | "type": "image/x-icon"
10 | },
11 | {
12 | "src": "icons/dbk/drabkirn-logo-72x72.png",
13 | "sizes": "72x72",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "icons/dbk/drabkirn-logo-96x96.png",
18 | "sizes": "96x96",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "icons/dbk/drabkirn-logo-128x128.png",
23 | "sizes": "128x128",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "icons/dbk/drabkirn-logo-144x144.png",
28 | "sizes": "144x144",
29 | "type": "image/png"
30 | },
31 | {
32 | "src": "icons/dbk/drabkirn-logo-152x152.png",
33 | "sizes": "152x152",
34 | "type": "image/png"
35 | },
36 | {
37 | "src": "icons/dbk/drabkirn-logo-192x192.png",
38 | "sizes": "192x192",
39 | "type": "image/png"
40 | },
41 | {
42 | "src": "icons/dbk/drabkirn-logo-384x384.png",
43 | "sizes": "384x384",
44 | "type": "image/png"
45 | },
46 | {
47 | "src": "icons/dbk/drabkirn-logo-512x512.png",
48 | "sizes": "512x512",
49 | "type": "image/png"
50 | }
51 | ],
52 | "start_url": "/dash",
53 | "display": "standalone",
54 | "theme_color": "#4a148c",
55 | "background_color": "#eeeeee"
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 |
4 | import Home from './Home/Home';
5 | import Login from './Login/Login';
6 | import Dash from './Dash/Dash';
7 | import New from './Notebook/New';
8 | import Edit from './Notebook/Edit';
9 | import Show from './Notebook/Show';
10 |
11 | import 'materialize-css/dist/css/materialize.min.css';
12 | import './Assets/scss/App.scss';
13 |
14 | import { goHomeShortcut, goBackShortcut, newNoteShortcut, saveNoteShortcut, editNoteShortcut, deleteNoteShortcut } from './Shared/keyboardShortcuts';
15 |
16 | function App() {
17 | useEffect(() => {
18 | document.addEventListener('keydown', keyboardShortcutsHandler);
19 |
20 | return () => {
21 | document.removeEventListener('keydown', keyboardShortcutsHandler);
22 | }
23 | }, []);
24 |
25 | const keyboardShortcutsHandler = (e) => {
26 | goHomeShortcut(e);
27 | goBackShortcut(e);
28 | newNoteShortcut(e);
29 | saveNoteShortcut(e);
30 | editNoteShortcut(e);
31 | deleteNoteShortcut(e);
32 | };
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default App;
47 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Your issue may already be reported!
4 | Please search on the [issue track](../) before creating a new one.
5 |
6 | ### I'm submitting a ...
7 |
8 | - [ ] bug report
9 | - [ ] feature request
10 |
11 |
12 | ### What is the current behavior?*
13 |
14 |
15 |
16 |
17 | ### If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem
18 |
19 |
20 | 1.
21 | 2.
22 | 3.
23 | 4.
24 |
25 |
26 | ### What is the expected behavior?
27 |
28 |
29 |
30 |
31 | ### What is the motivation/use case for changing the behavior?
32 |
33 |
34 |
35 | ### Please tell us about your environment:
36 | - Version: 2.0.0-beta.X
37 | - Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
38 | - Language: [all | Ruby 2.6.3 | ES6/7 | ES5]
39 |
40 |
41 | ### Other information:
42 | *Example: Detailed explanation, stack traces, related issues, suggestions on how to fix, links for us to have context*
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Router, BrowserRouter } from 'react-router-dom';
4 | import { createStore, applyMiddleware } from 'redux';
5 | import { Provider } from 'react-redux';
6 | import thunk from 'redux-thunk';
7 | import PiwikReactRouter from 'piwik-react-router';
8 |
9 | import App from './components/App';
10 | import * as serviceWorker from './serviceWorker';
11 |
12 | import rootReducer from './store/reducers/rootReducer';
13 | import history from './components/Shared/history';
14 |
15 | const store = createStore(rootReducer, applyMiddleware(thunk));
16 |
17 | if(process.env.NODE_ENV === "production") {
18 | // Matomo/Piwik Setup
19 | const piwik = PiwikReactRouter({
20 | url: 'https://analytics.cdadityang.xyz',
21 | siteId: 3
22 | });
23 |
24 | ReactDOM.render(
25 |
26 |
27 |
28 |
29 |
30 |
31 | ,
32 | document.getElementById('root')
33 | );
34 | } else {
35 | ReactDOM.render(
36 |
37 |
38 |
39 |
40 |
41 |
42 | ,
43 | document.getElementById('root')
44 | );
45 | }
46 |
47 | // If you want your app to work offline and load faster, you can change
48 | // unregister() to register() below. Note this comes with some pitfalls.
49 | // Learn more about service workers: https://bit.ly/CRA-PWA
50 | serviceWorker.unregister();
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notga",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "/notga/",
6 | "dependencies": {
7 | "@stacks/auth": "^2.0.1",
8 | "@stacks/connect": "^6.2.0",
9 | "@stacks/storage": "^2.0.1",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.3.2",
12 | "@testing-library/user-event": "^7.1.2",
13 | "blockstack": "^21.0.0",
14 | "easymde": "^2.10.1",
15 | "highlight.js": "^10.4.1",
16 | "history": "^4.10.1",
17 | "materialize-css": "^1.0.0",
18 | "piwik-react-router": "^0.12.1",
19 | "react": "^16.13.1",
20 | "react-dom": "^16.13.1",
21 | "react-redux": "^7.2.0",
22 | "react-router-dom": "^5.2.0",
23 | "react-scripts": "3.4.1",
24 | "redux": "^4.0.5",
25 | "redux-thunk": "^2.3.0",
26 | "sass": "^1.49.9"
27 | },
28 | "scripts": {
29 | "start": "BROWSER=none react-scripts start",
30 | "build": "react-scripts build",
31 | "minbuild": "react-scripts --max_old_space_size=1024 build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject",
34 | "rarstart": "react-app-rewired start",
35 | "rarbuild": "react-app-rewired build",
36 | "rartest": "react-app-rewired test --env=jsdom",
37 | "rareject": "react-app-rewired eject"
38 | },
39 | "eslintConfig": {
40 | "extends": "react-app"
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not ie <= 99",
46 | "not android <= 4.4.4",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | },
56 | "devDependencies": {
57 | "react-app-rewired": "^2.1.6"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Shared/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Loading() {
4 | return(
5 |
6 |
7 |
16 |
17 |
26 |
27 |
36 |
37 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default Loading;
--------------------------------------------------------------------------------
/src/store/actions/tagsAction.js:
--------------------------------------------------------------------------------
1 | import { tagsFileName, userStorage } from '../../components/Shared/defaults';
2 |
3 | export const fetchTagsFile = () => {
4 | return (dispatch) => {
5 | const options = { decrypt: true };
6 | userStorage.getFile(tagsFileName, options)
7 | .then((res) => {
8 | const tagsFileData = JSON.parse(res);
9 | dispatch({
10 | type: 'TAGS_FILE_FETCH_SUCCESS',
11 | payload: tagsFileData
12 | });
13 | })
14 | .catch((err) => {
15 | console.debug(`Error while fetching ${tagsFileName}`);
16 | if(err.code === "does_not_exist") {
17 | console.debug(`Creating new ${tagsFileName}`);
18 | const newOptions = { encrypt: true };
19 | const initialTagsData = {
20 | data: []
21 | };
22 | userStorage
23 | .putFile(tagsFileName, JSON.stringify(initialTagsData), newOptions)
24 | .then(() => {
25 | dispatch({
26 | type: 'TAGS_FILE_FETCH_SUCCESS',
27 | payload: initialTagsData
28 | })
29 | })
30 | .catch((err) => {
31 | console.debug(`Error while creating new ${tagsFileName}`, err);
32 | });
33 | } else {
34 | console.debug(err);
35 | }
36 | });
37 | };
38 | };
39 |
40 | export const postTagsFile = (userSession, tagsData) => {
41 | return (dispatch) => {
42 | const options = { encrypt: true };
43 | const allTagsData = {
44 | data: tagsData
45 | };
46 |
47 | userStorage
48 | .putFile(tagsFileName, JSON.stringify(allTagsData), options)
49 | .then(() => {
50 | dispatch({
51 | type: 'TAGS_FILE_POST_SUCCESS',
52 | payload: allTagsData
53 | });
54 | })
55 | .catch((err) => {
56 | console.debug(`Error while pushing ${tagsFileName}`, err);
57 | });
58 | };
59 | };
--------------------------------------------------------------------------------
/src/store/actions/notesAction.js:
--------------------------------------------------------------------------------
1 | import { notebookFileName, userStorage } from '../../components/Shared/defaults';
2 |
3 | export const fetchNotebookFile = () => {
4 | return (dispatch) => {
5 | const options = { decrypt: true };
6 | userStorage.getFile(notebookFileName, options)
7 | .then((res) => {
8 | const notebookFileData = JSON.parse(res);
9 | dispatch({
10 | type: 'NOTEBOOK_FILE_FETCH_SUCCESS',
11 | payload: notebookFileData
12 | });
13 | })
14 | .catch((err) => {
15 | console.debug(`Error while fetching ${notebookFileName}`);
16 | if(err.code === "does_not_exist") {
17 | console.debug(`Creating new ${notebookFileName}`);
18 | const newOptions = { encrypt: true };
19 | const initialNotebookData = {
20 | data: []
21 | };
22 | userStorage
23 | .putFile(notebookFileName, JSON.stringify(initialNotebookData), newOptions)
24 | .then(() => {
25 | dispatch({
26 | type: 'NOTEBOOK_FILE_FETCH_SUCCESS',
27 | payload: initialNotebookData
28 | })
29 | })
30 | .catch((err) => {
31 | console.debug(`Error while creating new ${notebookFileName}`, err);
32 | });
33 | } else {
34 | console.debug(err);
35 | }
36 | });
37 | };
38 | };
39 |
40 | export const postNotebookFile = (userSession, notebookData) => {
41 | return (dispatch) => {
42 | const options = { encrypt: true };
43 | const allNotesData = {
44 | data: notebookData
45 | };
46 |
47 | userStorage
48 | .putFile(notebookFileName, JSON.stringify(allNotesData), options)
49 | .then(() => {
50 | dispatch({
51 | type: 'NOTEBOOK_FILE_POST_SUCCESS',
52 | payload: allNotesData
53 | });
54 | })
55 | .catch((err) => {
56 | console.debug(`Error while pushing ${notebookFileName}`, err);
57 | });
58 | };
59 | };
--------------------------------------------------------------------------------
/public/css/fonts.min.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:'Rubik Mono One';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.eot');src:local('Rubik Mono One Regular'), local('RubikMonoOne-Regular'), url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff2') format('woff2'), url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.woff') format('woff'), url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.ttf') format('truetype'), url('../fonts/rubik-mono-one/rubik-mono-one-v8-latin-regular.svg#RubikMonoOne') format('svg')}@font-face{font-family:'Source Code Pro';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.eot');src:local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.woff2') format('woff2'), url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.woff') format('woff'), url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.ttf') format('truetype'), url('../fonts/source-code-pro/source-code-pro-v11-latin-regular.svg#SourceCodePro') format('svg')}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url("../fonts/material-icons/MaterialIcons-Regular.eot");src:local('Material Icons'), local('MaterialIcons-Regular'), url("../fonts/material-icons/MaterialIcons-Regular.woff2") format('woff2'), url("../fonts/material-icons/MaterialIcons-Regular.woff") format('woff'), url("../fonts/material-icons/MaterialIcons-Regular.ttf") format('truetype')}.material-icons{font-family:'Material Icons';font-display:swap;font-weight:normal;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}
--------------------------------------------------------------------------------
/src/components/Shared/keyboardShortcuts.js:
--------------------------------------------------------------------------------
1 | export const goHomeShortcut = (e) => {
2 | if((e.key === 'o' || e.key === 'O' ) && (e.ctrlKey || e.metaKey)) {
3 | e.preventDefault();
4 | const homeBtn = document.querySelector('.brand-logo');
5 | const pathName = window.location.pathname;
6 |
7 | if(pathName.includes("edit") || pathName.includes("new")) {
8 | if(confirmUnsavedProgressPrompt()) homeBtn.click();
9 | return;
10 | }
11 |
12 | homeBtn.click();
13 | }
14 | };
15 |
16 | export const goBackShortcut = (e) => {
17 | if((e.key === 'b' || e.key === 'B' ) && (e.ctrlKey || e.metaKey)) {
18 | e.preventDefault();
19 | const pathName = window.location.pathname;
20 | const backBtn = document.querySelector('.back-btn');
21 |
22 | if(pathName.includes("edit") || pathName.includes("new")) {
23 | if(confirmUnsavedProgressPrompt()) {
24 | if(backBtn) backBtn.click();
25 | }
26 | return;
27 | }
28 |
29 | if(backBtn) backBtn.click();
30 | }
31 | };
32 |
33 | export const newNoteShortcut = (e) => {
34 | if((e.key === 'y' || e.key === 'Y' ) && (e.ctrlKey || e.metaKey)) {
35 | e.preventDefault();
36 | const floatingBtn = document.querySelector('.btn-floating.btn-large.oxford-blue-bg');
37 |
38 | if(floatingBtn) floatingBtn.click();
39 | }
40 | };
41 |
42 | export const saveNoteShortcut = (e) => {
43 | if((e.key === 's' || e.key === 'S' ) && (e.ctrlKey || e.metaKey)) {
44 | e.preventDefault();
45 | const submitBtn = document.querySelector('button[type="submit"]');
46 |
47 | if(submitBtn) submitBtn.click();
48 | }
49 | };
50 |
51 | export const editNoteShortcut = (e) => {
52 | if((e.key === 'e' || e.key === 'E' ) && (e.ctrlKey || e.metaKey)) {
53 | console.log(e);
54 | e.preventDefault();
55 | const editBtn = document.querySelector('.edit-btn');
56 |
57 | if(editBtn) editBtn.click();
58 | }
59 | };
60 |
61 | export const deleteNoteShortcut = (e) => {
62 | if((e.key === 'd' || e.key === 'D' ) && (e.ctrlKey || e.metaKey)) {
63 | e.preventDefault();
64 | const deleteBtn = document.querySelector('.delete-btn');
65 |
66 | if(deleteBtn) deleteBtn.click();
67 | }
68 | };
69 |
70 | function confirmUnsavedProgressPrompt() {
71 | const confirmPrompt = window.confirm("You may have unsaved changes, are you sure you want to leave?");
72 | return confirmPrompt;
73 | }
--------------------------------------------------------------------------------
/src/components/Login/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | import { appConfig, userSession, isUserSignedIn, loginPopup } from '../Shared/defaults';
5 | import Navbar from '../Shared/Navbar';
6 | import Footer from '../Shared/Footer';
7 |
8 | function Login() {
9 | const [loading, setLoading] = useState(false);
10 |
11 | useEffect(() => {
12 | if(userSession.isSignInPending()) {
13 | setLoading(true);
14 | userSession
15 | .handlePendingSignIn()
16 | .then(() => {
17 | window.location = appConfig.redirectURI();
18 | })
19 | .catch((err) => {
20 | console.debug("Cannot sign you in", err);
21 | });
22 | }
23 | }, []);
24 |
25 | if(isUserSignedIn) {
26 | return ;
27 | }
28 |
29 | const handleLogin = (e) => {
30 | e.preventDefault();
31 | setLoading(true);
32 | // Deprecated
33 | // userSession.redirectToSignIn();
34 |
35 | const authOptions = {
36 | redirectTo: '/dash',
37 | onFinish: (authData) => {
38 | window.location = appConfig.redirectURI();
39 | },
40 | onCancel: () => {
41 | setLoading(false);
42 | },
43 | manifestPath: `${ appConfig.redirectURI() }/manifest.json`,
44 | appDetails: {
45 | name: 'Notga',
46 | icon: `${ appConfig.redirectURI().slice(0, (appConfig.redirectURI().length - 6)) }/icons/logo-180x180.png`,
47 | },
48 | };
49 |
50 | loginPopup(authOptions);
51 | };
52 |
53 | return(
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Get started by signing in with Blockstack. If you don't have one, you can create a new one. It's super fast and straightforward.
62 |
63 |
64 |
65 | handleLogin(e) } disabled={ loading } >
66 | { loading ? "Signing you in..." : "Sign in with Blockstack" }
67 | fingerprint
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default Login;
--------------------------------------------------------------------------------
/src/components/Shared/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import M from "materialize-css";
4 |
5 | import { appConfig, userSession, isUserSignedIn } from './defaults';
6 |
7 | function Navbar() {
8 | const [sideNavInstance, setSideNavInstance] = useState(null);
9 |
10 | useEffect(() => {
11 | const elems = document.querySelectorAll('.sidenav');
12 | const options = {};
13 | const instances = M.Sidenav.init(elems, options);
14 | setSideNavInstance(instances[0]);
15 | }, []);
16 |
17 | const logoLink = isUserSignedIn ? "/dash" : "/";
18 |
19 | const handleSideNav = () => {
20 | sideNavInstance.close();
21 | }
22 |
23 | const handleSignout = (e) => {
24 | e.preventDefault();
25 | userSession.signUserOut();
26 | window.location = appConfig.redirectURI();
27 | }
28 |
29 | return(
30 |
31 |
32 |
33 |
34 |
Notga
35 |
36 |
menu
37 |
38 | {
39 | isUserSignedIn ? (
40 | <>
41 | Tour
42 | Dash
43 | handleSignout(e) }>Sign out
44 | >
45 | ) : (
46 | <>
47 | Tour
48 | Get started
49 | >
50 | )
51 | }
52 |
53 |
54 |
55 |
56 |
57 | {
58 | isUserSignedIn ? (
59 | <>
60 | handleSideNav() }>Tour
61 | handleSideNav() }>Dash
62 | handleSignout(e) }>Sign out
63 | >
64 | ) : (
65 | <>
66 | handleSideNav() }>Tour
67 | handleSideNav() }>Get started
68 | >
69 | )
70 | }
71 |
72 |
73 | );
74 | };
75 |
76 | export default Navbar;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Drabkirn Notga
6 |
7 |
8 | > Take free, private, secure, unlimited, end-to-end encrypted, and decentralized notes in markdown/WYSIWYG editor. What else do you need?
9 |
10 |
11 | [](https://reactjs.org/)
12 | [](https://github.com/drabkirn/notga/issues)
13 | [](https://github.com/drabkirn/notga/issues)
14 | [](https://github.com/drabkirn/notga/pulls)
15 | [](https://github.com/drabkirn/notga/pulls)
16 | [](https://choosealicense.com/licenses/agpl-3.0/)
17 | [](https://cla-assistant.io/drabkirn/notga)
18 | []()
19 | []()
20 |
21 |
22 | Take unlimited private notes in markdown or WYSIWYG(What You See Is What You Get) editor. Powered by Blockstack technology, so it's decentralized and encrypted.
23 |
24 | **[Visit website here](https://go.cdadityang.xyz/notga)**
25 |
26 | -----
27 | -----
28 |
29 | ## Table of Contents
30 | - [Features](#features)
31 | - [Installation](#installation)
32 | - [Contributing](#contributing)
33 | - [Connect](#connect)
34 |
35 | -----
36 | -----
37 |
38 | ## Features
39 | * Unlimited Notes
40 | * Unlimited Storage
41 | * What You See Is What You Get(WYSIWYG) Editor or Markdown Editor
42 | * Drag and drop Images or Copy-Paste Images
43 | * Truly End-to-End encrypted notes
44 | * Add and search tags
45 | * Keyboard shortcuts:
46 | * `ctrl + O` (O for orange): Takes you to homepage
47 | * `ctrl + B`: Takes you Back
48 | * `ctrl + Y`: New Note
49 | * `ctrl + E`: Edit Note
50 | * `ctrl + S`: Save Note
51 | * `ctrl + D`: Delete Note
52 | * `ctrl + 1` to `ctrl + 9`: When you're on dashboard, you can select to show first 9 notes with shortcut.
53 | * Powered by Blockstack Technology
54 | * You decide where you store your data(Defaults to Gaia provided by blockstack)
55 |
56 | -----
57 | -----
58 |
59 | ## Installation
60 |
61 | **coming soon...**
62 |
63 | -----
64 | -----
65 |
66 | ## Contributing
67 | If you would like to contribute, please check [this contributing guide](https://github.com/drabkirn/notga/blob/master/CONTRIBUTING.md)
68 |
69 | Please check [this Code of Conduct guide](https://github.com/drabkirn/notga/blob/master/CODE_OF_CONDUCT.md) before contributing or having any kind of discussion(issues, pull requests etc.) with the Notga project!
70 |
71 | -----
72 |
73 | ## Connect:
74 | Need any help? Have any Questions? Or just say us hi!
75 |
76 | 1. [Drabkirn Website](https://go.cdadityang.xyz/drab)
77 | 2. [Our Blog](https://go.cdadityang.xyz/blog)
78 | 3. [Discord Server](https://go.cdadityang.xyz/discord)
79 | 4. [Twitter](https://go.cdadityang.xyz/DtwtK)
80 | 5. [Instagram](https://go.cdadityang.xyz/DinsK)
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Drabkirn Notga
2 |
3 | ## Before Submitting an Issue
4 | Check that [our issue database](https://github.com/drabkirn/notga/issues)
5 | doesn't already include your problem or suggestion before submitting your issue.
6 | If you find a match, you can use the "subscribe" button to get notified on
7 | updates. Do **NOT** leave random "+1" or "I have this too" comments, as they
8 | only clutter the discussion, and don't help to resolve it. However, if you
9 | have ways to reproduce the issue or have additional information that may help
10 | to resolve the issue, please leave a comment.
11 |
12 | ## Writing Good Bug Reports and Feature Requests
13 | Please file a single issue per problem and feature request. Do not submit combo issues.
14 |
15 | The more information you can provide, the more likely someone will be successful in reproducing the issue and finding a fix. Therefore:
16 | * Provide reproducible steps, what the result of the steps was, and what you would have expected.
17 | * A detailed description of the behavior that you expect.
18 | * Animated GIFs are a tremendous help.
19 | * Version information of your ruby and rails version.
20 | * Error outputs that may exist in your browser console.
21 |
22 | ## How to Contribute
23 | 1. Please add an issue or comment on issues that are open and mention that you are working on it. Then submit a pull request! This will let others know you're working on it.
24 |
25 | 2. Install the app on your local machine. You need to [Fork](https://help.github.com/articles/fork-a-repo/) this app and then clone it on your local machine. See the Installation section of [README.md](https://github.com/drabkirn/notga/blob/master/README.md) on how to do the installation.
26 |
27 | 3. Set the upstream remote so you can keep your copy of the app synced with the original. To do that, go to your terminal and cd into your cloned Drabkirn Notga app directory. Then use one of the following commands:
28 | * If you have ssh set up with Git
29 | ```bash
30 | $ git remote add upstream https://github.com/drabkirn/notga.git
31 | ```
32 | Or
33 | ```bash
34 | git remote add upstream git@github.com:drabkirn/notga.git
35 | ```
36 |
37 | 4. Before you start working on your issue, create a branch, and name it as the following examples:
38 | * If its a new feature:
39 | ```bash
40 | $ git checkout -b feature/new-feature-name
41 | ```
42 | * If its a bug fix
43 | ```bash
44 | git checkout -b fix/fixed-bug-name
45 | ```
46 |
47 | 5. When you have finished and are ready to submit a Pull Request:
48 | * Push your branch to your fork
49 | ```bash
50 | $ git push origin
51 | ```
52 | * Go to your fork on Github after you have pushed up your branch. A new button should be visible near the top of the page. It will allow you to create a pull request to the original Drabkirn Notga repo.
53 | * You'll see a `PULL_REQUEST_TEMPLATE` - Try to complete this in your own words.
54 | * Please Link to the issue your pull request resolves in the body of your pull request.
55 |
56 | 6. **Important:** After you submit your pull request, Drabkirn requires that every contributor sign a Drabkirn Contributor License Agreement (CLA) to a Drabkirn open source project. This Agreement is effective upon your acknowledgment via the CLA Assistant tool. You can read and sign this Agreement at [https://cla-assistant.io/drabkirn/notga](https://cla-assistant.io/drabkirn/notga) or follow the link in your pull request pending checks list. Until you agree by signing this Agreement, Drabkirn will not merge your pull request.
57 |
58 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Notga - Take unlimited secure notes on the go | Drabkirn
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | You need to enable JavaScript to run this app.
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/components/Shared/defaults.js:
--------------------------------------------------------------------------------
1 | import { UserSession, AppConfig } from '@stacks/auth'
2 | import { showConnect } from '@stacks/connect';
3 | import { Storage } from '@stacks/storage';
4 | import generateUUID from './generateUUID';
5 |
6 | const myAppConfig = new AppConfig(["store_write"]);
7 |
8 | if(process.env.NODE_ENV === "development") myAppConfig.appDomain = "http://localhost:3000/notga";
9 | else myAppConfig.appDomain = "https://drabkirn.cdadityang.xyz/notga";
10 |
11 | myAppConfig.redirectPath = "/login";
12 |
13 | export const appConfig = myAppConfig;
14 |
15 | export const loginPopup = showConnect;
16 |
17 | export const userSession = new UserSession({ appConfig: appConfig });
18 |
19 | export const userStorage = new Storage({ userSession: userSession });
20 |
21 | export const isUserSignedIn = userSession.isUserSignedIn();
22 |
23 |
24 |
25 | export const notebookFileName = "notebook_v1.json";
26 |
27 | export const tagsFileName = "tags_v1.json";
28 |
29 |
30 |
31 | export const easyMDEOptions = {
32 | autoDownloadFontAwesome: false,
33 | forceSync: true,
34 | indentWithTabs: false,
35 | placeholder: "Get started by taking your note here...",
36 | showIcons: ["strikethrough", "code", "table", "horizontal-rule"],
37 | hideIcons: ["side-by-side"],
38 | renderingConfig: {
39 | codeSyntaxHighlighting: true
40 | },
41 | spellChecker: false,
42 | uploadImage: true,
43 | imageAccept: "image/png, image/jpeg, image/jpg",
44 | imageUploadFunction: (file, onSuccess, onError) => uploadImage(file, onSuccess, onError),
45 | imageTexts: {
46 | sbInit: 'Drag & drop/copy-paste images!',
47 | sbOnDragEnter: 'Let it go, let it go',
48 | sbOnDrop: 'Uploading...',
49 | sbProgress: 'Uploading... (#progress#)',
50 | sbOnUploaded: 'Upload complete!',
51 | sizeUnits: 'b,Kb,Mb'
52 | },
53 | errorMessages: {
54 | noFileGiven: 'Please select a file',
55 | typeNotAllowed: 'This file type is not allowed!',
56 | fileTooLarge: 'Image too big',
57 | importError: 'Something went oops!',
58 | },
59 | errorCallback: (errorMessage) => {
60 | console.error(errorMessage);
61 | alert(errorMessage);
62 | }
63 | };
64 |
65 | async function uploadImage(file, onSuccess, onError) {
66 | const fileSizeLimit = 1024 * 1024 * 2; // 2 MB
67 | const allowedFileTypes = ["image/png", "image/jpeg", "image/jpg"];
68 |
69 | if(!file) {
70 | onError("Image not found, please select a image.");
71 | return;
72 | }
73 |
74 | if(file.size >= fileSizeLimit) {
75 | onError("Image too big, max is 2 MB. You can compress it and upload.");
76 | return;
77 | }
78 |
79 | if(!allowedFileTypes.includes(file.type)) {
80 | onError("This file type is not allowed! Only png, jpg, jpeg allowed.");
81 | return;
82 | }
83 |
84 | const uuid = generateUUID();
85 | const options = { encrypt: true };
86 | await userStorage.putFile(`image-${uuid}.${file.type.split("/")[1]}`, file, options)
87 | .then((res) => {
88 | onSuccess(res);
89 | })
90 | .catch((err) => {
91 | onError(err);
92 | });
93 | };
94 |
95 |
96 | export const handleImagesRender = (userSession) => {
97 | const allImages = document.querySelectorAll('img');
98 | const onlyNotesImages = [...allImages].filter((image) => image.src.startsWith("https://gaia.blockstack.org/hub"));
99 | onlyNotesImages.forEach(async (image) => {
100 | const splitURL = image.src.split("/");
101 | const splitURLLength = splitURL.length;
102 | const relativeImageURL = splitURL[splitURLLength - 1];
103 |
104 | const options = { decrypt: true };
105 | await userStorage.getFile(relativeImageURL, options)
106 | .then((res) => {
107 | const arrayBufferView = new Uint8Array(res);
108 | const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
109 | const urlCreator = window.URL || window.webkitURL;
110 | const imageUrl = urlCreator.createObjectURL(blob);
111 | image.src = imageUrl;
112 | image.className = "responsive-img";
113 | })
114 | .catch((err) => {
115 | console.log(err);
116 | });
117 | });
118 | };
--------------------------------------------------------------------------------
/src/components/Assets/scss/App.scss:
--------------------------------------------------------------------------------
1 | @import 'config';
2 |
3 |
4 | // Defaults
5 | html {
6 | scroll-behavior: smooth;
7 | }
8 |
9 | body {
10 | font-family: 'Source Code Pro', monospace;
11 | color: $rich_black;
12 | }
13 |
14 | h1, h2, h3, h4, h5, h6 {
15 | font-family: 'Rubik Mono One', sans-serif;
16 | }
17 |
18 | h1 {
19 | font-size: 3.5rem;
20 | margin: 2rem 0 1.6rem 0;
21 | }
22 |
23 | h2 {
24 | font-size: 2.5rem;
25 | margin: 1.5rem 0 1.3rem 0;
26 | }
27 |
28 | h3 {
29 | font-size: 2rem;
30 | margin: 1.5rem 0 1.3rem 0;
31 | }
32 |
33 | h4 {
34 | font-size: 1.6rem;
35 | }
36 |
37 | h5 {
38 | font-size: 1.4rem;
39 | }
40 |
41 | h6 {
42 | font-size: 1.2rem;
43 | }
44 |
45 | hr {
46 | color: $white;
47 | margin: 3rem 0;
48 | }
49 |
50 | strong {
51 | font-weight: bold;
52 | }
53 |
54 |
55 |
56 | // Helpers
57 | @each $key, $font-size in $font-amounts {
58 | .fs-#{$key} {
59 | font-size: #{$font-size}rem;
60 | }
61 | }
62 |
63 | @each $key, $margin-size in $margin-amounts {
64 | .mt-#{$key} {
65 | margin-top: #{$margin-size}rem;
66 | }
67 |
68 | .ml-#{$key} {
69 | margin-left: #{$margin-size}rem;
70 | }
71 |
72 | .mb-#{$key} {
73 | margin-bottom: #{$margin-size}rem;
74 | }
75 | }
76 |
77 | @each $key, $color in $available-colors-list {
78 | .#{$key}-btn, .#{$key}-btn:hover, .#{$key}-btn:focus {
79 | @include set-background($color);
80 | }
81 |
82 | .#{$key}-bg {
83 | @include set-background($color);
84 | }
85 |
86 | .#{$key}-text {
87 | color: $color !important;
88 | }
89 | }
90 |
91 | .bold {
92 | font-weight: bold;
93 | }
94 |
95 | .italic {
96 | font-style: italic;
97 | }
98 |
99 | .uppercase {
100 | text-transform: uppercase;
101 | }
102 |
103 | .flex-center-vh {
104 | display: flex;
105 | align-items: center;
106 | justify-content: center;
107 | }
108 |
109 | .block {
110 | display: block;
111 | }
112 |
113 |
114 | // Navbar
115 | nav.custom-nav {
116 | box-shadow: none;
117 | border-bottom: 1px solid lightgray;
118 |
119 | .brand-logo {
120 | height: inherit;
121 | display: flex;
122 | flex-direction: row;
123 | justify-content: center;
124 | align-items: center;
125 |
126 | h4 {
127 | margin: 0;
128 | }
129 | }
130 |
131 | ul li a {
132 | color: $rich_black;
133 | }
134 |
135 | ul a:hover {
136 | @include set-background($tea_green);
137 | }
138 | }
139 |
140 | ul.custom-sidenav {
141 | @include set-background($oxford_blue);
142 |
143 | li a {
144 | color: $white;
145 | }
146 |
147 | li a:hover {
148 | @include set-background($tea_green);
149 | }
150 | }
151 |
152 |
153 |
154 | // Home Header
155 | header {
156 | margin-top: 5rem !important;
157 |
158 | h1 {
159 | margin: 1.5rem 0;
160 | }
161 | }
162 |
163 |
164 |
165 | // Login
166 | .login-section {
167 | height: 78vh;
168 | display: flex;
169 | flex-direction: column;
170 | align-items: center;
171 | justify-content: center;
172 | }
173 |
174 |
175 |
176 | // Dash
177 | .profile-avatar {
178 | width: 150px;
179 | height: 150px;
180 | border-radius: 50%;
181 | }
182 |
183 | .dash-notes {
184 | & &-card {
185 | display: block;
186 | padding: 1rem 0;
187 | border-radius: 1rem;
188 | margin-bottom: 1rem;
189 | @include set-background($oxford_blue);
190 | }
191 | }
192 |
193 | .dash-tag-submit-btn {
194 | margin-top: 1.1rem;
195 | margin-right: 5px;
196 | i {
197 | margin-left: 0;
198 | }
199 | }
200 |
201 | .dash-note-tags {
202 | @include set-background($tea_green);
203 | padding: 0.3rem;
204 | margin-left: 0.5rem;
205 | border-radius: 1rem;
206 | font-size: 1rem;
207 | display: inline-block;
208 | margin-top: 0.5rem;
209 | }
210 |
211 | .dash-notes-col-m4-nth-clear:nth-child(3n + 1) {
212 | clear: both;
213 | }
214 |
215 |
216 | // Show
217 | .show-note-metadata {
218 | background-color: $honey_dew;
219 | padding: 0.5rem 0;
220 | border-radius: 3rem;
221 | }
222 |
223 | .show-note-tags {
224 | @include set-background($oxford_blue);
225 | padding: 0.5rem;
226 | margin-left: 0.8rem;
227 | border-radius: 1rem;
228 | display: inline-block;
229 | margin-top: 0.5rem;
230 | }
231 |
232 | .custom-editor-preview {
233 | position: static;
234 | margin-top: 2rem;
235 | width: 100%;
236 | padding-left: 1.3rem;
237 | }
238 |
239 |
240 |
241 | // New/Edit
242 | .form-tag {
243 | & &-title-field {
244 | margin: 0 auto;
245 | max-width: 650px;
246 |
247 | input {
248 | font-family: 'Rubik Mono One', sans-serif;
249 | font-size: 2rem;
250 | text-align: center;
251 | }
252 | }
253 |
254 | & &-tag-field {
255 | margin: 2rem auto 0 auto;
256 | max-width: 650px;
257 |
258 | input {
259 | font-family: 'Rubik Mono One', sans-serif;
260 | text-align: center;
261 | }
262 | }
263 | }
264 |
265 |
266 |
267 | // Footer
268 | footer {
269 | @include set-background($apple_red);
270 |
271 | & .row {
272 | margin-bottom: 0;
273 | }
274 |
275 | a {
276 | color: $white;
277 | text-decoration: underline;
278 | }
279 |
280 | & .footer-right {
281 | height: 63px;
282 | display: flex;
283 | flex-direction: row;
284 | align-items: center;
285 | justify-content: flex-end;
286 | }
287 | }
288 |
289 |
290 |
291 | @import 'media';
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/Notebook/New.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, Redirect } from 'react-router-dom';
3 | import { useSelector, useDispatch } from "react-redux";
4 | import EasyMDE from 'easymde';
5 |
6 | import { userSession, isUserSignedIn, easyMDEOptions, handleImagesRender } from '../Shared/defaults';
7 | import generateUUID from '../Shared/generateUUID';
8 | import { fetchNotebookFile, postNotebookFile } from '../../store/actions/notesAction';
9 | import { fetchTagsFile, postTagsFile } from '../../store/actions/tagsAction';
10 | import Navbar from '../Shared/Navbar';
11 | import Footer from '../Shared/Footer';
12 |
13 | import 'easymde/dist/easymde.min.css';
14 |
15 | function New() {
16 | // Get the Redux Dispatch
17 | const dispatch = useDispatch();
18 |
19 | // Get the Redux state
20 | const store = useSelector(store => store);
21 | const notes = store.notes;
22 | const notesData = notes.notesData;
23 |
24 | const tags = store.tags;
25 | const tagsData = tags.tagsData;
26 |
27 | const [noteId] = useState(generateUUID());
28 | const [noteTitle, setNoteTitle] = useState("");
29 | const [noteTags, setNoteTags] = useState("");
30 | const [isFormSubmitted, setIsFormSubmitted] = useState(false);
31 |
32 | useEffect(() => {
33 | if(isUserSignedIn && !notesData) dispatch(fetchNotebookFile());
34 |
35 | if(isUserSignedIn && !tagsData) dispatch(fetchTagsFile());
36 |
37 | if(isUserSignedIn && notesData && tagsData) {
38 | handleEasyMDE();
39 | }
40 | }, [notesData, tagsData]);
41 |
42 | if(!isUserSignedIn) {
43 | return ;
44 | }
45 |
46 | const handleEasyMDE = () => {
47 | const noteContentElement = document.getElementById('noteContent');
48 | const isMDEInited = document.querySelector('.editor-toolbar');
49 | const updatedEasyMDEOptions = {...easyMDEOptions, element: noteContentElement, previewRender: (text) => customMarkdownRender(text)};
50 |
51 | if(!isMDEInited) new EasyMDE(updatedEasyMDEOptions);
52 |
53 | const noteContentElement1 = document.getElementById('noteContent1');
54 | const updatedEasyMDEOptions1 = {...easyMDEOptions, element: noteContentElement1 };
55 | const myEasyMDE1 = new EasyMDE(updatedEasyMDEOptions1);
56 | // myEasyMDE1.toTextArea();
57 |
58 | const customMarkdownRender = (text) => {
59 | setTimeout(() => {
60 | handleImagesRender(userSession);
61 | }, 1000);
62 | return myEasyMDE1.options.previewRender(text);
63 | };
64 | };
65 |
66 | const handleSubmit = (e) => {
67 | e.preventDefault();
68 | const noteContentElement = document.getElementById('noteContent');
69 | const noteData = {
70 | id: noteId,
71 | title: noteTitle,
72 | content: noteContentElement.value,
73 | created_at: new Date().toLocaleString(),
74 | updated_at: new Date().toLocaleString()
75 | };
76 | notesData.push(noteData);
77 |
78 | handleTagsData();
79 |
80 | dispatch(postNotebookFile(userSession, notesData));
81 | dispatch(postTagsFile(userSession, tagsData));
82 | setIsFormSubmitted(true);
83 | };
84 |
85 | const handleTagsData = () => {
86 | const processedTagsArr = noteTags.length > 0 ? [...new Set(noteTags.split(",").map((b) => b.trim().toLowerCase()).filter(Boolean))] : "";
87 |
88 | if(tagsData.length === 0) {
89 | processedTagsArr.forEach((pTagName) => {
90 | const tagData = {
91 | id: generateUUID(),
92 | name: pTagName,
93 | note_ids: [noteId]
94 | };
95 | tagsData.push(tagData);
96 | });
97 | } else {
98 | processedTagsArr.forEach((pTagName) => {
99 | let updatedPTagName = false;
100 | tagsData.forEach((tData) => {
101 | if(tData.name === pTagName) {
102 | tData.note_ids.push(noteId);
103 | updatedPTagName = true;
104 | }
105 | });
106 |
107 | if(!updatedPTagName) {
108 | const tagData = {
109 | id: generateUUID(),
110 | name: pTagName,
111 | note_ids: [noteId]
112 | };
113 | tagsData.push(tagData);
114 | }
115 | });
116 | }
117 | };
118 |
119 | if(isFormSubmitted) {
120 | return ;
121 | }
122 |
123 | return(
124 | <>
125 |
126 |
127 |
128 |
152 |
153 |
154 |
155 | Cancel
156 | backspace
157 |
158 |
159 |
160 |
161 |
162 | >
163 | );
164 | };
165 |
166 | export default New;
--------------------------------------------------------------------------------
/src/components/Notebook/Show.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, Redirect } from 'react-router-dom';
3 | import { useSelector, useDispatch } from "react-redux";
4 | import EasyMDE from 'easymde';
5 |
6 | import { appConfig, userSession, isUserSignedIn, easyMDEOptions, handleImagesRender } from '../Shared/defaults';
7 | import Highlight from '../Shared/Highlight';
8 | import { fetchNotebookFile, postNotebookFile } from '../../store/actions/notesAction';
9 | import { fetchTagsFile, postTagsFile } from '../../store/actions/tagsAction';
10 | import Loading from '../Shared/Loading';
11 | import Navbar from '../Shared/Navbar';
12 | import FloatingIcon from '../Shared/FloatingIcon';
13 | import Footer from '../Shared/Footer';
14 |
15 | import 'easymde/dist/easymde.min.css';
16 |
17 | function Show(props) {
18 | // Get the Redux Dispatch
19 | const dispatch = useDispatch();
20 |
21 | // Get the Redux state
22 | const store = useSelector(store => store);
23 | const notes = store.notes;
24 | const notesData = notes.notesData;
25 |
26 | const tags = store.tags;
27 | const tagsData = tags.tagsData;
28 |
29 | const noteIdParam = props.match.params.id;
30 |
31 | const [noteContentRender, setNoteContentRender] = useState(null);
32 | const [note, setNote] = useState(null);
33 | const [noteNotFound, setNoteNotFound] = useState(false);
34 | const [noteTags, setNoteTags] = useState("");
35 |
36 | useEffect(() => {
37 | if(isUserSignedIn && !notesData) dispatch(fetchNotebookFile());
38 |
39 | if(isUserSignedIn && !tagsData) dispatch(fetchTagsFile());
40 |
41 | if(isUserSignedIn && notesData) {
42 | const getCurrentNote = notesData.filter((note) => note.id === noteIdParam)[0];
43 |
44 | if(getCurrentNote) {
45 | setNote(getCurrentNote);
46 | handleEasyMDE(getCurrentNote);
47 | } else {
48 | setNote(null);
49 | setNoteNotFound(true);
50 | }
51 | }
52 |
53 | if(isUserSignedIn && notesData && tagsData) {
54 | const getCurrentNote = notesData.filter((note) => note.id === noteIdParam)[0];
55 | const allTags = [];
56 |
57 | tagsData.forEach((tagData) => {
58 | if(tagData.note_ids.includes(getCurrentNote.id)) {
59 | allTags.push(tagData.name);
60 | }
61 | });
62 | setNoteTags(allTags);
63 | }
64 | }, [notesData, tagsData]);
65 |
66 | const handleEasyMDE = (note) => {
67 | const noteContentElement = document.getElementById('noteContent');
68 | const updatedEasyMDEOptions = {...easyMDEOptions, element: noteContentElement, initialValue: note.content};
69 | const myEasyMDE = new EasyMDE(updatedEasyMDEOptions);
70 | setNoteContentRender(myEasyMDE.options.previewRender(myEasyMDE.value()));
71 | // myEasyMDE.toTextArea();
72 |
73 | setTimeout(() => {
74 | handleImagesRender(userSession);
75 | }, 2000);
76 | };
77 |
78 | const handleNoteDelete = (deletedNote) => {
79 | const confirmDeletion = window.confirm("Are ou sure, you want to delete this note, it's irreversible?");
80 | if(confirmDeletion) {
81 | const modifiedNotesData = notesData.filter((note) => note.id !== deletedNote.id);
82 |
83 | handleTagsRemoveNote(deletedNote);
84 |
85 | dispatch(postNotebookFile(userSession, modifiedNotesData));
86 | dispatch(postTagsFile(userSession, tagsData));
87 | window.location = appConfig.redirectURI();
88 | }
89 | };
90 |
91 | const handleTagsRemoveNote = (deletedNote) => {
92 | noteTags.forEach((dTagName) => {
93 | tagsData.forEach((tData) => {
94 | if(tData.name === dTagName) {
95 | const idx = tData.note_ids.indexOf(deletedNote.id);
96 | tData.note_ids.splice(idx, 1);
97 | }
98 | });
99 | });
100 | };
101 |
102 | if(!isUserSignedIn) {
103 | return ;
104 | }
105 |
106 | if(noteNotFound) {
107 | return
108 | }
109 |
110 | const loading = notes.isFetching;
111 |
112 | return(
113 | <>
114 |
115 |
116 |
117 | {
118 | loading || !note ? (
119 |
120 | ) : (
121 | <>
122 |
123 |
{ note.title }
124 |
125 |
126 |
127 |
{ noteTags && noteTags.sort((a, b) => a.localeCompare(b)).map((tag, idx) => {
128 | return(
129 | { tag }
130 | )
131 | }) }
132 |
133 |
134 |
135 |
Created at: { note.created_at }
136 |
Updated at: { note.updated_at }
137 |
138 |
139 |
140 |
141 | Edit
142 | edit
143 |
144 | handleNoteDelete(note) }>
145 | Delete
146 | delete
147 |
148 |
149 | Back
150 | keyboard_backspace
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | Edit
161 | edit
162 |
163 | handleNoteDelete(note) }>
164 | Delete
165 | delete
166 |
167 |
168 | Back
169 | keyboard_backspace
170 |
171 |
172 | >
173 | )
174 | }
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | >
185 | );
186 | };
187 |
188 | export default Show;
--------------------------------------------------------------------------------
/src/components/Notebook/Edit.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, Redirect } from 'react-router-dom';
3 | import { useSelector, useDispatch } from "react-redux";
4 | import EasyMDE from 'easymde';
5 |
6 | import { userSession, isUserSignedIn, easyMDEOptions, handleImagesRender } from '../Shared/defaults';
7 | import generateUUID from '../Shared/generateUUID';
8 | import { fetchNotebookFile, postNotebookFile } from '../../store/actions/notesAction';
9 | import { fetchTagsFile, postTagsFile } from '../../store/actions/tagsAction';
10 | import Navbar from '../Shared/Navbar';
11 | import Loading from '../Shared/Loading';
12 | import Footer from '../Shared/Footer';
13 |
14 | import 'easymde/dist/easymde.min.css';
15 |
16 | function Edit(props) {
17 | // Get the Redux Dispatch
18 | const dispatch = useDispatch();
19 |
20 | // Get the Redux state
21 | const store = useSelector(store => store);
22 | const notes = store.notes;
23 | const notesData = notes.notesData;
24 |
25 | const tags = store.tags;
26 | const tagsData = tags.tagsData;
27 |
28 | const noteIdParam = props.match.params.id;
29 |
30 | const [note, setNote] = useState(null);
31 | const [noteTitle, setNoteTitle] = useState("");
32 | const [noteTags, setNoteTags] = useState("");
33 | const [initialNoteTags, setInitialNoteTags] = useState("");
34 | const [noteNotFound, setNoteNotFound] = useState(false);
35 | const [isFormSubmitted, setIsFormSubmitted] = useState(false);
36 |
37 | useEffect(() => {
38 | if(isUserSignedIn && !notesData) dispatch(fetchNotebookFile());
39 |
40 | if(isUserSignedIn && !tagsData) dispatch(fetchTagsFile());
41 |
42 | if(isUserSignedIn && notesData) {
43 | const getCurrentNote = notesData.filter((note) => note.id === noteIdParam)[0];
44 |
45 | if(getCurrentNote) {
46 | setNote(getCurrentNote);
47 | setNoteTitle(getCurrentNote.title);
48 | } else {
49 | setNote(null);
50 | setNoteNotFound(true);
51 | }
52 | }
53 |
54 | if(isUserSignedIn && notesData && tagsData) {
55 | const getCurrentNote = notesData.filter((note) => note.id === noteIdParam)[0];
56 | const allTags = [];
57 |
58 | tagsData.forEach((tagData) => {
59 | if(tagData.note_ids.includes(getCurrentNote.id)) {
60 | allTags.push(tagData.name);
61 | }
62 | });
63 | setInitialNoteTags(allTags);
64 | setNoteTags(allTags.join(", "));
65 | }
66 | }, [notesData, tagsData]);
67 |
68 | useEffect(() => {
69 | if(note) {
70 | handleEasyMDE(note);
71 | }
72 | }, [note]);
73 |
74 | const handleEasyMDE = (note) => {
75 | const noteContentElement = document.getElementById('noteContent');
76 | const updatedEasyMDEOptions = {...easyMDEOptions, element: noteContentElement, initialValue: note.content, previewRender: (text) => customMarkdownRender(text)};
77 | const isMDEInited = document.querySelector('.editor-toolbar');
78 | if(!isMDEInited) new EasyMDE(updatedEasyMDEOptions);
79 |
80 | const noteContentElement1 = document.getElementById('noteContent1');
81 | const updatedEasyMDEOptions1 = {...easyMDEOptions, element: noteContentElement1, initialValue: note.content };
82 | const myEasyMDE1 = new EasyMDE(updatedEasyMDEOptions1);
83 | // myEasyMDE1.toTextArea();
84 |
85 | const customMarkdownRender = (text) => {
86 | setTimeout(() => {
87 | handleImagesRender(userSession);
88 | }, 1000);
89 | return myEasyMDE1.options.previewRender(text);
90 | };
91 | };
92 |
93 | const handleSubmit = (e) => {
94 | e.preventDefault();
95 | const noteContentElement = document.getElementById('noteContent');
96 | notesData.forEach((eachNote) => {
97 | if(eachNote.id === note.id) {
98 | eachNote.title = noteTitle;
99 | eachNote.content = noteContentElement.value;
100 | eachNote.updated_at = new Date().toLocaleString();
101 | }
102 | });
103 |
104 | handleTagsData();
105 |
106 | dispatch(postNotebookFile(userSession, notesData));
107 | dispatch(postTagsFile(userSession, tagsData));
108 | setIsFormSubmitted(true);
109 | };
110 |
111 | const handleTagsData = () => {
112 | const processedTagsArr = noteTags.length > 0 ? [...new Set(noteTags.split(",").map((b) => b.trim().toLowerCase()).filter(Boolean))] : "";
113 |
114 | if(tagsData.length === 0) {
115 | processedTagsArr.forEach((pTagName) => {
116 | const tagData = {
117 | id: generateUUID(),
118 | name: pTagName,
119 | note_ids: [note.id]
120 | };
121 | tagsData.push(tagData);
122 | });
123 | } else {
124 | if(initialNoteTags.length > processedTagsArr.length) {
125 | let deletedTags = initialNoteTags.filter((nT) => !processedTagsArr.includes(nT));
126 | deletedTags.forEach((dTagName) => {
127 | tagsData.forEach((tData) => {
128 | if(tData.name === dTagName) {
129 | const idx = tData.note_ids.indexOf(note.id);
130 | tData.note_ids.splice(idx, 1);
131 | }
132 | });
133 | });
134 | }
135 |
136 | processedTagsArr.forEach((pTagName) => {
137 | let updatedPTagName = false;
138 | tagsData.forEach((tData) => {
139 | if(tData.name === pTagName) {
140 | if(!tData.note_ids.includes(note.id)) {
141 | tData.note_ids.push(note.id);
142 | updatedPTagName = true;
143 | return;
144 | } else {
145 | updatedPTagName = true;
146 | }
147 | }
148 | });
149 |
150 | if(!updatedPTagName) {
151 | const tagData = {
152 | id: generateUUID(),
153 | name: pTagName,
154 | note_ids: [note.id]
155 | };
156 | tagsData.push(tagData);
157 | }
158 | });
159 | }
160 | };
161 |
162 | if(!isUserSignedIn) {
163 | return ;
164 | }
165 |
166 | if(noteNotFound) {
167 | return
168 | }
169 |
170 | if(isFormSubmitted) {
171 | return ;
172 | }
173 |
174 | const loading = notes.isFetching;
175 | console.log(tagsData);
176 |
177 | return(
178 | <>
179 |
180 |
181 |
182 | {
183 | loading || !note ? (
184 |
185 | ) : (
186 | <>
187 | handleSubmit(e) } className="form-tag" >
188 |
189 | setNoteTitle(e.target.value) } minLength="3" maxLength="25" required placeholder="Note Title" className="validate" />
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | setNoteTags(e.target.value) } placeholder="Tag 1, Tag 2" className="validate" />
202 |
203 |
204 |
205 |
206 | Update
207 | send
208 |
209 |
210 |
211 |
212 |
213 |
214 | Cancel
215 | backspace
216 |
217 |
218 | >
219 | )
220 | }
221 |
222 |
223 |
224 | >
225 | );
226 | };
227 |
228 | export default Edit;
--------------------------------------------------------------------------------
/src/components/Dash/Dash.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, createRef, useState } from 'react';
2 | import { Link, Redirect } from 'react-router-dom';
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { Person } from 'blockstack';
5 | import M from "materialize-css";
6 |
7 | import { appConfig, userSession, isUserSignedIn } from '../Shared/defaults';
8 | import { fetchNotebookFile } from '../../store/actions/notesAction';
9 | import { fetchTagsFile } from '../../store/actions/tagsAction';
10 | import Navbar from '../Shared/Navbar';
11 | import Loading from '../Shared/Loading';
12 | import FloatingIcon from '../Shared/FloatingIcon';
13 | import Footer from '../Shared/Footer';
14 |
15 | function Dash() {
16 | // Get the Redux Dispatch
17 | const dispatch = useDispatch();
18 |
19 | // Get the Redux state
20 | const store = useSelector(store => store);
21 | const notes = store.notes;
22 | const notesData = notes.notesData;
23 |
24 | const tags = store.tags;
25 | const tagsData = tags.tagsData;
26 |
27 | const tagNameRef = createRef();
28 |
29 | const [tagNotes, setTagNotes] = useState(null);
30 |
31 | useEffect(() => {
32 | if(isUserSignedIn && !notesData) dispatch(fetchNotebookFile());
33 |
34 | if(isUserSignedIn && !tagsData) dispatch(fetchTagsFile());
35 |
36 | if(isUserSignedIn && notesData) {
37 | document.addEventListener('keydown', keyboardShortcutsHandler);
38 |
39 | return () => {
40 | document.removeEventListener('keydown', keyboardShortcutsHandler);
41 | }
42 | }
43 | }, [notesData, tagsData]);
44 |
45 | useEffect(() => {
46 | if(isUserSignedIn && notesData && tagsData) {
47 | const elems = document.querySelectorAll('.autocomplete');
48 | const options = {
49 | data: {},
50 | limit: 10,
51 | onAutocomplete: (val) => {
52 | handleTagSearchSubmit(null, val);
53 | }
54 | };
55 | tagsData.forEach((tD) => {
56 | options.data[tD.name] = null;
57 | });
58 | const instances = M.Autocomplete.init(elems, options);
59 | }
60 | }, [notesData, tagsData]);
61 |
62 | const keyboardShortcutsHandler = (e) => {
63 | if(e.key < 1 && e.key > 9) {
64 | return;
65 | }
66 |
67 | if((e.key > 0 && e.key < 10) && (e.ctrlKey || e.metaKey)) {
68 | e.preventDefault();
69 | const dncIndex = document.querySelector(`.dnc-${e.key}`);
70 |
71 | if(dncIndex) dncIndex.click();
72 | }
73 | };
74 |
75 | const handleTagSearchSubmit = (e, tempVal) => {
76 | if(e) e.preventDefault();
77 | const tagName = tagNameRef.current ? tagNameRef.current.value : tempVal;
78 | const noteIDS = [];
79 | const customTagNotes = [];
80 | tagsData.forEach((tagData) => {
81 | if(tagData.name.includes(tagName)) {
82 | noteIDS.push(...tagData.note_ids);
83 | }
84 | });
85 | if(noteIDS.length !== 0) {
86 | notesData.forEach((noteData) => {
87 | if(noteIDS.includes(noteData.id)) {
88 | customTagNotes.push(noteData);
89 | }
90 | });
91 |
92 | setTagNotes(customTagNotes);
93 | } else {
94 | setTagNotes(null);
95 | }
96 | };
97 |
98 | const handleTagSearchClear = (e) => {
99 | e.preventDefault();
100 | setTagNotes(null);
101 | tagNameRef.current.value = "";
102 | };
103 |
104 | if(!isUserSignedIn) {
105 | return ;
106 | }
107 |
108 | const userData = userSession.loadUserData();
109 | const userProfile = new Person(userData.profile);
110 | const username = userData.username;
111 | const usernameorFullName = userProfile.name() ? userProfile.name() : username;
112 | const userAvatarURL = userProfile.avatarUrl() ? userProfile.avatarUrl() : `${appConfig.appDomain}/icons/avatar.png`;
113 |
114 | const NotesLoading = notes.isFetching;
115 |
116 | return(
117 | <>
118 |
119 |
120 |
121 |
122 |
Dashboard
123 |
124 |
125 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | {
141 | NotesLoading ? (
142 |
143 | ) : (
144 | <>
145 |
146 |
Your notes
147 |
148 |
149 | {
150 | notesData.length === 0 ? (
151 |
152 |
153 |
It's lonely over here, start writing a new note by click on + icon from bottom right...
154 |
155 |
156 |
157 | ) : (<>>)
158 | }
159 |
160 | {
161 | tagsData && tagsData.length !== 0 ? (
162 |
163 |
164 |
handleTagSearchSubmit(e, null) }>
165 |
166 |
167 | timeline
168 |
169 | Tags
170 |
171 |
172 |
173 |
174 | search
175 |
176 | handleTagSearchClear(e) }>
177 | clear
178 |
179 |
180 |
181 |
182 |
183 |
184 | ) : ("")
185 | }
186 |
187 |
188 | {
189 | notesData && !tagNotes && notesData.map((note, index) => {
190 | return(
191 |
192 |
193 |
{ note.title }
194 |
195 | {
196 | tagsData && tagsData.map((tagData, idx) => {
197 | return tagData.note_ids.includes(note.id) ? (
198 | { tagData.name }
199 | ) : ("")
200 | })
201 | }
202 |
203 |
204 |
205 | );
206 | })
207 | }
208 |
209 | {
210 | notesData && tagNotes && tagNotes.map((note, index) => {
211 | return(
212 |
213 |
214 |
{ note.title }
215 |
216 | {
217 | tagsData && tagsData.map((tagData, idx) => {
218 | return tagData.note_ids.includes(note.id) ? (
219 | { tagData.name }
220 | ) : ("")
221 | })
222 | }
223 |
224 |
225 |
226 | );
227 | })
228 | }
229 |
230 |
231 | >
232 | )
233 | }
234 |
235 |
236 |
237 |
238 |
239 | >
240 | );
241 | };
242 |
243 | export default Dash;
--------------------------------------------------------------------------------
/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import Navbar from '../Shared/Navbar';
5 | import Footer from '../Shared/Footer';
6 |
7 | function Home() {
8 | return(
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Notga
18 |
19 |
20 |
TLDR; Take free, private, secure, unlimited, end-to-end encrypted, and decentralized notes in markdown/WYSIWYG editor. What else do you need? 🤔️
21 |
22 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Privacy in mind
45 |
46 | Notga is powered by Blockstack, an open-source, blockchain-based decentralized internet platform and app ecosystem that puts users in control of their identity and data. No company owns or controls Blockstack, it is independent.
47 |
48 | See how it works .
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
How it works
60 |
61 | When you create a Blockstack account, you'll get a secret key that will encrypt everything that you do in the Blockstack app. Because of this, Blockstack or Notga or anyone can't see or track your data. Your data can only be unlocked with the secret key that you own.
62 |
63 |
64 | So when you create a notebook in Notga, it is encrypted by your secret key on your device itself and then saved on the cloud . Without your secret key, no one can have access to your notebooks.
65 |
66 |
67 | Your only task is to keep your secret key confidential and do not share it with others(like a password). If you lose your secret key, you lose your data also.
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
How it works
83 |
84 | When you create a Blockstack account, you'll get a secret key that will encrypt everything that you do in the Blockstack app. Because of this, Blockstack or Notga or anyone can't see or track your data. Your data can only be unlocked with the secret key that you own.
85 |
86 |
87 | So when you create a notebook in Notga, it is encrypted by your secret key on your device itself and then saved on the cloud . Without your secret key, no one can have access to your notebooks.
88 |
89 |
90 | Your only task is to keep your secret key confidential and do not share it with others(like a password). If you lose your secret key, you lose your data also.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
Storage
106 |
107 | By default, all data is stored on free space provided by Blockstack. But you can even choose to store data on your own server using Gaia . Since it's end-to-end encrypted, it doesn't matter where it's stored.
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
Taking Notes
119 |
120 | Like we've mentioned, you can create unlimited notes using Notga. You can write your notes in markdown or WYSIWYG(What You See Is What You Get) editor. Other features include code-snippets with syntax highlighting and keyboard shortcuts to navigate around the web app faster and more comfortable.
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
Taking Notes
136 |
137 | Like we've mentioned, you can create unlimited notes using Notga. You can write your notes in markdown or WYSIWYG(What You See Is What You Get) editor. Other features include code-snippets with syntax highlighting and keyboard shortcuts to navigate around the web app faster and more comfortable.
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
No Lock-in's
153 |
154 | One question to ask, what if Notga stops working from tomorrow? Will I lose access to all my notes?
155 |
156 |
157 | Simple answer, NO. Notga doesn't store any of your data, you choose where to store your data(defaults to your Blockstack). You can access your notes with alternative interfaces even if Notga stops working from tomorrow.
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
Set it free
169 |
170 | We loved building Notga, so we set it free and even made it open-source whose codebase is available on GitHub . So, no hidden data hogs, and we feel like a free bird. 🤗️ 😇️
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
Set it free
186 |
187 | We loved building Notga, so we set it free and even made it open-source whose codebase is available on GitHub . So, no hidden data hogs, and we feel like a free bird. 🤗️ 😇️
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | Get started now
197 | send
198 |
199 |
200 |
201 |
202 |
203 |
204 | );
205 | };
206 |
207 | export default Home;
--------------------------------------------------------------------------------
/public/images/illustrations/privacy.svg:
--------------------------------------------------------------------------------
1 | privacy_protection
--------------------------------------------------------------------------------
/public/images/illustrations/factory.svg:
--------------------------------------------------------------------------------
1 | factory
--------------------------------------------------------------------------------
/public/images/illustrations/lockin.svg:
--------------------------------------------------------------------------------
1 | unlock
--------------------------------------------------------------------------------
/public/images/illustrations/storage.svg:
--------------------------------------------------------------------------------
1 | server status
--------------------------------------------------------------------------------
/public/images/illustrations/notebook.svg:
--------------------------------------------------------------------------------
1 | personal_notebook
--------------------------------------------------------------------------------