xref: /plugin/annotations/README.md (revision 43d2073c014d8cf78420fa47c6568a01e7249305)
1*43d2073cStracker-user# Annotations Plugin for DokuWiki
2*43d2073cStracker-user
3*43d2073cStracker-userWord- and sentence-level annotations on wiki pages, stored out-of-band with threaded replies. Inspired by [Hypothes.is](https://hypothes.is/) and [ep_comments_page](https://github.com/ether/ep_comments_page).
4*43d2073cStracker-user
5*43d2073cStracker-user## Features
6*43d2073cStracker-user
7*43d2073cStracker-user- **Text-quote anchoring** — select any word or sentence; the annotation is tied to the exact quoted text plus surrounding context, so it survives minor edits.
8*43d2073cStracker-user- **Threaded replies** — any reader can reply to an existing annotation.
9*43d2073cStracker-user- **Open / Resolved status** — mark discussions closed; resolved annotations turn green.
10*43d2073cStracker-user- **Gutter markers** — small icons in the left margin show at a glance where annotations live.
11*43d2073cStracker-user- **Orphan detection** — when the annotated text is removed from the page, the annotation is flagged as orphaned and accessible via the counter. Admins can bulk-delete orphans.
12*43d2073cStracker-user- **Per-user toggle** — users can turn the annotation overlay on or off via the usersettings plugin.
13*43d2073cStracker-user- **No page revisions** — annotations are stored in a separate JSON file per page; the wiki changelog is never touched.
14*43d2073cStracker-user
15*43d2073cStracker-user## Requirements
16*43d2073cStracker-user
17*43d2073cStracker-user- DokuWiki Librarian 2025-05-14b (or compatible release)
18*43d2073cStracker-user- PHP 8.0 or later with `mbstring` extension
19*43d2073cStracker-user- [usersettings plugin](https://github.com/tracker-user/dokuwiki-usersettings) *(optional — adds per-user on/off toggle)*
20*43d2073cStracker-user
21*43d2073cStracker-user## Installation
22*43d2073cStracker-user
23*43d2073cStracker-user1. Copy the `annotations/` directory into `{DokuWiki}/lib/plugins/`.
24*43d2073cStracker-user2. If you want the per-user toggle, install the usersettings plugin too.
25*43d2073cStracker-user3. No additional configuration is required.
26*43d2073cStracker-user
27*43d2073cStracker-user## Usage
28*43d2073cStracker-user
29*43d2073cStracker-user### Reading annotations
30*43d2073cStracker-user
31*43d2073cStracker-userAnnotated text is highlighted in **amber** (open) or **green** (resolved). Click any highlight to open the thread panel inline below that paragraph.
32*43d2073cStracker-user
33*43d2073cStracker-userThe counter bar above the page content shows the total number of annotations. If there are orphaned annotations (text was deleted), a clickable "N orphaned" link opens a drawer at the bottom of the page.
34*43d2073cStracker-user
35*43d2073cStracker-user### Creating an annotation
36*43d2073cStracker-user
37*43d2073cStracker-user1. Select any text on the page.
38*43d2073cStracker-user2. Click the **Annotate** button that appears.
39*43d2073cStracker-user3. Type your comment and click **Annotate** to save.
40*43d2073cStracker-user
41*43d2073cStracker-user> Requires: logged in + at least read access on the page.
42*43d2073cStracker-user
43*43d2073cStracker-user### Replying
44*43d2073cStracker-user
45*43d2073cStracker-userOpen the thread panel for any annotation and use the **Reply** field at the bottom.
46*43d2073cStracker-user
47*43d2073cStracker-user### Resolving / Reopening
48*43d2073cStracker-user
49*43d2073cStracker-userClick **Resolve** on any annotation to mark the discussion closed. Any reader can resolve or reopen.
50*43d2073cStracker-user
51*43d2073cStracker-user### Editing and deleting
52*43d2073cStracker-user
53*43d2073cStracker-user- Authors can edit or delete their own annotations and replies.
54*43d2073cStracker-user- Admins can edit or delete any annotation or reply.
55*43d2073cStracker-user
56*43d2073cStracker-user### Admin: bulk operations
57*43d2073cStracker-user
58*43d2073cStracker-userAdmins see two extra buttons in the counter bar:
59*43d2073cStracker-user
60*43d2073cStracker-user| Button | Effect |
61*43d2073cStracker-user|--------|--------|
62*43d2073cStracker-user| **Clear resolved** | Permanently deletes all resolved annotations on the page |
63*43d2073cStracker-user| **Clear orphaned** | Server-side re-check, then permanently deletes orphaned annotations |
64*43d2073cStracker-user
65*43d2073cStracker-user### Disabling the overlay
66*43d2073cStracker-user
67*43d2073cStracker-userVia **User Preferences** (usersettings plugin) → uncheck **Enable annotations**. The overlay is hidden for that user; annotations are still stored.
68*43d2073cStracker-user
69*43d2073cStracker-user## Permission model
70*43d2073cStracker-user
71*43d2073cStracker-user| Action | Who |
72*43d2073cStracker-user|--------|-----|
73*43d2073cStracker-user| Create annotation / reply | Logged in + AUTH_READ on page |
74*43d2073cStracker-user| Resolve / Reopen | Logged in + AUTH_READ on page |
75*43d2073cStracker-user| Edit / delete own annotation or reply | The author |
76*43d2073cStracker-user| Edit / delete any annotation or reply | Admins |
77*43d2073cStracker-user| Clear resolved / Clear orphaned (per-page) | Admins |
78*43d2073cStracker-user
79*43d2073cStracker-userNote: **edit access is not required** to create an annotation. Groups whose page edit access is blocked can still annotate.
80*43d2073cStracker-user
81*43d2073cStracker-user## Storage
82*43d2073cStracker-user
83*43d2073cStracker-userEach page's annotations are stored at:
84*43d2073cStracker-user
85*43d2073cStracker-user```
86*43d2073cStracker-user{data}/meta/{namespace}/{page}.annotations
87*43d2073cStracker-user```
88*43d2073cStracker-user
89*43d2073cStracker-userFormat (JSON):
90*43d2073cStracker-user
91*43d2073cStracker-user```json
92*43d2073cStracker-user{
93*43d2073cStracker-user  "version": 1,
94*43d2073cStracker-user  "annotations": [
95*43d2073cStracker-user    {
96*43d2073cStracker-user      "id": "a1b2c3d4e5f6g7h8",
97*43d2073cStracker-user      "anchor": {
98*43d2073cStracker-user        "exact": "lossless source",
99*43d2073cStracker-user        "prefix": "must use a ",
100*43d2073cStracker-user        "suffix": ". Transcodes",
101*43d2073cStracker-user        "start": 21
102*43d2073cStracker-user      },
103*43d2073cStracker-user      "author": "alice",
104*43d2073cStracker-user      "created": 1716336000,
105*43d2073cStracker-user      "modified": 1716336000,
106*43d2073cStracker-user      "body": "Does this cover remuxes?",
107*43d2073cStracker-user      "status": "open",
108*43d2073cStracker-user      "resolved_by": "",
109*43d2073cStracker-user      "resolved_at": 0,
110*43d2073cStracker-user      "replies": [
111*43d2073cStracker-user        {
112*43d2073cStracker-user          "id": "x1y2z3a4b5c6d7e8",
113*43d2073cStracker-user          "author": "bob",
114*43d2073cStracker-user          "created": 1716336100,
115*43d2073cStracker-user          "modified": 1716336100,
116*43d2073cStracker-user          "body": "Yes, remuxes count."
117*43d2073cStracker-user        }
118*43d2073cStracker-user      ]
119*43d2073cStracker-user    }
120*43d2073cStracker-user  ]
121*43d2073cStracker-user}
122*43d2073cStracker-user```
123*43d2073cStracker-user
124*43d2073cStracker-user## AJAX API
125*43d2073cStracker-user
126*43d2073cStracker-userAll actions go to `/lib/exe/ajax.php?call=annotations`.
127*43d2073cStracker-user
128*43d2073cStracker-user| Action | Method | Token | Required fields |
129*43d2073cStracker-user|--------|--------|-------|-----------------|
130*43d2073cStracker-user| `load` | GET | — | `id` |
131*43d2073cStracker-user| `create` | POST | ✓ | `id`, `anchor`, `body` |
132*43d2073cStracker-user| `reply` | POST | ✓ | `id`, `annId`, `body` |
133*43d2073cStracker-user| `edit_annotation` | POST | ✓ | `id`, `annId`, `body` |
134*43d2073cStracker-user| `edit_reply` | POST | ✓ | `id`, `annId`, `replyId`, `body` |
135*43d2073cStracker-user| `delete_annotation` | POST | ✓ | `id`, `annId` |
136*43d2073cStracker-user| `delete_reply` | POST | ✓ | `id`, `annId`, `replyId` |
137*43d2073cStracker-user| `resolve` | POST | ✓ | `id`, `annId`, `status` |
138*43d2073cStracker-user| `clear_resolved` | POST | ✓ | `id` |
139*43d2073cStracker-user| `clear_orphaned` | POST | ✓ | `id` |
140*43d2073cStracker-user
141*43d2073cStracker-userPOST bodies are `application/json`. All responses: `{"success": true, ...}` or `{"success": false, "error": "..."}`.
142*43d2073cStracker-user
143*43d2073cStracker-user## Files
144*43d2073cStracker-user
145*43d2073cStracker-user```
146*43d2073cStracker-userannotations/
147*43d2073cStracker-user├── plugin.info.txt     Plugin manifest
148*43d2073cStracker-user├── helper.php          Storage, CRUD, orphan detection, permission rules
149*43d2073cStracker-user├── action.php          Event hooks, AJAX endpoint
150*43d2073cStracker-user├── script.js           Front-end: selection, anchoring, highlights, panels, AJAX
151*43d2073cStracker-user├── style.css           Theme-compatible CSS (uses DokuWiki __token__ vars)
152*43d2073cStracker-user├── README.md           This file
153*43d2073cStracker-user└── lang/
154*43d2073cStracker-user    └── en/
155*43d2073cStracker-user        └── lang.php    English strings
156*43d2073cStracker-user```
157*43d2073cStracker-user
158*43d2073cStracker-user## Browser compatibility
159*43d2073cStracker-user
160*43d2073cStracker-userThe JavaScript targets Firefox 78 ESR and later (no `#private` fields, no `??=`, no `<dialog>`, no `Array.at`). Should work in all modern browsers.
161*43d2073cStracker-user
162*43d2073cStracker-user## License
163*43d2073cStracker-user
164*43d2073cStracker-userGPL 2, matching DokuWiki.
165