├── .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 |
33 | {/* */} 34 | onUpdate(event)} 39 | placeholder={placeholder} 40 | /> 41 | {/* */} 42 |
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=>
  1. noteActions.selectNoteAction(n._id)}>{n.title || "Untitled Note"}
  2. )} 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 `![](xxx)` 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 |
78 | 84 | 85 | 91 | 97 |
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 | {item.children.length}} > 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 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | searchNotes(e.target.value)} delay={2} className="ant-input-sm" /> 73 | 74 | 75 | 76 | createNote(selection, {})} disabled={addButtonDisabled}> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {/* */} 86 | { sorter === SORT_ALPHA && sortNotes(SORT_ALPHA_REVERSE)}> Title} 87 | { sorter === SORT_ALPHA_REVERSE && sortNotes(SORT_CREATED_AT)}> Title} 88 | { sorter === SORT_CREATED_AT && sortNotes(SORT_UPDATED_AT)}> Created } 89 | { sorter === SORT_UPDATED_AT && sortNotes(SORT_CUSTOM)}> Modified} 90 | { sorter === SORT_CUSTOM && sortNotes(SORT_ALPHA)}> Custom} 91 | {/* */} 92 | 93 |

{headerLabel}

94 | 95 |
96 | 97 | 98 |
99 | 100 | 101 | 102 | 103 | {/*
{JSON.stringify(visibleNotes, null, 2)}
*/} 104 | 105 | 106 | {visibleNotes.length > 0 ? ( 107 | visibleNotes.map(i => ( 108 | { 114 | moveNote(id, dropNoteCache) 115 | } 116 | } 117 | handlers={notecardContextHandlers} 118 | /> 119 | )) 120 | ) : ( 121 | 129 | 130 | 131 | )} 132 | 133 |
134 |
135 | 136 |
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 |
51 | 52 | e.currentTarget.select()} 57 | value={values.value} 58 | onChange={e => {handleChange(e); search(e.target.value)}} 59 | placeholder={placeholder} 60 | 61 | inputRef={reference} 62 | /> 63 | 64 |
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 |
50 | 51 | 59 | 60 |
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 | {note.title}} 53 | icon={} 54 | right={{children.length}} 55 | selected={selectedNotebook === _id} 56 | onClickHandler={e=>selectNotebook(note._id)} 57 | cacheParentId={e=>cacheParentId(_id)} 58 | /> 59 | 60 | } 61 | {subNotes && subNotes.length > 0 && <> 62 | {singleClickHandler() }} 67 | cacheParentId={e=>cacheParentId(_id)} 68 | indent={this.props.level*16} 69 | label={ 70 | 71 | {note.title } 72 | 73 | } 74 | icon={icon} 75 | selected={selectedNotebook === _id} 76 | 77 | // right={ 78 | // 79 | // } 80 | compKey={"parentnotebook"+_id} 81 | /> 82 | 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