├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .npmrc ├── .yarnrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── backend ├── jupyterlab_commenting_service_server │ ├── __init__.py │ ├── main.py │ └── service.py └── setup.py ├── binder ├── postBuild └── requirements.txt ├── docs ├── development.md ├── img │ ├── usage-1.gif │ ├── usage-10.gif │ ├── usage-11.gif │ ├── usage-12.gif │ ├── usage-2.gif │ ├── usage-3.gif │ ├── usage-4.gif │ ├── usage-5.gif │ ├── usage-6.gif │ ├── usage-7.gif │ ├── usage-8.gif │ └── usage-9.gif └── usage.md ├── etc ├── jest │ ├── jest-environment.js │ ├── jest-puppeteer.config.js │ └── jest.config.js ├── lint-staged │ └── lint-staged.config.js ├── prettier │ ├── .prettierignore │ └── .prettierrc ├── stylelint │ └── .stylelintrc.json └── tslint │ └── tslint.json ├── notebooks └── demo.ipynb ├── package.json ├── press_release.md ├── src ├── comments │ ├── commenting.tsx │ ├── components │ │ ├── App.tsx │ │ ├── AppBody.tsx │ │ ├── AppHeader.tsx │ │ ├── AppHeaderOptions.tsx │ │ ├── Comment.tsx │ │ ├── CommentBody.tsx │ │ ├── CommentCard.tsx │ │ ├── CommentFooter.tsx │ │ ├── CommentHeader.tsx │ │ ├── NewThreadCard.tsx │ │ └── UserSet.tsx │ ├── indicator.ts │ ├── notebook.ts │ ├── provider.ts │ ├── receiver.ts │ ├── service.ts │ ├── service_connection.ts │ ├── states.ts │ └── text.ts └── index.ts ├── style ├── images │ ├── backButton.png │ ├── chatIcon.png │ ├── chatIconBlue.png │ ├── nbCommentIcon.png │ ├── nbThreadIndicator.png │ └── newThreadIcon.png └── index.css ├── test └── ui │ └── test.ts ├── tsconfig.json └── tsconfig.test.json /.editorconfig: -------------------------------------------------------------------------------- 1 | #/ 2 | # @license BSD-3-Clause 3 | # 4 | # Copyright (c) 2019 Project Jupyter Contributors. 5 | # Distributed under the terms of the 3-Clause BSD License. 6 | #/ 7 | 8 | # EditorConfig configuration file (see ). 9 | 10 | # Indicate that this file is a root-level configuration file: 11 | root = true 12 | 13 | # Set properties for all files: 14 | [*] 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | # Set properties for JavaScript files: 21 | [*.js] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | # Set properties for TypeScript files: 26 | [*.ts] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | # Set properties for TSX files: 31 | [*.tsx] 32 | indent_style = space 33 | indent_size = 2 34 | 35 | # Set properties for Python files: 36 | [*.py] 37 | indent_style = space 38 | indent_size = 4 39 | 40 | # Set properties for Julia files: 41 | [*.jl] 42 | indent_style = tab 43 | 44 | # Set properties for R files: 45 | [*.R] 46 | indent_style = tab 47 | 48 | # Set properties for C files: 49 | [*.c] 50 | indent_style = tab 51 | 52 | # Set properties for C header files: 53 | [*.h] 54 | indent_style = tab 55 | 56 | # Set properties for C++ files: 57 | [*.cpp] 58 | indent_style = tab 59 | 60 | # Set properties for C++ header files: 61 | [*.hpp] 62 | indent_style = tab 63 | 64 | # Set properties for Fortran files: 65 | [*.f] 66 | indent_style = space 67 | indent_size = 2 68 | insert_final_newline = false 69 | 70 | # Set properties for shell files: 71 | [*.sh] 72 | indent_style = tab 73 | 74 | # Set properties for AWK files: 75 | [*.awk] 76 | indent_style = tab 77 | 78 | # Set properties for HTML files: 79 | [*.html] 80 | indent_style = tab 81 | tab_width = 2 82 | 83 | # Set properties for CSS files: 84 | [*.css] 85 | indent_style = tab 86 | 87 | # Set properties for Makefiles: 88 | [Makefile] 89 | indent_style = tab 90 | 91 | [*.mk] 92 | indent_style = tab 93 | 94 | # Set properties for Markdown files: 95 | [*.md] 96 | indent_style = space 97 | indent_size = 4 98 | trim_trailing_whitespace = false 99 | 100 | # Set properties for `package.json` files: 101 | [package.json] 102 | indent_style = space 103 | indent_size = 2 104 | 105 | # Set properties for `datapackage.json` files: 106 | [datapackage.json] 107 | indent_style = space 108 | indent_size = 2 109 | 110 | # Set properties for `lerna.json` files: 111 | [lerna.json] 112 | indent_style = space 113 | indent_size = 2 114 | 115 | # Set properties for `tslint.json` files: 116 | [tslint.json] 117 | indent_style = space 118 | indent_size = 2 119 | 120 | # Set properties for `tsconfig.json` files: 121 | [tsconfig.json] 122 | indent_style = space 123 | indent_size = 2 124 | 125 | # Set properties for LaTeX files: 126 | [*.tex] 127 | indent_style = tab 128 | 129 | # Set properties for LaTeX Bibliography files: 130 | [*.bib] 131 | indent_style = tab 132 | 133 | # Set properties for YAML files: 134 | [*.yml] 135 | indent_style = space 136 | indent_size = 2 137 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Something isn't working as I expected. 🤔 4 | --- 5 | 6 | 7 | 8 | ## Checklist 9 | 10 | > Please ensure the following tasks are completed before filing a bug report. 11 | 12 | - [ ] Read and understood the [Code of Conduct][code-of-conduct]. 13 | - [ ] Searched for existing issues and pull requests. 14 | 15 | ## Description 16 | 17 | > Description of the issue. 18 | 19 | Encountered an error when . 20 | 21 | ## Related Issues 22 | 23 | > Does this issue have any related issues? 24 | 25 | Related issues # , # , and # . 26 | 27 | ## Questions 28 | 29 | > Any questions for reviewers? 30 | 31 | No. 32 | 33 | ## Other 34 | 35 | > Any other information relevant to this issue? This may include screenshots, references, stack traces, sample output, and/or implementation notes. 36 | 37 | #### Demo 38 | 39 | > If relevant, provide a link to a live demo. 40 | 41 | For a live demo of the issue, see 42 | 43 | - (link) 44 | 45 | #### Reproduction 46 | 47 | > What steps are required to reproduce the unexpected output? 48 | 49 | In order to reproduce this bug, do the following: 50 | 51 | - Go to '...' 52 | - Click on '...' 53 | - Scroll down to '...' 54 | 55 | #### Expected Results 56 | 57 | > What are the expected results? 58 | 59 | The following results are expected: 60 | 61 | ```text 62 | (insert expected results here) 63 | ``` 64 | 65 | #### Actual Results 66 | 67 | > What are the actual results? 68 | 69 | The following are the actual results: 70 | 71 | ```text 72 | (insert actual results here) 73 | ``` 74 | 75 | #### Environments 76 | 77 | > What environments are affected (e.g., if unique to a particular browser, `Chrome`, `IE 11`)? Include the Python package version, extension version, and any other potentially relevant platform information. 78 | 79 | The following environments are affected: 80 | 81 | - Device: 82 | - OS: 83 | - Browser: 84 | - Version: 85 | 86 | 87 | 88 | [code-of-conduct]: https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md 89 | 90 | 91 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: I have a suggestion (and may want to help contribute 🙂)! 4 | --- 5 | 6 | 7 | 8 | ## Checklist 9 | 10 | > Please ensure the following tasks are completed before submitting a feature request. 11 | 12 | - [ ] Read and understood the [Code of Conduct][code-of-conduct]. 13 | - [ ] Searched for existing issues and pull requests. 14 | 15 | ## Description 16 | 17 | > Description of the feature request. 18 | 19 | This feature request proposes . 20 | 21 | ## Related Issues 22 | 23 | > Does this feature request have any related issues? 24 | 25 | Related issues # , # , and # . 26 | 27 | ## Questions 28 | 29 | > Any questions for reviewers? 30 | 31 | No. 32 | 33 | ## Other 34 | 35 | > Any other information relevant to this feature request? This may include screenshots, references, sample output, and/or implementation notes. 36 | 37 | No. 38 | 39 | 40 | 41 | [code-of-conduct]: https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md 42 | 43 | 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Resolves # . 2 | 3 | 4 | 5 | ## Checklist 6 | 7 | > Please ensure the following tasks are completed before submitting this pull request. 8 | 9 | - [ ] Read and understand the [Code of Conduct][code-of-conduct]. 10 | - [ ] Read and understood the [licensing terms][license]. 11 | - [ ] Searched for existing issues and pull requests **before** submitting this pull request. 12 | - [ ] Filed an issue (or an issue already existed) **prior to** submitting this pull request. 13 | - [ ] Submitted against `master` branch. 14 | 15 | ## Description 16 | 17 | > What is the purpose of this pull request? 18 | 19 | This pull request: 20 | 21 | - a 22 | - b 23 | - c 24 | 25 | ## Related Issues 26 | 27 | > Does this pull request have any related issues? 28 | 29 | This pull request: 30 | 31 | - resolves # 32 | - fixes # 33 | 34 | ## Questions 35 | 36 | > Any questions for reviewers of this pull request? 37 | 38 | No. 39 | 40 | ## Other 41 | 42 | > Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes. 43 | 44 | No. 45 | 46 | 47 | 48 | [code-of-conduct]: https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md 49 | [license]: https://github.com/jupyterlab/jupyterlab-commenting/blob/master/LICENSE 50 | 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | lint: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v1 7 | - uses: actions/setup-python@v1 8 | with: 9 | python-version: "3.6" 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: "10.x" 13 | - run: python -m pip install --upgrade pip 14 | - run: pip install jupyterlab 15 | - run: jlpm 16 | - run: jlpm run lint:check 17 | test: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v1 21 | - uses: actions/setup-python@v1 22 | with: 23 | python-version: "3.6" 24 | - uses: actions/setup-node@v1 25 | with: 26 | node-version: "10.x" 27 | - run: python -m pip install --upgrade pip 28 | - run: pip install jupyterlab 29 | - run: jlpm run build 30 | - run: jupyter lab build --debug-log-path log.txt 31 | - if: failure() 32 | run: cat log.txt 33 | - run: jlpm run test 34 | - name: upload screenshots 35 | if: failure() 36 | uses: actions/upload-artifact@v1 37 | with: 38 | name: screenshots 39 | path: screenshots 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #/ 2 | # @license BSD-3-Clause 3 | # 4 | # Copyright (c) 2019 Project Jupyter Contributors. 5 | # Distributed under the terms of the 3-Clause BSD License. 6 | #/ 7 | 8 | *.bundle.* 9 | lib/ 10 | node_modules/ 11 | *.egg-info/ 12 | __pycache__/ 13 | .ipynb_checkpoints 14 | package-lock.json 15 | .vscode 16 | .tsbuildinfo 17 | tsconfig.tsbuildinfo 18 | comments.json 19 | comments.db 20 | *.tgz 21 | yarn.lock 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | #/ 2 | # @license BSD-3-Clause 3 | # 4 | # Copyright (c) 2019 Project Jupyter Contributors. 5 | # Distributed under the terms of the 3-Clause BSD License. 6 | #/ 7 | 8 | # Configuration for [npm][1]. 9 | # 10 | # [1]: https://docs.npmjs.com/files/npmrc 11 | 12 | # Disable the creation of a lock file: 13 | package-lock = false 14 | shrinkwrap = false 15 | 16 | # Disable automatically "saving" dependencies on install: 17 | save = false -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | #/ 2 | # @license BSD-3-Clause 3 | # 4 | # Copyright (c) 2019 Project Jupyter Contributors. 5 | # Distributed under the terms of the 3-Clause BSD License. 6 | #/ 7 | 8 | # Configuration for [yarn][1]. 9 | # 10 | # [1]: https://yarnpkg.com/lang/en/docs/yarnrc/ 11 | 12 | # Disable the creation of a lock file: 13 | --install.no-lockfile true 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Contribution Guidelines 11 | 12 | > Project contribution guidelines. 13 | 14 | ## Introduction 15 | 16 | First off, thanks for your interest! While this guide focuses on technical development, if you are looking to contribute to the project but are non-technical, you can still contribute! For example, you can contribute by filing issues, writing feature requests, updating documentation, providing build and infrastructure support, and helping market and promote the project, among other things. Every bit helps, and we are grateful for your time and effort! 17 | 18 | ## Code of Conduct 19 | 20 | **Before** contributing, read the [Code of Conduct][jupyter-code-of-conduct], which details the _bare minimum_ behavior expectations that the project requires of its contributors.## Contributions 21 | 22 | ### Issues 23 | 24 | When filing new issues and commenting on existing issues on this repository, please ensure that discussions are related to concrete technical issues. 25 | 26 | **Before** filing a potential bug report, 27 | 28 | - Search for existing issues and pull requests. 29 | - Try some debugging techniques to help isolate the problem, including logging inputs and outputs. 30 | 31 | If the source of the problem is a third party package, file a bug report with the relevant package author, rather than on this repository. 32 | 33 | When filing an issue, provide the following, where possible: 34 | 35 | - A description of the issue. 36 | - Links to any related issues. 37 | - The full error message, including the stacktrace. 38 | - The sequence of steps required to reproduce the issue. 39 | - A minimal working example; i.e., the smallest chunk of code that triggers the error. If the code is larger than `50` lines, consider creating a [gist][github-gist]. 40 | - The expected results. 41 | - List of affected environments; e.g., browser, browser version, `npm` version, Node.js version, operating system, and the project version. 42 | 43 | When pasting code blocks or output, use triple backticks to enable proper formatting. Surround inline code with single backticks. For other Markdown formatting tips and trips, see GitHub's [Markdown guide][github-markdown-guide]. 44 | 45 | Be aware that the `@` symbol tags users on GitHub, so **always** surround package names with backticks (e.g., `@jupyterlab/metadata-extension`). 46 | 47 | ### Code 48 | 49 | > By contributing code to the project, you are agreeing to release it under the project [license][license]. 50 | 51 | **Before** contributing code, be sure to 52 | 53 | - read and understand the [licensing terms][license]. 54 | 55 | For instructions on how to setup and configure your environment, be sure to 56 | 57 | - read and follow the [development guide][development-guide]. 58 | 59 | If you want to contribute a new feature or a breaking change to this project, be sure to 60 | 61 | - file an issue detailing the proposed change. 62 | - wait for feature request approval. 63 | - adhere to the guidance set forth in the feature request comments. 64 | 65 | If you are unfamiliar with [Git][git], the version control system used by GitHub and this project, 66 | 67 | - see the [Git][git] docs. 68 | - try a tutorial, such as the [tutorial][github-git-tutorial] provided by GitHub. 69 | 70 | Next, take a look around the project, noting the style and organization of documentation, tests, examples, and source implementations. Consistency is highly **prioritized** within this project. Thus, the more you are able to match and adhere to project conventions and style, the more likely your contribution will be accepted. While we have done our best to automate linting and style guidelines, such automation is not perfect and cannot adequately capture the inevitable exceptions and nuance to many rules. In short, the more you study existing practice, the better prepared you will be to contribute to this project. 71 | 72 | #### Step 0: GitHub 73 | 74 | Create a [GitHub account][github-signup]. The project uses GitHub exclusively for hosting source code, managing issues and pull requests, triggering continuous integration, and reporting. 75 | 76 | #### Step 1: Fork 77 | 78 | [Fork][github-fork] the repository on GitHub and clone the repository to your local machine. 79 | 80 | ```bash 81 | $ git clone https://github.com//juptyerlab-commenting.git 82 | ``` 83 | 84 | where `` is your GitHub username. The repository may have a large commit history, leading to slow download times. If you are not interested in code archeology, you can reduce the download time by limiting the clone [depth][git-clone-depth]. 85 | 86 | ```bash 87 | $ git clone --depth= https://github.com//juptyerlab-commenting.git 88 | ``` 89 | 90 | where `` refers to the number of commits you want to download (as few as `1` and as many as the entire project history). 91 | 92 | If you are behind a firewall, you may need to use the `https` protocol, rather than the `git` protocol. 93 | 94 | ```bash 95 | $ git config --global url."https://".insteadOf git:// 96 | ``` 97 | 98 | Once you have finished cloning the repository into the destination directory, you should see the folder `jupyterlab-commenting`. To proceed with configuring your environment, navigate to the project folder. 99 | 100 | ```bash 101 | $ cd jupyterlab-commenting 102 | ``` 103 | 104 | And finally, add an `upstream` [remote][git-remotes] to allow syncing changes between this repository and your local version. 105 | 106 | ```bash 107 | $ git remote add upstream git://github.com/jupyterlab/jupyterlab-commenting.git 108 | ``` 109 | 110 | #### Step 2: Branch 111 | 112 | For modifications intended to be included in this repository, create a new local branch. 113 | 114 | ```bash 115 | $ git checkout -b 116 | ``` 117 | 118 | where `` is the branch name. The `master` branch for this repository is protected, and direct modifications to this branch will **not** be accepted. Instead, all contributions should be made on non-master local branches, including documentation changes and other non-code modifications. 119 | 120 | #### Step 3: Write 121 | 122 | Start making your changes and/or implementing the new feature. 123 | 124 | #### Step 4: Commit 125 | 126 | Ensure that you have configured [Git][git] to know your name and email address. 127 | 128 | ```bash 129 | $ git config --global user.name "Jane Doe" 130 | $ git config --global user.email "jane.doe@example.com" 131 | ``` 132 | 133 | Add changed files and commit. 134 | 135 | ```bash 136 | $ git add files/which/changed 137 | $ git commit 138 | ``` 139 | 140 | #### Step 5: Sync 141 | 142 | To incorporate recent changes from the `upstream` repository during development, you should [rebase][git-rebase] your local branch, reapplying your local commits on top of the current upstream `HEAD`. This procedure is in contrast to performing a standard [merge][git-merge], which may interleave development histories. The rationale is twofold: 143 | 144 | 1. interleaved histories make [squashing][git-rewriting-history] commits more difficult 145 | 2. a standard merge increases the risk of incomplete/broken commits appearing in the Git history. 146 | 147 | An ideal commit history is one in which, at no point in time, is the project in a broken state. While not always possible (mistakes happen), striving for this ideal facilitates time travel and software archeology. 148 | 149 | ```bash 150 | $ git fetch upstream 151 | $ git rebase upstream/master 152 | ``` 153 | 154 | #### Step 6: Test 155 | 156 | Tests should accompany **all** bug fixes and features. For guidance on how to write tests, consult existing tests within the project. 157 | 158 | **Before** submitting a [pull request][github-pull-request] to the `upstream` repository, ensure that all tests pass, including linting. 159 | 160 | Any [pull requests][github-pull-request] which include failing tests and/or lint errors will **not** be accepted. 161 | 162 | To run tests: 163 | 164 | ```bash 165 | $ jlpm run test 166 | ``` 167 | 168 | When debugging failing tests, to run UI tests in a non-headless browser window: 169 | 170 | ```bash 171 | $ jlpm run test:debug 172 | ``` 173 | 174 | During UI testing, when a test fails, a screenshot is taken of the current browser state and saved in a local `screenshots` folder to assist in debugging. 175 | 176 | If tests fail during continuous integration, a folder containing screenshots can be downloaded as an archive from the Actions panel on GitHub. 177 | 178 | #### Step 7: Push 179 | 180 | Push your changes to your remote GitHub repository. 181 | 182 | ```bash 183 | $ git push origin 184 | ``` 185 | 186 | where `` is the name of your branch. 187 | 188 | #### Step 8: Pull Request 189 | 190 | Once your contribution is ready to be incorporated in the `upstream` repository, open a [pull request][github-pull-request] against the `master` branch. A project contributor will review the contribution, provide feedback, and potentially request changes. 191 | 192 | > Receiving feedback is the most **important**, and often the most **valuable**, part of the submission process. Don't get disheartened! 193 | 194 | To make changes to your [pull request][github-pull-request], make changes to your branch. Each time you push changes to your forked repository, GitHub will automatically update the [pull request][github-pull-request]. 195 | 196 | ```bash 197 | $ git add files/which/changed 198 | $ git commit 199 | $ git push origin 200 | ``` 201 | 202 | Note that, once a [pull request][github-pull-request] has been made (i.e., your local repository commits have been pushed to a remote server), you should **not** perform any further [rewriting][git-rewriting-history] of Git history. If the history needs modification, a contributor will modify the history during the merge process. The rationale for **not** rewriting public history is that doing so invalidates the commit history for anyone else who has pulled your changes, thus imposing additional burdens on collaborators to ensure that their local versions match the modified history. 203 | 204 | #### Step 9: Land 205 | 206 | After any changes have been resolved, a contributor will approve a [pull request][github-pull-request] for inclusion in the project. Once merged, the [pull request][github-pull-request] will be updated with the merge commit, and the [pull request][github-pull-request] will be closed. 207 | 208 | Note that, during the merge process, multiple commits will often be [squashed][git-rewriting-history]. 209 | 210 | #### Step 10: Celebrate 211 | 212 | **Congratulations**! You are an official contributor to this project! Thank you for your hard work and patience! 213 | 214 | ## Notes 215 | 216 | ### GitHub 217 | 218 | - When linking to specific lines of code in an issue or a pull request, hit the `y` key while viewing a file on GitHub. Doing so reloads the page with a URL that includes the specific version of the file you are viewing. This ensures that, when you refer to specific lines, these same lines can be easily viewed in the future, even if the content of the file changes. 219 | - GitHub does not send notifications when you push a commit and update a [pull request][github-pull-request], so be sure to comment on the pull request thread to inform reviewers that you have made changes. 220 | 221 | ### Writing Tests 222 | 223 | > By contributing tests to the project, you are agreeing to release them under the project [license][license]. 224 | 225 | ### Writing Documentation 226 | 227 | > By contributing documentation to the project, you are agreeing to release it under the project [license][license]. 228 | 229 | Project documentation is localized within each package. Similar to code, you should modify documentation using [Git][git]. 230 | 231 | ## Developer's Certificate of Origin 1.1 232 | 233 | By making a contribution to this project, I certify that: 234 | 235 | - (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or 236 | - (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or 237 | - (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. 238 | - (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. 239 | 240 | ## Conclusion 241 | 242 | Phew. While the above may be a lot to remember, even for what seem like minor changes, eventually it becomes routine and part of the normal development flow. Part of the motivation for enforcing process is to ensure that all code contributions meet a certain quality threshold, thus helping reviewers focus less on non-substantive issues like style and failing tests and more on substantive issues such as contribution content and merit. Know that your patience, hard work, time, and effort are greatly appreciated! 243 | 244 | 245 | 246 | [jupyter-of-conduct]: https://jupyter.org/conduct/ 247 | [license]: https://github.com/jupyter/jupyterlab-commenting/blob/master/LICENSE 248 | [development-guide]: https://github.com/jupyter/jupyterlab-commenting/blob/master/docs/development.md 249 | [github-signup]: https://github.com/signup/free 250 | [github-pull-request]: https://help.github.com/articles/creating-a-pull-request/ 251 | [github-gist]: https://gist.github.com/ 252 | [github-markdown-guide]: https://guides.github.com/features/mastering-markdown/ 253 | [github-fork]: https://help.github.com/articles/fork-a-repo/ 254 | [github-git-tutorial]: http://try.github.io/levels/1/challenges/1 255 | [git]: http://git-scm.com/ 256 | [git-clone-depth]: https://git-scm.com/docs/git-clone#git-clone---depthltdepthgt 257 | [git-remotes]: https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes 258 | [git-rebase]: https://git-scm.com/docs/git-rebase 259 | [git-merge]: https://git-scm.com/docs/git-merge 260 | [git-rewriting-history]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History 261 | 262 | 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019 Project Jupyter Contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JupyterLab Commenting and Annotation 2 | 3 | ![Stability Experimental][badge-stability] 4 | 5 | To experiment with the extension in a live notebook environment, 6 | 7 | - latest release (stable version): [![Binder (stable)][badge-binder]][binder-stable] 8 | - latest master (bleeding edge): [![Binder (latest)][badge-binder]][binder-master] 9 | 10 | This [JupyterLab][jupyterlab] extension 11 | 12 | - allows commenting on [JupyterLab][jupyterlab] notebook cells and within text documents. 13 | - allows for comment resolution and editing. 14 | - supports filtering and sorting comments. 15 | - exposes a comment viewer in a dedicated comment window. 16 | - Check out the project vision in the ["Press Release from the Future"](./press_release.md)! 17 | 18 | ![Annotating notebook cells](https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/master/docs/img/usage-11.gif) 19 | 20 | ## Prerequisites 21 | 22 | - [JupyterLab][jupyterlab] (version >= 2.0.0) 23 | 24 | ## Installation 25 | 26 | First, install the commenting service [JupyterLab][jupyterlab] server extension, 27 | 28 | ```bash 29 | $ pip install jupyterlab-commenting-service 30 | ``` 31 | 32 | Then install the frontend [JupyterLab][jupyterlab] extension, 33 | 34 | ```bash 35 | $ jupyter labextension install @jupyterlab/commenting-extension 36 | ``` 37 | 38 | ## Usage 39 | 40 | See the [Usage Guide](./docs/usage.md) to learn more about what features this extension offers. 41 | 42 | ## Contributing 43 | 44 | This repository is in active development, and we welcome collaboration. For development guidance, please consult the [development guide](./docs/development.md). 45 | 46 | If you have ideas or questions, feel free to open an issue, or, if you feel like getting your hands dirty, feel free to consult the project [roadmap](./ROADMAP.md) or tackle an existing issue by contributing a pull request. 47 | 48 | We try to keep the current issues relevant and matched to relevant milestones. 49 | 50 | 51 | 52 | [badge-stability]: https://img.shields.io/badge/stability-experimental-red.svg 53 | [badge-binder]: https://mybinder.org/badge_logo.svg 54 | [binder-stable]: https://mybinder.org/v2/gh/jupyterlab/jupyterlab-commenting/b9bf77296ec56e247bb88795c1e7fe02e9de8b9b?urlpath=lab%2Ftree%2Fnotebooks%2Fdemo.ipynb 55 | [binder-master]: https://mybinder.org/v2/gh/jupyterlab/jupyterlab-commenting/master?urlpath=lab%2Ftree%2Fnotebooks%2Fdemo.ipynb 56 | [jupyterlab]: https://github.com/jupyterlab/jupyterlab 57 | 58 | 59 | 60 | ## Roadmap of Features (Not yet Prioritized) 61 | 62 | - [x] Opening a new thread... 63 | - [x] on files in the file broswer. 64 | - [x] on files open in the main work area. 65 | - [ ] on datasets in the data registry. 66 | - [ ] on a cell in a tabular dataset. 67 | - [ ] on a range of cells in a tabular dataset. 68 | - [ ] on character in a notebook cell input. 69 | - [ ] on a notebook cell output. 70 | - [ ] on a text selecting in a text file. 71 | - [ ] on a single line of code in a text file. 72 | - [ ] Replying to a thread. 73 | - [ ] Resolving a thread. 74 | - [ ] Deleting a thread. 75 | - [ ] Promoting a thread to the 'knowledge graph'. 76 | - [ ] Promoting an individual comment to the 'knowledge graph'. 77 | - [ ] Import an existing comment store. 78 | - [ ] Export a comment store. 79 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | > Project Roadmap. 4 | 5 | - [ ] Opening a new thread... 6 | - [ ] on files in the file broswer. 7 | - [ ] on files open in the main work area. 8 | - [ ] on datasets in the data registry. 9 | - [ ] on a cell in a tabular dataset. 10 | - [ ] on a range of cells in a tabular dataset. 11 | - [ ] on character in a notebook cell input. 12 | - [ ] on a notebook cell output. 13 | - [ ] on a text selecting in a text file. 14 | - [ ] on a single line of code in a text file. 15 | - [ ] Replying to a thread. 16 | - [ ] Resolving a thread. 17 | - [ ] Deleting a thread. 18 | - [ ] Promoting a thread to the 'knowledge graph'. 19 | - [ ] Promoting an individual comment to the 'knowledge graph'. 20 | - [ ] Import an existing comment store. 21 | - [ ] Export a comment store. 22 | - [ ] Filter/Sort 23 | - [ ] Only show comments with targets on screen 24 | - [ ] Filter by target type 25 | - [ ] Show all comments 26 | - [ ] Sort by 27 | - [ ] Recently edited 28 | - [ ] Thread opened date 29 | - [ ] Unresolved 30 | - [ ] Unread 31 | -------------------------------------------------------------------------------- /backend/jupyterlab_commenting_service_server/__init__.py: -------------------------------------------------------------------------------- 1 | # @license BSD-3-Clause 2 | # 3 | # Copyright (c) 2019 Project Jupyter Contributors. 4 | # Distributed under the terms of the 3-Clause BSD License. -------------------------------------------------------------------------------- /backend/jupyterlab_commenting_service_server/main.py: -------------------------------------------------------------------------------- 1 | # @license BSD-3-Clause 2 | # 3 | # Copyright (c) 2019 Project Jupyter Contributors. 4 | # Distributed under the terms of the 3-Clause BSD License. 5 | 6 | import os 7 | import json 8 | import sqlite_utils 9 | from fastapi import FastAPI 10 | 11 | app = FastAPI(openapi_prefix="/commenting-service") 12 | path = os.path.dirname(os.path.abspath(__file__)) 13 | database = os.path.join(path, 'comments.db') 14 | db = sqlite_utils.Database(database) 15 | 16 | 17 | @app.get("/") 18 | async def root(): 19 | return {"message": "Fastapi"} 20 | 21 | 22 | @app.get("/getAllComments/") 23 | async def getAllComments(): 24 | tables = db.table_names() 25 | all_comments = {} 26 | 27 | for target in tables: 28 | all_comments[target] = db[target].rows 29 | 30 | return {"comments": all_comments} 31 | 32 | 33 | @app.get("/file/") 34 | async def getByTarget(target: str): 35 | tables = db.table_names() 36 | 37 | if (target in tables): 38 | print('Found existing: ' + target) 39 | return {"data": db[target].rows} 40 | else: 41 | print('Creating new table: ' + target) 42 | 43 | db[target].create({ 44 | "id": int, 45 | "total": int, 46 | "resolved": bool, 47 | "indicator": str, 48 | "body": str 49 | }, pk=("id")) 50 | 51 | return {"table": target} 52 | 53 | 54 | @app.get("/saveComments/") 55 | async def saveComments(comments: str, target: str): 56 | table = db[target] 57 | 58 | threads = json.loads(comments) 59 | 60 | for thread in threads["comments"]: 61 | id = thread["id"] 62 | total = thread["total"] 63 | resolved = thread["resolved"] 64 | 65 | try: 66 | indicator = thread["indicator"] 67 | except KeyError: 68 | indicator = '' 69 | 70 | # body = json.loads(thread["body"]) 71 | body = thread["body"] 72 | 73 | # body_trimmed = json.dumps(body, separators=(',', ':')) 74 | 75 | table.upsert({ 76 | "id": id, 77 | "total": total, 78 | "resolved": resolved, 79 | "indicator": indicator, 80 | "body": body 81 | }, pk="id") 82 | 83 | return {"Comments to save": "no"} 84 | -------------------------------------------------------------------------------- /backend/jupyterlab_commenting_service_server/service.py: -------------------------------------------------------------------------------- 1 | # @license BSD-3-Clause 2 | # 3 | # Copyright (c) 2019 Project Jupyter Contributors. 4 | # Distributed under the terms of the 3-Clause BSD License. 5 | 6 | """Jupyterlab Commenting Service Server""" 7 | import os 8 | 9 | 10 | def start(): 11 | """ 12 | Start Jupyterlab Commenting Service Server Start 13 | 14 | Returns: 15 | dict -- A dictionary with the command to start the Commenting Service Server 16 | """ 17 | path = os.path.dirname(os.path.abspath(__file__)) 18 | database = os.path.join(path, 'comments.db') 19 | 20 | try: 21 | open(database, "w+") 22 | except FileNotFoundError as f: 23 | print('The file %s could not be found or opened' % (f.filename)) 24 | 25 | return { 26 | 'command': [ 27 | 'datasette', 28 | 'serve', 29 | database, 30 | '-p', 31 | '{port}', 32 | '--cors' 33 | ], 34 | 'timeout': 60, 35 | 'port': 0 # 40000 36 | } 37 | 38 | 39 | def fastapi(): 40 | path = os.path.dirname(os.path.abspath(__file__)) 41 | os.chdir(path) 42 | 43 | return { 44 | 'command': [ 45 | 'uvicorn', 46 | 'main:app', 47 | '--host', 48 | '0.0.0.0', 49 | '--port', 50 | '{port}', 51 | '--proxy-headers', 52 | '--reload' 53 | ], 54 | 'timeout': 60, 55 | 'port': 0, # 30000 56 | 'absolute_url': False 57 | } 58 | -------------------------------------------------------------------------------- /backend/setup.py: -------------------------------------------------------------------------------- 1 | # @license BSD-3-Clause 2 | # 3 | # Copyright (c) 2019 Project Jupyter Contributors. 4 | # Distributed under the terms of the 3-Clause BSD License. 5 | 6 | import setuptools 7 | 8 | from glob import glob 9 | import os 10 | 11 | 12 | def get_path_files(path): 13 | all_files = [] 14 | path_files = [] 15 | for f_path in glob(os.path.join(path, '*')): 16 | if os.path.isdir(f_path): 17 | all_files += get_path_files(f_path) 18 | elif f_path[-3:] != '.py': 19 | path_files.append(f_path) 20 | 21 | all_files += [(path, path_files)] 22 | return all_files 23 | 24 | 25 | path = os.path.join('jupyterlab_commenting_service_server') 26 | extra_files = get_path_files(path) 27 | 28 | setuptools.setup( 29 | name="jupyterlab-commenting-service-server", 30 | version='0.1', 31 | license='BSD-3-Clause', 32 | author='CalPoly/Quansight', 33 | author_email='jupyterlab@localhost', 34 | url='https://github.com/jupyterlab/jupyterlab-commenting', 35 | # py_modules rather than packages, since we only have 1 file 36 | packages=['jupyterlab_commenting_service_server'], 37 | entry_points={ 38 | 'jupyter_serverproxy_servers': [ 39 | # name = packagename:function_name 40 | 'comments = jupyterlab_commenting_service_server.service:start', 41 | 'commenting-service = jupyterlab_commenting_service_server.service:fastapi' 42 | ] 43 | }, 44 | install_requires=[ 45 | 'datasette~=0.30.1', 46 | 'fastapi[all]~=0.42.0', 47 | 'jupyter-server-proxy~=1.1.0', 48 | 'sqlite_utils~=1.11', 49 | 'uvicorn~=0.8.4' 50 | ], 51 | package_data=dict(extra_files), 52 | include_package_data=True 53 | ) 54 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # @license BSD-3-Clause 4 | # 5 | # Copyright (c) 2019 Project Jupyter Contributors. 6 | # Distributed under the terms of the 3-Clause BSD License. 7 | 8 | set -o errexit 9 | set -o xtrace 10 | 11 | jlpm run build 12 | jupyter lab build 13 | -------------------------------------------------------------------------------- /binder/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyterlab>=2.0 2 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Development 11 | 12 | > Development guide. 13 | 14 | ## Introduction 15 | 16 | Before we begin, development requires some setup and configuration. What follows is an overview of environment requirements and a sequence of steps for getting up and running. We use [Git][git] for version control, and for most tasks, we use [npm][npm] scripts to help us get things done quickly and effectively. For the most part, the project is able to internally manage dependencies for testing and linting, so, once you follow the steps below, you should be ready to start developing! 17 | 18 | So, without further ado, let's get you started! 19 | 20 | ## Prerequisites 21 | 22 | Development requires the following prerequisites: 23 | 24 | - [Git][git]: version control 25 | - [Python][python]: general purpose language (version `>= 3.6`) 26 | - [pip][pip]: Python package manager (version `>= 9.0.0`) 27 | - [Node.js][node-js]: JavaScript runtime (latest stable version is **strongly** recommended) 28 | - [npm][npm]: package manager (version `> 2.7.0`) 29 | - [JupyterLab][jupyterlab]: computational environment (version `>= 1.0.0`) 30 | 31 | While not required, you are encouraged to create an [Anaconda][anaconda] environment. 32 | 33 | ```bash 34 | $ conda create -n jupyterlab-commenting -c conda-forge python=3.7 jupyterlab nodejs 35 | ``` 36 | 37 | To activate the environment, 38 | 39 | ```bash 40 | $ conda activate jupyterlab-commenting 41 | ``` 42 | 43 | > **NOTE**: for each new terminal window, you'll need to explicitly activate the [Anaconda][anaconda] environment. 44 | 45 | ## Download 46 | 47 | To acquire the source code, first navigate to the parent directory in which you want to place the project [Git][git] repository. 48 | 49 | > **NOTE**: avoid directory paths which include spaces or any other shell meta characters such as `$` or `:`, as these characters can be problematic for certain build tools. 50 | 51 | ```bash 52 | $ cd /path/to/parent/destination/directory 53 | ``` 54 | 55 | Next, clone the repository. 56 | 57 | ```bash 58 | $ git clone https://github.com/jupyterlab/jupyterlab-commenting.git 59 | ``` 60 | 61 | If you are wanting to contribute to this GitHub repository, first [fork][github-fork] the repository and amend the previous command. 62 | 63 | ```bash 64 | $ git clone https://github.com//jupyterlab-commenting.git 65 | ``` 66 | 67 | where `` is your GitHub username (assuming you are using GitHub to manage public repositories). The repository may have a large commit history, leading to slow download times. If you are not interested in code archeology, you can reduce the download time by limiting the [depth][git-clone-depth]. 68 | 69 | ```bash 70 | $ git clone --depth= https://github.com//jupyterlab-commenting.git 71 | ``` 72 | 73 | where `` refers to the number of commits you want to download (as few as 1 and as many as the entire project history). 74 | 75 | If you are behind a firewall, you may need to use the `http` protocol, rather than the [`git`][git] protocol. 76 | 77 | ```bash 78 | $ git config --global url."https://".insteadOf git:// 79 | ``` 80 | 81 | Once you have finished cloning the repository into the destination directory, you should see the folder `jupyterlab-commenting`. To proceed with configuring your environment, navigate to the project folder. 82 | 83 | ```bash 84 | $ cd jupyterlab-commenting 85 | ``` 86 | 87 | ## Installation 88 | 89 | To install development dependencies (e.g., [Node.js][node-js] module dependencies), 90 | 91 | ```bash 92 | $ jlpm install 93 | ``` 94 | 95 | where `jlpm` is the JupyterLab package manager which is bundled with [JupyterLab][jupyterlab]. 96 | 97 | ## Build 98 | 99 | To build extension packages, 100 | 101 | ```bash 102 | $ jlpm run build 103 | ``` 104 | 105 | If your environment has been configured correctly, the previous command should complete without errors. 106 | 107 | To build the [JupyterLab][jupyterlab] extensions found in this repository and to launch the [JupyterLab][jupyterlab] environment, 108 | 109 | ```bash 110 | $ jlpm run build:jupyter 111 | ``` 112 | 113 | ## Clean 114 | 115 | To clean your local environment, including [Node.js][node-js] module dependencies, 116 | 117 | ```bash 118 | $ jlpm run clean 119 | ``` 120 | 121 | To remove build artifacts, such as compiled JavaScript files, from extension packages, 122 | 123 | ```bash 124 | $ jlpm run clean:packages 125 | ``` 126 | 127 | To remove [JupyterLab][jupyterlab] extension artifacts, such as linked extensions, 128 | 129 | ```bash 130 | $ jlpm run clean:jupyter 131 | ``` 132 | 133 | ## Reset 134 | 135 | To clean and rebuild the extension(s), 136 | 137 | ```bash 138 | $ jlpm run all 139 | ``` 140 | 141 | ## Watch 142 | 143 | During development, you'll likely want extensions to automatically recompile and update. Accordingly, in a separate terminal window, 144 | 145 | ```bash 146 | $ jlpm run build:watch 147 | ``` 148 | 149 | which will automatically trigger recompilation upon updates to source files. 150 | 151 | In another terminal window, 152 | 153 | ```bash 154 | $ jlpm run build:jupyter:watch 155 | ``` 156 | 157 | which will launch the [JupyterLab][jupyterlab] environment and automatically update the running lab environment upon recompilation changes. 158 | 159 | ## Update 160 | 161 | If you have previously downloaded the repository using `git clone`, you can update an existing source tree from the base project directory using `git pull`. 162 | 163 | ```bash 164 | $ git pull 165 | ``` 166 | 167 | If you are working with a [forked][github-fork] repository and wish to [sync][github-fork-sync] your local repository with the [upstream][git-remotes] project (i.e., incorporate changes from the main project repository into your local repository), assuming you have [configured a remote][github-remote] which points to the upstream repository, 168 | 169 | ```bash 170 | $ git fetch upstream 171 | $ git merge upstream/ 172 | ``` 173 | 174 | where `upstream` is the remote name and `` refers to the branch you want to merge into your local copy. 175 | 176 | ## Organization 177 | 178 | The repository is organized as follows: 179 | 180 | ```text 181 | backend Jupyter lab server extension 182 | binder Binder configuration 183 | docs top-level documentation 184 | etc project configuration files 185 | notebooks Jupyter notebooks 186 | src frontend source code 187 | style frontend style sheets 188 | test project tests 189 | ``` 190 | 191 | ## Editors 192 | 193 | - This repository uses [EditorConfig][editorconfig] to maintain consistent coding styles between different editors and IDEs, including [browsers][editorconfig-chrome]. 194 | 195 | 196 | 197 | [git]: http://git-scm.com/ 198 | [python]: https://www.python.org/ 199 | [pip]: https://github.com/pypa/pip 200 | [node-js]: https://nodejs.org/en/ 201 | [npm]: https://www.npmjs.com/ 202 | [jupyterlab]: https://github.com/jupyterlab/jupyterlab 203 | [anaconda]: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html 204 | [github-fork]: https://help.github.com/articles/fork-a-repo/ 205 | [github-fork-sync]: https://help.github.com/articles/syncing-a-fork/ 206 | [github-remote]: https://help.github.com/articles/configuring-a-remote-for-a-fork/ 207 | [git-clone-depth]: https://git-scm.com/docs/git-clone#git-clone---depthltdepthgt 208 | [git-remotes]: https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes 209 | [editorconfig]: http://editorconfig.org/ 210 | [editorconfig-chrome]: https://chrome.google.com/webstore/detail/github-editorconfig/bppnolhdpdfmmpeefopdbpmabdpoefjh?hl=en-US 211 | 212 | 213 | -------------------------------------------------------------------------------- /docs/img/usage-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-1.gif -------------------------------------------------------------------------------- /docs/img/usage-10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-10.gif -------------------------------------------------------------------------------- /docs/img/usage-11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-11.gif -------------------------------------------------------------------------------- /docs/img/usage-12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-12.gif -------------------------------------------------------------------------------- /docs/img/usage-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-2.gif -------------------------------------------------------------------------------- /docs/img/usage-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-3.gif -------------------------------------------------------------------------------- /docs/img/usage-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-4.gif -------------------------------------------------------------------------------- /docs/img/usage-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-5.gif -------------------------------------------------------------------------------- /docs/img/usage-6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-6.gif -------------------------------------------------------------------------------- /docs/img/usage-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-7.gif -------------------------------------------------------------------------------- /docs/img/usage-8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-8.gif -------------------------------------------------------------------------------- /docs/img/usage-9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterlab/jupyterlab-commenting/5196c9787c21b7aebfeb5dde3aa30e27dde28675/docs/img/usage-9.gif -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Overview 4 | 5 | The commenting panel is located on the right side panel on Jupyter's main area. 6 | 7 | ![](./img/usage-1.gif) 8 | 9 | When opened for the first time it will ask for you GitHub username to know who is commenting. This uses the [public GitHub API](https://developer.github.com/v3/) to retrieve your name and profile image. 10 | 11 | Once logged in, you are able to do a variety of things. 12 | 13 | --- 14 | 15 | - **[General Usage](general-usage)** 16 | - [Creating a comment thread](#creating-a-comment-thread) 17 | - [Resolving a thread](#resolving-a-thread) 18 | - [Edit a comment](#edit-a-comment) 19 | - [Deleting comments](#deleting-comments) 20 | - [Filtering and sorting threads](#filtering-and-sorting-threads) 21 | - **[Commenting on text files](#commenting-on-text-files)** 22 | - [Entire Line](#entire-line) 23 | - [Specific selection](#specific-selection) 24 | - [Focus indicator](#focus-indicator) 25 | - **[Where do comments save?](#where-do-comments-save)** 26 | 27 | --- 28 | 29 | ## General Usage 30 | 31 | ### Creating a comment thread 32 | 33 | ![](./img/usage-2.gif) 34 | 35 | Once signed in, you can create a comment thread by clicking on `New Comment Thread`. Once the thread is created others can reply to your thread. 36 | 37 | ### Resolving a thread 38 | 39 | ![](./img/usage-3.gif) 40 | 41 | Threads can be resolved by clicking on the `Resolve` button. A thread can be re-opened by clicking on the `Re-open` button. 42 | 43 | ### Edit a comment 44 | 45 | ![](./img/usage-4.gif) 46 | 47 | Clicking on a comment thread will expand the conversation and enable the option to edit by hovering over a comment and clicking on the edit button. 48 | 49 | ### Deleting comments 50 | 51 | ![](./img/usage-5.gif) 52 | 53 | In the same way as editing, once a thread is expanded you can delete a comment with the delete button. 54 | 55 | ### Filtering and sorting threads 56 | 57 | ![](./img/usage-6.gif) 58 | 59 | By using the drop-down on the top, you can sort comment threads. The show resolved check-box can be used to filter resolved threads. 60 | 61 | --- 62 | 63 | ## Commenting on text files 64 | 65 | To comment on a text file, you can either select exactly what you want to comment on, right click, and select the option `Create New Comment`, then go about creating the thread. Or you can just right click the line you want to comment on, and the entire line will be marked when `Create New Comment` is selected. 66 | 67 | ### Entire line 68 | 69 | ![](./img/usage-7.gif) 70 | 71 | ### Specific selection 72 | 73 | ![](./img/usage-8.gif) 74 | 75 | ### Focus indicator 76 | 77 | To focus an indicator you can either click on the comment thread in the commenting panel, or you can click on the indicator in the text editor to focus the related thread. 78 | 79 | #### Indicator focus 80 | 81 | ![](./img/usage-9.gif) 82 | 83 | #### Thread focus 84 | 85 | ![](./img/usage-10.gif) 86 | 87 | --- 88 | 89 | ## Commenting on Notebooks 90 | 91 | ### Commenting on a specific cell 92 | 93 | Comments can be created on notebook cells. 94 | 95 | ![](./img/usage-11.gif) 96 | 97 | To make a comment on a rendered cell, like a Markdown cell you can click into the edit view and make a comment. 98 | 99 | ![](./img/usage-12.gif) 100 | 101 | --- 102 | 103 | ## Where do comments save? 104 | 105 | Currently, comments are saved in a generated file named `comments.db`. This file saves all your comments and indicators. The file can be shared and others can copy it to their file tree. 106 | -------------------------------------------------------------------------------- /etc/jest/jest-environment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | // Based on from https://yarnpkg.com/en/package/@rws-air/jestscreenshot 9 | 10 | const resolve = require('path').resolve; 11 | const PuppeteerEnvironment = require('jest-environment-puppeteer'); 12 | const JestScreenshot = require('@rws-air/jestscreenshot'); 13 | 14 | class CustomEnvironment extends PuppeteerEnvironment { 15 | async setup() { 16 | await super.setup(); 17 | } 18 | async teardown() { 19 | await this.global.page.waitFor(2000); 20 | await super.teardown(); 21 | } 22 | 23 | async handleTestEvent(event, state) { 24 | if (event.name === 'test_fn_failure') { 25 | const testName = state.currentlyRunningTest.name; 26 | 27 | const jestScreenshot = new JestScreenshot({ 28 | page: this.global.page, 29 | dirName: resolve(__dirname, '..', '..'), 30 | testName 31 | }); 32 | 33 | await jestScreenshot.setup(); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Exports. 40 | */ 41 | module.exports = CustomEnvironment; 42 | -------------------------------------------------------------------------------- /etc/jest/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | const config = { 9 | launch: { 10 | headless: process.env.HEADLESS !== 'false', 11 | slowMo: process.env.SLOWMO === 'true' 12 | }, 13 | // https://github.com/smooth-code/jest-puppeteer/tree/master/packages/jest-dev-server#options 14 | server: { 15 | command: "jupyter lab --port 8080 --no-browser --LabApp.token=''", 16 | port: 8080 17 | } 18 | }; 19 | 20 | /** 21 | * Exports. 22 | */ 23 | module.exports = config; 24 | -------------------------------------------------------------------------------- /etc/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | const resolve = require('path').resolve; 9 | const { defaults: tsjPreset } = require('ts-jest/presets'); 10 | 11 | // Resolve the root project directory: 12 | const ROOT = resolve(__dirname, '..', '..'); 13 | 14 | const config = { 15 | rootDir: ROOT, 16 | 17 | // Needed for jest-screenshots 18 | testRunner: 'jest-circus/runner', 19 | 20 | testEnvironment: resolve(__dirname, 'jest-environment.js'), 21 | globalSetup: 'jest-environment-puppeteer/setup', 22 | globalTeardown: 'jest-environment-puppeteer/teardown', 23 | setupFilesAfterEnv: ['expect-puppeteer'], 24 | transform: { 25 | ...tsjPreset.transform 26 | }, 27 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 28 | testMatch: ['**/test/**/test*.ts?(x)'], 29 | testPathIgnorePatterns: ['/build/', '/lib/', '/node_modules/'], 30 | globals: { 31 | 'ts-jest': { 32 | tsConfig: resolve(ROOT, 'tsconfig.test.json') 33 | } 34 | } 35 | }; 36 | 37 | /** 38 | * Exports. 39 | */ 40 | module.exports = config; 41 | -------------------------------------------------------------------------------- /etc/lint-staged/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | const resolve = require('path').resolve; 9 | 10 | const rc = resolve(__dirname, '..', 'prettier', '.prettierrc'); 11 | const ignore = resolve(__dirname, '..', 'prettier', '.prettierignore'); 12 | 13 | const config = { 14 | '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}': [ 15 | 'prettier --config=' + rc + ' --ignore-path=' + ignore + ' --write', 16 | 'git add' 17 | ] 18 | }; 19 | 20 | /** 21 | * Exports. 22 | */ 23 | module.exports = config; 24 | -------------------------------------------------------------------------------- /etc/prettier/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/build 5 | **/static 6 | tests/**/coverage 7 | **/package.json 8 | .eggs 9 | .mypy_cache 10 | .ipynb_checkpoints 11 | -------------------------------------------------------------------------------- /etc/prettier/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /etc/stylelint/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /etc/tslint/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["tslint-plugin-prettier"], 3 | "rules": { 4 | "prettier": [true, { "singleQuote": true }], 5 | "align": [true, "parameters", "statements"], 6 | "await-promise": true, 7 | "ban": [ 8 | true, 9 | ["_", "forEach"], 10 | ["_", "each"], 11 | ["$", "each"], 12 | ["angular", "forEach"] 13 | ], 14 | "class-name": true, 15 | "comment-format": [true, "check-space"], 16 | "curly": true, 17 | "eofline": true, 18 | "forin": false, 19 | "indent": [true, "spaces", 2], 20 | "interface-name": [true, "always-prefix"], 21 | "jsdoc-format": true, 22 | "label-position": true, 23 | "max-line-length": [false], 24 | "member-access": false, 25 | "member-ordering": [false], 26 | "new-parens": true, 27 | "no-angle-bracket-type-assertion": true, 28 | "no-any": false, 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-conditional-assignment": true, 32 | "no-consecutive-blank-lines": false, 33 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 34 | "no-construct": true, 35 | "no-debugger": true, 36 | "no-default-export": false, 37 | "no-duplicate-variable": true, 38 | "no-empty": true, 39 | "no-eval": true, 40 | "no-floating-promises": true, 41 | "no-inferrable-types": false, 42 | "no-internal-module": true, 43 | "no-invalid-this": [true, "check-function-in-method"], 44 | "no-null-keyword": false, 45 | "no-reference": true, 46 | "no-require-imports": false, 47 | "no-shadowed-variable": false, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-use-before-declare": false, 52 | "no-var-keyword": true, 53 | "no-var-requires": true, 54 | "object-literal-sort-keys": false, 55 | "one-line": [ 56 | true, 57 | "check-open-brace", 58 | "check-catch", 59 | "check-else", 60 | "check-finally", 61 | "check-whitespace" 62 | ], 63 | "one-variable-per-declaration": [true, "ignore-for-loop"], 64 | "quotemark": { 65 | "options": [true, "single", "avoid-escape"], 66 | "severity": "off" 67 | }, 68 | "radix": true, 69 | "semicolon": [true, "always", "ignore-bound-class-methods"], 70 | "switch-default": true, 71 | "trailing-comma": [ 72 | false, 73 | { 74 | "multiline": "never", 75 | "singleline": "never" 76 | } 77 | ], 78 | "triple-equals": [true, "allow-null-check", "allow-undefined-check"], 79 | "typedef": [false], 80 | "typedef-whitespace": [ 81 | false, 82 | { 83 | "call-signature": "nospace", 84 | "index-signature": "nospace", 85 | "parameter": "nospace", 86 | "property-declaration": "nospace", 87 | "variable-declaration": "nospace" 88 | }, 89 | { 90 | "call-signature": "space", 91 | "index-signature": "space", 92 | "parameter": "space", 93 | "property-declaration": "space", 94 | "variable-declaration": "space" 95 | } 96 | ], 97 | "use-isnan": true, 98 | "use-strict": [false], 99 | "variable-name": [ 100 | true, 101 | "check-format", 102 | "allow-leading-underscore", 103 | "ban-keywords", 104 | "allow-pascal-case" 105 | ], 106 | "whitespace": [ 107 | true, 108 | "check-branch", 109 | "check-operator", 110 | "check-separator", 111 | "check-type" 112 | ] 113 | }, 114 | "linterOptions": { 115 | "exclude": ["../../node_modules/**/*", "../../lib/**/*"] 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jupyterlab/commenting-extension", 3 | "version": "0.3.0", 4 | "description": "A JupyterLab extension to support commenting and annotation.", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/jupyterlab/jupyterlab-commenting", 11 | "bugs": { 12 | "url": "https://github.com/jupyterlab/jupyterlab-commenting/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/jupyterlab/jupyterlab-commenting.git" 17 | }, 18 | "license": "BSD-3-Clause", 19 | "author": "", 20 | "files": [ 21 | "lib/**", 22 | "style/**", 23 | "*.md", 24 | "docs/**" 25 | ], 26 | "main": "lib/index.js", 27 | "types": "lib/index.d.ts", 28 | "style": "style/index.css", 29 | "scripts": { 30 | "all": "jlpm run clean && jlpm run build && jlpm run build:jupyter", 31 | "all:watch": "jlpm run clean && jlpm run build && jlpm run build:jupyter:watch", 32 | "build": "jlpm run build:dev", 33 | "build:backend": "pip install -e ./backend", 34 | "build:dev": "jlpm install && jlpm run clean:packages && jlpm run clean:backend && jlpm run build:packages && jlpm run build:backend && jlpm run link:packages && jupyter labextension list", 35 | "build:jupyter": "jupyter lab build && jupyter lab", 36 | "build:jupyter:watch": "jupyter lab build && jupyter lab --watch", 37 | "build:packages": "tsc --build", 38 | "build:watch": "tsc --build --watch --listEmittedFiles", 39 | "clean": "jlpm run clean:jupyter && jlpm clean:packages && jlpm clean:backend && jlpm clean:node", 40 | "clean:backend": "pip uninstall --yes jupyterlab-commenting-service-server", 41 | "clean:jupyter": "jlpm run uninstall:extensions && jlpm run unlink:packages && jupyter lab clean", 42 | "clean:node": "rimraf node_modules", 43 | "clean:packages": "rimraf lib tsconfig.tsbuildinfo", 44 | "link": "jlpm run link:packages", 45 | "link:packages": "jupyter labextension link ./ --no-build", 46 | "lint": "jlpm run prettier && jlpm run lint:css && jlpm run lint:typescript", 47 | "lint:check": "jlpm run prettier:check && jlpm run lint:css:check && jlpm run lint:typescript:check", 48 | "lint:css": "stylelint **/*.css --fix --config ./etc/stylelint/.stylelintrc.json", 49 | "lint:css:check": "stylelint **/*.css --config ./etc/stylelint/.stylelintrc.json", 50 | "lint:typescript": "tslint --fix -c ./etc/tslint/tslint.json --project tsconfig.json '**/*{.ts,.tsx}'", 51 | "lint:typescript:check": "tslint -c ./etc/tslint/tslint.json --project tsconfig.json '**/*{.ts,.tsx}'", 52 | "prepublishOnly": "npm run clean && npm install && npm run build:packages", 53 | "prettier": "prettier --write '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}' --config ./etc/prettier/.prettierrc --ignore-path ./etc/prettier/.prettierignore", 54 | "prettier:check": "prettier --list-different '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}' --config ./etc/prettier/.prettierrc --ignore-path ./etc/prettier/.prettierignore", 55 | "rebuild:backend": "jlpm run clean:backend && jlpm run build:backend", 56 | "rebuild:packages": "jlpm run clean:packages && jlpm run build:packages", 57 | "test": "env JEST_PUPPETEER_CONFIG=./etc/jest/jest-puppeteer.config.js jest --runInBand --config ./etc/jest/jest.config.js", 58 | "test:debug": "env HEADLESS=false SLOWMO=true jlpm test", 59 | "uninstall:extensions": "jupyter labextension uninstall --all --no-build", 60 | "unlink": "jlpm run unlink:packages", 61 | "unlink:packages": "jupyter labextension unlink ./ --no-build || echo 'Unlink command failed, but continuing...'" 62 | }, 63 | "husky": { 64 | "hooks": { 65 | "pre-commit": "lint-staged --config ./etc/lint-staged/lint-staged.config.js" 66 | } 67 | }, 68 | "dependencies": { 69 | "@jupyterlab/application": "^2.0.0", 70 | "@jupyterlab/apputils": "^2.0.0", 71 | "@jupyterlab/codemirror": "^2.0.0", 72 | "@jupyterlab/cells": "^2.0.0", 73 | "@jupyterlab/docmanager": "^2.0.0", 74 | "@jupyterlab/docregistry": "^2.0.0", 75 | "@jupyterlab/filebrowser": "^2.0.0", 76 | "@jupyterlab/fileeditor": "^2.0.0", 77 | "@jupyterlab/notebook": "^2.0.0", 78 | "@lumino/disposable": "^1.2.0", 79 | "@lumino/messaging": "^1.2.3", 80 | "@lumino/signaling": "^1.2.3", 81 | "@lumino/widgets": "^1.8.0", 82 | "codemirror": "~5.47.0", 83 | "react": "~16.9.0" 84 | }, 85 | "devDependencies": { 86 | "@rws-air/jestscreenshot": "^3.0.3", 87 | "@types/expect-puppeteer": "^3.3.2", 88 | "@types/jest": "^24.0.19", 89 | "@types/jest-environment-puppeteer": "^4.3.1", 90 | "@types/puppeteer": "^1.20.2", 91 | "husky": "^3.0.9", 92 | "jest": "^24.9.0", 93 | "jest-circus": "^24.9.0", 94 | "jest-puppeteer": "^4.3.0", 95 | "lint-staged": "^9.4.2", 96 | "prettier": "^1.18.2", 97 | "puppeteer": "^2.0.0", 98 | "rimraf": "~2.6.2", 99 | "stylelint": "^11.0.0", 100 | "stylelint-config-prettier": "^6.0.0", 101 | "stylelint-config-standard": "^19.0.0", 102 | "ts-jest": "^24.1.0", 103 | "tslint": "^5.20.0", 104 | "tslint-config-prettier": "^1.18.0", 105 | "tslint-plugin-prettier": "^2.0.1", 106 | "typescript": "~3.5.1" 107 | }, 108 | "publishConfig": { 109 | "access": "public" 110 | }, 111 | "jupyterlab": { 112 | "extension": true 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /press_release.md: -------------------------------------------------------------------------------- 1 | [back to project](./README.md) 2 | 3 | # Press Release from the Future 4 | 5 | ## JupyterLab Commenting 6 | 7 | ### JupyterLab Commenting helps you coordinate with your team on code and data. 8 | 9 | JupyterLab, the next generation of Jupyter notebook software, is one step closer to becoming a _fully collaborative_ tool for data science, machine learning, and computational research. The new Commenting extension can now be used by teams to ask questions, delegate tasks, address issues, request edits, make decisions, and ultimately _to resolve_ problems as a team. Comments the team find particularly _useful_ can be memorialized as **_annotations_**. 10 | 11 | In the past, JupyterLab users would use third-party apps to coordinate their efforts around shared code and data. _JupyterLab Commenting_ streamlines contextual coordination by providing a rich commenting interface _within_ JupyterLab where everything in JupyterLab can be the ‘target’ of a comment-thread. While similar to the commenting experiences in Google Docs, Dropbox Paper, and other multi-user environments, JupyterLab Commenting is the first to bring a commenting system at full-scale to a data analytics platform where users can comment and resolve _anything_. 12 | 13 | **Really, I can comment on anything?** 14 | 15 | Yes! Not only can comment threads be created on a file, users are also able to comment on _any_ and _every_ entity in JupyterLab. From Notebooks, data grids, text files, and graphs, all the way down to notebook outputs, individual lines or characters in a text file, the contents of a CSV cell, row, or column, etc. This flexibility frees the user to focus on achieving their _goal_ instead of focusing on how to coerce their _tool_. 16 | 17 | All interactions are designed to be quick and concise. Only comments with visible on-screen targets show by default. A right-side panel is used to create and manage, and review comments inside JupyterLab on a per-file basis. While the commenting panel is opened, all targets that have comments will be indicated and interactive. The indicators, when clicked, will focus their associated comment thread. When the panel is closed, comment indicators will be hidden to declutter the workspace. This interface can also be used to memorialize a comment into the associated Knowledge Graph (JupyterLab Metadata!). With the seamless integration and clutterless design, this extension allows for collaboration and coordination within JupyterLab that was not previously possible! 18 | 19 | When JupyterLab’s real-time collaborative editing feature is completed, this commenting system will be incorporated into it. In the meantime it will work independently (working example [here](https://mybinder.org/v2/gh/Quansight/rich-context-demo-1.git/commenting-test)). If users are connected to the same server, a new thread or comment created by one user will immediately appear to all other users connected to the same server, and comment target indicators will update as well. Thus, comments are _created_ and _resolved_ with a real-time feel for users. 20 | 21 | This extension unlocks the exciting new ability to comment on code and data within JupyterLab. Teams can now more easily coordinate their efforts to resolve issues together, without leaving the JupyterLab application. Speed, efficiently, quality of work, and user satisfaction are positively impacted. Go download the JupyterLab Commenting extension today! 22 | -------------------------------------------------------------------------------- /src/comments/commenting.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | import '../../style/index.css'; 11 | 12 | import { ReactWidget } from '@jupyterlab/apputils'; 13 | 14 | import { UseSignal } from '@jupyterlab/apputils'; 15 | 16 | import { Signal, ISignal } from '@lumino/signaling'; 17 | 18 | import { IPerson, ICommentThread } from './service'; 19 | import { CommentingDataProvider } from './provider'; 20 | import { CommentingDataReceiver } from './receiver'; 21 | import { indicatorHandler } from '..'; 22 | 23 | // Components 24 | import { App } from './components/App'; 25 | import { AppBody } from './components/AppBody'; 26 | import { CommentCard } from './components/CommentCard'; 27 | import { AppHeader } from './components/AppHeader'; 28 | import { AppHeaderOptions } from './components/AppHeaderOptions'; 29 | import { NewThreadCard } from './components/NewThreadCard'; 30 | import { UserSet } from './components/UserSet'; 31 | 32 | /** 33 | * CommentingUI React Widget 34 | */ 35 | export class CommentingWidget extends ReactWidget { 36 | constructor( 37 | provider: CommentingDataProvider, 38 | receiver: CommentingDataReceiver 39 | ) { 40 | super(); 41 | 42 | this._provider = provider; 43 | this._receiver = receiver; 44 | 45 | this.getAllCommentCards = this.getAllCommentCards.bind(this); 46 | this.setExpandedCard = this.setExpandedCard.bind(this); 47 | this.getExpandedCard = this.getExpandedCard.bind(this); 48 | this.setSortState = this.setSortState.bind(this); 49 | this.setShowResolved = this.setShowResolved.bind(this); 50 | this.setNewThreadActive = this.setNewThreadActive.bind(this); 51 | this.setReplyActiveCard = this.setReplyActiveCard.bind(this); 52 | this.getReplyActiveCard = this.getReplyActiveCard.bind(this); 53 | this.getNewThreadButton = this.getNewThreadButton.bind(this); 54 | this.update = this.update.bind(this); 55 | this.render = this.render.bind(this); 56 | this.checkIsEditing = this.checkIsEditing.bind(this); 57 | this.setIsEditing = this.setIsEditing.bind(this); 58 | } 59 | 60 | /** 61 | * Called before the widget is shown 62 | */ 63 | protected onBeforeShow(): void { 64 | // Sets the interval of when to periodically query for comments and indicators 65 | this._periodicUpdate = setInterval(this._receiver.getAllComments, 1000); 66 | this._showSignal.emit(true); 67 | } 68 | 69 | /** 70 | * Called before the widget is hidden 71 | */ 72 | protected onBeforeHide(): void { 73 | // Stops the periodic query of comments 74 | clearInterval(this._periodicUpdate); 75 | this._showSignal.emit(false); 76 | } 77 | 78 | /** 79 | * React Render function 80 | */ 81 | protected render(): React.ReactElement | React.ReactElement[] { 82 | return ( 83 | 84 | {(sender, args) => { 85 | try { 86 | let target = this._provider.getState('target') as string; 87 | return this.getApp(target.split('/').pop()); 88 | } catch { 89 | return this.getApp(undefined); 90 | } 91 | }} 92 | 93 | ); 94 | } 95 | 96 | /** 97 | * Returns the Commenting UI. 98 | * 99 | * @param target Type: string | undefined - target / file path. 100 | * undefined used for no target. Anything else is a target. 101 | */ 102 | getApp(target: string | undefined): React.ReactNode { 103 | return ( 104 | 105 | {this._provider.getState('userSet') ? ( 106 |
107 | 126 | } 127 | /> 128 | 140 | ] 141 | : this.getAllCommentCards() 142 | } 143 | expanded={this._provider.getState('expandedCard') !== ' '} 144 | newThreadButton={ 145 | this._provider.getState('newThreadActive') || 146 | target === undefined 147 | ? undefined 148 | : this.getNewThreadButton() 149 | } 150 | /> 151 |
152 | ) : ( 153 |
154 | 155 |
156 | )} 157 |
158 | ); 159 | } 160 | 161 | /** 162 | * Checks if a card should be rendered in based on the states of 163 | * the current view 164 | * 165 | * @param resolved Type: boolean - resolved state of the card 166 | * @param expandedCard Type: boolean - State if there is a card expanded 167 | * @param curCardExpanded Type: boolean - State if the current card is expanded 168 | */ 169 | shouldRenderCard( 170 | resolved: boolean, 171 | expandedCard: boolean, 172 | curCardExpanded: boolean 173 | ): boolean { 174 | if (!this._provider.getState('showResolved') as boolean) { 175 | if (!resolved) { 176 | if (expandedCard) { 177 | return curCardExpanded; 178 | } 179 | return true; 180 | } else { 181 | return false; 182 | } 183 | } else { 184 | if (expandedCard) { 185 | return curCardExpanded; 186 | } 187 | return true; 188 | } 189 | } 190 | 191 | /** 192 | * Creates and returns all CommentCard components with correct data 193 | * 194 | * @param allData Type: any - Comment data from this.props.data 195 | * @return Type: React.ReactNode[] - List of CommentCard Components / ReactNodes 196 | */ 197 | getAllCommentCards(): React.ReactNode[] { 198 | const threads = this._provider.getState('response') as Array< 199 | ICommentThread 200 | >; 201 | try { 202 | let cards: React.ReactNode[] = []; 203 | for (let index in threads) { 204 | let curThread = threads[index]; 205 | 206 | if ( 207 | this.shouldRenderCard( 208 | threads[index].resolved, 209 | (this._provider.getState('expandedCard') as string) !== ' ', 210 | (this._provider.getState('expandedCard') as string) === curThread.id 211 | ) 212 | ) { 213 | cards.push( 214 | 231 | ); 232 | } 233 | } 234 | return cards.reverse(); 235 | } catch (e) { 236 | return []; 237 | } 238 | } 239 | 240 | /** 241 | * JSX of new thread button 242 | * 243 | * @return Type: React.ReactNode - New thread button 244 | */ 245 | getNewThreadButton(): React.ReactNode { 246 | return ( 247 |
this.setNewThreadActive(true)} 250 | > 251 | 252 | Start New Thread 253 |
254 | ); 255 | } 256 | 257 | /** 258 | * Used to check if the cardId passed in is the current expanded card 259 | * 260 | * @param threadId Type: string - CommentCard unique id 261 | * @return Type: boolean - True if cardId is expanded, false if cardId is not expanded 262 | */ 263 | getExpandedCard(threadId: string): boolean { 264 | return threadId === this._provider.getState('expandedCard'); 265 | } 266 | 267 | /** 268 | * Used to check if the cardId passed in has reply box active 269 | * 270 | * @param threadId Type: string - CommentCard unique id 271 | * @return type: boolean - True if cardId has reply box open, false if not active 272 | */ 273 | getReplyActiveCard(threadId: string): boolean { 274 | return threadId === this._provider.getState('replyActiveCard'); 275 | } 276 | 277 | /** 278 | * Sets this.state.expandedCard to the passed in cardId 279 | * 280 | * @param threadId Type: string - CommentCard unique id 281 | */ 282 | setExpandedCard(threadId: string) { 283 | this._receiver.setState({ expandedCard: threadId }); 284 | 285 | if (threadId === ' ') { 286 | this.setIsEditing(''); 287 | this._backPressed.emit(void 0); 288 | } 289 | 290 | indicatorHandler.activeIndicatorWidget.scrollIntoView(threadId); 291 | } 292 | 293 | /** 294 | * Sets this.state.replyActiveCard to the passed in cardId 295 | * 296 | * @param threadId Type: string - CommentCard unique id 297 | */ 298 | setReplyActiveCard(threadId: string) { 299 | this._receiver.setState({ replyActiveCard: threadId }); 300 | } 301 | 302 | /** 303 | * Sets this.state fields for active new thread card 304 | * 305 | * @param state Type: boolean - State to set if new thread card is active 306 | * @param target Type: string - target of the file to add new thread to 307 | */ 308 | setNewThreadActive(value: boolean) { 309 | this._receiver.setState({ 310 | newThreadActive: value, 311 | newThreadFile: this._provider.getState('target') 312 | }); 313 | } 314 | 315 | /** 316 | * Sets this.state.sortState to the selected sort by 317 | * 318 | * @param state Type: string - Sort by type 319 | */ 320 | setSortState(value: string) { 321 | this._receiver.setState({ sortState: value }); 322 | } 323 | 324 | /** 325 | * Sets this.state.showResolved to the state of the checkbox 326 | * "Show resolved" 327 | */ 328 | setShowResolved(value: boolean) { 329 | this._receiver.setState({ showResolved: value }); 330 | } 331 | 332 | /** 333 | * Used to check if a key is being edited 334 | * 335 | * @param key Type: string - key of what is being edited 336 | */ 337 | checkIsEditing(key: string): boolean { 338 | return this._provider.getState('isEditing') === key; 339 | } 340 | 341 | /** 342 | * Used to set what is being edited 343 | * 344 | * @param key Type: string - key of what is being edited 345 | */ 346 | setIsEditing(key: string): void { 347 | if (key === this._provider.getState('isEditing')) { 348 | return; 349 | } 350 | 351 | this._receiver.setState({ isEditing: key }); 352 | } 353 | 354 | /** 355 | * Signal that is emitted when the commentingUI panel is opened or closed 356 | * args - boolean: true open, false closed 357 | */ 358 | get showSignal(): ISignal { 359 | return this._showSignal; 360 | } 361 | 362 | /** 363 | * Signal when new thread is created 364 | */ 365 | get newThreadCreated(): ISignal { 366 | return this._newThreadCreated; 367 | } 368 | 369 | /** 370 | * Signal when the back button is pressed 371 | */ 372 | get backPressed(): ISignal { 373 | return this._backPressed; 374 | } 375 | 376 | // CommentingDataProvider to get data from CommentingStates 377 | private _provider: CommentingDataProvider; 378 | 379 | // CommentingDataReceiver to pass data to CommentingStates 380 | private _receiver: CommentingDataReceiver; 381 | 382 | // setInterval of when to poll new data 383 | private _periodicUpdate: any; 384 | 385 | // Signal when commenting UI is shown or hidden 386 | private _showSignal = new Signal(this); 387 | 388 | // Signal emitted when back button is pressed 389 | private _backPressed = new Signal(this); 390 | 391 | // Signal when new thread is created or canceled 392 | private _newThreadCreated = new Signal(this); 393 | } 394 | -------------------------------------------------------------------------------- /src/comments/components/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | /** 11 | * Main App React Component 12 | */ 13 | export class App extends React.Component { 14 | /** 15 | * Constructor 16 | * 17 | * @param props React props 18 | */ 19 | constructor(props: any) { 20 | super(props); 21 | } 22 | 23 | /** 24 | * React render function 25 | */ 26 | render(): React.ReactNode { 27 | return this.props.children; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/comments/components/AppBody.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | /** 11 | * React Props interface 12 | */ 13 | interface IAppBodyProps { 14 | /** 15 | * Array of CommentCard React Components 16 | * 17 | * @type React.ReactNode 18 | */ 19 | cards: React.ReactNode[]; 20 | /** 21 | * Tracks if card is expanded 22 | * 23 | * @type boolean 24 | */ 25 | expanded: boolean; 26 | /** 27 | * New thread button card 28 | */ 29 | newThreadButton: React.ReactNode | undefined; 30 | } 31 | 32 | /** 33 | * App Body React Component 34 | */ 35 | export class AppBody extends React.Component { 36 | /** 37 | * Constructor 38 | * 39 | * @param props React props 40 | */ 41 | constructor(props: IAppBodyProps) { 42 | super(props); 43 | } 44 | 45 | /** 46 | * React render function 47 | */ 48 | render(): React.ReactNode { 49 | const items = this.props.cards.map((card, i) =>
{card}
); 50 | 51 | return ( 52 |
59 | {!this.props.expanded && this.props.newThreadButton} 60 | {items} 61 |
62 | ); 63 | } 64 | 65 | /** 66 | * CSS styles 67 | */ 68 | styles = { 69 | 'jp-commenting-body-area': { 70 | width: '100%', 71 | maxHeight: '94%', 72 | overflowY: 'scroll' as 'scroll', 73 | overflowX: 'hidden' as 'hidden', 74 | boxSizing: 'border-box' as 'border-box', 75 | justifyContent: 'center', 76 | padding: '8px' 77 | }, 78 | 'jp-commenting-body-area-expanded': { 79 | width: '100%', 80 | maxHeight: '96%', 81 | overflowY: 'scroll' as 'scroll', 82 | overflowX: 'hidden' as 'hidden', 83 | boxSizing: 'border-box' as 'border-box', 84 | justifyContent: 'center', 85 | padding: '8px' 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/comments/components/AppHeader.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | /** 11 | * React Props interface 12 | */ 13 | interface IAppHeaderProps { 14 | /** 15 | * Tracks if card is expanded 16 | * 17 | * @type boolean 18 | */ 19 | cardExpanded: boolean; 20 | /** 21 | * Receives the AppHeaderOption component for render purposes 22 | * 23 | * @type React.ReactNode 24 | */ 25 | headerOptions: React.ReactNode; 26 | /** 27 | * Function to set the state of the current expanded card in "App.tsx" 28 | * 29 | * @param cardId - string: Card unique id 30 | */ 31 | setExpandedCard: (cardId: string) => void; 32 | /** 33 | * Receives a value for a header 34 | * 35 | * @type string 36 | */ 37 | target: string; 38 | /** 39 | * Tracks if the new thread window is active 40 | * 41 | * @type boolean 42 | */ 43 | threadOpen: boolean; 44 | } 45 | 46 | /** 47 | * AppHeader React Component 48 | */ 49 | export class AppHeader extends React.Component { 50 | /** 51 | * Constructor 52 | * 53 | * @param props React props 54 | */ 55 | constructor(props: IAppHeaderProps) { 56 | super(props); 57 | 58 | this.getAppHeader = this.getAppHeader.bind(this); 59 | this.getBackButton = this.getBackButton.bind(this); 60 | this.setShrink = this.setShrink.bind(this); 61 | } 62 | 63 | /** 64 | * React render function 65 | */ 66 | render(): React.ReactNode { 67 | return ( 68 |
69 |
70 | {this.props.cardExpanded && this.props.target !== undefined && ( 71 |
72 | {this.getBackButton()} 73 |
74 | )} 75 | {this.getAppHeader(this.props.target)} 76 |
77 | {this.shouldRenderOptions()} 78 |
79 | ); 80 | } 81 | 82 | /** 83 | * Checks the state of New Thread 84 | * 85 | * @returns Type: React.ReactNode. If the New Thread is not open 86 | */ 87 | shouldRenderOptions(): React.ReactNode { 88 | if (!this.props.threadOpen && !this.props.cardExpanded) { 89 | return
{this.props.headerOptions}
; 90 | } else { 91 | return
; 92 | } 93 | } 94 | 95 | /** 96 | * Checks the value of the header prop and returns a React component 97 | * with a value held by the header prop, or a header placeholder 98 | * 99 | * @param header Type: string 100 | * @return Type: React.ReactNode - App Header with correct string 101 | */ 102 | getAppHeader(header: string): React.ReactNode { 103 | if (header === undefined) { 104 | return ( 105 |
106 |
107 | 110 |
111 |
112 | ); 113 | } else { 114 | return ( 115 |
116 | {this.getFileIcon(this.props.target)} 117 |
118 | 121 |
122 |
123 | ); 124 | } 125 | } 126 | 127 | /** 128 | * Strips the header of the extension ending and compares it to the list of supported extensions 129 | * 130 | * @param header Type: string 131 | * @return Type: React.ReactNode - span with a correct image class 132 | */ 133 | getFileIcon(header: string): React.ReactNode { 134 | try { 135 | let extensionName = header.slice(header.indexOf('.')); 136 | for (let key in this.fileTypes) { 137 | for (let value in this.fileTypes[key].extensions) { 138 | if (extensionName === this.fileTypes[key].extensions[value]) { 139 | return ( 140 |
141 | 145 |
146 | ); 147 | } 148 | } 149 | } 150 | return ( 151 |
152 | 156 |
157 | ); 158 | } catch { 159 | return ; 160 | } 161 | } 162 | /** 163 | * Renders a back button inside the header 164 | * 165 | * @return Type: React.ReactNode - 166 | * Input button @type Image 167 | */ 168 | getBackButton(): React.ReactNode { 169 | return ( 170 |
174 | ); 175 | } 176 | 177 | /** 178 | * Sets the state expandedCard to ' ' in App.tsx, which will shrink 179 | * or closes "New Thread" window 180 | */ 181 | setShrink(): void { 182 | this.props.setExpandedCard(' '); 183 | } 184 | 185 | /** 186 | * Stores all the available file extension types 187 | */ 188 | fileTypes = [ 189 | { 190 | extensions: ['.md'], 191 | mimeTypes: ['text/markdown'], 192 | iconClass: 'jp-Icon jp-MarkdownIcon' 193 | }, 194 | { 195 | extensions: ['.py'], 196 | mimeTypes: ['text/x-python'], 197 | iconClass: 'jp-Icon jp-PythonIcon' 198 | }, 199 | { 200 | extensions: ['.ipynb'], 201 | mimeType: ['notebook'], 202 | iconClass: 'jp-Icon jp-NotebookIcon' 203 | }, 204 | { 205 | extensions: ['.json'], 206 | mimeTypes: ['application/json'], 207 | iconClass: 'jp-Icon jp-JSONIcon' 208 | }, 209 | { 210 | extensions: ['.csv'], 211 | mimeTypes: ['text/csv'], 212 | iconClass: 'jp-Icon jp-SpreadsheetIcon' 213 | }, 214 | { 215 | extensions: ['.tsv'], 216 | mimeTypes: ['text/csv'], 217 | iconClass: 'jp-Icon jp-SpreadsheetIcon' 218 | }, 219 | { 220 | mimeTypes: ['text/x-rsrc'], 221 | extensions: ['.r'], 222 | iconClass: 'jp-Icon jp-RKernelIcon' 223 | }, 224 | { 225 | mimeTypes: ['text/x-yaml', 'text/yaml'], 226 | extensions: ['.yaml', '.yml'], 227 | iconClass: 'jp-Icon jp-YAMLIcon' 228 | }, 229 | { 230 | mimeTypes: ['image/svg+xml'], 231 | extensions: ['.svg'], 232 | iconClass: 'jp-Icon jp-ImageIcon' 233 | }, 234 | { 235 | mimeTypes: ['image/tiff'], 236 | extensions: ['.tif', '.tiff'], 237 | iconClass: 'jp-Icon jp-ImageIcon' 238 | }, 239 | { 240 | mimeTypes: ['image/jpeg'], 241 | extensions: ['.jpg', '.jpeg'], 242 | iconClass: 'jp-Icon jp-ImageIcon' 243 | }, 244 | { 245 | mimeTypes: ['image/gif'], 246 | extensions: ['.gif'], 247 | iconClass: 'jp-Icon jp-ImageIcon' 248 | }, 249 | { 250 | mimeTypes: ['image/png'], 251 | extensions: ['.png'], 252 | iconClass: 'jp-Icon jp-ImageIcon' 253 | }, 254 | { 255 | mimeTypes: ['image/bmp'], 256 | extensions: ['.bmp'], 257 | iconClass: 'jp-Icon jp-ImageIcon' 258 | } 259 | ]; 260 | 261 | /** 262 | * App header styles 263 | */ 264 | styles = { 265 | 'jp-commenting-app-header-area': { 266 | display: 'flex', 267 | flexDirection: 'column' as 'column', 268 | borderBottom: '1px solid var(--jp-border-color1)', 269 | boxSizing: 'border-box' as 'border-box' 270 | }, 271 | 'jp-commenting-header-area': { 272 | display: 'flex', 273 | flexShrink: 1, 274 | flexDirection: 'row' as 'row', 275 | padding: '8px', 276 | minWidth: '52px' 277 | }, 278 | 'jp-commenting-back-arrow-area': { 279 | display: 'flex', 280 | flexDirection: 'column' as 'column', 281 | justifyContent: 'center', 282 | width: '20px' 283 | }, 284 | 'jp-commenting-header-back-arrow': { 285 | width: '12px', 286 | height: '12px', 287 | backgroundImage: 'var(--jp-image-back-arrow)', 288 | backgroundRepeat: 'no-repeat', 289 | backgroundSize: 'contain', 290 | cursor: 'pointer' 291 | }, 292 | 'jp-commenting-header-target-area': { 293 | display: 'flex', 294 | flexDirection: 'row' as 'row', 295 | minWidth: '52px', 296 | flexShrink: 1 297 | }, 298 | 'jp-commenting-header-target-icon-area': { 299 | display: 'flex' 300 | }, 301 | 'jp-commenting-header-target-icon': { 302 | minWidth: '20px', 303 | minHeight: '20px', 304 | backgroundSize: '20px' 305 | }, 306 | 'jp-commenting-header-label-area': { 307 | paddingLeft: '4px', 308 | textAlign: 'left' as 'left', 309 | whiteSpace: 'nowrap' as 'nowrap', 310 | overflow: 'hidden', 311 | textOverflow: 'ellipsis', 312 | flexShrink: 1 313 | }, 314 | 'jp-commenting-header-label': { 315 | fontSize: 'var(--jp-ui-font-size1)', 316 | color: 'var(--jp-ui-font-color1)' 317 | } 318 | }; 319 | } 320 | -------------------------------------------------------------------------------- /src/comments/components/AppHeaderOptions.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | /** 11 | * React Props interface 12 | */ 13 | interface IAppHeaderOptionsProps { 14 | /** 15 | * Function to set the state of the current sort option in "App.tsx" 16 | * 17 | * @param state - string: card sort controller 18 | * 19 | * @type void function 20 | */ 21 | setSortState: (state: string) => void; 22 | /** 23 | * Function to set the resolved state. Controls if resolved comments are shown in "App.tsx" 24 | * 25 | * @type void function 26 | */ 27 | showResolvedState: (state: boolean) => void; 28 | /** 29 | * State of if a card is currently expanded 30 | * 31 | * @type boolean 32 | */ 33 | cardExpanded: boolean; 34 | /** 35 | * Current target name 36 | * 37 | * @type string 38 | */ 39 | target: string; 40 | /** 41 | * Tracks if the current open thread had cards 42 | * 43 | * @type boolean 44 | */ 45 | hasThreads: boolean; 46 | /** 47 | * Tracks when to show resolved threads 48 | * 49 | * @type boolean 50 | */ 51 | showResolved: boolean; 52 | /** 53 | * String that tracks what to sort by 54 | * 55 | * @type string 56 | */ 57 | sortState: string; 58 | } 59 | 60 | /** 61 | * React State Interface 62 | */ 63 | interface IAppHeaderOptionsState { 64 | /** 65 | * dropdown state, shows dropdown menu if true 66 | * 67 | * @type boolean 68 | */ 69 | isOpen: boolean; 70 | } 71 | 72 | /** 73 | * AppHeaderOptions React Component 74 | */ 75 | export class AppHeaderOptions extends React.Component< 76 | IAppHeaderOptionsProps, 77 | IAppHeaderOptionsState 78 | > { 79 | /** 80 | * Constructor 81 | * 82 | * @param props React props 83 | */ 84 | constructor(props: IAppHeaderOptionsProps) { 85 | super(props); 86 | 87 | this.state = { isOpen: false }; 88 | 89 | this.setResolvedState = this.setResolvedState.bind(this); 90 | this.matchCheckBoxState = this.matchCheckBoxState.bind(this); 91 | this.toggleOpen = this.toggleOpen.bind(this); 92 | } 93 | 94 | /** 95 | * Checks the state of the component and controlls Show Resolved 96 | */ 97 | componentDidMount(): void { 98 | if (document.getElementById('controls') !== null) { 99 | this.matchCheckBoxState(); 100 | } 101 | } 102 | 103 | /** 104 | * React render function 105 | */ 106 | render() { 107 | const menuClass = `jp-commenting-header-options-dropdown-menu${ 108 | this.state.isOpen ? ' show' : '' 109 | }`; 110 | return ( 111 |
112 |
113 | {this.getCheckbox()} 114 | {this.getDropdown()} 115 |
116 |
119 |
{this.getSortItems()}
120 |
121 |
122 | ); 123 | } 124 | 125 | /** 126 | * Renders the checkbox and label 127 | * 128 | * @return React.ReactNode 129 | */ 130 | getCheckbox(): React.ReactNode { 131 | return ( 132 |
135 |
140 | 155 |
156 |
163 | 167 | this.setResolvedState( 168 | document.getElementById('controls') as HTMLInputElement 169 | ) 170 | } 171 | className={'bp3-checkbox'} 172 | disabled={ 173 | this.props.cardExpanded || 174 | this.props.target === undefined || 175 | !this.props.hasThreads 176 | } 177 | /> 178 |
179 |
180 | ); 181 | } 182 | 183 | /** 184 | * Renders the dropdown menu and label 185 | * 186 | * @return React.ReactNode 187 | */ 188 | getDropdown(): React.ReactNode { 189 | return ( 190 |
194 |
199 | 214 |
215 |
220 | 223 |
224 |
225 | ); 226 | } 227 | 228 | /** 229 | * Gets values for the dropdown menu 230 | * 231 | * @returns React.ReactNode with dropdown menu items 232 | */ 233 | getSortItems(): React.ReactNode { 234 | let table = []; 235 | for (let key in this.sortItems) { 236 | table.push( 237 | this.setSortState(this.sortItems[key].state)} 246 | > 247 | {this.sortItems[key].name} 248 | 249 | ); 250 | } 251 | return table; 252 | } 253 | 254 | /** 255 | * Sets "showResolved" state in "App.tsx" 256 | */ 257 | setResolvedState(e: HTMLInputElement) { 258 | this.props.showResolvedState(e.checked); 259 | } 260 | 261 | /** 262 | * Sets "sortState" state in "App.tsx" 263 | * Adds a name to the Sort by: label 264 | * 265 | * @param state Type: string - Passed as an argument to the SetSortState function in "App.tsx" 266 | * @param name Type: string - Assigns passed value to show as a Sort by: label 267 | */ 268 | setSortState(state: string) { 269 | this.toggleOpen(); 270 | this.props.setSortState(state); 271 | } 272 | 273 | /** 274 | * Sets the "isOpen" state to control the dropdown menu 275 | */ 276 | toggleOpen(): void { 277 | (!this.props.target === undefined || this.props.hasThreads) && 278 | this.setState({ 279 | isOpen: !this.state.isOpen 280 | }); 281 | } 282 | 283 | /** 284 | * Sets the resolve state based on the state of the checkbox 285 | */ 286 | matchCheckBoxState(): void { 287 | let checkBox: HTMLInputElement = document.getElementById( 288 | 'controls' 289 | ) as HTMLInputElement; 290 | 291 | checkBox.checked = this.props.showResolved; 292 | } 293 | 294 | /** 295 | * Dropdown menu items 296 | */ 297 | sortItems = [ 298 | { name: 'Latest Reply', state: 'latest' }, 299 | { name: 'Date Created', state: 'date' }, 300 | { name: 'Most Replies', state: 'mostReplies' } 301 | ]; 302 | 303 | /** 304 | * Custom styles 305 | */ 306 | styles = { 307 | optionBar: { 308 | height: '24px', 309 | display: 'flex', 310 | flexDirection: 'row' as 'row', 311 | justifyContent: 'center' 312 | }, 313 | 'jp-commenting-header-options-showResolved-area': { 314 | height: '24px', 315 | display: 'flex', 316 | flexDirection: 'row' as 'row', 317 | paddingRight: '8px', 318 | width: '50%', 319 | minWidth: '50px', 320 | flexShrink: 1 321 | }, 322 | 'jp-commenting-header-options-showResolved-label-area': { 323 | display: 'flex', 324 | justifyContent: 'center', 325 | flexDirection: 'column' as 'column', 326 | flexShrink: 1, 327 | 328 | whiteSpace: 'nowrap' as 'nowrap', 329 | overflow: 'hidden', 330 | 331 | height: '24px', 332 | minWidth: '52px', 333 | 334 | textAlign: 'right' as 'right', 335 | paddingLeft: '8px', 336 | paddingRight: '8px' 337 | }, 338 | 'jp-commenting-header-options-showResolved-label-enable': { 339 | fontSize: 'var(--jp-ui-font-size1)', 340 | color: 'var(--jp-ui-font-color1)', 341 | fontWeight: '600' 342 | }, 343 | 'jp-commenting-header-options-showResolved-label-disable': { 344 | fontSize: 'var(--jp-ui-font-size1)', 345 | color: 'var(--md-grey-300)' 346 | }, 347 | 'jp-commenting-header-options-showResolved-checkbox-area': { 348 | alignSelf: 'center', 349 | minWidth: '20px' 350 | }, 351 | 'jp-commenting-header-options-dropdown-area': { 352 | height: '24px', 353 | display: 'flex', 354 | flexDirection: 'row' as 'row', 355 | borderLeft: '1px solid var(--jp-border-color2)', 356 | width: '50%', 357 | minWidth: '50px', 358 | flexShrink: 1 359 | }, 360 | 'jp-commenting-header-options-dropdown-label-area': { 361 | display: 'flex', 362 | justifyContent: 'center', 363 | flexDirection: 'column' as 'column', 364 | flexShrink: 1, 365 | 366 | overflow: 'hidden', 367 | whiteSpace: 'nowrap' as 'nowrap', 368 | 369 | height: '24px', 370 | paddingLeft: '8px', 371 | paddingRight: '4px', 372 | minWidth: '10px', 373 | width: '100%' 374 | }, 375 | 'jp-commenting-header-options-dropdown-label-enabled': { 376 | lineHeight: 'normal', 377 | fontSize: '13px', 378 | color: 'var(--jp-ui-font-color1)' 379 | }, 380 | 'jp-commenting-header-options-dropdown-label-disabled': { 381 | lineHeight: 'normal', 382 | fontSize: '13px', 383 | color: 'var(--md-grey-300)' 384 | }, 385 | 'jp-commenting-header-options-dropdown-button-area': { 386 | display: 'flex', 387 | height: '24px', 388 | width: '40px', 389 | minWidth: '40px' 390 | }, 391 | 'jp-commenting-header-options-dropdown-menu-area': {}, 392 | 'jp-commenting-header-options-dropdown-button': { 393 | minWidth: '40px', 394 | minHeight: '24px', 395 | backgroundImage: 'var(--jp-icon-caretdown)', 396 | backgroundRepeat: 'no-repeat', 397 | backgroundPosition: 'center' 398 | } 399 | }; 400 | } 401 | -------------------------------------------------------------------------------- /src/comments/components/Comment.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license BSD-3-Clause 3 | * 4 | * Copyright (c) 2019 Project Jupyter Contributors. 5 | * Distributed under the terms of the 3-Clause BSD License. 6 | */ 7 | 8 | import * as React from 'react'; 9 | 10 | interface ICommentProps { 11 | /** 12 | * The contents of the comment by the user 13 | * 14 | * @type string 15 | */ 16 | context: string; 17 | /** 18 | * Deletes a comment based on index 19 | * 20 | * @param index Type: number - index of comment 21 | */ 22 | deleteComment(index: number): void; 23 | /** 24 | * State when the CommentCard is expanded 25 | * 26 | * @type string 27 | */ 28 | expanded: boolean; 29 | /** 30 | * State when the comment is edited 31 | * 32 | * @type boolean 33 | */ 34 | edited: boolean; 35 | /** 36 | * Handles expanding 37 | * 38 | * @type void 39 | */ 40 | handleShouldExpand: (state: boolean) => void; 41 | /** 42 | * Index of comment in datastore 43 | */ 44 | index: number; 45 | /** 46 | * Checks if a comment is being edited 47 | * 48 | * @param key Type: string - key of what is being edited, 49 | * for comment it is the index 50 | */ 51 | isEditing(key: string): boolean; 52 | /** 53 | * Name of person commenting 54 | * 55 | * @type string 56 | */ 57 | name: string; 58 | /** 59 | * Source of the profile picture 60 | * 61 | * @type string 62 | */ 63 | photo: string; 64 | /** 65 | * Used to update a comment to the edited value 66 | * 67 | * @param comment Type: string - new comment to push 68 | * @param index Type: number - index of comment to push edits to 69 | */ 70 | pushEdit(comment: string, index: number): void; 71 | /** 72 | * State if thread is resolved 73 | * 74 | * @type boolean 75 | */ 76 | resolved: boolean; 77 | /** 78 | * Handles setting the state of isEditing 79 | * 80 | * @param key Type: string - sets the state to the given key (index) 81 | */ 82 | setIsEditing(key: string): void; 83 | /** 84 | * Time comment was made 85 | * 86 | * @type string 87 | */ 88 | timestamp: string; 89 | } 90 | 91 | /** 92 | * Comment React States 93 | */ 94 | interface ICommentStates { 95 | /** 96 | * Tracks if the comment was edited when the edit button is clicked 97 | */ 98 | contextEdited: boolean; 99 | /** 100 | * Text of the edit box 101 | */ 102 | editBox: string; 103 | /** 104 | * Boolean to track if mouse is hovering over comment 105 | */ 106 | hover: boolean; 107 | } 108 | 109 | /** 110 | * Comment React Component 111 | */ 112 | export class Comment extends React.Component { 113 | /** 114 | * Constructor 115 | * 116 | * @param props React props 117 | */ 118 | constructor(props: ICommentProps) { 119 | super(props); 120 | 121 | this.state = { 122 | hover: false, 123 | editBox: '', 124 | contextEdited: false 125 | }; 126 | 127 | this.handleChangeEditBox = this.handleChangeEditBox.bind(this); 128 | this.handleKeyPress = this.handleKeyPress.bind(this); 129 | this.handleCancelSaveButton = this.handleCancelSaveButton.bind(this); 130 | this.handleEditSaveButton = this.handleEditSaveButton.bind(this); 131 | } 132 | 133 | /** 134 | * React render function 135 | */ 136 | render(): React.ReactNode { 137 | return this.props.resolved ? ( 138 |
139 |
142 |
143 | 147 |
148 |
149 |
150 |

151 | {this.props.name} 152 |

153 |
154 |
155 |

160 | {(this.props.edited && 161 | 'Edited on: ' + this.getStyledTimeStamp()) || 162 | this.getStyledTimeStamp()} 163 |

164 |
165 |
166 |
167 |
168 |

169 | {this.props.context.length >= 125 && !this.props.expanded 170 | ? this.props.context.slice(0, 125) + '...' 171 | : this.props.context} 172 |

173 |
174 |
175 | ) : ( 176 |
this.handleMouseOver()} 179 | onMouseLeave={() => this.handleMouseLeave()} 180 | > 181 |
182 |
183 | 187 |
188 |
189 |
190 |

191 | {this.props.name} 192 |

193 |
194 |
195 |

196 | {(this.props.edited && 197 | 'Edited on: ' + this.getStyledTimeStamp()) || 198 | this.getStyledTimeStamp()} 199 |

200 | {this.state.hover && 201 | this.props.expanded && 202 | !this.props.isEditing(this.props.index.toString()) && ( 203 | 230 | )} 231 |
232 |
233 |
234 |
235 | {this.props.isEditing(this.props.index.toString()) && 236 | this.props.expanded ? ( 237 |