├── .dockerignore
├── .editorconfig
├── .env.sample
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── 1-Bug_report.md
│ └── 2-Feature_request.md
├── config.yml
├── stale.yml
└── workflows
│ ├── master.yml
│ └── master_old.yml
├── .gitignore
├── .prettierrc.json
├── .stylelintrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── COPYING
├── Dockerfile.prod
├── README.md
├── app
├── .testcafe-electron-rc
├── AppUpdater.ts
├── PouchInit.tsx
├── Routes.tsx
├── app.global.css
├── app.html
├── app.icns
├── assets
│ └── sample.png
├── components
│ ├── Counter
│ │ ├── Counter.css
│ │ └── Counter.tsx
│ ├── EditorPane
│ │ ├── CollectionEditor
│ │ │ └── CollectionEditor.tsx
│ │ ├── ColumnEditor
│ │ │ └── ColumnEditor.tsx
│ │ ├── EditorPane.tsx
│ │ ├── FieldForm
│ │ │ ├── FieldForm.tsx
│ │ │ └── MultiSelect.tsx
│ │ ├── GroupEditor
│ │ │ └── GroupEditor.tsx
│ │ ├── IndexEditor
│ │ │ └── IndexEditor.tsx
│ │ ├── MarkdownEditor
│ │ │ ├── MarkdownEditor.tsx
│ │ │ └── codemirror
│ │ │ │ └── addon
│ │ │ │ └── fold-image.ts
│ │ ├── RichEditor
│ │ │ └── RichEditor.tsx
│ │ └── style.js
│ ├── Home
│ │ ├── Home.css
│ │ └── Home.tsx
│ ├── MainMenu
│ │ ├── FlatMenu.tsx
│ │ ├── MainMenu.tsx
│ │ ├── MenuItem.tsx
│ │ ├── TreeMenu.tsx
│ │ └── style.js
│ ├── MiddleMenu
│ │ ├── MiddleMenu.style.js
│ │ ├── MiddleMenu.tsx
│ │ └── NoteCard
│ │ │ ├── NoteCard.style.js
│ │ │ └── NoteCard.tsx
│ ├── css.d.ts
│ └── util
│ │ ├── Finder.tsx
│ │ ├── FinderResultList.tsx
│ │ ├── ModalForm.tsx
│ │ └── utils.style.js
├── constants
│ └── routes.json
├── containers
│ ├── App.tsx
│ ├── ContentAreaCont
│ │ ├── ContentAreaCont.tsx
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── CounterPage
│ │ ├── CounterPage.tsx
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── HomePage
│ │ ├── HomePage.tsx
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── MainMenu
│ │ ├── MainMenuCont.tsx
│ │ ├── actions.ts
│ │ ├── reducers.ts
│ │ └── selectors.ts
│ ├── MiddleMenu
│ │ ├── MiddleMenuCont.tsx
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── Root.tsx
│ ├── TreeMenuCont
│ │ ├── TreeMenuCont.tsx
│ │ └── actions.ts
│ └── util
│ │ └── NotesWrapper.tsx
├── file_templates
│ ├── component
│ │ ├── component.styles.js
│ │ └── component.tsx
│ └── container
│ │ ├── TemplateCont.tsx
│ │ ├── actions.ts
│ │ └── reducers.ts
├── index.tsx
├── main.dev.ts
├── main.prod.js.LICENSE
├── menu.ts
├── monaco-editor-worker-loader-proxy.js
├── package.json
├── reducers
│ ├── config
│ │ ├── README.md
│ │ ├── configs.electron.ts
│ │ └── configs.web.ts
│ ├── configActions.ts
│ ├── configs.ts
│ ├── index.ts
│ ├── modalActions.ts
│ ├── modals.ts
│ ├── noteActions.ts
│ ├── notebookActions.ts
│ ├── notebooks.ts
│ ├── notes.ts
│ └── types.ts
├── store
│ ├── configureStore.dev.ts
│ ├── configureStore.prod.ts
│ └── configureStore.ts
├── style
│ ├── ant-customisation.css
│ ├── code-mirror-markdown.css
│ ├── context-menu.css
│ ├── react-split-pane.css
│ └── utils.style.js
├── utils
│ ├── .gitkeep
│ ├── DragItemTypes.js
│ ├── config.electron.ts
│ ├── config.ts
│ ├── config.web.ts
│ ├── configuration.json
│ ├── dark_theme.json
│ ├── dark_theme_original.json
│ ├── localStorage.js
│ └── utils.js
└── yarn.lock
├── babel.config.js
├── build.sh
├── configs
├── .eslintrc
├── webpack.config.base.js
├── webpack.config.eslint.js
├── webpack.config.main.prod-web.babel.js
├── webpack.config.main.prod.babel.js
├── webpack.config.renderer.dev.babel.js
├── webpack.config.renderer.dev.dll.babel.js
├── webpack.config.renderer.prod.babel.js
├── webpack.config.web.dev.babel.js
└── webpack.config.web.prod.babel.js
├── docker-build.sh
├── docker-compose.sample.yaml
├── docs
├── fonts
│ ├── OpenSans-Bold-webfont.eot
│ ├── OpenSans-Bold-webfont.svg
│ ├── OpenSans-Bold-webfont.woff
│ ├── OpenSans-BoldItalic-webfont.eot
│ ├── OpenSans-BoldItalic-webfont.svg
│ ├── OpenSans-BoldItalic-webfont.woff
│ ├── OpenSans-Italic-webfont.eot
│ ├── OpenSans-Italic-webfont.svg
│ ├── OpenSans-Italic-webfont.woff
│ ├── OpenSans-Light-webfont.eot
│ ├── OpenSans-Light-webfont.svg
│ ├── OpenSans-Light-webfont.woff
│ ├── OpenSans-LightItalic-webfont.eot
│ ├── OpenSans-LightItalic-webfont.svg
│ ├── OpenSans-LightItalic-webfont.woff
│ ├── OpenSans-Regular-webfont.eot
│ ├── OpenSans-Regular-webfont.svg
│ └── OpenSans-Regular-webfont.woff
├── index.html
├── scripts
│ ├── linenumber.js
│ └── prettify
│ │ ├── Apache-License-2.0.txt
│ │ ├── lang-css.js
│ │ └── prettify.js
└── styles
│ ├── jsdoc-default.css
│ ├── prettify-jsdoc.css
│ └── prettify-tomorrow.css
├── env
├── development.js
└── production.js
├── images
├── context_menu.png
├── global_search.png
├── md_boilerplate.png
├── notebook_context_menu.png
└── tag_editor.png
├── internals
├── img
│ ├── erb-banner.png
│ ├── erb-logo.png
│ ├── eslint-padded-90.png
│ ├── eslint-padded.png
│ ├── eslint.png
│ ├── jest-padded-90.png
│ ├── jest-padded.png
│ ├── jest.png
│ ├── js-padded.png
│ ├── js.png
│ ├── npm.png
│ ├── react-padded-90.png
│ ├── react-padded.png
│ ├── react-router-padded-90.png
│ ├── react-router-padded.png
│ ├── react-router.png
│ ├── react.png
│ ├── redux-padded-90.png
│ ├── redux-padded.png
│ ├── redux.png
│ ├── webpack-padded-90.png
│ ├── webpack-padded.png
│ ├── webpack.png
│ ├── yarn-padded-90.png
│ ├── yarn-padded.png
│ └── yarn.png
├── mocks
│ └── fileMock.js
└── scripts
│ ├── .eslintrc
│ ├── BabelRegister.js
│ ├── CheckBuildsExist.js
│ ├── CheckNativeDep.js
│ ├── CheckNodeEnv.js
│ ├── CheckPortInUse.js
│ ├── CheckYarn.js
│ ├── DeleteSourceMaps.js
│ └── ElectronRebuild.js
├── nightly.sh
├── package.json
├── renovate.json
├── resources
├── icon.icns
├── icon.ico
├── icon.png
└── icons
│ ├── 1024x1024.png
│ ├── 128x128.png
│ ├── 16x16.png
│ ├── 256x256.png
│ ├── 32x32.png
│ ├── 48x48.png
│ ├── 512x512.png
│ └── 64x64.png
├── serve.js
├── test
├── .eslintrc.json
├── actions
│ ├── __snapshots__
│ │ └── counter.spec.ts.snap
│ └── counter.spec.ts
├── components
│ ├── Counter.spec.tsx
│ └── __snapshots__
│ │ └── Counter.spec.tsx.snap
├── containers
│ └── CounterPage.spec.tsx
├── e2e
│ ├── HomePage.e2e.ts
│ └── helpers.ts
├── example.js
└── reducers
│ ├── __snapshots__
│ └── counter.spec.ts.snap
│ └── counter.spec.ts
├── tsconfig.json
├── web
├── env.sh
├── gzip.conf
├── index.html
├── index.web.html
└── nginx.conf
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # App packaged
34 | release
35 | app/main.prod.js
36 | app/main.prod.js.map
37 | app/renderer.prod.js
38 | app/renderer.prod.js.map
39 | app/style.css
40 | app/style.css.map
41 | dist
42 | web/dist
43 | dll
44 | main.js
45 | main.js.map
46 |
47 | .idea
48 | npm-debug.log.*
49 | .*.dockerfile
50 | .cache
51 | data
52 | .vscode
53 | .github
54 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | COUCHDB_USER=admin
2 | COUCHDB_PASSWORD=admin
3 | COUCHDB_PASSWORD=admin
4 | DOMAIN_NAME=example.com
5 | COUCH_SUBDOMAIN=couch
6 | NOTORIOUS_SUBDOMAIN=notorious
7 | COUCHDB_SCHEME=http
8 | DB_URL=couch.example.com
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # App packaged
34 | release
35 | app/main.prod.js
36 | app/main.prod.js.map
37 | app/renderer.prod.js
38 | app/renderer.prod.js.map
39 | app/style.css
40 | app/style.css.map
41 | dist
42 | dll
43 | main.js
44 | main.js.map
45 |
46 | .idea
47 | npm-debug.log.*
48 | __snapshots__
49 |
50 | # Package.json
51 | package.json
52 | .travis.yml
53 | *.css.d.ts
54 | *.sass.d.ts
55 | *.scss.d.ts
56 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb/typescript',
3 | rules: {
4 | // A temporary hack related to IDE not resolving correct package.json
5 | 'import/no-extraneous-dependencies': 'off'
6 | },
7 | settings: {
8 | 'import/resolver': {
9 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
10 | node: {},
11 | webpack: {
12 | config: require.resolve('./configs/webpack.config.eslint.js')
13 | }
14 | }
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.png binary
3 | *.jpg binary
4 | *.jpeg binary
5 | *.ico binary
6 | *.icns binary
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | custom: ["https://paypal.me/danielb160", "https://gofund.me/7a2487d5"]
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues.
4 | labels: 'bug'
5 | ---
6 |
7 |
8 |
9 |
10 |
11 | ## Prerequisites
12 |
13 |
14 |
15 | - [ ] Using yarn
16 | - [ ] Using node 10.x
17 | - [ ] Using an up-to-date [`master` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/master)
18 | - [ ] Using latest version of devtools. See [wiki for howto update](https://github.com/electron-react-boilerplate/electron-react-boilerplate/wiki/DevTools)
19 | - [ ] Link to stacktrace in a Gist (for bugs)
20 | - [ ] For issue in production release, devtools output of `DEBUG_PROD=true yarn build && yarn start`
21 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)
22 |
23 | ## Expected Behavior
24 |
25 |
26 |
27 |
28 | ## Current Behavior
29 |
30 |
31 |
32 |
33 | ## Possible Solution
34 |
35 |
36 |
37 |
38 | ## Steps to Reproduce (for bugs)
39 |
40 |
41 |
42 |
43 | 1.
44 |
45 | 2.
46 |
47 | 3.
48 |
49 | 4.
50 |
51 | ## Context
52 |
53 |
54 |
55 |
56 |
57 | ## Your Environment
58 |
59 |
60 |
61 | - Node version :
62 | - Version or Branch used :
63 | - Operating System and version :
64 | - Link to your project :
65 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something added to the boilerplate.
4 | labels: 'enhancement'
5 | ---
6 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pr
8 | - discussion
9 | - e2e
10 | - enhancement
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/master_old.yml:
--------------------------------------------------------------------------------
1 | name: Release Application
2 |
3 | on:
4 | push:
5 | branches:
6 | - master_old
7 | jobs:
8 | release_management:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v1
13 | - name: Setting up Node
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: '10.15.1'
17 | - name: Preparing release
18 | run: |
19 | git config --local user.email "action@github.com"
20 | git config --local user.name "GitHub Action"
21 | npx standard-version -a --no-verify --prerelease alpha --skip.commit --skip.tag --skip.changelog
22 | echo ::set-env name=PACKAGE_VERSION::$(node -p -e "require('./package.json').version")
23 | env:
24 | CI: true
25 | - name: Push to master
26 | uses: ad-m/github-push-action@v0.5.0
27 | with:
28 | github_token: ${{ secrets.GITHUB_TOKEN }}
29 | # - name: Merge back to develop
30 | # uses: ad-m/github-push-action@v0.5.0
31 | # with:
32 | # github_token: ${{ secrets.GITHUB_TOKEN }}
33 | # branch: develop
34 |
35 | build:
36 | if: success()
37 | runs-on: ${{ matrix.os }}
38 |
39 | strategy:
40 | matrix:
41 | os: [windows-2019] # macos-10.15, ubuntu-18.04,
42 | steps:
43 | - uses: actions/checkout@v1
44 | - name: Setting up Node
45 | uses: actions/setup-node@v1
46 | with:
47 | node-version: '10.15.1'
48 |
49 | env:
50 | CI: true
51 | - name: Get Release Version
52 | run: |
53 | echo ::set-env name=PACKAGE_VERSION::$(node -p -e "require('./package.json').version")
54 | - name: Build/release Electron app
55 | uses: samuelmeuli/action-electron-builder@v1
56 | with:
57 | # GitHub token, automatically provided to the action
58 | # (No need to define this secret in the repo settings)
59 | github_token: ${{ secrets.GITHUB_TOKEN }}
60 |
61 | # If the commit is tagged with a version (e.g. "v1.0.0"),
62 | # release the app after building
63 | release: ${{ startsWith(github.ref, 'refs/tags/v') }}
64 | - name: Cleanup artifacts
65 | run: |
66 | npx rimraf "dist/!(*.exe|*.deb|*.AppImage|*.dmg)"
67 | - name: Archive production artifacts
68 | uses: actions/upload-artifact@v1
69 | with:
70 | name: Linux
71 | path: release
72 |
73 | - name: Create Github Release
74 | id: create_release
75 | uses: actions/create-release@v1
76 | env:
77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
78 | with:
79 | tag_name: v${{ env.PACKAGE_VERSION }}
80 | release_name: ${{ env.PACKAGE_VERSION }}
81 | draft: true
82 | prerelease: false
83 | docker_build:
84 | if: success()
85 | runs-on: ubuntu-latest
86 |
87 | steps:
88 | - uses: actions/checkout@v1
89 | - name: Setting up Node
90 | uses: actions/setup-node@v1
91 | with:
92 | node-version: '10.15.1'
93 | - uses: mr-smithers-excellent/docker-build-push@v2
94 | with:
95 | image: danobot/notorious
96 | tag: ${{ env.PACKAGE_VERSION }}
97 | registry: hub.docker.com
98 | dockerfile: Dockerfile.prod
99 | username: ${{ secrets.DOCKER_USERNAME }}
100 | password: ${{ secrets.DOCKER_PASSWORD }}
101 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # App packaged
34 | release
35 | app/main.prod.js
36 | app/main.prod.js.map
37 | app/renderer.prod.js
38 | app/renderer.prod.js.map
39 | app/style.css
40 | app/style.css.map
41 | dist
42 | dll
43 | main.js
44 | main.js.map
45 |
46 | .idea
47 | npm-debug.log.*
48 | *.css.d.ts
49 | *.sass.d.ts
50 | *.scss.d.ts
51 | .cache/*
52 | data/*
53 | .env
54 | docker-compose.yaml
55 | nullnotes/*
56 | # Temporary env files
57 | /public/env-config.js
58 | env-config.js
59 | null/
60 | ws.code-workspace
61 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": [
3 | {
4 | "files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"],
5 | "options": {
6 | "parser": "json"
7 | }
8 | }
9 | ],
10 | "singleQuote": true
11 | }
12 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 making 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 electronreactboilerplate@gmail.com. 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 |
--------------------------------------------------------------------------------
/Dockerfile.prod:
--------------------------------------------------------------------------------
1 | # build environment
2 | FROM node:10.15.1 as build
3 | WORKDIR /app
4 | ENV PATH /app/node_modules/.bin:$PATH
5 | COPY package.json /app/package.json
6 | COPY /app/package.json /app/app/package.json
7 | # RUN apt instad --no-cache bash
8 | COPY /yarn.lock /app/yarn.lock
9 | COPY /app/yarn.lock /app/app/yarn.lock
10 | COPY babel.config.js /app/babel.config.js
11 | COPY /internals/scripts /app/internals/scripts
12 | COPY /app/index.tsx /app/app/index.tsx
13 | # RUN rm -rf node_modules && yarn cache clean
14 | COPY /configs/webpack.config.base.js /app/configs/webpack.config.base.js
15 | COPY /configs/webpack.config.eslint.js /app/configs/webpack.config.eslint.js
16 | COPY /configs/webpack.config.renderer.dev.babel.js /app/configs/webpack.config.renderer.dev.babel.js
17 | COPY /configs/webpack.config.renderer.dev.dll.babel.js /app/configs/webpack.config.renderer.dev.dll.babel.js
18 | COPY /configs/webpack.config.renderer.prod.babel.js /app/configs/webpack.config.renderer.prod.babel.js
19 | COPY /configs/webpack.config.main.prod.babel.js /app/configs/webpack.config.main.prod.babel.js
20 | RUN yarn --non-interactive --network-concurrency 10
21 | COPY /configs /app/configs
22 | COPY . /app
23 | RUN yarn --non-interactive build-web
24 |
25 | # production environment
26 | FROM nginx:1.16.0-alpine
27 | WORKDIR /usr/share/nginx/html
28 | RUN apk update && apk upgrade && apk add bash
29 | COPY --from=build /app/web/dist /usr/share/nginx/html
30 | RUN rm /etc/nginx/conf.d/default.conf
31 | COPY web/nginx.conf /etc/nginx/conf.d/default.conf
32 | COPY web/gzip.conf /etc/nginx/conf.d/gzip.conf
33 | EXPOSE 80
34 | COPY web/env.sh .
35 | RUN touch .env
36 | RUN chmod +x env.sh
37 |
38 | # Start Nginx server
39 | CMD ["/bin/sh", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]
40 |
--------------------------------------------------------------------------------
/app/.testcafe-electron-rc:
--------------------------------------------------------------------------------
1 | {
2 | "mainWindowUrl": "./app.html",
3 | "appPath": "."
4 | }
5 |
--------------------------------------------------------------------------------
/app/AppUpdater.ts:
--------------------------------------------------------------------------------
1 | import { autoUpdater } from "electron-updater"
2 | const log = require("electron-log")
3 |
4 | export default class AppUpdater {
5 | constructor() {
6 | console.log("AppUpdater Created")
7 | log.transports.file.level = "debug"
8 | autoUpdater.logger = log
9 | autoUpdater.checkForUpdatesAndNotify()
10 | setInterval(() => {
11 | autoUpdater.checkForUpdatesAndNotify();
12 | }, 1000 * 60 * 15);
13 | // console.log("autoUpdater",autoUpdater)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/Routes.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router';
3 | import routes from './constants/routes.json';
4 | import App from './containers/App';
5 | import HomePage from './containers/HomePage/HomePage';
6 | import CounterPage from './containers/CounterPage/CounterPage';
7 |
8 | export default function Routes() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/app.global.css:
--------------------------------------------------------------------------------
1 | /*
2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules
3 | * See https://github.com/webpack-contrib/sass-loader#imports
4 | */
5 | @import '~@fortawesome/fontawesome-free/css/all.css';
6 | @import '~antd/dist/antd.css';
7 | @import '~jodit/build/jodit.min.css';
8 | @import '~codemirror/lib/codemirror.css';
9 | @import '~hypermd/mode/hypermd.css';
10 | @import '~hypermd/theme/hypermd-light.css';
11 |
12 |
13 | @import 'style/react-split-pane.css';
14 | @import 'style/ant-customisation.css';
15 | @import 'style/context-menu.css';
16 | @import 'style/code-mirror-markdown.css';
17 | /* @import "~easymde/dist/easymde.min.css"; */
18 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Notorious
6 |
20 |
21 |
22 |
23 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/app/app.icns
--------------------------------------------------------------------------------
/app/assets/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/app/assets/sample.png
--------------------------------------------------------------------------------
/app/components/Counter/Counter.css:
--------------------------------------------------------------------------------
1 | .backButton {
2 | position: absolute;
3 | }
4 |
5 | .counter {
6 | position: absolute;
7 | top: 30%;
8 | left: 45%;
9 | font-size: 10rem;
10 | font-weight: bold;
11 | letter-spacing: -0.025em;
12 | }
13 |
14 | .btnGroup {
15 | position: relative;
16 | top: 500px;
17 | width: 480px;
18 | margin: 0 auto;
19 | }
20 |
21 | .btn {
22 | font-size: 1.6rem;
23 | font-weight: bold;
24 | background-color: #fff;
25 | border-radius: 50%;
26 | margin: 10px;
27 | width: 100px;
28 | height: 100px;
29 | opacity: 0.7;
30 | cursor: pointer;
31 | font-family: Arial, Helvetica, Helvetica Neue, sans-serif;
32 | }
33 |
34 | .btn:hover {
35 | color: white;
36 | background-color: rgba(0, 0, 0, 0.5);
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/Counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import styles from './Counter.css';
5 |
6 | type Props = {
7 | increment: () => void;
8 | incrementIfOdd: () => void;
9 | incrementAsync: () => void;
10 | decrement: () => void;
11 | counter: number;
12 | };
13 |
14 | export default function Counter(props: Props) {
15 | const {
16 | increment,
17 | incrementIfOdd,
18 | incrementAsync,
19 | decrement,
20 | counter
21 | } = props;
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {counter}
32 |
33 |
34 |
35 |
43 |
51 |
59 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/app/components/EditorPane/CollectionEditor/CollectionEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 |
3 |
4 | import SimpleMDE from "react-simplemde-editor";
5 | import FieldForm from './FieldForm/FieldForm';
6 | import Moment from "react-moment";
7 |
8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
9 |
10 | import { faClock } from "@fortawesome/free-regular-svg-icons";
11 | import { faHistory, faTrashAlt, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
12 |
13 | export default function CollectionEditor({ subNotes, noteActions }) {
14 |
15 | return (
16 | <>
17 | {subNotes.map(editor => {editor.title}
)}
18 |
19 | >
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/app/components/EditorPane/ColumnEditor/ColumnEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 |
3 |
4 | import SimpleMDE from "react-simplemde-editor";
5 | import FieldForm from './FieldForm/FieldForm';
6 | import Moment from "react-moment";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import Editor from '@monaco-editor/react';
9 | import { faClock } from "@fortawesome/free-regular-svg-icons";
10 | import { faHistory, faTrashAlt, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
11 | import { Button } from 'antd';
12 |
13 | import SplitPane from 'react-split-pane';
14 | export default function ColumnEditor({ subNotes, noteActions, note }) {
15 | const [isEditorReady, setIsEditorReady] = useState(false);
16 | const [state, setState] = useState({});
17 | const valueGetter = useRef();
18 |
19 | function handleEditorDidMount(_valueGetter) {
20 | console.log('Editor did mount');
21 | setIsEditorReady(true);
22 | valueGetter.current = _valueGetter;
23 | console.log(valueGetter.current());
24 | }
25 | const handleBlur = note => value => {
26 | console.log("editor content",value.getValue());
27 | console.log("note.content: ",note.content);
28 | // setState({ content: value.getValue() });
29 | if (note.content !== value.getValue()) {
30 | console.log("updateNote");
31 | noteActions.updateNote(note._id, { content: value.getValue() });
32 | }
33 | };
34 | const noteref = note._id+note._rev
35 |
36 | return (
37 | <>
38 |
39 | {subNotes.map(editor => {
40 | const unique = "column-editor-" + editor._id + editor._ref
41 | return
49 |
50 | })}
51 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/app/components/EditorPane/FieldForm/FieldForm.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {DelayInput} from 'react-delay-input';
3 | import { Formik } from 'formik';
4 | import { Form } from 'antd';
5 | const FieldForm = (props) => {
6 | const { label, value, onUpdate , placeholder, className} = props
7 | const delay = props.delay ? props.delay : 1000
8 |
9 | return (
10 | {
14 | const errors = {};
15 | return errors;
16 | }}
17 | onSubmit={(values, { setSubmitting }) => {
18 | if (values !== issue) {
19 | console.log("onSubmit:: ", values)
20 | }
21 | }}
22 | >
23 | {({
24 | values,
25 | errors,
26 | touched,
27 | handleChange,
28 | handleBlur,
29 | handleSubmit,
30 | isSubmitting,setValues
31 | }) => (
32 |
43 | )}
44 |
45 | )
46 | }
47 |
48 | export default FieldForm;
49 |
--------------------------------------------------------------------------------
/app/components/EditorPane/FieldForm/MultiSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MyInput, CustomSelect } from '../style';
3 |
4 | import CreatableSelect from 'react-select/creatable';
5 | // import { colourOptions } from '../data';
6 | const MultiSelect = (props) => {
7 | const { label, value, onUpdate , placeholder, className, values, options, id} = props
8 | const styles = {
9 | control: (provided, state) =>({
10 | ...provided,
11 | border: 'none',
12 | borderWidth: 0,
13 | borderStyle: 'none',
14 | borderColor: 'transparent',
15 | boxShadow: 'none',
16 | backgroundColor: 'unset',
17 | // display: 'block',
18 | // zIndex: 1000
19 | }),
20 | valueContainer: (provided, state) =>({
21 | ...provided,
22 | padding: 0,
23 | border: 'none',
24 | cursor: 'text',
25 |
26 | }),
27 | container: (provided, state) =>({
28 | ...provided,
29 | padding: 0,
30 | border: 'none',
31 | backgroundColor: 'unset'
32 | }),
33 | menu: (provided, state) =>({
34 | ...provided,
35 | // padding: 0,
36 | // border: 'none',
37 | // zIndex: 10000,
38 | transform: 'translateZ(1000)',
39 | // width: '100px'
40 |
41 | }),
42 | multiValueLabel: (provided, state) =>({
43 | ...provided,
44 | padding: 0
45 | }),
46 | multiValueRemove: (provided, state) =>({
47 | ...provided,
48 | padding: '0 2px 0 2px'
49 | }),
50 | indicatorsContainer: (provided, state) =>({
51 | ...provided,
52 | visibility: 'hidden'
53 | })
54 |
55 | }
56 | const submitHander = (newValue: any, actionMeta: any) => {
57 | console.group('Value Changed');
58 | console.log(newValue);
59 | console.log(`action: ${actionMeta.action}`);
60 | console.groupEnd();
61 | if (newValue) {
62 |
63 | const tags = newValue.map(t => t.value)
64 | onUpdate(tags)
65 | } else {
66 | onUpdate([])
67 | }
68 |
69 | }
70 |
71 | return (
72 |
73 |
74 | ({label: t,value:t})) : []}
84 | // value={[{label: "gfsd",value:"hi"}]}
85 |
86 | onChange={submitHander}
87 | options={options}
88 | />
89 |
90 | )
91 | }
92 |
93 | export default MultiSelect;
94 |
--------------------------------------------------------------------------------
/app/components/EditorPane/GroupEditor/GroupEditor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 |
5 | // import { faClock } from "@fortawesome/free-regular-svg-icons";
6 | // import { faHistory, faTrashAlt, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
7 | import { Padding } from '../style';
8 |
9 | export default function GroupEditor({ subNotes, note, noteActions }) {
10 |
11 | return (
12 |
13 | {subNotes.map(n=> noteActions.selectNoteAction(n._id)}>{n.title || "Untitled Note"}
)}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/components/EditorPane/IndexEditor/IndexEditor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 |
5 | // import { faClock } from "@fortawesome/free-regular-svg-icons";
6 | // import { faHistory, faTrashAlt, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
7 | import { Padding } from '../style';
8 |
9 | export default function IndexEditor({ subNotes, note, noteActions }) {
10 |
11 | return (
12 |
13 | Index of "{note.title || "Untitled"}"
14 |
15 | {subNotes.map(n=> noteActions.selectNoteAction(n._id)}>{n.title || "Untitled Note"}
)}
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/app/components/EditorPane/MarkdownEditor/codemirror/addon/fold-image.ts:
--------------------------------------------------------------------------------
1 | // HyperMD, copyright (c) by laobubu
2 | // Distributed under an MIT license: http://laobubu.net/HyperMD/LICENSE
3 | //
4 | // DESCRIPTION: Fold Image Markers ``
5 | //
6 |
7 | import { FolderFunc, registerFolder, RequestRangeResult, breakMark } from "hypermd/addon/fold";
8 | import { Position } from "codemirror";
9 | import { splitLink } from "hypermd/addon/read-link";
10 | import config from '../../../../../utils/config';
11 |
12 | const DEBUG = false
13 |
14 | export const ImageFolder: FolderFunc = function (stream, token) {
15 | const cm = stream.cm
16 | // console.log("ImageFolder token", token)
17 | const customRE = /\@.*/
18 | const imgRE = /\bimage-marker\b/
19 | const urlRE = /\bformatting-link-string\b/ // matches the parentheses
20 | if (imgRE.test(token.type) && token.string === "!") {
21 | var lineNo = stream.lineNo
22 |
23 | // find the begin and end of url part
24 | var url_begin = stream.findNext(urlRE)
25 | var url_end = stream.findNext(urlRE, url_begin.i_token + 1)
26 |
27 | let from: Position = { line: lineNo, ch: token.start }
28 | let to: Position = { line: lineNo, ch: url_end.token.end }
29 | let rngReq = stream.requestRange(from, to, from, from)
30 |
31 | if (rngReq === RequestRangeResult.OK) {
32 | var url: string
33 | var title: string
34 |
35 | { // extract the URL
36 | let rawurl = cm.getRange( // get the URL or footnote name in the parentheses
37 | { line: lineNo, ch: url_begin.token.start + 1 },
38 | { line: lineNo, ch: url_end.token.start }
39 | )
40 | if (url_end.token.string === "]") {
41 | let tmp = cm.hmdReadLink(rawurl, lineNo)
42 | if (!tmp) return null // Yup! bad URL?!
43 | rawurl = tmp.content
44 | }
45 | url = splitLink(rawurl).url
46 | console.log("Insdie fold-image url", url)
47 | if (customRE.test(url)) {
48 | console.log("url", url)
49 | const t = url.split("@")[1]
50 | console.log("tt", t)
51 | const note = t.split(':')[0]
52 | console.log("note", note)
53 | const attachment = t.split(':')[1]
54 | console.log("attachment", attachment)
55 | url = `${config.scheme}://${config.url}/notes/${note}/${attachment}`
56 | console.log("url", url)
57 | } else {
58 | url = cm.hmdResolveURL(url)
59 | }
60 | }
61 |
62 | { // extract the title
63 | title = cm.getRange(
64 | { line: lineNo, ch: from.ch + 2 },
65 | { line: lineNo, ch: url_begin.token.start - 1 }
66 | )
67 | }
68 | var img = document.createElement("img")
69 | var marker = cm.markText(
70 | from, to,
71 | {
72 | clearOnEnter: true,
73 | collapsed: true,
74 | replacedWith: img,
75 | }
76 | )
77 |
78 | img.addEventListener('load', () => {
79 | img.classList.remove("hmd-image-loading")
80 | marker.changed()
81 | }, false)
82 | img.addEventListener('error', () => {
83 | img.classList.remove("hmd-image-loading")
84 | img.classList.add("hmd-image-error")
85 | marker.changed()
86 | }, false)
87 | img.addEventListener('click', () => breakMark(cm, marker), false)
88 |
89 | img.className = "hmd-image hmd-image-loading"
90 | img.src = url
91 | img.title = title
92 | return marker
93 | } else {
94 | if (DEBUG) {
95 | console.log("[image]FAILED TO REQUEST RANGE: ", rngReq)
96 | }
97 | }
98 | }
99 |
100 | return null
101 | }
102 |
103 | registerFolder("image", ImageFolder, true)
104 |
--------------------------------------------------------------------------------
/app/components/EditorPane/RichEditor/RichEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component, useCallback } from 'react';
2 |
3 | import { debounce } from "lodash";
4 |
5 | import 'jodit';
6 | import JoditEditor from "jodit-react";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { } from "@fortawesome/free-regular-svg-icons";
9 | import { } from "@fortawesome/free-solid-svg-icons";
10 |
11 | const handler = f => useCallback(debounce(f, 2000), []);
12 |
13 | class RichEditor extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | content: props.note.content,
18 | }
19 | }
20 |
21 | updateContent(value) {
22 | // handler(()=>this.props.noteActions.updateNote(this.props.note._id, {content: value}))
23 | // console.log(value)
24 | // this.props.noteActions.updateNote(this.props.note._id, {content: value})
25 | // this.setState({content:value})
26 | clearTimeout(this.timeout)
27 | this.timeout = setTimeout(()=> {this.setState({content:value}) ;this.props.noteActions.updateNote(this.props.note._id, {content: value})}, 5000)
28 | }
29 |
30 | render() {
31 | return
39 | }
40 | }
41 | // export default function ColumnEditor({ subNotes, noteActions, note }) {
42 | // const [isEditorReady, setIsEditorReady] = useState(false);
43 | // const [state, setState] = useState({});
44 | // const valueGetter = useRef();
45 |
46 | // function handleEditorDidMount(_valueGetter) {
47 | // console.log('Editor did mount');
48 | // setIsEditorReady(true);
49 | // valueGetter.current = _valueGetter;
50 | // console.log(valueGetter.current());
51 | // }
52 | // const handleBlur = note => value => {
53 | // console.log("editor content",value.getValue());
54 | // console.log("note.content: ",note.content);
55 | // // setState({ content: value.getValue() });
56 | // if (note.content !== value.getValue()) {
57 | // console.log("updateNote");
58 | // noteActions.updateNote(note._id, { content: value.getValue() });
59 | // }
60 | // };
61 | // const noteref = note._id+note._rev
62 |
63 | // return (
64 | // <>
65 | //
66 | // {subNotes.map(editor => {
67 | // const unique = "column-editor-" + editor._id + editor._ref
68 | // return
76 |
77 | // })}
78 | //
79 | // >
80 | // );
81 | // }
82 | export default RichEditor;
83 |
--------------------------------------------------------------------------------
/app/components/EditorPane/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Tree } from 'antd';
3 | import { TopBar } from '../MiddleMenu/MiddleMenu.style';
4 | const { TreeNode } = Tree;
5 |
6 |
7 | export const MainMenuStyle = styled.div`
8 |
9 | background-color: ${props => props.theme.colors.background.dark} !important;
10 | color: ${props => props.theme.colors.text.light} !important;
11 |
12 | height: 100%;
13 | /* cursor: pointer; */
14 |
15 |
16 | `
17 | export const NoteHeader = styled(TopBar)`
18 | /* padding: 10px 10px 0 10px; */
19 | z-index: 10;
20 | `
21 | export const NoteTitle = styled.h1`
22 |
23 | `
24 | export const NoteMeta = styled.div`
25 | padding: 2px 0 2px 5px;
26 | vertical-align: center;
27 | color: ${props => props.theme.colors.text.muted}
28 | `
29 |
30 | export const NoteMetaIcon = styled.span`
31 | margin-right: 5px;
32 | `
33 | export const EditorStyle = styled.div`
34 | border: none;
35 |
36 | width: 100%;
37 |
38 | .CodeMirror {
39 | border-left: none;
40 | border-right: none;
41 | border-radius: 0;
42 | z-index: 0;
43 | }
44 | .editor-toolbar {
45 | color: ${props => props.theme.colors.text.muted};
46 | border-top: none;
47 | border-left: none;
48 | border-right: none;
49 |
50 | }
51 | `
52 |
53 | export const MenuItem = styled.div`
54 | height: 25px;
55 | cursor: hand;
56 | `
57 |
58 | export const Padding = styled.div`
59 | padding: 0px 5px 0px 5px;
60 | display: block;
61 | width: 100%;
62 | `
63 | export const MyInput = styled.div`
64 | .ant-input, .ant-input:focus {
65 | border: none;
66 | width: 100%;
67 | border-color: ${props => props.theme.colors.text.light};
68 | outline: 0;
69 | border-radius: 0;
70 | -webkit-box-shadow: none;
71 | box-shadow: none;
72 | width:100%;
73 | }
74 | `
75 |
76 | export const CustomSelect = styled.div`
77 | z-index: 1000;
78 | .rs__indicators {
79 | visibility: hidden;
80 | }
81 | .rs__value-container, .rs__control, .rs__control--is-focused, .rs__control--menu-is-open {
82 | border: none;
83 | padding: 0;
84 | outline: none;
85 | border-color: 'transparent' !important;
86 | box-shadow: 'none' !important;
87 | z-index: 1000;
88 | }
89 | .rs__menu {
90 | z-index: 1000;
91 | }
92 | `
93 |
94 |
95 | /* Wrapper */
96 | export const EditorPaneStyle = styled.div`
97 | /* background: yellow; */
98 | display: grid;
99 | grid-template-columns: 100%;
100 | grid-template-rows: 40px 40px 40px auto;
101 | height: 100%;
102 |
103 | `
104 |
105 |
106 | export const EditorRowMeta = styled.div`
107 | /* background: green; */
108 | grid-row-start: 1;
109 | grid-row-end: 1;
110 | width: 100%;
111 | position: sticky;
112 | border-bottom: 1px solid ${props => props.theme.colors.background.lift};
113 |
114 |
115 | `
116 | export const EditorRowTitle = styled.div`
117 | /* background: orange; */
118 | grid-row-start: 2;
119 | grid-row-end: 2;
120 | width: 100%;
121 | position: sticky;
122 |
123 |
124 | `
125 | export const EditorRowTags = styled.div`
126 | /* background: purple; */
127 | grid-row-start: 3;
128 | grid-row-end: 3;
129 | width: 100%;
130 | border-bottom: 1px solid ${props => props.theme.colors.background.lift};
131 | `
132 |
133 | export const EditorRowMain = styled.div`
134 | /* background: brown; */
135 | grid-row-start: 4;
136 | grid-row-end: 4;
137 | overflow: overlay;
138 |
139 | `
140 |
141 |
142 |
--------------------------------------------------------------------------------
/app/components/Home/Home.css:
--------------------------------------------------------------------------------
1 | #sidemenus {
2 |
3 | }
4 | .content {
5 |
6 | }
7 | .middlemenu {
8 |
9 | }
10 | .mainmenu {
11 |
12 | }
13 | @media screen and (max-width: 700px) {
14 | .sidemenus {
15 | width: 100%;
16 | height: auto;
17 | position: relative;
18 | }
19 | .sidemenus a {float: left;}
20 | .content {margin-left: 0;}
21 | }
22 |
23 | /* On screens that are less than 400px, display the bar vertically, instead of horizontally */
24 | @media screen and (max-width: 400px) {
25 | .side-menus a {
26 | text-align: center;
27 | float: none;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/components/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import styled from 'styled-components';
3 | // import SplitPane, { Pane } from 'react-split-pane';
4 | // import SplitterLayout from 'react-splitter-layout';
5 |
6 | import MainMenuCont from '../../containers/MainMenu/MainMenuCont';
7 | import CounterPage from '../../containers/CounterPage/CounterPage';
8 | const Content = styled.div`
9 | margin-left: 500px;
10 | padding: 1px 16px;
11 | height: 100%;
12 | `;
13 | import MiddleMenuCont from '../../containers/MiddleMenu/MiddleMenuCont';
14 | import ContentAreaCont from '../../containers/ContentAreaCont/ContentAreaCont';
15 | import { Input, Modal } from 'antd';
16 | import { Form, Formik } from 'formik';
17 | import isElectron from 'is-electron';
18 | import SplitPane from 'react-split-pane';
19 | import Pane from 'react-split-pane'
20 | import { waitSync} from 'redux-pouchdb';
21 |
22 | const mapStateToProps = (state) => {
23 | return {
24 | settings: state.settings
25 | };
26 | }
27 | /**
28 | * Top most Component containing the general layout of the page with 3 panels
29 | * @param props resizeSidebarAction, resizeMainMenuAction, resizeMiddleMenuAction, settings, saveStoreConfig, sizeMain, sizeSidebar, sizeMiddle, config, setConfig, syncNotesSuccess, syncNotesError
30 | */
31 | export default function Home(props) {
32 | const {resizeSidebarAction, resizeMainMenuAction, resizeMiddleMenuAction, settings, saveStoreConfig, sizeMain, sizeSidebar, sizeMiddle, config, setConfig, syncNotesSuccess, syncNotesError} = props
33 |
34 | return (<>
35 |
36 |
37 |
38 |
39 |
40 |
41 | { isElectron() && config && config.url === null ? {
45 | const errors = {};
46 | return errors;
47 | }}
48 | onSubmit={(values, { setSubmitting }) => {
49 | console.log("values", values)
50 | const s = values.url.split('://')
51 | setConfig('scheme', s[0])
52 | setConfig('url', s[1])
53 | setConfig('username', values.username)
54 | setConfig('password', values.password)
55 | // saveStoreConfig('db', values.connectionString)
56 | location.reload()
57 | }}
58 | >
59 | {({
60 | values,
61 | errors,
62 | touched,
63 | handleChange,
64 | handleBlur,
65 | handleSubmit,
66 | isSubmitting,
67 | }) => (
68 |
73 | Let's get you started with Notorious! Enter the URL to your CouchDB backend. Examples are shown below:
74 | https://couchdb.example.com (no trailing slash)
75 | local_data/
76 | {errors && {errors["all"]}
}
77 |
98 |
99 | )}
100 | :<>>
101 | }
102 |
103 | >
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/app/components/MainMenu/FlatMenu.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MenuItemNormal,MenuItemSelected, MenuItemRightFloat } from './style';
3 | import MenuItem from './MenuItem'
4 | export default function FlatMenu({items, selectNotebook, selectedNotebook, icon}) {
5 | let style = {}
6 | return (
7 | <>
8 | { items && items.map(item => {
9 | const MenuItemComponent = (selectedNotebook === item._id) ? MenuItemSelected : MenuItemNormal
10 |
11 |
12 | return selectNotebook(item._id)} key={item._id}>
13 |
14 |
15 | }
16 | )}
17 | >
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/app/components/MainMenu/MenuItem.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import { MenuItemStyle, MenuItemIcon, MenuItemLabel, MenuItemNormal, MenuItemSelected } from './style';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { useDrop } from 'react-dnd';
5 | import { faCaretSquareRight, faCaretSquareDown, faCaretSquareLeft } from '@fortawesome/free-solid-svg-icons';
6 | import { DragItemTypes } from '../../utils/DragItemTypes';
7 |
8 | export default function MenuItem({
9 | noteId,
10 | onClickHandler = () => {},
11 | cacheParentId = () => {},
12 | icon,
13 | label,
14 | compKey,
15 | right,
16 | indent,
17 | droppable=false,
18 | collapsible=false,
19 | visible=false,
20 | skipIcon=false,
21 | selected,
22 | children
23 | }) {
24 | const MenuItemComponent = (selected) ? MenuItemSelected : MenuItemNormal;
25 | const [v, setVis] = useState(visible )
26 | const handleClick = () => {
27 | setVis(!v)
28 | }
29 | // console.log("MainItem "+noteId)
30 | const [{ canDrop, isOver }, drop] = useDrop({
31 | accept: DragItemTypes.NOTE,
32 | drop: (data) => {
33 | cacheParentId()
34 | return { name: noteId}
35 | },
36 | collect: (monitor) => {
37 | return {
38 | isOver: monitor.isOver(),
39 | canDrop: monitor.canDrop(),
40 | }
41 | },
42 | })
43 | const isActive = canDrop && isOver && droppable
44 | let style = {};
45 | if (isActive) {
46 | style.backgroundColor = '#292d4e';
47 | // style.border= '1px solid white';
48 | } else if (canDrop && droppable) {
49 | // style.backgroundColor = 'blue'
50 | }
51 | return (
52 |
56 | onClickHandler()}
58 | key={compKey}
59 | indent={indent}
60 |
61 | >
62 | {!collapsible && !skipIcon && handleClick()}>{icon}}
63 | {collapsible && handleClick()}>{}}
66 | {label}
67 | { right }
68 |
69 | {v ? children : <>>}
70 |
71 | );
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/app/components/MainMenu/TreeMenu.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Tree } from 'antd';
3 |
4 | const { TreeNode } = Tree;
5 | import {Treebeard} from 'react-treebeard';
6 |
7 | import { makeStyles } from '@material-ui/core/styles';
8 | import TreeView from '@material-ui/lab/TreeView';
9 | // import ExpandMoreIcon from 'material-ui/icons/ExpandMore';
10 | // import ChevronRightIcon from 'material-ui/icons/ChevronRight';
11 | import TreeItem from '@material-ui/lab/TreeItem';
12 |
13 | const useStyles = makeStyles({
14 | root: {
15 | height: 110,
16 | flexGrow: 1,
17 | maxWidth: 400,
18 | },
19 | });
20 |
21 |
22 | class TreeMenu extends React.Component {
23 |
24 | renderTree = nodes => {
25 | console.log("rendering: ", nodes)
26 | console.log("children: ", this.props.items.filter(e => e._id === e._id)[0])
27 | return
28 | {Array.isArray(nodes.children) ? nodes.children.map(node => this.renderTree(this.props.items.filter(e => node._id === e._id)[0])) : null}
29 |
30 | }
31 | renderTreeMaterial = nodes => {
32 | console.log("rendering: ", nodes)
33 | console.log("children: ", this.props.items.filter(e => e._id === e._id)[0])
34 | return
35 | {Array.isArray(nodes.children) ? nodes.children.map(node => this.renderTree(this.props.items.filter(e => node._id === e._id)[0])) : null}
36 |
37 | }
38 |
39 | onDragEnter = info => {
40 | console.log(info);
41 | // expandedKeys
42 | // this.setState({
43 | // expandedKeys: info.expandedKeys,
44 | // });
45 | };
46 |
47 | onDrop = info => {
48 | console.log(info);
49 | const dropKey = info.node.props.eventKey;
50 | const dragKey = info.dragNode.props.eventKey;
51 | const dropPos = info.node.props.pos.split('-');
52 | const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
53 |
54 | const loop = (data, key, callback) => {
55 | data.forEach((item, index, arr) => {
56 | if (item.key === key) {
57 | return callback(item, index, arr);
58 | }
59 | if (item.children) {
60 | return loop(item.children, key, callback);
61 | }
62 | });
63 | };
64 | const data = [...this.state.gData];
65 |
66 | // Find dragObject
67 | let dragObj;
68 | loop(data, dragKey, (item, index, arr) => {
69 | arr.splice(index, 1);
70 | dragObj = item;
71 | });
72 |
73 | if (!info.dropToGap) {
74 | // Drop on the content
75 | loop(data, dropKey, item => {
76 | item.children = item.children || [];
77 | // where to insert
78 | item.children.push(dragObj);
79 | });
80 | } else if (
81 | (info.node.props.children || []).length > 0 && // Has children
82 | info.node.props.expanded && // Is expanded
83 | dropPosition === 1 // On the bottom gap
84 | ) {
85 | loop(data, dropKey, item => {
86 | item.children = item.children || [];
87 | // where to insert
88 | item.children.unshift(dragObj);
89 | });
90 | } else {
91 | let ar;
92 | let i;
93 | loop(data, dropKey, (item, index, arr) => {
94 | ar = arr;
95 | i = index;
96 | });
97 | if (dropPosition === -1) {
98 | ar.splice(i, 0, dragObj);
99 | } else {
100 | ar.splice(i + 1, 0, dragObj);
101 | }
102 | }
103 |
104 | this.setState({
105 | gData: data,
106 | });
107 | };
108 | onToggle(node, toggled){
109 | const {cursor, data} = this.state;
110 | if (cursor) {
111 | this.setState(() => ({cursor, active: false}));
112 | }
113 | node.active = true;
114 | if (node.children) {
115 | node.toggled = toggled;
116 | }
117 | this.setState(() => ({cursor: node, data: Object.assign({}, data)}));
118 | }
119 |
120 |
121 |
122 | render() {
123 | // console.log(this.props.items)
124 |
125 | return (
126 | <>
127 | { this.props.items && this.props.items.filter(i=> i.isChild === false).map(prent => {
128 | // return
129 | return
137 | {this.renderTree(prent)}
138 |
139 | })
140 | }
141 | >
142 | );
143 |
144 |
145 | }
146 | }
147 |
148 | export default TreeMenu;
149 |
--------------------------------------------------------------------------------
/app/components/MainMenu/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Tree } from 'antd';
3 | import { RightFloaty } from '../../style/utils.style';
4 | const { TreeNode } = Tree;
5 |
6 |
7 | export const MainMenuStyle = styled.div`
8 |
9 | background-color: ${props => props.theme.colors.background.dark} !important;
10 | color: ${props => props.theme.colors.text.light} !important;
11 | height: 100%;
12 | `
13 | export const MenuHeading = styled.div`
14 | padding: 0 0 0 0;
15 | color: ${props => props.theme.colors.text.muted} !important;
16 | font-size: 110%;
17 | font-weight: bold;
18 | margin: 10px 0 0 0;
19 | font-size: 12pt;
20 | font-variant-caps: all-small-caps;
21 | `
22 | export const TreeHeading = styled.div`
23 | padding: 0 0 0 0;
24 | color: ${props => props.theme.colors.text.muted} !important;
25 | font-size: 90%;
26 | font-weight: bold;
27 | `
28 |
29 |
30 |
31 |
32 | export const MenuItemNormal = styled.div`
33 | `
34 | export const MenuItemRowItem = styled.div`
35 | display: table-cell;
36 | margin: 0 3px 0 0;
37 | `
38 | export const MenuItemRightFloat = styled(MenuItemRowItem)`
39 | color: ${props => props.theme.colors.text.muted};
40 | font-size: smaller;
41 | vertical-align: center;
42 | font-weight: bold;
43 |
44 | `
45 | export const MenuItemIcon = styled(MenuItemRowItem)`
46 | width: 15px;
47 | color: ${props => props.theme.colors.text.muted};
48 | font-size: smaller;
49 | padding: 0 5px 0 0 ;
50 |
51 | `
52 | export const MenuItemLabel = styled(MenuItemRowItem)`
53 | width: 100%;
54 | td {
55 | overflow: hidden;
56 | text-overflow: ellipsis;
57 | width: 100%;
58 | max-width: 0;
59 | }
60 |
61 | `
62 |
63 |
64 |
65 | export const MenuItemStyle = styled.div`
66 | padding: 5px 10px 5px ${props => 7+props.indent || 7}px;
67 | color: ${props => props.theme.colors.text.muted} !important;
68 | cursor: default;
69 | white-space: nowrap;
70 | `
71 | export const MenuItemSelected = styled(MenuItemNormal)`
72 | background-color: ${props => props.theme.colors.menu.selected}
73 | `
74 | export const MainMenuBottom = styled.div`
75 | font-size: x-small;
76 | color: ${props => props.theme.colors.text.muted} !important;
77 | padding: 0 10px 0 10px;
78 | position: absolute;
79 | bottom: 0;
80 | `
81 |
--------------------------------------------------------------------------------
/app/components/MiddleMenu/MiddleMenu.style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Button } from 'antd';
3 |
4 |
5 | export const NoteList = styled.div`
6 | /* color: ${props => props.theme.colors.text.dark}; */
7 | /* margin-bottom: 30px; required to make the last note in scroll list fully visible */
8 | `
9 | export const TopBar = styled.div`
10 | z-index: 2;
11 | padding: 7px 5px 7px 5px;
12 | background-color: ${props => props.theme.colors.background.light};
13 |
14 | `
15 |
16 | export const NotoriousButtonStyle = styled(Button)`
17 | border: none;
18 | background: transparent;
19 |
20 | .ant-btn:hover, .ant-btn:focus, .ant-btn:active, .ant-btn.active {
21 | background-color: ${props => props.theme.colors.background.selected};
22 | background: transparent;
23 | }
24 |
25 | `
26 | export const MiddleLayout = styled.div`
27 |
28 | `
29 |
30 | export const TopBarItem = styled.div`
31 | display: table-cell;
32 | margin: 0 3px 0 0;
33 | width: 100%;
34 | padding: 0 2px 0 2px;
35 | `
36 |
37 |
38 |
39 | export const TopBarButton = styled.div`
40 |
41 | border: none;
42 | `
43 |
44 |
45 | // Layout
46 |
47 | export const StickyLayoutWrapper = styled.div`
48 | display: grid;
49 | grid-template-columns: 5%%;
50 | grid-template-rows: 39px 25px auto;
51 | height: 100%;
52 | `
53 |
54 | export const MiddleMenuStyle = styled(StickyLayoutWrapper)`
55 | background-color: ${props => props.theme.colors.background.light};
56 | color: ${props => props.theme.colors.background.dark};
57 | height: 100%;
58 | `
59 | export const StickyLayoutTitle = styled.div`
60 | /* background: green; */
61 | grid-row-start: 2;
62 | grid-row-end: 2;
63 | grid-column-start: 1;
64 | grid-column-end: 20;
65 | vertical-align: center;
66 | border-top: 1px solid ${props => props.theme.colors.background.lift};
67 | border-bottom: 1px solid ${props => props.theme.colors.background.lift};
68 |
69 | `
70 | export const StickyLayoutMiddle = styled.div`
71 | /* background: orange; */
72 | grid-row-start: 1;
73 | grid-row-end: 1;
74 | grid-column-start: 1;
75 | grid-column-end: 20;
76 |
77 | justify-content: center;
78 | align-items: center;
79 | /* padding: 8px 0px 1px 5px; */
80 | `
81 | // export const StickyLayoutAddButton = styled.div`
82 | // /* background: orange; */
83 | // grid-row-start: 1;
84 | // grid-row-end: 1;
85 | // grid-column-start: 18;
86 | // grid-column-end: 20;
87 | // text-align: center;
88 | // padding: 8px 3px 3px 3px;
89 |
90 |
91 | // `
92 |
93 | export const StickyLayoutMain = styled.div`
94 | /* background: brown; */
95 | grid-row-start: 3;
96 | grid-row-end: 3;
97 | grid-column-start: 1;
98 | grid-column-end: 20;
99 |
100 | `
101 | export const SortToggler = styled.span`
102 | white-space: nowrap;
103 | color: ${props => props.theme.colors.text.muted};
104 | font-size: xx-small;
105 | font-weight: bold;
106 | padding: 5px 0 0px 5px;
107 | cursor: pointer;
108 | position: absolute;
109 | `
110 |
--------------------------------------------------------------------------------
/app/components/MiddleMenu/MiddleMenu.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Scrollbars } from 'react-custom-scrollbars';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import styled from 'styled-components';
5 | import { Button, Empty } from 'antd';
6 | import { faPlus, faSortAlphaDown, faSortAlphaUp, faSortAmountDownAlt } from '@fortawesome/free-solid-svg-icons';
7 | import {
8 | TopBar,
9 | TopBarItem,
10 | NotoriousButtonStyle,
11 | StickyLayoutMiddle,
12 | StickyLayoutTitle,
13 | StickyLayoutMain,
14 | MiddleMenuStyle,
15 | NoteList,
16 | SortToggler
17 | } from './MiddleMenu.style';
18 | import FieldForm from '../EditorPane/FieldForm/FieldForm';
19 |
20 | import NoteCard from './NoteCard/NoteCard';
21 | import { SORT_ALPHA_REVERSE, SORT_ALPHA, SORT_CUSTOM, SORT_CREATED_AT, SORT_UPDATED_AT } from '../../containers/MiddleMenu/actions';
22 |
23 | export const SearchBarInput = styled.div`
24 | .ant-input {
25 | border-color: ${props => props.theme.colors.text.light};
26 |
27 | outline: 0;
28 | border-radius: 0;
29 | -webkit-box-shadow: none;
30 | box-shadow: none;
31 | }
32 | `
33 | export default function MiddleMenu({
34 | visibleNotes,
35 | sorter,
36 | selection,
37 | selectNoteAction,
38 | createNote,
39 | selectedNote,
40 | softDeleteNote,
41 | updateNote,
42 | moveNote,
43 | dropNoteCache,
44 | savingNew,
45 | searchNotes,
46 | addButtonDisabled,
47 | restoreNote,
48 | deleteNote,
49 | headerLabel,
50 | sortNotes
51 | }) {
52 |
53 | const notecardContextHandlers = {
54 | cmPinNoteHandler: (e, {note}) => updateNote(note._id, {pinned: !note.pinned}),
55 | cmSwitchEditorHandler: (e, {note, editor}) => updateNote(note._id, {editor: editor}),
56 | cmCreateChildNoteHandler: (e, {note}) => createNote(note._id, {}),
57 | cmShowInMenuHandler: (e, {note}) => updateNote(note._id, {showInMenu: !note.showInMenu, kind: "collection" }),
58 | cmChangeKindHandler: (e, {note, kind}) => updateNote(note._id, {kind}),
59 | cmDeleteNoteHandler: (e, {note}) => softDeleteNote(note._id),
60 | cmHardDeleteNoteHandler: (e, {note}) => deleteNote(note._id),
61 | cmRestoreNoteHandler: (e, {note}) => restoreNote(note._id)
62 | };
63 |
64 | return (
65 |
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/app/components/MiddleMenu/NoteCard/NoteCard.style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { hasChildren } from '../../../utils/utils';
3 |
4 | export const NoteCardStyle = styled.div`
5 | // background-color: green;
6 | background-color: ${props => props.selected ? props.theme.colors.background.selected : 'inherit'};
7 | height: ${props => hasChildren(props.note.children) ? '31px': '100px'};
8 | padding: 5px;
9 | overflow: hidden;
10 | border-bottom: 1px solid ${props => props.theme.colors.background.lift};
11 | color: ${props => props.selected ? props.theme.colors.text.light : props.theme.colors.text.muted};
12 | .noteListTitle {
13 | color: ${props => props.selected ? props.theme.colors.text.light : props.theme.colors.text.black};
14 | font-weight: bold;
15 | }
16 |
17 | .noteCardMeta {
18 | font-size: 80%;
19 |
20 | }
21 | .noteTags {
22 | .ant-tag {
23 | font-size: 8pt;
24 | padding: 0px 2px 0px 2px;
25 | margin-right: 2px;
26 | border-radius: 1px;
27 | border: none;
28 | color: ${props => props.selected ? props.theme.colors.text.light : props.theme.colors.text.black};
29 | background: transparent;
30 |
31 | }
32 | }
33 | `
34 |
35 |
--------------------------------------------------------------------------------
/app/components/css.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.scss' {
2 | const content: { [className: string]: string };
3 | export default content;
4 | }
5 |
6 | declare module '*.css' {
7 | const content: { [className: string]: string };
8 | export default content;
9 | }
10 |
--------------------------------------------------------------------------------
/app/components/util/Finder.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Formik, Form } from 'formik';
3 | import { Modal, Input, List } from 'antd';
4 | import { MyInput } from '../EditorPane/style';
5 | import { NotoModal } from './utils.style';
6 | import FinderResultList from './FinderResultList';
7 | import {DelayInput} from 'react-delay-input';
8 |
9 | export default function Finder({
10 | onSearchResultSelect,
11 | placeholder,
12 | visible,
13 | title,
14 | initialValue,
15 | handleCancel,
16 | data,
17 | search,
18 | reference
19 | }) {
20 |
21 |
22 |
23 | return (
24 | {
28 | const errors = {};
29 | return errors;
30 | }}
31 | onSubmit={(values, { setSubmitting }) => {
32 | search(values.value);
33 | setSubmitting(false);
34 | }}
35 | >
36 | {({ values, handleChange, handleSubmit, setValues }) => (
37 |
38 |
50 |
65 | {data && data.results && (
66 | <>
67 | {onSearchResultSelect(id);setValues({value: ""})}}
71 | />
72 | {onSearchResultSelect(id); setValues({value: ""})}}
76 | />
77 | {/* {onSearchResultSelect(id); setValues({value: ""})}}
82 | /> */}
83 | >
84 | )}
85 |
86 |
87 | )}
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/app/components/util/FinderResultList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { List } from 'antd';
3 | import NotesWrapper from '../../containers/util/NotesWrapper';
4 | import { FinderListItemStyle } from './utils.style'
5 | export default function FinderResultList({ data, header, onResultClick }) {
6 | if ( data.length > 0) {
7 | return {header}}
10 | dataSource={data}
11 | renderItem={nId => (
12 |
13 | {({ note }) => {
14 | return note ? onResultClick(nId)} style={{padding: '0 0 0 10px', cursor: "pointer"}}>
15 | {note.title}
16 | : <>>
17 | }}
18 |
19 | )}
20 | />
21 | } else {
22 | return <>>
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/components/util/ModalForm.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FieldForm from '../EditorPane/FieldForm/FieldForm';
3 | import { Formik, Form } from 'formik';
4 | import { Modal, Input } from 'antd';
5 | import { MyInput } from '../EditorPane/style';
6 | import { NotoModal } from './utils.style';
7 |
8 | export default function ModalForm({formSubmitHandler, placeholder,visible, title, initialValue, handleCancel, data, reference}) {
9 |
10 | return (
11 | {
15 | const errors = {};
16 | return errors;
17 | }}
18 | onSubmit={(values, { setSubmitting }) => {
19 | console.log("onSubmit:: ", values)
20 | formSubmitHandler(values, data)
21 | values.value = ""
22 | setSubmitting(false)
23 | }}
24 | >
25 | {({
26 | values,
27 | errors,
28 | touched,
29 | handleChange,
30 | handleBlur,
31 | handleSubmit,
32 | isSubmitting,
33 | }) => (
34 |
35 |
36 |
49 |
61 |
62 |
63 |
64 | )}
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/app/components/util/utils.style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const DotLine = styled.div`
4 | white-space: nowrap;
5 | overflow: hidden;
6 | text-overflow: ellipsis;
7 |
8 | `
9 | export const NotoModal = styled.div`
10 | /* DOes not apply style correctly.. using Modals props to style body. */
11 | .ant-modal-content {
12 |
13 | .ant-modal-header {
14 |
15 | }
16 | .ant-modal-body {
17 | }
18 |
19 | .ant-modal-footer {
20 | padding: 5px 6px;
21 | }
22 | }
23 |
24 | `
25 | export const FinderListItemStyle = styled.div`
26 | :hover, :focus {
27 | background-color: ${props => props.theme.colors.background.lift};
28 | }
29 |
30 | `
31 |
--------------------------------------------------------------------------------
/app/constants/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "HOME": "/",
3 | "COUNTER": "/counter"
4 | }
5 |
--------------------------------------------------------------------------------
/app/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | export default function App(props: Props) {
8 | const { children } = props;
9 | return <>{children}>;
10 | }
11 |
--------------------------------------------------------------------------------
/app/containers/ContentAreaCont/ContentAreaCont.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 |
3 | import { bindActionCreators, Dispatch } from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import * as actions from './actions';
7 | import * as noteActions from '../../reducers/noteActions';
8 | import EditorPane from '../../components/EditorPane/EditorPane';
9 | import { findChildren, findSelectedNote, findExistingTags, findNote } from '../MainMenu/selectors';
10 |
11 | class ContentAreaCont extends PureComponent {
12 |
13 | render() {
14 | return ;
15 |
16 | }
17 | }
18 |
19 | function mapStateToProps(state, props) {
20 | const note = findSelectedNote(state)
21 | return {
22 | contentArea: state.contentArea,
23 | note: note, //state.notes.filter(e=> e._id === state.configs.selectedNote)[0],
24 | subNotes: findChildren(state),
25 | existingTags: findExistingTags(state),
26 | parent: note && findNote(note.parent)(state)
27 |
28 | };
29 | }
30 |
31 | function mapDispatchToProps(dispatch: Dispatch) {
32 | return {
33 | noteActions: bindActionCreators(noteActions, dispatch),
34 | ...bindActionCreators(actions, dispatch)
35 | }
36 | }
37 |
38 | export default connect(mapStateToProps, mapDispatchToProps)(ContentAreaCont);
39 |
--------------------------------------------------------------------------------
/app/containers/ContentAreaCont/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 | import '../../reducers/noteActions';
3 | import { setConfig } from '../../reducers/configActions';
4 | import { updateNote } from '../../reducers/noteActions';
5 |
6 | export const SELECT_NOTE = 'SELECT_NOTE';
7 |
8 |
9 | export function selectNoteAction(note: String) {
10 | return (dispatch: Dispatch, getState: GetState) => {
11 | dispatch(setConfig("selectedNote", note))
12 | // dispatch(updateNote(note._id, {"viewedAt": Date.now(), skipUpdatedAt: true}))
13 | dispatch(
14 | {
15 | type: SELECT_NOTE,
16 | id: note
17 | }
18 | );
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/app/containers/ContentAreaCont/reducers.ts:
--------------------------------------------------------------------------------
1 | import { Action } from 'redux';
2 | import { SELECT_NOTE } from './actions';
3 | import { createReducer } from '../../utils/utils';
4 | import config from '../../utils/config';
5 | const initialState = {
6 | selectedNote: config.selectedNote
7 | }
8 |
9 | const contentAreaReducer = createReducer(initialState, {
10 | [SELECT_NOTE]: (state, action) => {
11 | return {...state, selectedNote: action.id};
12 | }
13 | // [SELECT_NOTEBOOK]: (state, action) => {
14 | // return {...state, selectedNote: action.id};
15 | // }
16 |
17 | });
18 | export default contentAreaReducer;
19 |
--------------------------------------------------------------------------------
/app/containers/CounterPage/CounterPage.tsx:
--------------------------------------------------------------------------------
1 | import { bindActionCreators, Dispatch } from 'redux';
2 | import { connect } from 'react-redux';
3 | import Counter from '../../components/Counter/Counter';
4 | import { counterStateType } from '../../reducers/types';
5 | import * as actions from './actions';
6 |
7 | function mapStateToProps(state: counterStateType) {
8 | return {
9 | counter: state.counter
10 | };
11 | }
12 |
13 | function mapDispatchToProps(dispatch: Dispatch) {
14 | return bindActionCreators(actions, dispatch);
15 | }
16 |
17 | export default connect(mapStateToProps, mapDispatchToProps)(Counter);
18 |
--------------------------------------------------------------------------------
/app/containers/CounterPage/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 |
3 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
4 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
5 |
6 | export function increment() {
7 | return {
8 | type: INCREMENT_COUNTER
9 | };
10 | }
11 |
12 | export function decrement() {
13 | return {
14 | type: DECREMENT_COUNTER
15 | };
16 | }
17 |
18 | export function incrementIfOdd() {
19 | return (dispatch: Dispatch, getState: GetState) => {
20 | const { counter } = getState();
21 |
22 | if (counter % 2 === 0) {
23 | return;
24 | }
25 |
26 | dispatch(increment());
27 | };
28 | }
29 |
30 | export function incrementAsync(delay = 1000) {
31 | return (dispatch: Dispatch) => {
32 | setTimeout(() => {
33 | dispatch(increment());
34 | }, delay);
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/app/containers/CounterPage/reducers.ts:
--------------------------------------------------------------------------------
1 | import { Action } from 'redux';
2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from './actions';
3 |
4 | export default function counter(state = 0, action: Action) {
5 | switch (action.type) {
6 | case INCREMENT_COUNTER:
7 | return state + 1;
8 | case DECREMENT_COUNTER:
9 | return state - 1;
10 | default:
11 | return state;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/containers/HomePage/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Home from '../../components/Home/Home';
3 | import * as actions from './actions';
4 | import * as configActions from '../../reducers/configActions';
5 |
6 | import { connect, ReactReduxContext } from 'react-redux';
7 | import { bindActionCreators } from 'redux';
8 | import config from '../../utils/config';
9 |
10 | // class HomePage extends React.Component {
11 |
12 | // componentDidMount =() => {
13 |
14 |
15 | // }
16 | // render = () => {
17 |
18 | // return {JSON.stringify(this.props, true, 2)}
19 | // }
20 | // }
21 | function mapStateToProps(state: MiddleMenuStateType) {
22 | return { ...state.settings, config: config }
23 | }
24 |
25 | function mapDispatchToProps(dispatch: Dispatch) {
26 | return bindActionCreators(Object.assign(actions,configActions), dispatch)
27 |
28 | }
29 |
30 | export default connect(mapStateToProps, mapDispatchToProps)(Home);
31 | //
32 | // export default connect(mapStateToProps, mapDispatchToProps)();
33 |
34 |
--------------------------------------------------------------------------------
/app/containers/HomePage/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 | export const SYNC_NOTES_SUCCESS = 'SYNC_NOTES_SUCCESS';
3 | export const SYNC_NOTES_ERROR = 'SYNC_NOTES_ERROR';
4 |
5 | export const SYNC_ON_CHANGE = "ON_CHANGE";
6 | export const SYNC_ON_PAUSED = "ON_PAUSED";
7 | export const SYNC_ON_ACTIVE = "ON_ACTIVE";
8 | export const SYNC_ON_DENIED = "ON_DENIED";
9 | export const SYNC_ON_COMPLETE = "ON_COMPLETE";
10 | export const SYNC_ON_ERROR = "ON_ERROR";
11 | export const SYNC_START = "SYNC_START";
12 | export const SYNC_FIRST_TIME_SYNC_SUCCESS = "SYNC_FIRST_TIME_SYNC_SUCCESS";
13 | export const SYNC_REMOTE_TEMPORARILY_UNAVAILABLE = "SYNC_REMOTE_TEMPORARILY_UNAVAILABLE";
14 | export const SYNC_FIRST_TIME_SYNC_ERROR = "FIRST_TIME_SYNC_ERROR";
15 | export const SHUT_DOWN_APP = "SHUT_DOWN_APP";
16 |
17 |
18 | // export const RESIZE_SIDEBAR = 'RESIZE_SIDEBAR';
19 | // export const RESIZE_MAIN_MENU = 'RESIZE_MAIN_MENU';
20 | // export const RESIZE_MIDDLE_MENU = 'RESIZE_MIDDLE_MENU';
21 | // export const SET_VISIBILITY_MAIN_MENU = 'SET_VISIBILITY_MAIN_MENU';
22 | // export const SAVE_STORE_CONFIG = 'SAVE_STORE_CONFIG';
23 |
24 |
25 | export function syncNotesSuccess() {
26 | return (dispatch: Dispatch, getState: GetState) => {
27 | dispatch(
28 | {
29 | type: SYNC_NOTES_SUCCESS
30 | }
31 | );
32 | }
33 | }
34 | export function shutdown() {
35 | return (dispatch: Dispatch, getState: GetState) => {
36 | dispatch(
37 | {
38 | type: SHUT_DOWN_APP
39 | }
40 | );
41 | }
42 | }
43 | export function syncNotesError() {
44 | return (dispatch: Dispatch, getState: GetState) => {
45 | dispatch(
46 | {
47 | type: SYNC_NOTES_ERROR
48 | }
49 | );
50 | }
51 | }
52 |
53 |
54 | // export function resizeSidebarAction(currentSize: number, size: number) {
55 | // return (dispatch: Dispatch, getState: GetState) => {
56 | // if( currentSize !== size) {
57 | // console.log("Action: ", size)
58 | // dispatch(
59 | // {
60 | // type: RESIZE_SIDEBAR,
61 | // size
62 | // }
63 | // );
64 |
65 | // }
66 |
67 | // }
68 | // }
69 | // export function resizeMainMenuAction(currentSize: number, size: number) {
70 | // return (dispatch: Dispatch, getState: GetState) => {
71 | // if( currentSize !== size) {
72 | // console.log("Action: ", size)
73 | // dispatch(
74 | // {
75 | // type: RESIZE_MAIN_MENU,
76 | // size
77 | // }
78 | // );
79 |
80 | // }
81 | // };
82 | // }
83 | // export function resizeMiddleMenuAction(currentSize: number, size: number) {
84 | // return (dispatch: Dispatch, getState: GetState) => {
85 | // if( currentSize !== size) {
86 | // console.log("Action: ", size)
87 | // dispatch(
88 | // {
89 | // type: RESIZE_MIDDLE_MENU,
90 | // size
91 | // }
92 | // );
93 |
94 | // }
95 | // };
96 | // }
97 | // export function setMainMenuVisibility(visible: boolean) {
98 | // return (dispatch: Dispatch, getState: GetState) => {
99 | // console.log("Action: ", visible)
100 | // dispatch(
101 | // {
102 | // type: SET_VISIBILITY_MAIN_MENU,
103 | // visible
104 | // }
105 | // );
106 |
107 | // };
108 | // }
109 |
--------------------------------------------------------------------------------
/app/containers/HomePage/reducers.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '../../utils/utils'
2 | import { SYNC_NOTES_ERROR, SYNC_NOTES_SUCCESS,
3 | SYNC_ON_CHANGE,
4 | SYNC_ON_PAUSED,
5 | SYNC_ON_ACTIVE,
6 | SYNC_ON_DENIED,
7 | SYNC_ON_COMPLETE,
8 | SYNC_ON_ERROR,
9 | SYNC_FIRST_TIME_SYNC_ERROR,
10 | SYNC_FIRST_TIME_SYNC_SUCCESS,
11 | SHUT_DOWN_APP,
12 | SYNC_REMOTE_TEMPORARILY_UNAVAILABLE,
13 | SYNC_START
14 | } from './actions';
15 | import { UPDATE_NOTE } from '../../reducers/noteActions';
16 |
17 |
18 | const initialState = {
19 | notesSync: "Replicating data from server",
20 | syncType: "",
21 | spinner: true,
22 | close: false
23 | }
24 |
25 | const homePageReducer = createReducer(initialState, {
26 | [SYNC_START]: (state, action) => {
27 | return {...state, syncType: action.type, notesSync: "Getting ready...", spinner: true}
28 | },
29 | [SYNC_NOTES_ERROR]: (state, action) => {
30 | return {...state, syncType: action.type, notesSync: "error"}
31 | },
32 | [SYNC_NOTES_SUCCESS]: (state, action) => {
33 | return {...state, syncType: action.type, notesSync: "success"}
34 | },
35 | [UPDATE_NOTE]: (state, action) => {
36 | return {...state, syncType: action.type, notesSync: "Pending update"}
37 | },
38 | [SYNC_ON_CHANGE]: (state, action) => {
39 | return {...state, syncType: action.type, notesSync: "Syncing", spinner: true}
40 | },
41 | [SYNC_ON_PAUSED]: (state, action) => {
42 | return {...state, syncType: action.type, notesSync: "", spinner: false}
43 | },
44 | [SYNC_ON_ACTIVE]: (state, action) => {
45 | return {...state, syncType: action.type, notesSync: "Starting sync", spinner: true}
46 | },
47 | [SYNC_ON_DENIED]: (state, action) => {
48 | return {...state, syncType: action.type, notesSync: "Permission Issue: Sync was rejected", spinner: true}
49 | },
50 | [SYNC_ON_COMPLETE]: (state, action) => {
51 | return {...state, syncType: action.type, notesSync: "Sync Completed", spinner: false}
52 | },
53 | [SYNC_ON_ERROR]: (state, action) => {
54 | alert("There was a synchronisation error. Please capture the console log from the views > Developer Tools > Console and raise an issue."+JSON.stringify(action))
55 | return {...state, syncType: action.type, notesSync: "Sync Error" + action.error}
56 | },
57 | [SYNC_FIRST_TIME_SYNC_ERROR]: (state, action) => {
58 | alert("First time sync from server failed. Please check backend is available and restart notorious application before beginning work." + JSON.stringify(action))
59 |
60 | return {...state, syncType: action.type, notesSync: "Initial Sync Failed"}
61 | },
62 | [SYNC_FIRST_TIME_SYNC_SUCCESS]: (state, action) => {
63 | // alert(JSON.stringify(action))
64 | return {...state, syncType: action.type, notesSync: "Initial sync complete"}
65 | },
66 | [SYNC_REMOTE_TEMPORARILY_UNAVAILABLE]: (state, action) => {
67 | // alert(JSON.stringify(action))
68 | return {...state, syncType: action.type, notesSync: "Remote unavailable. Retrying soon.", spinner: false}
69 | },
70 | [SHUT_DOWN_APP]: (state, action) => {
71 | // alert(JSON.stringify(action))
72 | return {...state, close: true}
73 | }
74 | });
75 | export default homePageReducer;
76 |
--------------------------------------------------------------------------------
/app/containers/MainMenu/MainMenuCont.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { bindActionCreators, Dispatch } from 'redux';
3 | import { connect } from 'react-redux';
4 | import MainMenu from '../../components/MainMenu/MainMenu';
5 | import { notebookSelector, findExistingTags } from './selectors'
6 | import { MainMenuStateType } from '../../reducers/types';
7 | import * as actions from './actions';
8 | import * as notebookActions from '../../reducers/notebookActions';
9 | import * as noteActions from '../../reducers/noteActions';
10 | import * as modalActions from '../../reducers/modalActions';
11 | import * as contentAreaActions from '../ContentAreaCont/actions';
12 | import * as homeActions from '../HomePage/actions';
13 | import { menuItemSorter, alphaSorter } from '../../utils/utils';
14 | import { waitSync } from 'redux-pouchdb';
15 |
16 |
17 |
18 | class MainMenuCont extends PureComponent {
19 | // componentDidUpdate = (prevProps) => {
20 | // if (prevProps.notes !== this.props.notes) {
21 | // const { syncNotesSuccess, syncNotesError} = this.props
22 | // waitSync("notes").then(d => {
23 | // syncNotesSuccess()
24 | // }).catch(e=> {
25 | // console.log("error sync: ", e)
26 | // syncNotesError()
27 | // })
28 | // }
29 | // }
30 | render() {
31 |
32 | return ;
33 |
34 | }
35 | }
36 |
37 | function mapStateToProps(state: MainMenuStateType) {
38 | return {
39 | notebooks: notebookSelector(state).filter( n => n.parent === "root").sort(menuItemSorter),
40 | selectedNotebook: state.configs ? state.configs.selectedNotebook : null,
41 | modals: state.modals,
42 | tags: findExistingTags(state).sort(alphaSorter),
43 | notesSync: state.settings.notesSync,
44 | syncType: state.settings.syncType,
45 | spinner: state.settings.spinner,
46 | ...state.mainMenu
47 |
48 | };
49 | }
50 |
51 | function mapDispatchToProps(dispatch: Dispatch) {
52 | return bindActionCreators(Object.assign(actions, modalActions, notebookActions, noteActions,contentAreaActions, homeActions), dispatch)
53 | }
54 |
55 | export default connect(mapStateToProps, mapDispatchToProps)(MainMenuCont);
56 |
--------------------------------------------------------------------------------
/app/containers/MainMenu/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 | import { setConfig } from '../../reducers/configActions';
3 | import { selectNoteAction } from '../ContentAreaCont/actions';
4 | import {allNotes} from './selectors'
5 | export const SELECT_NOTEBOOK = 'SELECT_NOTEBOOK';
6 | export const SELECT_NOTES_FILTER = 'SELECT_NOTES_FILTER';
7 |
8 |
9 | export function selectNotebook(nb: String) {
10 | return (dispatch: Dispatch, getState) => {
11 | const state = getState()
12 | dispatch(setConfig("selectedNotebook", nb))
13 | const notes = allNotes(state);
14 | const notebook = notes && notes.filter(n => n._id === nb)[0]
15 | const notesInNotebook = notes && notes.filter(n => n.parent === nb)
16 | let selectedId = null;
17 | // console.log("notebook", notebook)
18 | // console.log("notesInNotebook", notesInNotebook)
19 | if (notebook && notebook.lastSelectedChild) {
20 | selectedId = notebook.lastSelectedChild // ideally use last selected note in notebook
21 | } else if (notesInNotebook && notesInNotebook.length > 0) {
22 | selectedId = notesInNotebook[0]._id // if unavailable, use first note in notebook.
23 | }
24 | // console.log("selectedId", selectedId)
25 | if (selectedId) {
26 | dispatch(selectNoteAction(selectedId))
27 | }
28 | dispatch(
29 | {
30 | type: SELECT_NOTEBOOK,
31 | filter: nb
32 | }
33 | );
34 | };
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/app/containers/MainMenu/reducers.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '../../utils/utils'
2 | import { configStorage } from '../../utils/localStorage';
3 | import { SEARCH_NOTES_RESULTS, CACHE_NOTE_PARENT_ID, MOVE_NOTE } from '../../reducers/noteActions';
4 | import { SELECT_NOTEBOOK } from './actions';
5 |
6 |
7 | const initialState = {
8 | visibleNotes: [],
9 | filter: configStorage['selectedNotebook']
10 | }
11 |
12 | const mainMenuReducer = createReducer(initialState, {
13 | [CACHE_NOTE_PARENT_ID]: (state, action) => {
14 | return {...state, moveNoteDropTargetId: action.id}
15 | },
16 | [MOVE_NOTE]: (state, action) => {
17 | return {...state, moveNoteDropTargetId: undefined}
18 | },
19 |
20 | [SELECT_NOTEBOOK]: (state, action) => {
21 | return {...state, filter: action.filter}
22 | },
23 | [SEARCH_NOTES_RESULTS]: (state, action) => {
24 | if (action.target === "MIDDLE_MENU_SEARCH") {
25 | console.log("mainMenuReducer state", state)
26 | console.log("mainMenuReducer action", action)
27 | return {...state, filter: action.results}
28 | }
29 | return state
30 | }
31 | });
32 | export default mainMenuReducer;
33 |
--------------------------------------------------------------------------------
/app/containers/MainMenu/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { uniq } from '../../utils/utils'
3 |
4 | const allNotesInternal = state => {return (state && state.notes )? state.notes.filter(n=>n.schema === "note" && !n.deleted) : [] }
5 | const notes = state => {return (state && state.notes ) ? state.notes : [] }
6 | export const configs = state => state && state.configs
7 |
8 | export const notebookSelector = createSelector(
9 | allNotesInternal,
10 | (all) => all.filter(n => n.showInMenu)
11 | )
12 | export const findSelectedNote = createSelector(
13 | allNotesInternal,
14 | configs,
15 | (all, configs) => {
16 | return all.filter(n=> n._id === configs.selectedNote)[0]
17 | }
18 | )
19 | export const allNotes = createSelector(
20 | allNotesInternal,
21 | (all) => all
22 | )
23 | export const allNotesEverything = createSelector(
24 | notes,
25 | (all) => all
26 | )
27 |
28 | export const findExistingTags = createSelector(
29 | allNotesInternal,
30 | (all) => (all.reduce((initial, item) => {
31 | return (item.tags && item.tags.length > 0 )? uniq([initial, item.tags].flat()) : initial
32 | }, []))
33 | )
34 |
35 | export const findChildren = createSelector(
36 | allNotesInternal,
37 | findSelectedNote,
38 | (all, note) => all.filter(n => note && note.children && note.children.indexOf(n._id) > -1)
39 | )
40 | export const findChildrenOfNote = (note) => {
41 | return createSelector(
42 | allNotesInternal,
43 | (all) => {
44 | return all.filter(n => n.parent === note._id)
45 |
46 | }
47 | )
48 | }
49 | export const findChildrenOfNoteInclDeleted = (note) => {
50 | return createSelector(
51 | allNotesEverything,
52 | (notes) => {
53 | return notes.filter(n => n.parent === note._id)
54 |
55 | }
56 | )
57 | }
58 | export const findNote = (id) => {
59 | return createSelector(
60 | allNotesInternal,
61 | (all) => {
62 | const results = all.filter(n => n._id === id)
63 | return results.length === 1 ? results[0] : null
64 |
65 | }
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/app/containers/MiddleMenu/MiddleMenuCont.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators, Dispatch } from 'redux';
3 | import { connect } from 'react-redux';
4 | import MiddleMenu from '../../components/MiddleMenu/MiddleMenu';
5 | import * as contentAreaActions from '../ContentAreaCont/actions';
6 | import * as actions from './actions';
7 | import * as noteActions from '../../reducers/noteActions';
8 | import { allNotes } from '../MainMenu/selectors';
9 | import { alphaTitleSorter, alphaTitleSorterReverse, customNoteSorter, updatedAtSorter } from '../../utils/utils';
10 |
11 |
12 |
13 |
14 |
15 | class MiddleMenuCont extends React.PureComponent {
16 | // componentDidUpdate = (prevProps) => {
17 | // if (prevProps.selection !== this.props.selection && this.props.selection) {
18 |
19 | // }
20 | // }
21 | render() {
22 | return ;
23 | }
24 | }
25 | function mapStateToProps(state) {
26 | const notebookLabelMaker = (filter, notes, middleMenu) => {
27 | if (typeof filter === "object") {
28 | if (middleMenu.search) {
29 | return "Search: " + middleMenu.search
30 |
31 | } else {
32 | return filter
33 | }
34 | }
35 | if (filter && filter.indexOf("tag::") > -1 ) {
36 | const tag = filter.split("::")[1]
37 | return "Tag: " + tag
38 | }
39 |
40 | switch(filter) {
41 | case "ALL":
42 | return "All Notes"
43 | case "TRASH":
44 | return "Trash"
45 | case "FAV":
46 | return "My Favourites"
47 | default:
48 | const match = notes.filter(i => (i._id === filter))
49 | if (match.length === 1){
50 | return match[0].title
51 | }
52 | return ""
53 | }
54 | }
55 | const noteSetSelector = (filter, allNotes, notes) => {
56 | // console.log("noteSetSelector", filter)
57 | if (typeof filter === "object") {
58 | return allNotes.filter(n => filter.indexOf(n._id) > -1)
59 | }
60 | if (filter && filter.indexOf("tag::") > -1 ) {
61 | const tag = filter.split("::")[1]
62 | return allNotes.filter(n => n.tags && n.tags.indexOf(tag) > -1)
63 | }
64 |
65 | switch(filter) {
66 | case "ALL":
67 | return allNotes
68 | case "TRASH":
69 | return notes.filter(i => i.deleted)
70 | case "FAV":
71 | return allNotes.filter(i => i.starred)
72 | default:
73 | return allNotes.filter(i => (i.parent === filter))
74 | }
75 | }
76 | const noteSorter = (notes, middleMenu) => {
77 |
78 | switch(middleMenu.sorter) {
79 | case actions.SORT_ALPHA:
80 | return notes.sort(alphaTitleSorter)
81 | case actions.SORT_ALPHA_REVERSE:
82 | return notes.sort(alphaTitleSorterReverse)
83 | case actions.SORT_UPDATED_AT:
84 | return notes.sort(updatedAtSorter)
85 | case actions.SORT_CUSTOM:
86 | return notes.sort(customNoteSorter)
87 | default:
88 | return notes
89 | }
90 | }
91 |
92 | return {
93 | selection: state.mainMenu.filter,
94 | visibleNotes: noteSorter(noteSetSelector(state.mainMenu.filter, allNotes(state), state.notes), state.middleMenu),
95 |
96 | // visibleNotes: noteSetSelector(state.mainMenu.filter, allNotes(state), state.notes),
97 | addButtonDisabled: state.mainMenu.filter === "TRASH",
98 | selectedNote: state.contentArea.selectedNote,
99 | headerLabel: notebookLabelMaker(state.mainMenu.filter, state.notes, state.middleMenu),
100 | dropNoteCache: state.mainMenu.moveNoteDropTargetId,
101 | sorter: state.middleMenu.sorter
102 | };
103 | }
104 |
105 | function mapDispatchToProps(dispatch: Dispatch) {
106 | return bindActionCreators(Object.assign(contentAreaActions, noteActions, actions), dispatch)
107 | }
108 |
109 |
110 | export default connect(mapStateToProps, mapDispatchToProps)(MiddleMenuCont);
111 |
--------------------------------------------------------------------------------
/app/containers/MiddleMenu/actions.ts:
--------------------------------------------------------------------------------
1 |
2 | export const SORT_NOTES = 'SORT_NOTES';
3 |
4 | // Constants
5 | export const SORT_ALPHA = 'SORT_ALPHA';
6 | export const SORT_ALPHA_REVERSE = 'SORT_ALPHA_REVERSE';
7 | export const SORT_CUSTOM = 'SORT_CUSTOM';
8 | export const SORT_UPDATED_AT = 'SORT_UPDATED_AT';
9 | export const SORT_CREATED_AT = 'SORT_CREATED_AT';
10 |
11 |
12 | export function sortNotes(sorter: String) {
13 | return (dispatch: Dispatch, getState) => {
14 | dispatch({
15 | type: SORT_NOTES,
16 | sorter
17 | });
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/app/containers/MiddleMenu/reducers.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '../../utils/utils'
2 | import { SORT_ALPHA, SORT_NOTES } from './actions';
3 |
4 | import { SELECT_NOTEBOOK } from '../MainMenu/actions';
5 | import {
6 | GLOBAL_SEARCH,
7 | SEARCH_NOTES_RESULTS
8 | } from '../../reducers/noteActions';
9 |
10 | const initialState = {
11 | visibleNotes: [],
12 | sorter: SORT_ALPHA
13 | }
14 |
15 | const middleMenuReducer = createReducer(initialState, {
16 | [SEARCH_NOTES_RESULTS]: (state, action) => {
17 | if (action.target === "MIDDLE_MENU_SEARCH") {
18 | return {...state, visibleNotes: action.results, search: action.search}
19 | }
20 | return state
21 | },
22 | [SORT_NOTES]: (state, action) => {
23 | return {...state, sorter: action.sorter}
24 | },
25 | [SELECT_NOTEBOOK]: (state, action) => {
26 | return {...state, search: null}
27 | },
28 | });
29 | export default middleMenuReducer;
30 |
--------------------------------------------------------------------------------
/app/containers/Root.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { ConnectedRouter } from 'connected-react-router';
4 | import { hot } from 'react-hot-loader/root';
5 | import { History } from 'history';
6 | import { ThemeProvider } from 'styled-components';
7 | import { Store } from '../reducers/types';
8 |
9 | import Routes from '../Routes';
10 | import darkTheme from '../utils/dark_theme.json';
11 | import { DndProvider } from 'react-dnd';
12 | import { HTML5Backend } from 'react-dnd-html5-backend';
13 | type Props = {
14 | store: Store;
15 | history: History;
16 | };
17 |
18 | const Root = ({ store, history }: Props) => (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | export default hot(Root);
31 |
--------------------------------------------------------------------------------
/app/containers/TreeMenuCont/TreeMenuCont.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators, Dispatch } from 'redux';
3 | import { connect } from 'react-redux';
4 | import MiddleMenu from '../../components/MiddleMenu/MiddleMenu';
5 | import * as actions from '../ContentAreaCont/actions';
6 | import * as noteActions from '../../reducers/noteActions';
7 | import { allNotes, findSelectedNote, findChildrenOfNote } from '../MainMenu/selectors';
8 | import MenuItem from '../../components/MainMenu/MenuItem';
9 | import { MenuHeading, MenuItemRightFloat, MenuItemSelected, MenuItemNormal, MenuItemStyle, TreeHeading } from '../../components/MainMenu/style';
10 | import {
11 | ContextMenu,
12 | MenuItem as ContexMenuItem,
13 | ContextMenuTrigger
14 | } from 'react-contextmenu';
15 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
16 | import { faChevronRight, faChevronDown, faPlusCircle, faFolder } from '@fortawesome/free-solid-svg-icons';
17 | import { DotLine } from '../../components/util/utils.style';
18 |
19 |
20 |
21 |
22 | class TreeMenuCont extends React.Component {
23 | state = {
24 | open: false
25 | }
26 |
27 |
28 | render() {
29 | const {selectedNotebook, selectNotebook, note, subNotes, handlers, children, cacheParentId} = this.props
30 | const {
31 | cmCreateNotebookInside,
32 | cmShowInMenuHandler,
33 | cmDeleteNoteHandler,
34 | cmOpenInEditor
35 | } = handlers;
36 | const {_id, title} = note
37 | // console.log(selectedNotebook)
38 | const icon = this.state.open ? this.setState({open: false})} icon={faChevronDown} /> : this.setState({open: true})} icon={faChevronRight} />
39 | // console.log("TreeMenuCont Note id " + _id)
40 | // we dont want to close a notebook menu when it is first selected. We want to close it on click (given its already selected) and open it given its closed.
41 | // const singleClickHandler = (selectedNotebook !== _id) ? () => {selectNotebook(note._id); this.setState({open: true})} : () => {selectNotebook(note._id); this.setState({open: !this.state.open})}
42 | const singleClickHandler = () => {selectNotebook(note._id); this.setState({open: !this.state.open})}
43 | return
44 |
45 | {subNotes && subNotes.length ===0 &&
46 |
47 |
60 | }
61 | {subNotes && subNotes.length > 0 && <>
62 |
83 |
84 |
85 | { this.state.open && subNotes.map(n=> ) }
92 |
93 |
94 | >
95 | }
96 |
97 |
98 |
99 |
100 | Open in editor
101 |
102 | {
105 | this.setState({open: true})
106 | cmCreateNotebookInside(e, data)
107 | }}
108 | >
109 | New sub-notebook
110 |
111 |
112 | {note.showInMenu ? (
113 | Remove from menu
114 | ) : (
115 | Show in menu
116 | )}
117 |
118 |
119 | 0}
124 | >
125 | Delete
126 |
127 |
128 |
;
129 | }
130 | }
131 |
132 | function mapStateToProps(state, props) {
133 | return {
134 | allNotes: allNotes(state),
135 | subNotes: findChildrenOfNote(props.note)(state).filter(n => n.showInMenu), // children that can be displayed in menu
136 | children: findChildrenOfNote(props.note)(state), // all children
137 | level: props.level || 0,
138 | selectedNotebook: state.mainMenu.filter,
139 | };
140 | }
141 |
142 | function mapDispatchToProps(dispatch: Dispatch) {
143 | return bindActionCreators(Object.assign(actions, noteActions), dispatch)
144 | }
145 |
146 | const TreeMenuContWrapped = connect(mapStateToProps, mapDispatchToProps)(TreeMenuCont)
147 | export default TreeMenuContWrapped;
148 |
--------------------------------------------------------------------------------
/app/containers/TreeMenuCont/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 |
3 | // export const CREATE_NOTE = 'CREATE_NOTE';
4 |
5 |
6 | // export function selectNote(nb: String) {
7 | // return (dispatch: Dispatch, getState: GetState) => {
8 | // const { counter } = getState();
9 |
10 | // dispatch(
11 | // {
12 | // type: CREATE_NOTE
13 | // }
14 | // );
15 | // };
16 | // }
17 |
--------------------------------------------------------------------------------
/app/containers/util/NotesWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 |
3 | import { bindActionCreators, Dispatch } from 'redux';
4 | import { connect } from 'react-redux';
5 | import NotesWrapper from '../../components/NotesWrapper/NotesWrapper';
6 | import { notebookSelector, findExistingTags } from './selectors'
7 | import * as notebookActions from '../../reducers/notebookActions';
8 | import * as noteActions from '../../reducers/noteActions';
9 | import { allNotes } from '../MainMenu/selectors';
10 | class MainMenuCont extends React.Component {
11 | render() {
12 | const Comp = this.props.children
13 | return ;
14 |
15 | }
16 | }
17 |
18 | function mapStateToProps(state, {noteId, childIds}) {
19 | return {
20 | subnotes: childIds ? allNotes(state).filter( n => childIds.indexOf(n._id) > -1 ) : [],
21 | note: noteId ? allNotes(state).filter( n => n._id == noteId )[0] : null,
22 | };
23 | }
24 |
25 | function mapDispatchToProps(dispatch: Dispatch) {
26 | return bindActionCreators(Object.assign(notebookActions, noteActions), dispatch)
27 | }
28 |
29 | export default connect(mapStateToProps, mapDispatchToProps)(MainMenuCont);
30 |
--------------------------------------------------------------------------------
/app/file_templates/component/component.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HomeStyle = styled.div`
4 | background-color: ${props => props.theme.colors.dev.new};
5 | `
6 |
--------------------------------------------------------------------------------
/app/file_templates/component/component.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HomeStyle } from './component.styles';
3 |
4 | export default function Home(props) {
5 | return (
6 |
7 | Home Sample Text
8 | {JSON.stringify(props, null, "\t")}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/app/file_templates/container/TemplateCont.tsx:
--------------------------------------------------------------------------------
1 | import { bindActionCreators, Dispatch } from 'redux';
2 | import { connect } from 'react-redux';
3 | import Template from '../components/Template/Template';
4 | import * as actions from './actions';
5 |
6 | import { TemplateStateType } from '../../reducers/types';
7 | class TemplateCont extends PureComponent {
8 | componentDidMount = () => {
9 | console.log('TemplateCont will mount');
10 | dispatchAction.Read({
11 | targetKey: URL,
12 | url: URL
13 | })
14 | }
15 | render() {
16 | return ;
17 |
18 | }
19 | }
20 |
21 |
22 | function mapStateToProps(state: TemplateStateType) {
23 | return {
24 | Template: []
25 | };
26 | }
27 |
28 | function mapDispatchToProps(dispatch: Dispatch) {
29 | return bindActionCreators(actions, dispatch);
30 | }
31 |
32 | export default connect(mapStateToProps, mapDispatchToProps)(TemplateCont);
33 |
--------------------------------------------------------------------------------
/app/file_templates/container/actions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 |
3 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
4 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
5 |
6 | export function increment() {
7 | return {
8 | type: INCREMENT_COUNTER
9 | };
10 | }
11 |
12 | export function decrement() {
13 | return {
14 | type: DECREMENT_COUNTER
15 | };
16 | }
17 |
18 | export function incrementIfOdd() {
19 | return (dispatch: Dispatch, getState: GetState) => {
20 | const { counter } = getState();
21 |
22 | if (counter % 2 === 0) {
23 | return;
24 | }
25 |
26 | dispatch(increment());
27 | };
28 | }
29 |
30 | export function incrementAsync(delay = 1000) {
31 | return (dispatch: Dispatch) => {
32 | setTimeout(() => {
33 | dispatch(increment());
34 | }, delay);
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/app/file_templates/container/reducers.ts:
--------------------------------------------------------------------------------
1 | import { Action } from 'redux';
2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from './actions';
3 |
4 | export default function counter(state = 0, action: Action) {
5 | switch (action.type) {
6 | case INCREMENT_COUNTER:
7 | return state + 1;
8 | case DECREMENT_COUNTER:
9 | return state - 1;
10 | default:
11 | return state;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
4 | import Root from './containers/Root';
5 | import { configureStore, history } from './store/configureStore';
6 | import './app.global.css';
7 |
8 |
9 | import PouchInit from "./PouchInit";
10 |
11 |
12 | // Configure Store
13 | export const store = configureStore();
14 | export const pouchInit = new PouchInit(store);
15 |
16 | const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
17 |
18 | document.addEventListener('DOMContentLoaded', () =>
19 | render(
20 |
21 |
22 | ,
23 | document.getElementById('root')
24 | )
25 | );
26 |
27 | function close() {
28 | pouchInit.cancel();
29 | }
30 |
31 | document.addEventListener("online", (ev) =>{
32 | console.log("oneline")
33 | });
34 |
35 | document.addEventListener("offline", (ev) =>{
36 | console.log("offline")
37 | });
38 |
39 |
40 | // this causes mulitple windows to open
41 | document.addEventListener("unload", (ev) =>
42 | {
43 | console.log("Before closing")
44 | close()
45 | // // store.dispatch({type: SHUT_DOWN_APP})
46 |
47 | // // ev.preventDefault();
48 | // //alert("Before closing")
49 | // alert("after closing")
50 | // return true
51 | // // return ev.returnValue = 'Are you sure you want to close?';
52 | });
53 |
--------------------------------------------------------------------------------
/app/main.dev.ts:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off, no-console: off */
2 |
3 | /**
4 | * This module executes inside of electron's main process. You can start
5 | * electron renderer process from here and communicate with the other processes
6 | * through IPC.
7 | *
8 | * When running `yarn build` or `yarn build-main`, this file is compiled to
9 | * `./app/main.prod.js` using webpack. This gives us some performance wins.
10 | */
11 | import path from 'path';
12 | import { app, BrowserWindow } from 'electron';
13 | import MenuBuilder from './menu';
14 |
15 | import AppUpdater from "./AppUpdater";
16 | // import { store } from '.';
17 | // import { SHUT_DOWN_APP } from './containers/HomePage/actions';
18 | // import { pouchInit } from '.';
19 |
20 |
21 | let mainWindow: BrowserWindow | null = null;
22 |
23 | if (process.env.NODE_ENV === 'production') {
24 | const sourceMapSupport = require('source-map-support');
25 | sourceMapSupport.install();
26 | }
27 |
28 | if (
29 | process.env.NODE_ENV === 'development' ||
30 | process.env.DEBUG_PROD === 'true' || true
31 | ) {
32 | require('electron-debug')();
33 | }
34 |
35 | const installExtensions = async () => {
36 | const installer = require('electron-devtools-installer');
37 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
38 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
39 |
40 | return Promise.all(
41 | extensions.map(name => installer.default(installer[name], forceDownload))
42 | ).catch(console.log);
43 | };
44 |
45 | const createWindow = async () => {
46 | if (
47 | process.env.NODE_ENV === 'development' ||
48 | process.env.DEBUG_PROD === 'true' || true
49 | ) {
50 | await installExtensions();
51 | }
52 |
53 |
54 | mainWindow = new BrowserWindow({
55 | show: false,
56 | webPreferences:
57 | process.env.NODE_ENV === 'development' || process.env.E2E_BUILD === 'true'
58 | ? {
59 | nodeIntegration: true
60 | }
61 | : {
62 | preload: path.join(__dirname, 'dist/renderer.prod.js') // this one causes font awesome icons to be really big [danobot/noteapp#25]. dirty fix by enabling E2E_BUILD
63 | }
64 | });
65 |
66 | mainWindow.loadURL(`file://${__dirname}/app.html`);
67 |
68 | // @TODO: Use 'ready-to-show' event
69 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event
70 | mainWindow.webContents.on('did-finish-load', () => {
71 | if (!mainWindow) {
72 | throw new Error('"mainWindow" is not defined');
73 | }
74 | if (process.env.START_MINIMIZED) {
75 | mainWindow.minimize();
76 | } else {
77 | mainWindow.show();
78 | mainWindow.focus();
79 | }
80 | });
81 |
82 | mainWindow.on('closed', () => {
83 | mainWindow = null;
84 | });
85 |
86 | const menuBuilder = new MenuBuilder(mainWindow);
87 | menuBuilder.buildMenu();
88 |
89 | // Remove this if your app does not use auto updates
90 | // eslint-disable-next-line
91 | new AppUpdater();
92 | };
93 |
94 | /**
95 | * Add event listeners...
96 | */
97 | // function closed() {
98 | // store.dispatch({type: SHUT_DOWN_APP})
99 |
100 | // }
101 | app.on('window-all-closed', () => {
102 | // Respect the OSX convention of having the application in memory even
103 | // after all windows have been closed
104 | if (process.platform !== 'darwin') {
105 | app.quit();
106 | }
107 | // closed()
108 | console.log("Cancelling sync")
109 | // pouchInit.cancel()
110 | });
111 |
112 | app.on('ready', createWindow);
113 |
114 | app.on('activate', () => {
115 | // On macOS it's common to re-create a window in the app when the
116 | // dock icon is clicked and there are no other windows open.
117 | if (mainWindow === null) createWindow();
118 | });
119 |
--------------------------------------------------------------------------------
/app/main.prod.js.LICENSE:
--------------------------------------------------------------------------------
1 | /*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
2 |
--------------------------------------------------------------------------------
/app/monaco-editor-worker-loader-proxy.js:
--------------------------------------------------------------------------------
1 |
2 | self.MonacoEnvironment = {
3 | baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.13.1/min/'
4 | }
5 |
6 | importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.13.1/min/vs/base/worker/workerMain.js') // eslint-disable-line
7 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notorious",
3 | "productName": "Notorious",
4 | "version": "0.2.1-beta.19",
5 | "description": "Note taking application with cross-device synchronisation and offline support in mind.",
6 | "main": "./main.prod.js",
7 | "author": {
8 | "name": "Daniel Mason",
9 | "email": "danielbaker210@gmail.com"
10 | },
11 | "scripts": {
12 | "electron-rebuild": "node -r ../internals/scripts/BabelRegister.js ../internals/scripts/ElectronRebuild.js",
13 | "postinstall": "yarn electron-rebuild"
14 | },
15 | "license": "GPL-3.0-or-later",
16 | "dependencies": {
17 | "pouchdb": "^7.2.1",
18 | "redux-pouchdb": "^1.0.0-rc.3"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/reducers/config/README.md:
--------------------------------------------------------------------------------
1 |
2 | Webpack is replacing configs.electron.ts with `configs.web.ts` in `configs\webpack.config.web.prod.babel.js` when the application is built for Docker web deployment.
3 |
4 | This will achieve the following:
5 | * usable of local storage for web deployment
6 | * usage of electron-store for Electron deployment.
7 |
--------------------------------------------------------------------------------
/app/reducers/config/configs.electron.ts:
--------------------------------------------------------------------------------
1 |
2 | // this electron implementation is served by webpack by default using alias in webpack.config.base.js
3 |
4 | import {
5 | SET_CONFIG
6 | } from '../configActions';
7 | const Store = require('electron-store');
8 | /**
9 | * For Electron deployment, wewant to use electron-store to store configuration.
10 | */
11 | const store = new Store('config');
12 | const c = store.store
13 | console.log("configs.electron.ts store: ", c)
14 | const initialState = c || {}
15 | // const initialState = {"_id": "_local/config"}
16 |
17 | function configReducer(state = initialState, action: Action) {
18 | switch (action.type) {
19 | case SET_CONFIG:
20 | const newState = {...state, [action.id]: action.attributes} // leave it, it works.
21 | store.set(action.id, action.attributes)
22 |
23 | return newState
24 | default:
25 | return state
26 | }
27 |
28 | }
29 |
30 | // export default persistentDocumentReducer(
31 | // configDB,
32 | // 'configs'
33 | // )(configReducer);
34 | export default configReducer;
35 |
--------------------------------------------------------------------------------
/app/reducers/config/configs.web.ts:
--------------------------------------------------------------------------------
1 | import { persistentDocumentReducer } from 'redux-pouchdb';
2 | import {
3 | SET_CONFIG
4 | } from '../configActions';
5 | import { loadState, saveState } from '../../utils/localStorage';
6 |
7 | /**
8 | * For web deployment, we want to use localstorage to store configuration.
9 | */
10 | const initialState = loadState() || {selectedNotebook: "ALL"}
11 | console.log("configs.web.ts initial state: ", initialState)
12 |
13 | function configReducer(state = initialState, action: Action) {
14 | switch (action.type) {
15 | case SET_CONFIG:
16 | const newState = {...state, [action.id]: action.attributes}
17 | saveState(newState)
18 |
19 | return newState
20 | default:
21 | return state
22 | }
23 |
24 | }
25 |
26 | export default configReducer;
27 |
--------------------------------------------------------------------------------
/app/reducers/configActions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 |
3 | export const SET_CONFIG = 'SET_CONFIG';
4 |
5 |
6 | export function setConfig(id: string, attributes: string) {
7 | return (dispatch: Dispatch, getState: GetState) => {
8 | dispatch(
9 | {
10 | type: SET_CONFIG,
11 | id,
12 | attributes
13 | }
14 | );
15 |
16 | };
17 | }
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/reducers/configs.ts:
--------------------------------------------------------------------------------
1 | const configReducer = require('ConfigReducer').default // webpack will alias this to either config/configs.electron.ts or config/configs.web.ts
2 | export default configReducer;
3 |
--------------------------------------------------------------------------------
/app/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { connectRouter } from 'connected-react-router';
3 | import { History } from 'history';
4 | import mainMenuReducer from '../containers/MainMenu/reducers';
5 | import middleMenuReducer from '../containers/MiddleMenu/reducers';
6 | import homePageReducer from '../containers/HomePage/reducers';
7 | import contentAreaReducer from '../containers/ContentAreaCont/reducers';
8 | import notesReducer from './notes';
9 | import notebooksReducer from './notebooks';
10 | import configReducer from './configs';
11 | import modalReducer from './modals';
12 |
13 | export default function createRootReducer(history: History) {
14 | return combineReducers({
15 | router: connectRouter(history),
16 | notes: notesReducer,
17 | mainMenu: mainMenuReducer,
18 | middleMenu: middleMenuReducer,
19 | settings: homePageReducer,
20 | contentArea: contentAreaReducer,
21 | configs: configReducer,
22 | modals: modalReducer
23 | });;
24 | }
25 |
--------------------------------------------------------------------------------
/app/reducers/modalActions.ts:
--------------------------------------------------------------------------------
1 | import { GetState, Dispatch } from '../../reducers/types';
2 | import { setConfig } from '../../reducers/configActions';
3 |
4 | export const SHOW_NOTEBOOK_MODAL = 'SHOW_NOTEBOOK_MODAL';
5 | export const SHOW_GENERIC_MODAL = 'SHOW_GENERIC_MODAL';
6 | export const HIDE_NOTEBOOK_MODAL = 'HIDE_NOTEBOOK_MODAL';
7 | export const HIDE_GENERIC_MODAL = 'HIDE_GENERIC_MODAL';
8 |
9 |
10 |
11 |
12 | export function showNotebookModal(attributes: object) {
13 | return (dispatch: Dispatch, getState) => {
14 | dispatch(
15 | {
16 | type: SHOW_NOTEBOOK_MODAL,
17 | attributes
18 | }
19 | );
20 | };
21 | }
22 | export function hideNotebookModal() {
23 | return (dispatch: Dispatch, getState) => {
24 | dispatch(
25 | {
26 | type: HIDE_NOTEBOOK_MODAL
27 | }
28 | );
29 | };
30 | }
31 | export function showModal(name: string, data: object) {
32 | return (dispatch: Dispatch, getState) => {
33 | dispatch(
34 | {
35 | type: SHOW_GENERIC_MODAL,
36 | [name+"ModalToggle"]: true,
37 | [name+"ModalData"]: data
38 | }
39 | );
40 | };
41 | }
42 | export function hideModal(name: string) {
43 | return (dispatch: Dispatch, getState) => {
44 | dispatch(
45 | {
46 | type: HIDE_GENERIC_MODAL,
47 | [name+"ModalToggle"]: false,
48 | [name+"ModalData"]: null
49 |
50 | }
51 | );
52 | };
53 | }
54 |
55 | // Helpers
56 | export function showFinderModal(data: object) {
57 | return showModal("finder", data)
58 | }
59 |
60 | export function hideFinderModal() {
61 | return hideModal("finder")
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/app/reducers/modals.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '../utils/utils'
2 | import {
3 | SHOW_NOTEBOOK_MODAL,
4 | HIDE_NOTEBOOK_MODAL,
5 | SHOW_GENERIC_MODAL,
6 | HIDE_GENERIC_MODAL
7 | } from './modalActions';
8 | import { GLOBAL_SEARCH, SEARCH_NOTES_RESULTS } from './noteActions';
9 |
10 | const initialState = {
11 | showNotebookModalToggle: false,
12 | finderModalToggle: false,
13 | finderModalData: null
14 | }
15 |
16 | const modalReducer = createReducer(initialState, {
17 | [SHOW_NOTEBOOK_MODAL]: (state, action) => {
18 | return {...state, showNotebookModalToggle: true, showNotebookData: action.attributes}
19 | },
20 | [HIDE_NOTEBOOK_MODAL]: (state, action) => {
21 | return {...state, showNotebookModalToggle: false, showNotebookData: null}
22 | },
23 | [SHOW_GENERIC_MODAL]: (state, action) => {
24 | return {...state, ...action}
25 | },
26 | [HIDE_GENERIC_MODAL]: (state, action) => {
27 | return {...state, ...action}
28 | },
29 | [SEARCH_NOTES_RESULTS]: (state, action) => {
30 | if (action.target === GLOBAL_SEARCH) {
31 | return {...state, finderModalData: {results: action.orderedResults, search: action.search }}
32 | }
33 | return state
34 | },
35 |
36 | });
37 | export default modalReducer;
38 |
--------------------------------------------------------------------------------
/app/reducers/notebookActions.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from '../../reducers/types';
2 | import { CREATE_NOTE, createNote, createNoteWithId } from './noteActions';
3 | import { v4 as uuid } from 'uuid';
4 | import { selectNotebook } from '../containers/MainMenu/actions';
5 |
6 | export const CREATE_NOTEBOOK = 'CREATE_NOTEBOOK';
7 |
8 | export function createNotebook(parent: string, attributes: object) {
9 | return (dispatch: Dispatch) => {
10 | const noteId = uuid()
11 | dispatch({type: CREATE_NOTEBOOK, attributes: { ...attributes, id: noteId }})
12 | dispatch(selectNotebook(noteId))
13 | createNoteWithId(noteId, parent, { ...attributes, showInMenu: true, kind: 'collection' })(dispatch)
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/app/reducers/notebooks.ts:
--------------------------------------------------------------------------------
1 | // import { persistentCollectionReducer } from 'redux-pouchdb';
2 | // import PouchDB from 'pouchdb'
3 | // import config from '../utils/config';
4 | // import {createReducer} from '../utils/utils'
5 |
6 | // const initialState = []
7 | // const notebooksReducer = createReducer(initialState, {
8 |
9 |
10 |
11 | // }
12 |
13 | // )
14 | // export default persistentCollectionReducer(
15 | // new PouchDB(`${config.url}notebooks`),
16 | // 'notebooks'
17 | // )(notebooksReducer);
18 |
--------------------------------------------------------------------------------
/app/reducers/notes.ts:
--------------------------------------------------------------------------------
1 | import { persistentCollectionReducer } from 'redux-pouchdb';
2 | import PouchDB from 'pouchdb'
3 | import config from '../utils/config';
4 | import {createReducer, removeNoteFromParentsChildArray, updateNoteAttributesInArray, addChildToParent} from '../utils/utils'
5 | // import { createReducer } from '@reduxjs/toolkit'
6 | import { UPDATE_NOTE, CREATE_NOTE, DELETE_NOTE, REMOVE_EDITOR, ADD_EDITOR, TOGGLE_MENU_SHOW_NOTE, TOGGLE_PIN_NOTE, ADD_ATTACHMENT, MOVE_NOTE} from './noteActions';
7 | import { notesDB } from '../PouchInit';
8 | import { SELECT_NOTE } from '../containers/ContentAreaCont/actions';
9 | import { findChildrenOfNoteInclDeleted } from '../containers/MainMenu/selectors'
10 |
11 | const initialState = []
12 | const notesReducer = createReducer(initialState, {
13 |
14 | [CREATE_NOTE]: (state, action) => {
15 | const noteId = action.id
16 |
17 | const newState = addChildToParent(state, action.id, action.parent)
18 | const newNote = {_id: noteId,
19 | title: "",
20 | createdAt: Date.now(),
21 | updatedAt: Date.now(),
22 | parent: action.parent,
23 | children: [],
24 | schema: "note",
25 | content: "",
26 | editor: config.defaultEditor || "markdown",
27 | ...action.attributes
28 | }
29 | return [...newState, newNote] // and add new note to array
30 | },
31 | [DELETE_NOTE]: (state, action) => {
32 | // find parent of note to be deleted
33 | let newState = state.filter(n => n._id !== action.noteId); // remove note to be deleted
34 | const noteToBeNuked = state.filter(n => n._id === action.noteId)[0]
35 | console.log("noteToBeNuked: ",noteToBeNuked)
36 | if (noteToBeNuked.children && noteToBeNuked.children.length > 0) {
37 | return state // we cannot delete a note with children... what would we do with the children? THINK OF THE CHILDREN!!! D:
38 |
39 | // unless all of the children are also marked as deleted
40 | // const children = state.filter(n => n.parent === noteToBeNuked._id)
41 | // console.log("Children note objects", children)
42 |
43 | // const allChildrenAreDeleted = children.filter(c => c.deleted).length === children.length
44 | // if (allChildrenAreDeleted) {
45 | // console.log("Children have been deleted", children)
46 | // } else {
47 | // return state
48 | // }
49 | }
50 | newState = removeNoteFromParentsChildArray(newState, noteToBeNuked)
51 |
52 | return newState // and add new note to array
53 | },
54 | [UPDATE_NOTE]: (state, action) => {
55 | console.log(action)
56 |
57 | return updateNoteAttributesInArray(state, action.id, action.attributes)
58 |
59 | },
60 | [MOVE_NOTE]: (state, action) => {
61 |
62 | const noteToBeMoved = state.filter(n => n._id === action.id)[0]
63 | let newState = removeNoteFromParentsChildArray(state, noteToBeMoved )
64 | newState = addChildToParent(newState, action.id, action.parent)
65 | newState = updateNoteAttributesInArray(newState, action.id, {parent: action.parent})
66 | return newState
67 |
68 |
69 |
70 | },
71 | [SELECT_NOTE]: (state, action) => { // when we select the note, save its id in the parents lastSelectedChild field
72 | console.group("SELECT_NOTE in notes reducer")
73 | console.log("action", action)
74 | console.groupEnd()
75 | return state.map((item, id) => {
76 | if (item.children && item.children.indexOf(action.id) === -1) { return item }
77 | console.log("updating", item.title)
78 | let newState = item
79 | // if (!item.deleted) { // unless we are selecting the note in the Trash, set last selected child
80 | // }
81 | newState.lastSelectedChild = action.id
82 | return newState
83 | })
84 | }
85 |
86 |
87 | }
88 | );
89 |
90 | export default persistentCollectionReducer(
91 | notesDB,
92 | 'notes'
93 | )(notesReducer);
94 |
--------------------------------------------------------------------------------
/app/reducers/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch as ReduxDispatch, Store as ReduxStore, Action } from 'redux';
2 |
3 | export type mainMenuStateType = {
4 | counter: number;
5 | mainMenu: object;
6 | };
7 |
8 | export type GetState = () => mainMenuStateType;
9 |
10 | export type Dispatch = ReduxDispatch>;
11 |
12 | export type Store = ReduxStore>;
13 |
--------------------------------------------------------------------------------
/app/store/configureStore.dev.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createHashHistory } from 'history';
4 | import { routerMiddleware, routerActions } from 'connected-react-router';
5 | import { createLogger } from 'redux-logger';
6 | import createRootReducer from '../reducers';
7 | import * as counterActions from '../containers/CounterPage/actions';
8 | import { counterStateType } from '../reducers/types';
9 | import PouchDB from 'pouchdb'
10 | import { persistStore } from 'redux-pouchdb';
11 |
12 | declare global {
13 | interface Window {
14 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: (
15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
16 | obj: Record
17 | ) => Function;
18 | }
19 | interface NodeModule {
20 | hot?: {
21 | accept: (path: string, cb: () => void) => void;
22 | };
23 | }
24 | }
25 |
26 | const history = createHashHistory();
27 |
28 | const rootReducer = createRootReducer(history);
29 |
30 | const configureStore = (initialState?: counterStateType) => {
31 | // Redux Configuration
32 | const middleware = [];
33 | const enhancers = [];
34 |
35 | // Thunk Middleware
36 | middleware.push(thunk);
37 |
38 |
39 | // middleware.push();
40 |
41 | // Logging Middleware
42 | const logger = createLogger({
43 | level: 'info',
44 | collapsed: true
45 | });
46 |
47 | // Skip redux logs in console during the tests
48 | if (process.env.NODE_ENV !== 'test') {
49 | middleware.push(logger);
50 | }
51 |
52 | // Router Middleware
53 | const router = routerMiddleware(history);
54 | middleware.push(router);
55 |
56 | // Redux DevTools Configuration
57 | const actionCreators = {
58 | ...counterActions,
59 | ...routerActions
60 | };
61 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
62 | /* eslint-disable no-underscore-dangle */
63 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
64 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
65 | // Options: http://extension.remotedev.io/docs/API/Arguments.html
66 | actionCreators
67 | })
68 | : compose;
69 | /* eslint-enable no-underscore-dangle */
70 | // const pouch = persistentStore({db: pouchDbInstance,
71 | // name: 'testStore',
72 | // onInit: (reducerName, reducerState, store) => {
73 | // console.log("onInit")
74 | // // Called when this reducer was initialized
75 | // // (the state was loaded from or saved to the
76 | // // database for the first time or after a reinit action).
77 | // },
78 | // onUpdate: (reducerName, reducerState, store) => {
79 | // console.log("onUpdate")
80 |
81 | // // Called when the state of reducer was updated with
82 | // // data from the database.
83 | // // Cave! The store still contains the state before
84 | // // the updated reducer state was applied to it.
85 | // },
86 | // onSave: (reducerName, reducerState, store) => {
87 | // console.log("onSave")
88 |
89 | // // Called every time the state of this reducer was
90 | // // saved to the database.
91 | // },
92 | // onReady: (store) => {
93 | // console.log("onReady")
94 | // // Called when all reducers are initialized (also after
95 | // // a reinit for all reducers is finished).
96 | // }
97 | // })
98 | // Apply Middleware & Compose Enhancers
99 | enhancers.push(applyMiddleware(...middleware)); // pouch
100 | const enhancer = composeEnhancers(...enhancers);
101 |
102 | // Create Store
103 | const store = createStore(rootReducer, initialState, enhancer);
104 | persistStore(store);
105 | if (module.hot) {
106 | module.hot.accept(
107 | '../reducers',
108 | // eslint-disable-next-line global-require
109 | () => store.replaceReducer(require('../reducers').default)
110 | );
111 | }
112 |
113 | return store;
114 | };
115 |
116 | export default { configureStore, history};
117 |
--------------------------------------------------------------------------------
/app/store/configureStore.prod.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createHashHistory } from 'history';
4 | import { routerMiddleware } from 'connected-react-router';
5 | import { persistStore } from 'redux-pouchdb';
6 | import createRootReducer from '../reducers';
7 | import { Store, counterStateType } from '../reducers/types';
8 |
9 |
10 |
11 | const history = createHashHistory();
12 | const rootReducer = createRootReducer(history);
13 | const router = routerMiddleware(history);
14 | const enhancer = applyMiddleware(thunk, router);
15 |
16 | function configureStore(initialState?: counterStateType): Store {
17 | const store = createStore(rootReducer, initialState, enhancer);
18 | persistStore(store);
19 |
20 | return store;
21 | }
22 |
23 | export default { configureStore, history };
24 |
--------------------------------------------------------------------------------
/app/store/configureStore.ts:
--------------------------------------------------------------------------------
1 | import configureStoreDev from './configureStore.dev';
2 | import configureStoreProd from './configureStore.prod';
3 |
4 | const selectedConfigureStore =
5 | process.env.NODE_ENV === 'production'
6 | ? configureStoreProd
7 | : configureStoreDev;
8 |
9 | export const { configureStore } = selectedConfigureStore;
10 |
11 | export const { history } = selectedConfigureStore;
12 |
--------------------------------------------------------------------------------
/app/style/ant-customisation.css:
--------------------------------------------------------------------------------
1 |
2 | #root {
3 | height: 100%;
4 | }
5 | html {
6 | overflow: hidden;
7 | }
8 |
9 | .noselect {
10 | -webkit-touch-callout: none; /* iOS Safari */
11 | -webkit-user-select: none; /* Safari */
12 | -khtml-user-select: none; /* Konqueror HTML */
13 | -moz-user-select: none; /* Old versions of Firefox */
14 | -ms-user-select: none; /* Internet Explorer/Edge */
15 | user-select: none; /* Non-prefixed version, currently
16 | supported by Chrome, Opera and Firefox */
17 | }
18 |
19 | .CodeMirror {
20 | height: 100%;
21 | /* overflow-y: hidden; */
22 | }
23 | .CodeMirror-scroll {
24 | /* width: 100%; */
25 | }
26 |
27 | .react-codemirror2 {
28 | padding: 0 2px 0 10px;
29 | /* position: fixed; */
30 | /* top: 120px; */
31 | /* width: 100%; */
32 | /* right: 0; */
33 | /* bottom: 0; */
34 | /* height: 88vh; */
35 | }
36 |
37 | .ant-spin-dot {
38 | width: 10px;
39 | height: 10px;
40 | }
41 | .ant-spin-dot-item {
42 | width: 4px;
43 | height: 4px;
44 | }
45 |
--------------------------------------------------------------------------------
/app/style/code-mirror-markdown.css:
--------------------------------------------------------------------------------
1 | .CodeMirror {
2 | height: auto;
3 | }
4 | .cm-formatting-lis {
5 | font-family: unset;
6 | }
7 | .CodeMirror-scroll {
8 | overflow-y: hidden;
9 | overflow-x: auto;
10 | }
11 |
12 | .cm-s-hypermd-light {
13 | font-size: 10pt !important;
14 | }
15 |
16 | .HyperMD-header {
17 | padding-top: 10px !important;
18 | }
19 | .HyperMD-header-1 {
20 | font-size: 16pt !important;
21 | }
22 |
23 | .HyperMD-header-2 {
24 | font-size: 14pt !important;
25 | }
26 | .HyperMD-header-3 {
27 | font-size: 12pt !important;
28 | }
29 | .HyperMD-header-4 {
30 | font-size: 12pt;
31 | text-decoration: underline;
32 | }
33 | .HyperMD-header-5 {
34 | font-size: 12pt;
35 | text-decoration: underline;
36 | }
37 |
38 | span.hmd-fold-math {
39 | text-align: center;
40 | padding: 10px 0 10px 0;
41 | }
42 |
--------------------------------------------------------------------------------
/app/style/context-menu.css:
--------------------------------------------------------------------------------
1 | .react-contextmenu {
2 | background-color: #fff;
3 | background-clip: padding-box;
4 | border: 1px solid rgba(0,0,0,.15);
5 | border-radius: .25rem;
6 | color: #373a3c;
7 | font-size: 16px;
8 | margin: 2px 0 0;
9 | min-width: 160px;
10 | outline: none;
11 | opacity: 0;
12 | padding: 5px 0;
13 | pointer-events: none;
14 | text-align: left;
15 | transition: opacity 250ms ease !important;
16 | }
17 |
18 | .react-contextmenu.react-contextmenu--visible {
19 | opacity: 1;
20 | pointer-events: auto;
21 | z-index: 9999;
22 | }
23 |
24 | .react-contextmenu-item {
25 | background: 0 0;
26 | border: 0;
27 | color: #373a3c;
28 | cursor: pointer;
29 | font-weight: 400;
30 | line-height: 1.5;
31 | padding: 3px 20px;
32 | text-align: inherit;
33 | white-space: nowrap;
34 | }
35 |
36 | .react-contextmenu-item.react-contextmenu-item--active,
37 | .react-contextmenu-item.react-contextmenu-item--selected {
38 | color: #fff;
39 | background-color: #20a0ff;
40 | border-color: #20a0ff;
41 | text-decoration: none;
42 | }
43 |
44 | .react-contextmenu-item.react-contextmenu-item--disabled,
45 | .react-contextmenu-item.react-contextmenu-item--disabled:hover {
46 | background-color: transparent;
47 | border-color: rgba(0,0,0,.15);
48 | color: #878a8c;
49 | }
50 |
51 | .react-contextmenu-item--divider {
52 | border-bottom: 1px solid rgba(0,0,0,.15);
53 | cursor: inherit;
54 | margin-bottom: 3px;
55 | padding: 2px 0;
56 | }
57 | .react-contextmenu-item--divider:hover {
58 | background-color: transparent;
59 | border-color: rgba(0,0,0,.15);
60 | }
61 |
62 | .react-contextmenu-item.react-contextmenu-submenu {
63 | padding: 0;
64 | }
65 |
66 | .react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item {
67 | }
68 |
69 | .react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item:after {
70 | content: "▸";
71 | display: inline-block;
72 | position: absolute;
73 | right: 7px;
74 | }
75 |
76 | .example-multiple-targets::after {
77 | content: attr(data-count);
78 | display: block;
79 | }
80 |
--------------------------------------------------------------------------------
/app/style/react-split-pane.css:
--------------------------------------------------------------------------------
1 | .Resizer {
2 | background: #000;
3 | opacity: 0.2;
4 | z-index: 999;
5 | -moz-box-sizing: border-box;
6 | -webkit-box-sizing: border-box;
7 | box-sizing: border-box;
8 | -moz-background-clip: padding;
9 | -webkit-background-clip: padding;
10 | background-clip: padding-box;
11 | }
12 |
13 | .Resizer:hover {
14 | -webkit-transition: all 2s ease;
15 | transition: all 2s ease;
16 | }
17 |
18 | .Resizer.horizontal {
19 | height: 11px;
20 | margin: -5px 0;
21 | border-top: 5px solid rgba(255, 255, 255, 0);
22 | border-bottom: 5px solid rgba(255, 255, 255, 0);
23 | cursor: row-resize;
24 | width: 100%;
25 | }
26 |
27 | .Resizer.horizontal:hover {
28 | border-top: 5px solid rgba(0, 0, 0, 0.5);
29 | border-bottom: 5px solid rgba(0, 0, 0, 0.5);
30 | }
31 |
32 | .Resizer.vertical {
33 | width: 11px;
34 | margin: 0 -5px;
35 | border-left: 1px solid transparent;
36 | border-right: 1px solid transparent;
37 | cursor: col-resize;
38 | }
39 |
40 | .Resizer.vertical:hover {
41 | border-left: 5px solid transparent;
42 | border-right: 5px solid transparent;
43 | }
44 | .Resizer.disabled {
45 | cursor: not-allowed;
46 | }
47 | .Resizer.disabled:hover {
48 | border-color: transparent;
49 | }
50 | .Pane {
51 | height: 100%;
52 | }
53 | .splitter-layout .layout-splitter {
54 | background-color: transparent;
55 |
56 | }
57 | .splitter-layout .layout-splitter {
58 | width: 1px;
59 | background-color: transparent;
60 | }
61 |
--------------------------------------------------------------------------------
/app/style/utils.style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RightFloaty = styled.div`
4 | float: right;
5 | display: inline-block;
6 | /* padding: 3px 4px 0px 0px; */
7 | `;
8 | export const InlineItem = styled.div`
9 | display: inline;
10 | margin-right: 10px;
11 | font-size: smaller;
12 | > .svg-inline--fa {
13 | margin-right: 4px;
14 | }
15 | `
16 |
--------------------------------------------------------------------------------
/app/utils/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/app/utils/.gitkeep
--------------------------------------------------------------------------------
/app/utils/DragItemTypes.js:
--------------------------------------------------------------------------------
1 | export const DragItemTypes = {
2 | NOTE: 'note',
3 | }
4 |
--------------------------------------------------------------------------------
/app/utils/config.electron.ts:
--------------------------------------------------------------------------------
1 | // this electron implementation is served by webpack by default using alias in webpack.config.base.js
2 | const config = {}
3 |
4 | // Environment setup for React Docker app from:
5 | // https://www.freecodecamp.org/news/how-to-implement-runtime-environment-variables-with-create-react-app-docker-and-nginx-7f9d42a91d70/
6 |
7 |
8 |
9 | const Store = require('electron-store');
10 |
11 | const store = new Store('config');
12 |
13 | const c = store.get('url')
14 | // const configDB = new PouchDB('data/config');
15 | // const con = configDB.get("_local/config")
16 | console.log("Using backend: ", c)
17 | if (c) {
18 | config.url = c
19 | config.username = store.get('username')
20 | config.password = store.get('password')
21 | config.scheme = store.get('scheme')
22 |
23 | } else {
24 | config.url = null
25 | }
26 | export default config
27 |
--------------------------------------------------------------------------------
/app/utils/config.ts:
--------------------------------------------------------------------------------
1 | const config = require('configz').default; // webpack will alias this to either config.electron.ts or config.web.ts
2 |
3 | export default config
4 |
--------------------------------------------------------------------------------
/app/utils/config.web.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const config = {}
4 |
5 | // Environment setup for React Docker app from:
6 | // https://www.freecodecamp.org/news/how-to-implement-runtime-environment-variables-with-create-react-app-docker-and-nginx-7f9d42a91d70/
7 |
8 |
9 | console.log("Web deployment. .env file config: ", window._env_)
10 |
11 | if (window._env_.DB_URL) {
12 | config.url = window._env_.DB_URL
13 | config.username = window._env_.COUCHDB_USER
14 | config.password = window._env_.COUCHDB_PASSWORD
15 | config.scheme = window._env_.COUCHDB_SCHEME
16 | } else {
17 | console.error("App is not running in electron. DB_URL environment variable containing the full database connection string must be defined on docker container")
18 | }
19 | export default config
20 |
--------------------------------------------------------------------------------
/app/utils/configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev": {
3 | "db": "http://admin:admin@localhost:5984/"
4 | },
5 | "prod": {
6 | "db": "http://admin:admin@localhost:5984/"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/utils/dark_theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors": {
3 | "background": {
4 | "lift":"#E0E0E0",
5 | "light":"#FFF3F3",
6 | "menu":"#2D3047",
7 | "dark": "#0C0D15",
8 | "selected": "#1890ff"
9 | },
10 | "menu": {
11 | "selected": "#383e50"
12 | },
13 | "text": {
14 | "black": "#000000",
15 | "dark": "#0C0D15",
16 | "light": "#FFEDED",
17 | "muted": "#858585",
18 | "coloured": "#5E89FF",
19 | "selected": "#FFEDED"
20 | },
21 | "dev": {
22 | "new": "orange"
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/utils/dark_theme_original.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors": {
3 | "background": {
4 | "lift":"#E0E0E0",
5 | "light":"#f5f5f4",
6 | "menu":"#f5f5f5",
7 | "dark": "#171a21",
8 | "selected": "#4f8eff"
9 | },
10 | "menu": {
11 | "selected": "#383e50"
12 | },
13 | "text": {
14 | "black": "#000000",
15 | "dark": "#171a21",
16 | "light": "#f5f5f4",
17 | "muted": "#858585",
18 | "coloured": "#5E89FF",
19 | "selected": "#f5f5f4"
20 | },
21 | "dev": {
22 | "new": "orange"
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/utils/localStorage.js:
--------------------------------------------------------------------------------
1 | export const loadState = () => {
2 | try {
3 | const serializedState = localStorage.getItem('state');
4 | if (serializedState === null) {
5 | return {};
6 | }
7 | return JSON.parse(serializedState);
8 | } catch (err) {
9 | return {};
10 | }
11 | };
12 |
13 |
14 | export const saveState = (state) => {
15 | try {
16 | const serializedState = JSON.stringify(state);
17 | localStorage.setItem('state', serializedState);
18 | } catch {
19 | // ignore write errors
20 | }
21 | };
22 |
23 | export const configStorage = loadState()
24 |
25 |
--------------------------------------------------------------------------------
/app/utils/utils.js:
--------------------------------------------------------------------------------
1 |
2 | import config from './config';
3 | export function createReducer(initialState, handlers) {
4 | return function reducer(state = initialState, action) {
5 | if (handlers.hasOwnProperty(action.type)) {
6 | return handlers[action.type](state, action)
7 | } else {
8 | return state
9 | }
10 | }
11 | }
12 |
13 |
14 | export const uniq = (d) => {
15 | return d.reduce((accum, cur) => {
16 | if (accum.indexOf(cur) === -1) accum.push(cur);
17 | return accum;
18 | }, [] );
19 | }
20 |
21 | export const menuItemSorter = ( a, b ) => {
22 | // if (a.children && a.children.length > 0) {
23 | // return -1
24 | // }
25 | // if (b.children && b.children.length > 0) {
26 | // return 1
27 | // }
28 | const aTitle = a.title || ""
29 | const bTitle = b.title || ""
30 | if ( aTitle < bTitle ){
31 | return -1;
32 | }
33 | if ( aTitle > bTitle ){
34 | return 1;
35 | }
36 | return 0;
37 | }
38 |
39 | export const alphaSorter = ( a, b ) => {
40 | const aTitle = a || ""
41 | const bTitle = b || ""
42 | if ( aTitle < bTitle ){
43 | return -1;
44 | }
45 | if ( aTitle > bTitle ){
46 | return 1;
47 | }
48 | return 0;
49 | }
50 | export const alphaSorterReverse = ( a, b ) => {
51 | const aTitle = a || ""
52 | const bTitle = b || ""
53 | if ( aTitle < bTitle ){
54 | return 1;
55 | }
56 | if ( aTitle > bTitle ){
57 | return -1;
58 | }
59 | return 0;
60 | }
61 | export const alphaTitleSorter = ( a, b ) => {
62 |
63 | const aTitle = a.title || "Untitled Note"
64 | const bTitle = b.title || "Untitled Note"
65 | if ( aTitle < bTitle ){
66 | return -1;
67 | }
68 | if ( aTitle > bTitle ){
69 | return 1;
70 | }
71 | return 0;
72 | }
73 | export const alphaTitleSorterReverse = ( a, b ) => {
74 | const aTitle = a.title || "Untitled Note"
75 | const bTitle = b.title || "Untitled Note"
76 | if ( aTitle < bTitle ){
77 | return 1;
78 | }
79 | if ( aTitle > bTitle ){
80 | return -1;
81 | }
82 | return 0;
83 | }
84 | export const updatedAtSorter = ( a, b ) => {
85 | const aTitle = a.updatedAt || Date.now()
86 | const bTitle = b.updatedAt || Date.now()
87 | if ( aTitle < bTitle ){
88 | return 1;
89 | }
90 | if ( aTitle > bTitle ){
91 | return -1;
92 | }
93 | return 0;
94 | }
95 | export const customNoteSorter = ( a, b ) => {
96 | console.log(a)
97 | const aPin = a.pinned
98 | const bPin = b.pinned
99 | if (aPin && !bPin) {
100 | return -1
101 | }
102 | if (bPin && !aPin) {
103 | return 1
104 | }
105 |
106 | const aTitle = a.order || ""
107 | const bTitle = b.order || ""
108 | if ( aTitle < bTitle ){
109 | return 1;
110 | }
111 | if ( aTitle > bTitle ){
112 | return -1;
113 | }
114 | return 0;
115 | }
116 | export const imageURL = ( note, attachment ) => {
117 |
118 | return `${config.url}/notes/${note._id}/${attachment}`;
119 | }
120 | export const hasChildren = ( children ) => {
121 |
122 | if (children != null) {
123 | return children.length > 0;
124 | } else {
125 | return false;
126 | }
127 | }
128 |
129 | // Immutable state updaters for Notes
130 | export const updateNoteAttributesInArray = (state, noteId, attributes) => {
131 | return state.map((item, id) => {
132 | if (item._id !== noteId) { return item }
133 | let extra = {}
134 | let c = attributes
135 | // if (action.attributes.skipUpdatedAt || false) {
136 | // } else {
137 | // c.updatedAt = Date.now()
138 | // }
139 | if (c.hasOwnProperty("title") || c.hasOwnProperty("content")) {
140 | console.log("Title or content updated")
141 | c.updatedAt = Date.now()
142 | console.log(c)
143 | }
144 |
145 | delete c.skipUpdatedAt
146 | return {
147 | ...item,
148 | ...c
149 | }
150 | })
151 | }
152 | export const addChildToParent = (state, noteId, parentId ) => {
153 | return state.map((note, id) => {
154 | if (note._id !== parentId) { return note }
155 | return {...note, children: [...note.children, noteId]} // add new child to parents `children` array (for easy read operation)
156 | })
157 | }
158 |
159 | export const removeNoteFromParentsChildArray = (newState, noteToBeNuked ) => {
160 | const parent = noteToBeNuked.parent
161 | if ( parent !== undefined && parent !== "root") {
162 | console.log("parent: ",parent)
163 | console.log("parent.children: ",parent.children)
164 |
165 | // find the parent note is all notes:
166 | newState = newState.map((note, id) => {
167 | if (note._id !== parent) { return note }
168 | // we found the parent note:
169 | let splicedChildren = note.children
170 | const index = splicedChildren.indexOf(noteToBeNuked._id) // Remove deleted note from the parent's child array
171 | if (index > -1) {
172 | splicedChildren.splice(index, 1)
173 | }
174 | console.log("parent: ",parent)
175 | return {...note, children: splicedChildren} // remove child entry
176 | })
177 | }
178 | return newState
179 | }
180 |
181 |
182 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off, import/no-extraneous-dependencies: off */
2 |
3 | const developmentEnvironments = ['development', 'test'];
4 |
5 | const developmentPlugins = [require('react-hot-loader/babel')];
6 |
7 | const productionPlugins = [
8 | require('babel-plugin-dev-expression'),
9 |
10 | // babel-preset-react-optimize
11 | require('@babel/plugin-transform-react-constant-elements'),
12 | require('@babel/plugin-transform-react-inline-elements'),
13 | require('babel-plugin-transform-react-remove-prop-types')
14 | ];
15 |
16 | module.exports = api => {
17 | // See docs about api at https://babeljs.io/docs/en/config-files#apicache
18 |
19 | const development = api.env(developmentEnvironments);
20 |
21 | return {
22 | presets: [
23 | // @babel/preset-env will automatically target our browserslist targets
24 | require('@babel/preset-env'),
25 | require('@babel/preset-typescript'),
26 | [require('@babel/preset-react'), { development }]
27 | ],
28 | plugins: [
29 | // Stage 0
30 | require('@babel/plugin-proposal-function-bind'),
31 |
32 | // Stage 1
33 | require('@babel/plugin-proposal-export-default-from'),
34 | require('@babel/plugin-proposal-logical-assignment-operators'),
35 | [require('@babel/plugin-proposal-optional-chaining'), { loose: false }],
36 | [
37 | require('@babel/plugin-proposal-pipeline-operator'),
38 | { proposal: 'minimal' }
39 | ],
40 | [
41 | require('@babel/plugin-proposal-nullish-coalescing-operator'),
42 | { loose: false }
43 | ],
44 | require('@babel/plugin-proposal-do-expressions'),
45 |
46 | // Stage 2
47 | [require('@babel/plugin-proposal-decorators'), { legacy: true }],
48 | require('@babel/plugin-proposal-function-sent'),
49 | require('@babel/plugin-proposal-export-namespace-from'),
50 | require('@babel/plugin-proposal-numeric-separator'),
51 | require('@babel/plugin-proposal-throw-expressions'),
52 |
53 | // Stage 3
54 | require('@babel/plugin-syntax-dynamic-import'),
55 | require('@babel/plugin-syntax-import-meta'),
56 | [require('@babel/plugin-proposal-class-properties'), { loose: true }],
57 | require('@babel/plugin-proposal-json-strings'),
58 |
59 | ...(development ? developmentPlugins : productionPlugins)
60 | ]
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | npm install -g n
2 | n stable
3 | yarn add node-sass
4 | yarn
5 | yarn package-win
6 |
--------------------------------------------------------------------------------
/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/configs/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { dependencies as externals } from '../app/package.json';
8 |
9 | export default {
10 | externals: [...Object.keys(externals || {})],
11 |
12 | module: {
13 | rules: [
14 | {
15 | test: /\.tsx?$/,
16 | exclude: /node_modules/,
17 | use: {
18 | loader: 'babel-loader',
19 | options: {
20 | cacheDirectory: true
21 | }
22 | }
23 | }
24 | ]
25 | },
26 |
27 | output: {
28 | path: path.join(__dirname, '..', 'app'),
29 | // https://github.com/webpack/webpack/issues/1114
30 | libraryTarget: 'commonjs2'
31 | },
32 |
33 | /**
34 | * Determine the array of extensions that should be used to resolve modules.
35 | */
36 | resolve: {
37 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
38 | modules: [path.join(__dirname, '..', 'app'), 'node_modules'],
39 | alias: {
40 | ConfigReducer: "./config/configs.electron.ts",
41 | configz: "./config.electron.ts",
42 | }
43 | },
44 |
45 | plugins: [
46 | new webpack.EnvironmentPlugin({
47 | NODE_ENV: 'production'
48 | }),
49 |
50 | new webpack.NamedModulesPlugin()
51 | ]
52 | };
53 |
--------------------------------------------------------------------------------
/configs/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 | require('@babel/register');
3 |
4 | module.exports = require('./webpack.config.renderer.dev.babel').default;
5 |
--------------------------------------------------------------------------------
/configs/webpack.config.main.prod-web.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import merge from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
12 | import DeleteSourceMaps from '../internals/scripts/DeleteSourceMaps';
13 |
14 | CheckNodeEnv('production');
15 | DeleteSourceMaps();
16 |
17 | export default merge.smart(baseConfig, {
18 | devtool: process.env.DEBUG_PROD === 'true' ? 'source-map' : 'none',
19 |
20 | mode: 'production',
21 |
22 | target: 'electron-main-web',
23 |
24 | entry: './app/main.dev.ts',
25 |
26 | output: {
27 | path: path.join(__dirname, '..'),
28 | filename: './app/main.prod.js'
29 | },
30 |
31 | optimization: {
32 | minimizer: process.env.E2E_BUILD
33 | ? []
34 | : [
35 | new TerserPlugin({
36 | parallel: true,
37 | sourceMap: true,
38 | cache: true
39 | })
40 | ]
41 | },
42 |
43 | plugins: [
44 | new BundleAnalyzerPlugin({
45 | analyzerMode:
46 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
47 | openAnalyzer: process.env.OPEN_ANALYZER === 'true'
48 | }),
49 |
50 | /**
51 | * Create global constants which can be configured at compile time.
52 | *
53 | * Useful for allowing different behaviour between development builds and
54 | * release builds
55 | *
56 | * NODE_ENV should be production so that modules do not perform certain
57 | * development checks
58 | */
59 | new webpack.EnvironmentPlugin({
60 | NODE_ENV: 'production',
61 | DEBUG_PROD: false,
62 | START_MINIMIZED: false,
63 | E2E_BUILD: false
64 | })
65 | ],
66 |
67 | /**
68 | * Disables webpack processing of __dirname and __filename.
69 | * If you run the bundle in node.js it falls back to these values of node.js.
70 | * https://github.com/webpack/webpack/issues/2010
71 | */
72 | node: {
73 | __dirname: false,
74 | __filename: false
75 | }
76 | });
77 |
--------------------------------------------------------------------------------
/configs/webpack.config.main.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import merge from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
12 | import DeleteSourceMaps from '../internals/scripts/DeleteSourceMaps';
13 |
14 | CheckNodeEnv('production');
15 | DeleteSourceMaps();
16 |
17 | export default merge.smart(baseConfig, {
18 | devtool: process.env.DEBUG_PROD === 'true' ? 'source-map' : 'none',
19 |
20 | mode: 'production',
21 |
22 | target: 'electron-main',
23 |
24 | entry: './app/main.dev.ts',
25 |
26 | output: {
27 | path: path.join(__dirname, '..'),
28 | filename: './app/main.prod.js'
29 | },
30 |
31 | optimization: {
32 | minimizer: process.env.E2E_BUILD
33 | ? []
34 | : [
35 | new TerserPlugin({
36 | parallel: true,
37 | sourceMap: true,
38 | cache: true
39 | })
40 | ]
41 | },
42 |
43 | plugins: [
44 | new BundleAnalyzerPlugin({
45 | analyzerMode:
46 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
47 | openAnalyzer: process.env.OPEN_ANALYZER === 'true'
48 | }),
49 |
50 | /**
51 | * Create global constants which can be configured at compile time.
52 | *
53 | * Useful for allowing different behaviour between development builds and
54 | * release builds
55 | *
56 | * NODE_ENV should be production so that modules do not perform certain
57 | * development checks
58 | */
59 | new webpack.EnvironmentPlugin({
60 | NODE_ENV: 'production',
61 | DEBUG_PROD: false,
62 | START_MINIMIZED: false,
63 | E2E_BUILD: false
64 | })
65 | ],
66 |
67 | /**
68 | * Disables webpack processing of __dirname and __filename.
69 | * If you run the bundle in node.js it falls back to these values of node.js.
70 | * https://github.com/webpack/webpack/issues/2010
71 | */
72 | node: {
73 | __dirname: false,
74 | __filename: false
75 | }
76 | });
77 |
--------------------------------------------------------------------------------
/configs/webpack.config.renderer.dev.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import merge from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import { dependencies } from '../package.json';
10 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
11 |
12 | CheckNodeEnv('development');
13 |
14 | const dist = path.join(__dirname, '..', 'dll');
15 |
16 | export default merge.smart(baseConfig, {
17 | context: path.join(__dirname, '..'),
18 |
19 | devtool: 'eval',
20 |
21 | mode: 'development',
22 |
23 | target: 'electron-renderer',
24 |
25 | externals: ['fsevents', 'crypto-browserify'],
26 |
27 | /**
28 | * Use `module` from `webpack.config.renderer.dev.js`
29 | */
30 | module: require('./webpack.config.renderer.dev.babel').default.module,
31 |
32 | entry: {
33 | renderer: Object.keys(dependencies || {})
34 | },
35 |
36 | output: {
37 | library: 'renderer',
38 | path: dist,
39 | filename: '[name].dev.dll.js',
40 | libraryTarget: 'var'
41 | },
42 |
43 | plugins: [
44 | new webpack.DllPlugin({
45 | path: path.join(dist, '[name].json'),
46 | name: '[name]'
47 | }),
48 |
49 | /**
50 | * Create global constants which can be configured at compile time.
51 | *
52 | * Useful for allowing different behaviour between development builds and
53 | * release builds
54 | *
55 | * NODE_ENV should be production so that modules do not perform certain
56 | * development checks
57 | */
58 | new webpack.EnvironmentPlugin({
59 | NODE_ENV: 'development'
60 | }),
61 |
62 | new webpack.LoaderOptionsPlugin({
63 | debug: true,
64 | options: {
65 | context: path.join(__dirname, '..', 'app'),
66 | output: {
67 | path: path.join(__dirname, '..', 'dll')
68 | }
69 | }
70 | })
71 | ]
72 | });
73 |
--------------------------------------------------------------------------------
/docker-build.sh:
--------------------------------------------------------------------------------
1 | docker build -t danobot/notorious:latest -f Dockerfile.prod .
2 |
--------------------------------------------------------------------------------
/docker-compose.sample.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | fauxton:
4 | image: 3apaxicom/fauxton
5 | ports:
6 | - 6945:8000
7 | depends_on:
8 | - couchdb
9 | command:
10 | - sh
11 | - "-c"
12 | - "fauxton -c http://couchdb:5984"
13 | couchdb:
14 | image: couchdb
15 | container_name: couchdb
16 | env_file: .env
17 | ports:
18 | - '5984:5984'
19 | volumes:
20 | - db_data:/opt/couchdb/data
21 | networks:
22 | - "public"
23 | labels:
24 | - "traefik.enable=true"
25 | - "traefik.docker.network=public"
26 | - "traefik.http.routers.notorious-backend.rule=Host(`${COUCH_SUBDOMAIN}.${DOMAIN_NAME}`)"
27 | - "traefik.http.routers.notorious-backend.entrypoints=https"
28 | - "traefik.http.routers.notorious-backend.tls.certresolver=myhttpchallenge"
29 | - "traefik.http.services.notorious-backend.loadbalancer.server.port=5984"
30 | notorious_web:
31 | container_name: notorious_web
32 | image: danobot/notorious:latest
33 | ports:
34 | - '9645:80'
35 | networks:
36 | - public
37 | env_file: .env
38 | volumes:
39 | - .env:/usr/share/nginx/html/.env
40 | labels:
41 | - "traefik.enable=true"
42 | - "traefik.docker.network=public"
43 | - "traefik.http.routers.notorious.rule=Host(`${NOTORIOUS_SUBDOMAIN}.${DOMAIN_NAME}`)"
44 | - "traefik.http.routers.notorious.entrypoints=https"
45 | - "traefik.http.routers.notorious.tls.certresolver=myhttpchallenge"
46 |
47 | volumes:
48 | db_data:
49 | driver: local
50 | networks:
51 | public:
52 | external: true
53 |
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Bold-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Bold-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-BoldItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-BoldItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Italic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Italic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Light-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Light-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-LightItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-LightItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Regular-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/docs/fonts/OpenSans-Regular-webfont.woff
--------------------------------------------------------------------------------
/docs/scripts/linenumber.js:
--------------------------------------------------------------------------------
1 | /*global document */
2 | (() => {
3 | const source = document.getElementsByClassName('prettyprint source linenums');
4 | let i = 0;
5 | let lineNumber = 0;
6 | let lineId;
7 | let lines;
8 | let totalLines;
9 | let anchorHash;
10 |
11 | if (source && source[0]) {
12 | anchorHash = document.location.hash.substring(1);
13 | lines = source[0].getElementsByTagName('li');
14 | totalLines = lines.length;
15 |
16 | for (; i < totalLines; i++) {
17 | lineNumber++;
18 | lineId = `line${lineNumber}`;
19 | lines[i].id = lineId;
20 | if (lineId === anchorHash) {
21 | lines[i].className += ' selected';
22 | }
23 | }
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/docs/scripts/prettify/lang-css.js:
--------------------------------------------------------------------------------
1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
3 |
--------------------------------------------------------------------------------
/docs/styles/prettify-jsdoc.css:
--------------------------------------------------------------------------------
1 | /* JSDoc prettify.js theme */
2 |
3 | /* plain text */
4 | .pln {
5 | color: #000000;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | /* string content */
11 | .str {
12 | color: #006400;
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | /* a keyword */
18 | .kwd {
19 | color: #000000;
20 | font-weight: bold;
21 | font-style: normal;
22 | }
23 |
24 | /* a comment */
25 | .com {
26 | font-weight: normal;
27 | font-style: italic;
28 | }
29 |
30 | /* a type name */
31 | .typ {
32 | color: #000000;
33 | font-weight: normal;
34 | font-style: normal;
35 | }
36 |
37 | /* a literal value */
38 | .lit {
39 | color: #006400;
40 | font-weight: normal;
41 | font-style: normal;
42 | }
43 |
44 | /* punctuation */
45 | .pun {
46 | color: #000000;
47 | font-weight: bold;
48 | font-style: normal;
49 | }
50 |
51 | /* lisp open bracket */
52 | .opn {
53 | color: #000000;
54 | font-weight: bold;
55 | font-style: normal;
56 | }
57 |
58 | /* lisp close bracket */
59 | .clo {
60 | color: #000000;
61 | font-weight: bold;
62 | font-style: normal;
63 | }
64 |
65 | /* a markup tag name */
66 | .tag {
67 | color: #006400;
68 | font-weight: normal;
69 | font-style: normal;
70 | }
71 |
72 | /* a markup attribute name */
73 | .atn {
74 | color: #006400;
75 | font-weight: normal;
76 | font-style: normal;
77 | }
78 |
79 | /* a markup attribute value */
80 | .atv {
81 | color: #006400;
82 | font-weight: normal;
83 | font-style: normal;
84 | }
85 |
86 | /* a declaration */
87 | .dec {
88 | color: #000000;
89 | font-weight: bold;
90 | font-style: normal;
91 | }
92 |
93 | /* a variable name */
94 | .var {
95 | color: #000000;
96 | font-weight: normal;
97 | font-style: normal;
98 | }
99 |
100 | /* a function name */
101 | .fun {
102 | color: #000000;
103 | font-weight: bold;
104 | font-style: normal;
105 | }
106 |
107 | /* Specify class=linenums on a pre to get line numbering */
108 | ol.linenums {
109 | margin-top: 0;
110 | margin-bottom: 0;
111 | }
112 |
--------------------------------------------------------------------------------
/docs/styles/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/env/development.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | db: 'http://admin:admin@localhost:5984/'
3 | };
4 |
--------------------------------------------------------------------------------
/env/production.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | db: "http://admin:admin@localhost:5984"
3 | };
4 |
--------------------------------------------------------------------------------
/images/context_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/images/context_menu.png
--------------------------------------------------------------------------------
/images/global_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/images/global_search.png
--------------------------------------------------------------------------------
/images/md_boilerplate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/images/md_boilerplate.png
--------------------------------------------------------------------------------
/images/notebook_context_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/images/notebook_context_menu.png
--------------------------------------------------------------------------------
/images/tag_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/images/tag_editor.png
--------------------------------------------------------------------------------
/internals/img/erb-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/erb-banner.png
--------------------------------------------------------------------------------
/internals/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/erb-logo.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/eslint-padded-90.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/eslint-padded.png
--------------------------------------------------------------------------------
/internals/img/eslint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/eslint.png
--------------------------------------------------------------------------------
/internals/img/jest-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/jest-padded-90.png
--------------------------------------------------------------------------------
/internals/img/jest-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/jest-padded.png
--------------------------------------------------------------------------------
/internals/img/jest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/jest.png
--------------------------------------------------------------------------------
/internals/img/js-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/js-padded.png
--------------------------------------------------------------------------------
/internals/img/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/js.png
--------------------------------------------------------------------------------
/internals/img/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/npm.png
--------------------------------------------------------------------------------
/internals/img/react-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react-router-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react-router-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react-router.png
--------------------------------------------------------------------------------
/internals/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/react.png
--------------------------------------------------------------------------------
/internals/img/redux-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/redux-padded-90.png
--------------------------------------------------------------------------------
/internals/img/redux-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/redux-padded.png
--------------------------------------------------------------------------------
/internals/img/redux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/redux.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/webpack-padded-90.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/webpack-padded.png
--------------------------------------------------------------------------------
/internals/img/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/webpack.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/yarn-padded-90.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/yarn-padded.png
--------------------------------------------------------------------------------
/internals/img/yarn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/internals/img/yarn.png
--------------------------------------------------------------------------------
/internals/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/internals/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/internals/scripts/BabelRegister.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | require('@babel/register')({
4 | extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
5 | cwd: path.join(__dirname, '..', '..')
6 | });
7 |
--------------------------------------------------------------------------------
/internals/scripts/CheckBuildsExist.js:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 |
6 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');
7 | const rendererPath = path.join(
8 | __dirname,
9 | '..',
10 | '..',
11 | 'app',
12 | 'dist',
13 | 'renderer.prod.js'
14 | );
15 |
16 | if (!fs.existsSync(mainPath)) {
17 | throw new Error(
18 | chalk.whiteBright.bgRed.bold(
19 | 'The main process is not built yet. Build it by running "yarn build-main"'
20 | )
21 | );
22 | }
23 |
24 | if (!fs.existsSync(rendererPath)) {
25 | throw new Error(
26 | chalk.whiteBright.bgRed.bold(
27 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"'
28 | )
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNativeDep.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import chalk from 'chalk';
3 | import { execSync } from 'child_process';
4 | import { dependencies } from '../../package.json';
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 | const nativeDeps = fs
9 | .readdirSync('node_modules')
10 | .filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`));
11 | try {
12 | // Find the reason for why the dependency is installed. If it is installed
13 | // because of a devDependency then that is okay. Warn when it is installed
14 | // because of a dependency
15 | const { dependencies: dependenciesObject } = JSON.parse(
16 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
17 | );
18 | const rootDependencies = Object.keys(dependenciesObject);
19 | const filteredRootDependencies = rootDependencies.filter(rootDependency =>
20 | dependenciesKeys.includes(rootDependency)
21 | );
22 | if (filteredRootDependencies.length > 0) {
23 | const plural = filteredRootDependencies.length > 1;
24 | console.log(`
25 | ${chalk.whiteBright.bgYellow.bold(
26 | 'Webpack does not work with native dependencies.'
27 | )}
28 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
29 | plural ? 'are native dependencies' : 'is a native dependency'
30 | } and should be installed inside of the "./app" folder.
31 | First uninstall the packages from "./package.json":
32 | ${chalk.whiteBright.bgGreen.bold('yarn remove your-package')}
33 | ${chalk.bold(
34 | 'Then, instead of installing the package to the root "./package.json":'
35 | )}
36 | ${chalk.whiteBright.bgRed.bold('yarn add your-package')}
37 | ${chalk.bold('Install the package to "./app/package.json"')}
38 | ${chalk.whiteBright.bgGreen.bold('cd ./app && yarn add your-package')}
39 | Read more about native dependencies at:
40 | ${chalk.bold(
41 | 'https://github.com/electron-react-boilerplate/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure'
42 | )}
43 | `);
44 | process.exit(1);
45 | }
46 | } catch (e) {
47 | console.log('Native dependencies could not be checked');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNodeEnv.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export default function CheckNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
12 | )
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/internals/scripts/CheckPortInUse.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | const port = process.env.PORT || '1212';
5 |
6 | detectPort(port, (err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev`
11 | )
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/internals/scripts/CheckYarn.js:
--------------------------------------------------------------------------------
1 | if (!/yarn\.js$/.test(process.env.npm_execpath || '')) {
2 | console.warn(
3 | "\u001b[33mYou don't seem to be using yarn. This could produce unexpected results.\u001b[39m"
4 | );
5 | }
6 |
--------------------------------------------------------------------------------
/internals/scripts/DeleteSourceMaps.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import rimraf from 'rimraf';
3 |
4 | export default function deleteSourceMaps() {
5 | rimraf.sync(path.join(__dirname, '../../app/dist/*.js.map'));
6 | rimraf.sync(path.join(__dirname, '../../app/*.js.map'));
7 | }
8 |
--------------------------------------------------------------------------------
/internals/scripts/ElectronRebuild.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { execSync } from 'child_process';
3 | import fs from 'fs';
4 | import { dependencies } from '../../app/package.json';
5 |
6 | const nodeModulesPath = path.join(__dirname, '..', '..', 'app', 'node_modules');
7 |
8 | if (
9 | Object.keys(dependencies || {}).length > 0 &&
10 | fs.existsSync(nodeModulesPath)
11 | ) {
12 | const electronRebuildCmd =
13 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .';
14 | const cmd =
15 | process.platform === 'win32'
16 | ? electronRebuildCmd.replace(/\//g, '\\')
17 | : electronRebuildCmd;
18 | execSync(cmd, {
19 | cwd: path.join(__dirname, '..', '..', 'app'),
20 | stdio: 'inherit'
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/nightly.sh:
--------------------------------------------------------------------------------
1 | pushd /mnt/user/repos/noteapp
2 | git pull
3 | bash dist.sh
4 | docker-compose up -d --build --force-recreate fastnote_web
5 | popd
6 |
7 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["bliss"]
3 | }
4 |
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icon.icns
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icon.png
--------------------------------------------------------------------------------
/resources/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/1024x1024.png
--------------------------------------------------------------------------------
/resources/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/128x128.png
--------------------------------------------------------------------------------
/resources/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/16x16.png
--------------------------------------------------------------------------------
/resources/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/256x256.png
--------------------------------------------------------------------------------
/resources/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/32x32.png
--------------------------------------------------------------------------------
/resources/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/48x48.png
--------------------------------------------------------------------------------
/resources/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/512x512.png
--------------------------------------------------------------------------------
/resources/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danobot/notorious/1ab44ef95220bd97af29a18765797d3c00304db7/resources/icons/64x64.png
--------------------------------------------------------------------------------
/serve.js:
--------------------------------------------------------------------------------
1 | var finalhandler = require('finalhandler')
2 | var http = require('http')
3 | var serveStatic = require('serve-static')
4 |
5 | // Serve up public/ftp folder
6 | var serve = serveStatic('web/dist', {'index': ['index.html', 'index.htm']})
7 |
8 | // Create server
9 | var server = http.createServer(function onRequest (req, res) {
10 | serve(req, res, finalhandler(req, res))
11 | })
12 |
13 | // Listen
14 | server.listen(2525)
15 | console.log("Serving @ http://localhost:2525")
16 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "plugin:testcafe/recommended",
3 | "env": {
4 | "jest/globals": true
5 | },
6 | "plugins": ["jest", "testcafe"],
7 | "rules": {
8 | "jest/no-disabled-tests": "warn",
9 | "jest/no-focused-tests": "error",
10 | "jest/no-identical-title": "error",
11 | "no-console": "off"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/actions/__snapshots__/counter.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`actions should decrement should create decrement action 1`] = `
4 | Object {
5 | "type": "DECREMENT_COUNTER",
6 | }
7 | `;
8 |
9 | exports[`actions should increment should create increment action 1`] = `
10 | Object {
11 | "type": "INCREMENT_COUNTER",
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/test/actions/counter.spec.ts:
--------------------------------------------------------------------------------
1 | import { spy } from 'sinon';
2 | import * as actions from '../../app/actions/counter';
3 |
4 | describe('actions', () => {
5 | it('should increment should create increment action', () => {
6 | expect(actions.increment()).toMatchSnapshot();
7 | });
8 |
9 | it('should decrement should create decrement action', () => {
10 | expect(actions.decrement()).toMatchSnapshot();
11 | });
12 |
13 | it('should incrementIfOdd should create increment action', () => {
14 | const fn = actions.incrementIfOdd();
15 | expect(fn).toBeInstanceOf(Function);
16 | const dispatch = spy();
17 | const getState = () => ({ counter: 1 });
18 | fn(dispatch, getState);
19 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true);
20 | });
21 |
22 | it('should incrementIfOdd shouldnt create increment action if counter is even', () => {
23 | const fn = actions.incrementIfOdd();
24 | const dispatch = spy();
25 | const getState = () => ({ counter: 2 });
26 | fn(dispatch, getState);
27 | expect(dispatch.called).toBe(false);
28 | });
29 |
30 | // There's no nice way to test this at the moment...
31 | it('should incrementAsync', () => {
32 | return new Promise(resolve => {
33 | const fn = actions.incrementAsync(1);
34 | expect(fn).toBeInstanceOf(Function);
35 | const dispatch = spy();
36 | fn(dispatch);
37 | setTimeout(() => {
38 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(
39 | true
40 | );
41 | resolve();
42 | }, 5);
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/test/components/Counter.spec.tsx:
--------------------------------------------------------------------------------
1 | /* eslint react/jsx-props-no-spreading: off */
2 | import { spy } from 'sinon';
3 | import React from 'react';
4 | import Enzyme, { shallow } from 'enzyme';
5 | import Adapter from 'enzyme-adapter-react-16';
6 | import { BrowserRouter as Router } from 'react-router-dom';
7 | import renderer from 'react-test-renderer';
8 | import Counter from '../../app/components/Counter';
9 |
10 | Enzyme.configure({ adapter: new Adapter() });
11 |
12 | function setup() {
13 | const actions = {
14 | increment: spy(),
15 | incrementIfOdd: spy(),
16 | incrementAsync: spy(),
17 | decrement: spy()
18 | };
19 | const component = shallow();
20 | return {
21 | component,
22 | actions,
23 | buttons: component.find('button'),
24 | p: component.find('.counter')
25 | };
26 | }
27 |
28 | describe('Counter component', () => {
29 | it('should should display count', () => {
30 | const { p } = setup();
31 | expect(p.text()).toMatch(/^1$/);
32 | });
33 |
34 | it('should first button should call increment', () => {
35 | const { buttons, actions } = setup();
36 | buttons.at(0).simulate('click');
37 | expect(actions.increment.called).toBe(true);
38 | });
39 |
40 | it('should match exact snapshot', () => {
41 | const { actions } = setup();
42 | const counter = (
43 |
44 |
45 |
46 |
47 |
48 | );
49 | const tree = renderer.create(counter).toJSON();
50 |
51 | expect(tree).toMatchSnapshot();
52 | });
53 |
54 | it('should second button should call decrement', () => {
55 | const { buttons, actions } = setup();
56 | buttons.at(1).simulate('click');
57 | expect(actions.decrement.called).toBe(true);
58 | });
59 |
60 | it('should third button should call incrementIfOdd', () => {
61 | const { buttons, actions } = setup();
62 | buttons.at(2).simulate('click');
63 | expect(actions.incrementIfOdd.called).toBe(true);
64 | });
65 |
66 | it('should fourth button should call incrementAsync', () => {
67 | const { buttons, actions } = setup();
68 | buttons.at(3).simulate('click');
69 | expect(actions.incrementAsync.called).toBe(true);
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/test/components/__snapshots__/Counter.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Counter component should match exact snapshot 1`] = `
4 |
5 |
6 |
19 |
23 | 1
24 |
25 |
28 |
38 |
48 |
56 |
64 |
65 |
66 |
67 | `;
68 |
--------------------------------------------------------------------------------
/test/containers/CounterPage.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme, { mount } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Provider } from 'react-redux';
5 | import { createBrowserHistory } from 'history';
6 | import { ConnectedRouter } from 'connected-react-router';
7 | import CounterPage from '../../app/containers/CounterPage';
8 | import { configureStore } from '../../app/store/configureStore';
9 |
10 | Enzyme.configure({ adapter: new Adapter() });
11 |
12 | function setup(initialState = {}) {
13 | const store = configureStore(initialState);
14 | const history = createBrowserHistory();
15 | const provider = (
16 |
17 |
18 |
19 |
20 |
21 | );
22 | const app = mount(provider);
23 | return {
24 | app,
25 | buttons: app.find('button'),
26 | p: app.find('.counter')
27 | };
28 | }
29 |
30 | describe('containers', () => {
31 | describe('App', () => {
32 | it('should display initial count', () => {
33 | const { p } = setup();
34 | expect(p.text()).toMatch(/^0$/);
35 | });
36 |
37 | it('should display updated count after increment button click', () => {
38 | const { buttons, p } = setup();
39 | buttons.at(0).simulate('click');
40 | expect(p.text()).toMatch(/^1$/);
41 | });
42 |
43 | it('should display updated count after decrement button click', () => {
44 | const { buttons, p } = setup();
45 | buttons.at(1).simulate('click');
46 | expect(p.text()).toMatch(/^-1$/);
47 | });
48 |
49 | it('shouldnt change if even and if odd button clicked', () => {
50 | const { buttons, p } = setup();
51 | buttons.at(2).simulate('click');
52 | expect(p.text()).toMatch(/^0$/);
53 | });
54 |
55 | it('should change if odd and if odd button clicked', () => {
56 | const { buttons, p } = setup({ counter: 1 });
57 | buttons.at(2).simulate('click');
58 | expect(p.text()).toMatch(/^2$/);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/e2e/HomePage.e2e.ts:
--------------------------------------------------------------------------------
1 | /* eslint jest/expect-expect: off, jest/no-test-callback: off */
2 | import { ClientFunction, Selector } from 'testcafe';
3 | import { getPageUrl } from './helpers';
4 |
5 | const getPageTitle = ClientFunction(() => document.title);
6 | const counterSelector = Selector('[data-tid="counter"]');
7 | const buttonsSelector = Selector('[data-tclass="btn"]');
8 | const clickToCounterLink = t =>
9 | t.click(Selector('a').withExactText('to Counter'));
10 | const incrementButton = buttonsSelector.nth(0);
11 | const decrementButton = buttonsSelector.nth(1);
12 | const oddButton = buttonsSelector.nth(2);
13 | const asyncButton = buttonsSelector.nth(3);
14 | const getCounterText = () => counterSelector().innerText;
15 | const assertNoConsoleErrors = async t => {
16 | const { error } = await t.getBrowserConsoleMessages();
17 | await t.expect(error).eql([]);
18 | };
19 |
20 | fixture`Home Page`.page('../../app/app.html').afterEach(assertNoConsoleErrors);
21 |
22 | test('e2e', async t => {
23 | await t.expect(getPageTitle()).eql('Hello Electron React!');
24 | });
25 |
26 | test('should open window and contain expected page title', async t => {
27 | await t.expect(getPageTitle()).eql('Hello Electron React!');
28 | });
29 |
30 | test(
31 | 'should not have any logs in console of main window',
32 | assertNoConsoleErrors
33 | );
34 |
35 | test('should navigate to Counter with click on the "to Counter" link', async t => {
36 | await t
37 | .click('[data-tid=container] > a')
38 | .expect(getCounterText())
39 | .eql('0');
40 | });
41 |
42 | test('should navigate to /counter', async t => {
43 | await t
44 | .click('a')
45 | .expect(getPageUrl())
46 | .contains('/counter');
47 | });
48 |
49 | fixture`Counter Tests`
50 | .page('../../app/app.html')
51 | .beforeEach(clickToCounterLink)
52 | .afterEach(assertNoConsoleErrors);
53 |
54 | test('should display updated count after the increment button click', async t => {
55 | await t
56 | .click(incrementButton)
57 | .expect(getCounterText())
58 | .eql('1');
59 | });
60 |
61 | test('should display updated count after the descrement button click', async t => {
62 | await t
63 | .click(decrementButton)
64 | .expect(getCounterText())
65 | .eql('-1');
66 | });
67 |
68 | test('should not change even counter if odd button clicked', async t => {
69 | await t
70 | .click(oddButton)
71 | .expect(getCounterText())
72 | .eql('0');
73 | });
74 |
75 | test('should change odd counter if odd button clicked', async t => {
76 | await t
77 | .click(incrementButton)
78 | .click(oddButton)
79 | .expect(getCounterText())
80 | .eql('2');
81 | });
82 |
83 | test('should change if async button clicked and a second later', async t => {
84 | await t
85 | .click(asyncButton)
86 | .expect(getCounterText())
87 | .eql('0')
88 | .expect(getCounterText())
89 | .eql('1');
90 | });
91 |
92 | test('should back to home if back button clicked', async t => {
93 | await t
94 | .click('[data-tid="backButton"] > a')
95 | .expect(Selector('[data-tid="container"]').visible)
96 | .ok();
97 | });
98 |
--------------------------------------------------------------------------------
/test/e2e/helpers.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: off */
2 | import { ClientFunction } from 'testcafe';
3 |
4 | export const getPageUrl = ClientFunction(() => window.location.href);
5 |
--------------------------------------------------------------------------------
/test/example.js:
--------------------------------------------------------------------------------
1 | describe('description', () => {
2 | it('should have description', () => {
3 | expect(1 + 2).toBe(3);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/test/reducers/__snapshots__/counter.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`;
4 |
5 | exports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`;
6 |
7 | exports[`reducers counter should handle initial state 1`] = `0`;
8 |
9 | exports[`reducers counter should handle unknown action type 1`] = `1`;
10 |
--------------------------------------------------------------------------------
/test/reducers/counter.spec.ts:
--------------------------------------------------------------------------------
1 | import counter from '../../app/reducers/counter';
2 | import {
3 | INCREMENT_COUNTER,
4 | DECREMENT_COUNTER
5 | } from '../../app/actions/counter';
6 |
7 | describe('reducers', () => {
8 | describe('counter', () => {
9 | it('should handle initial state', () => {
10 | expect(counter(undefined, {})).toMatchSnapshot();
11 | });
12 |
13 | it('should handle INCREMENT_COUNTER', () => {
14 | expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot();
15 | });
16 |
17 | it('should handle DECREMENT_COUNTER', () => {
18 | expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot();
19 | });
20 |
21 | it('should handle unknown action type', () => {
22 | expect(counter(1, { type: 'unknown' })).toMatchSnapshot();
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "CommonJS",
5 | "lib": ["dom", "esnext"],
6 | "declaration": true,
7 | "declarationMap": true,
8 | "noEmit": true,
9 | "jsx": "react",
10 | "strict": true,
11 | "pretty": true,
12 | "sourceMap": true,
13 | /* Additional Checks */
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": true,
18 | /* Module Resolution Options */
19 | "moduleResolution": "node",
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "resolveJsonModule": true,
23 | "allowJs": true
24 | },
25 | "exclude": [
26 | "test",
27 | "release",
28 | "app/main.prod.js",
29 | "app/main.prod.js.map",
30 | "app/renderer.prod.js",
31 | "app/renderer.prod.js.map",
32 | "app/style.css",
33 | "app/style.css.map",
34 | "app/dist",
35 | "dll",
36 | "app/main.js",
37 | "app/main.js.map"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/web/env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Recreate config file
4 | rm -rf ./env-config.js
5 | touch ./env-config.js
6 |
7 | # Add assignment
8 | echo "window._env_ = {" >> ./env-config.js
9 |
10 | # Read each line in .env file
11 | # Each line represents key=value pairs
12 | while read -r line || [[ -n "$line" ]];
13 | do
14 | # Split env variables by character `=`
15 | if printf '%s\n' "$line" | grep -q -e '='; then
16 | varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
17 | varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
18 | fi
19 |
20 | # Read value of current variable if exists as Environment variable
21 | value=$(printf '%s\n' "${!varname}")
22 | # Otherwise use value from .env file
23 | [[ -z $value ]] && value=${varvalue}
24 |
25 | # Append configuration property to JS file
26 | echo " $varname: \"$value\"," >> ./env-config.js
27 | done < .env
28 |
29 | echo "}" >> ./env-config.js
30 |
--------------------------------------------------------------------------------
/web/gzip.conf:
--------------------------------------------------------------------------------
1 | gzip on;
2 | gzip_http_version 1.0;
3 | gzip_comp_level 5; # 1-9
4 | gzip_min_length 256;
5 | gzip_proxied any;
6 | gzip_vary on;
7 |
8 | # MIME-types
9 | gzip_types
10 | application/atom+xml
11 | application/javascript
12 | application/json
13 | application/rss+xml
14 | application/vnd.ms-fontobject
15 | application/x-font-ttf
16 | application/x-web-app-manifest+json
17 | application/xhtml+xml
18 | application/xml
19 | font/opentype
20 | image/svg+xml
21 | image/x-icon
22 | text/css
23 | text/plain
24 | text/x-component;
25 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Notorious
6 |
7 |
8 |
9 |
10 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/web/index.web.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Notorious
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/web/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 |
3 | listen 80;
4 |
5 | location / {
6 | root /usr/share/nginx/html;
7 | index index.html index.htm;
8 | try_files $uri $uri/ /index.html;
9 | }
10 |
11 | error_page 500 502 503 504 /50x.html;
12 |
13 | location = /50x.html {
14 | root /usr/share/nginx/html;
15 | }
16 |
17 | }
--------------------------------------------------------------------------------