').attr('id', 'comments');
50 | for (const [commentId, comment] of Object.entries(comments)) {
51 | div.append(
52 | $('
')
53 | .attr('role', 'comment')
54 | .addClass('comment')
55 | .attr('id', commentId)
56 | .text(`* ${comment.text}`));
57 | }
58 | // adds additional HTML to the body, we get this HTML from the database of comments:padId
59 | return $.html(div);
60 | };
61 |
--------------------------------------------------------------------------------
/static/tests/backend/specs/padRemove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const common = require('ep_etherpad-lite/tests/backend/common');
4 | const utils = require('../../utils');
5 | const createPad = utils.createPad;
6 | const createComment = utils.createComment;
7 | const createCommentReply = utils.createCommentReply;
8 | const commentsEndPointFor = utils.commentsEndPointFor;
9 | const commentRepliesEndPointFor = utils.commentRepliesEndPointFor;
10 |
11 | let api;
12 | const apiKey = common.apiKey;
13 |
14 | describe(__filename, function () {
15 | let padID;
16 |
17 | before(async function () { api = await common.init(); });
18 |
19 | beforeEach(function (done) {
20 | createPad((err, newPadID) => {
21 | padID = newPadID;
22 | done(err);
23 | });
24 | });
25 |
26 | it('removes pad comments when pad is deleted', function (done) {
27 | // create comment...
28 | createComment(padID, {}, (err, comment) => {
29 | if (err) throw err;
30 | // ... remove pad...
31 | deletePad(padID, () => {
32 | // ... and finally check if comments are returned
33 | const getCommentsRoute = `${commentsEndPointFor(padID)}?apikey=${apiKey}`;
34 | api.get(getCommentsRoute)
35 | .expect((res) => {
36 | const commentsFound = Object.keys(res.body.data.comments);
37 | if (commentsFound.length !== 0) {
38 | throw new Error('Comments from pad should had been removed. ' +
39 | `Found ${commentsFound.length} comment(s)`);
40 | }
41 | })
42 | .end(done);
43 | });
44 | });
45 | });
46 |
47 | it('removes pad comments replies when pad is deleted', function (done) {
48 | // create comment...
49 | createComment(padID, {}, (err, comment) => {
50 | if (err) throw err;
51 |
52 | // ... create reply...
53 | createCommentReply(padID, comment, {}, (err, reply) => {
54 | if (err) throw err;
55 |
56 | // ... remove pad...
57 | deletePad(padID, () => {
58 | // ... and finally check if replies are returned
59 | const getRepliesRoute = `${commentRepliesEndPointFor(padID)}?apikey=${apiKey}`;
60 | api.get(getRepliesRoute)
61 | .expect((res) => {
62 | const repliesFound = Object.keys(res.body.data.replies);
63 | if (repliesFound.length !== 0) {
64 | throw new Error('Comment replies from pad should had been removed. ' +
65 | `Found ${repliesFound.length} reply(ies)`);
66 | }
67 | })
68 | .end(done);
69 | });
70 | });
71 | });
72 | });
73 | });
74 |
75 | const deletePad = function (padID, callback) {
76 | const deletePadRoute = `/api/1/deletePad?apikey=${apiKey}&padID=${padID}`;
77 | api.get(deletePadRoute).end((err, res) => {
78 | if (err || res.body.code !== 0) {
79 | throw (err || res.body.message || `unknown error while calling API route ${deletePadRoute}`);
80 | }
81 |
82 | callback();
83 | });
84 | };
85 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to the npm registry when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | workflow_call:
8 |
9 | jobs:
10 | publish-npm:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: 20
16 | registry-url: https://registry.npmjs.org/
17 | - name: Check out Etherpad core
18 | uses: actions/checkout@v3
19 | with:
20 | repository: ether/etherpad-lite
21 | - uses: pnpm/action-setup@v3
22 | name: Install pnpm
23 | with:
24 | version: 9
25 | run_install: false
26 | - name: Get pnpm store directory
27 | shell: bash
28 | run: |
29 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
30 | - uses: actions/cache@v4
31 | name: Setup pnpm cache
32 | with:
33 | path: ${{ env.STORE_PATH }}
34 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
35 | restore-keys: |
36 | ${{ runner.os }}-pnpm-store-
37 | -
38 | uses: actions/checkout@v3
39 | with:
40 | fetch-depth: 0
41 | -
42 | name: Bump version (patch)
43 | run: |
44 | LATEST_TAG=$(git describe --tags --abbrev=0) || exit 1
45 | NEW_COMMITS=$(git rev-list --count "${LATEST_TAG}"..) || exit 1
46 | [ "${NEW_COMMITS}" -gt 0 ] || exit 0
47 | git config user.name 'github-actions[bot]'
48 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
49 | pnpm i
50 | pnpm version patch
51 | git push --follow-tags
52 | # This is required if the package has a prepare script that uses something
53 | # in dependencies or devDependencies.
54 | -
55 | run: pnpm i
56 | # `npm publish` must come after `git push` otherwise there is a race
57 | # condition: If two PRs are merged back-to-back then master/main will be
58 | # updated with the commits from the second PR before the first PR's
59 | # workflow has a chance to push the commit generated by `npm version
60 | # patch`. This causes the first PR's `git push` step to fail after the
61 | # package has already been published, which in turn will cause all future
62 | # workflow runs to fail because they will all attempt to use the same
63 | # already-used version number. By running `npm publish` after `git push`,
64 | # back-to-back merges will cause the first merge's workflow to fail but
65 | # the second's will succeed.
66 | -
67 | run: pnpm publish
68 | env:
69 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
70 | #-
71 | # name: Add package to etherpad organization
72 | # run: pnpm access grant read-write etherpad:developers
73 | # env:
74 | # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
75 |
--------------------------------------------------------------------------------
/static/tests/backend/specs/padCopy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const common = require('ep_etherpad-lite/tests/backend/common');
4 | const utils = require('../../utils');
5 | const createPad = utils.createPad;
6 | const createComment = utils.createComment;
7 | const createCommentReply = utils.createCommentReply;
8 | const commentsEndPointFor = utils.commentsEndPointFor;
9 | const commentRepliesEndPointFor = utils.commentRepliesEndPointFor;
10 |
11 | let api;
12 | const apiKey = common.apiKey;
13 |
14 | describe(__filename, function () {
15 | let padID;
16 |
17 | before(async function () { api = await common.init(); });
18 |
19 | beforeEach(function (done) {
20 | createPad((err, newPadID) => {
21 | padID = newPadID;
22 | done(err);
23 | });
24 | });
25 |
26 | it('creates copies of pad comments when pad is duplicated', function (done) {
27 | // create comment...
28 | createComment(padID, {}, (err, comment) => {
29 | if (err) throw err;
30 | // ... duplicate pad...
31 | const copiedPadID = `${padID}-copy`;
32 | copyPad(padID, copiedPadID, () => {
33 | // ... and finally check if comments are returned
34 | const getCommentsRoute = `${commentsEndPointFor(copiedPadID)}?apikey=${apiKey}`;
35 | api.get(getCommentsRoute)
36 | .expect((res) => {
37 | const commentsFound = Object.keys(res.body.data.comments);
38 | if (commentsFound.length !== 1) {
39 | throw new Error('Comments from pad should had been copied.');
40 | }
41 | })
42 | .end(done);
43 | });
44 | });
45 | });
46 |
47 | it('creates copies of pad comment replies when pad is duplicated', function (done) {
48 | // create comment...
49 | createComment(padID, {}, (err, comment) => {
50 | if (err) throw err;
51 |
52 | // ... create reply...
53 | createCommentReply(padID, comment, {}, (err, reply) => {
54 | if (err) throw err;
55 |
56 | // ... duplicate pad...
57 | const copiedPadID = `${padID}-copy`;
58 | copyPad(padID, copiedPadID, () => {
59 | // ... and finally check if replies are returned
60 | const getRepliesRoute = `${commentRepliesEndPointFor(copiedPadID)}?apikey=${apiKey}`;
61 | api.get(getRepliesRoute)
62 | .expect((res) => {
63 | const repliesFound = Object.keys(res.body.data.replies);
64 | if (repliesFound.length !== 1) {
65 | throw new Error('Comment replies from pad should had been copied.');
66 | }
67 | })
68 | .end(done);
69 | });
70 | });
71 | });
72 | });
73 | });
74 |
75 | const copyPad = function (originalPadID, copiedPadID, callback) {
76 | const copyPadRoute =
77 | `/api/1.2.9/copyPad?apikey=${apiKey}&sourceID=${originalPadID}&destinationID=${copiedPadID}`;
78 | api.get(copyPadRoute).end((err, res) => {
79 | if (err || res.body.code !== 0) {
80 | throw (err || res.body.message || `unknown error while calling API route ${copyPadRoute}`);
81 | }
82 |
83 | callback();
84 | });
85 | };
86 |
--------------------------------------------------------------------------------
/.github/workflows/backend-tests.yml:
--------------------------------------------------------------------------------
1 | name: Backend Tests
2 |
3 | # any branch is useful for testing before a PR is submitted
4 | on:
5 | workflow_call:
6 |
7 | jobs:
8 | withplugins:
9 | # run on pushes to any branch
10 | # run on PRs from external forks
11 | if: |
12 | (github.event_name != 'pull_request')
13 | || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id)
14 | name: with Plugins
15 | runs-on: ubuntu-latest
16 | steps:
17 | -
18 | name: Install libreoffice
19 | uses: awalsh128/cache-apt-pkgs-action@v1.4.2
20 | with:
21 | packages: libreoffice libreoffice-pdfimport
22 | version: 1.0
23 | -
24 | name: Install etherpad core
25 | uses: actions/checkout@v3
26 | with:
27 | repository: ether/etherpad-lite
28 | path: etherpad-lite
29 | - uses: pnpm/action-setup@v3
30 | name: Install pnpm
31 | with:
32 | version: 8
33 | run_install: false
34 | - name: Get pnpm store directory
35 | shell: bash
36 | run: |
37 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
38 | - uses: actions/cache@v4
39 | name: Setup pnpm cache
40 | with:
41 | path: ${{ env.STORE_PATH }}
42 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
43 | restore-keys: |
44 | ${{ runner.os }}-pnpm-store-
45 | -
46 | name: Checkout plugin repository
47 | uses: actions/checkout@v3
48 | with:
49 | path: plugin
50 | -
51 | name: Determine plugin name
52 | id: plugin_name
53 | working-directory: ./plugin
54 | run: |
55 | npx -c 'printf %s\\n "::set-output name=plugin_name::${npm_package_name}"'
56 | -
57 | name: Link plugin directory
58 | working-directory: ./plugin
59 | run: |
60 | pnpm link --global
61 | - name: Remove tests
62 | working-directory: ./etherpad-lite
63 | run: rm -rf ./src/tests/backend/specs
64 | -
65 | name: Install Etherpad core dependencies
66 | working-directory: ./etherpad-lite
67 | run: bin/installDeps.sh
68 | - name: Link plugin to etherpad-lite
69 | working-directory: ./etherpad-lite
70 | run: |
71 | pnpm link --global $PLUGIN_NAME
72 | pnpm run install-plugins --path ../../plugin
73 | env:
74 | PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
75 | - name: Link ep_etherpad-lite
76 | working-directory: ./etherpad-lite/src
77 | run: |
78 | pnpm link --global
79 | - name: Link etherpad to plugin
80 | working-directory: ./plugin
81 | run: |
82 | pnpm link --global ep_etherpad-lite
83 | -
84 | name: Run the backend tests
85 | working-directory: ./etherpad-lite
86 | run: |
87 | res=$(find .. -path "./node_modules/ep_*/static/tests/backend/specs/**" | wc -l)
88 | if [ $res -eq 0 ]; then
89 | echo "No backend tests found"
90 | else
91 | pnpm run test
92 | fi
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  
2 |
3 | # Comments and annotations for Etherpad
4 |
5 | 
6 |
7 | ## Installing this plugin with npm.
8 | ```
9 | npm install ep_comments_page
10 | ```
11 |
12 | ## Extra settings
13 | This plugin has some extra features that can be enabled by changing values on `settings.json` of your Etherpad instance.
14 |
15 | ### Alternative comment display
16 | There is an alternative way to display comments. Instead of having all comments visible on the right of the page, you can have just an icon on the right margin of the page. Comment details are displayed when user clicks on the comment icon:
17 |
18 | 
19 |
20 | To use this way of displaying comments, add the following to your `settings.json`:
21 | ```
22 | // Display comments as icons, not boxes
23 | "ep_comments_page": {
24 | "displayCommentAsIcon": true
25 | },
26 | ```
27 |
28 | ### Highlight selected text when creating a comment
29 | It is also possible to mark the text originally selected when user adds a comment:
30 | 
31 |
32 | To enable this feature, add the following code to your `settings.json`:
33 | ```
34 | // Highlight selected text when adding comment
35 | "ep_comments_page": {
36 | "highlightSelectedText": true
37 | },
38 | ```
39 |
40 | **Warning**: there is a side effect when you enable this feature: a revision is created everytime the text is highlighted, resulting on apparently "empty" changes when you check your pad on the timeslider. If that is an issue for you, we don't recommend you to use this feature.
41 |
42 | ### Disable HTML export
43 | By default comments are exported to HTML, but if you don't wish to do that then you can disable it by adding the following to your `settings.json`:
44 | ```
45 | "ep_comments_page": {
46 | "exportHtml": false
47 | },
48 | ```
49 |
50 | ## Creating comment via API
51 | If you need to add comments to a pad:
52 |
53 | * Call this route to create the comments on Etherpad and get the comment ids:
54 | ```
55 | curl -X POST http://localhost:9001/p/THE_PAD_ID/comments -d "apikey=YOUR_API_KEY" -d 'data=[{"name":"AUTHOR","text":"COMMENT"}, {"name":"ANOTHER_AUTHOR","text":"ANOTHER_COMMENT"}]'
56 | ```
57 |
58 | The response will be:
59 | ```
60 | {"code":0,"commentIds":["c-VEtzKolgD5krJOVU","c-B8MEmAT0NJ9usUwc"]}
61 | ```
62 |
63 | * Use the returned comment ids to set the pad HTML [via API](http://etherpad.org/doc/v1.5.6/#index_sethtml_padid_html):
64 | ```
65 | My comment goes