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