annotepage-client 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/HASHES.txt +2 -0
- package/dist/annotepage.js +143 -5
- package/labels/fr.json +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,8 +33,8 @@ Load the client once, on a page of the site, with `data-setup` and **without**
|
|
|
33
33
|
`data-project`:
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
|
-
<script src="https://<your-cdn>/annotepage-client@2.
|
|
37
|
-
integrity="sha384-
|
|
36
|
+
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
37
|
+
integrity="sha384-PVE6tjzC72/VHyRLdxHLpwMLIp9ji+VUBapD+j3vmoi69rpkXn0h+ZicxwORUdQt"
|
|
38
38
|
crossorigin="anonymous"
|
|
39
39
|
data-server="https://<your-server>/annotepage/api.php"
|
|
40
40
|
data-setup
|
|
@@ -54,8 +54,8 @@ on the server. No network request is made at that point.
|
|
|
54
54
|
### 2. Paste the final tag, at the end of `<body>`
|
|
55
55
|
|
|
56
56
|
```html
|
|
57
|
-
<script src="https://<your-cdn>/annotepage-client@2.
|
|
58
|
-
integrity="sha384-
|
|
57
|
+
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
58
|
+
integrity="sha384-PVE6tjzC72/VHyRLdxHLpwMLIp9ji+VUBapD+j3vmoi69rpkXn0h+ZicxwORUdQt"
|
|
59
59
|
crossorigin="anonymous"
|
|
60
60
|
data-server="https://<your-server>/annotepage/api.php"
|
|
61
61
|
data-project="7Qb1kZ3xNvA9dLpEqKf2Zt"
|
package/dist/HASHES.txt
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
2.6.0 sha384-PVE6tjzC72/VHyRLdxHLpwMLIp9ji+VUBapD+j3vmoi69rpkXn0h+ZicxwORUdQt 167714 bytes
|
|
2
|
+
2.5.0 sha384-8bHU9VMghgfLL4PSwE3XJZXcDYqhd5SlUE31yMgaI/2ZNhH3Z18he6X7BsF7kvx1 167714 bytes
|
|
1
3
|
2.4.0 sha384-RbO8lASKNckSOum5Gm462DTZ8y6/+yR6ZFKyphrAe9Hf3x95tJ57eY/6GX4XfBJr 160669 bytes
|
|
2
4
|
2.3.0 sha384-oZAAGkI1KL5aAJCD+v7QPGqxcg5fzizz8hsnUHfNJPXeNVccDX251y5+EZRDCHEl 154823 bytes
|
|
3
5
|
2.2.0 sha384-+/SJuJv8IJRDsV5d5yXvlmLRiVmZHmX1NMph76iwNJuqFbQgQgbdDmseIxUl+DWz 154823 bytes
|
package/dist/annotepage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
2
|
annotepage -- the annotation layer, browser side.
|
|
3
3
|
|
|
4
|
-
Package version : 2.
|
|
4
|
+
Package version : 2.6.0
|
|
5
5
|
Format version : 2 (see FORMAT.md)
|
|
6
6
|
Licence : MIT
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
/* Injected by the build: they come from package.json and from
|
|
17
17
|
src/styles.css, so that no value is written in two places and can
|
|
18
18
|
therefore diverge. */
|
|
19
|
-
const TOOL_VERSION = "2.
|
|
19
|
+
const TOOL_VERSION = "2.6.0";
|
|
20
20
|
const FORMAT = 2;
|
|
21
21
|
const STYLES = "/* ============================================================================\n styles.css -- THE STYLES OF THE TOOL, AND OF NO OTHER ELEMENT.\n\n This sheet is INLINED into the served file by the build, then put into the\n tool's shadow root -- as a constructed sheet when the browser can do it, in\n a <style> otherwise. It was loaded by a <link> in the original tool; the\n move to a CDN under SRI brought it inside the file, so that there is only\n one digest to keep up to date. The containment itself has not changed, and\n is still twofold:\n\n - from the tool towards the site: no rule from here can reach an element\n of the host site, the browser sees to that. That is what makes the\n claim \"the layer touches nothing\" checkable rather than promised;\n - from the site towards the tool: no rule of the site can reach an\n element here. A redesign of the site's stylesheet therefore cannot\n distort the tool, nor the other way round.\n\n The \"ap-\" prefix on every class is the third safeguard: the day somebody\n loads these styles WITHOUT a shadow root -- by mistake, or to debug --\n nothing would answer a selector of the site.\n\n NO RULE TARGETS html, body, * OR ANY SELECTOR OF THE SITE. That is the one\n absolute prohibition of this file.\n\n COLOURS: the tool has its OWN palette, defined on the shadow root. It\n reads neither the site's variables nor its theme attribute: it has no\n reason to know how the site names its colours, and it must stay readable\n on a light site as on a dark one. The switch follows the system\n preference, the only information the tool has without asking anyone.\n ============================================================================ */\n\n\n:host {\n --ap-bg: #ffffff;\n --ap-bg-soft: #f4f6f8;\n --ap-bg-raised: #e9edf2;\n --ap-text: #1a1d21;\n --ap-text-soft: #5b6570;\n --ap-border: #d5dbe2;\n --ap-accent: #2f6fed;\n --ap-accent-dark: #1d55c8;\n --ap-accent-text: #ffffff;\n --ap-accent-veil: rgba(47, 111, 237, 0.14);\n --ap-alert-bg: #fdeceb;\n --ap-alert-border: #e3a9a4;\n --ap-alert-text: #8a1f16;\n --ap-shadow: 0 6px 24px rgba(16, 24, 40, 0.18);\n --ap-radius: 10px;\n --ap-font: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\",\n Arial, sans-serif;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --ap-bg: #1d2126;\n --ap-bg-soft: #262b32;\n --ap-bg-raised: #323942;\n --ap-text: #e9ecf0;\n --ap-text-soft: #a4adb8;\n --ap-border: #3a424c;\n --ap-accent: #6d9bff;\n --ap-accent-dark: #8fb4ff;\n --ap-accent-text: #10151c;\n --ap-accent-veil: rgba(109, 155, 255, 0.18);\n --ap-alert-bg: #3a1f1c;\n --ap-alert-border: #7c3a33;\n --ap-alert-text: #ffb9b1;\n --ap-shadow: 0 6px 24px rgba(0, 0, 0, 0.55);\n }\n}\n\n/* ----------------------------------------------------------------------------\n The layer.\n\n It covers the viewport and receives NO click: that is what lets the page\n behave exactly as usual as long as the tool is not in annotation mode.\n Each widget re-enables clicks for itself alone.\n ---------------------------------------------------------------------------- */\n\n.ap-layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n font-family: var(--ap-font);\n font-size: 14px;\n line-height: 1.45;\n color: var(--ap-text);\n text-align: left;\n -webkit-font-smoothing: antialiased;\n}\n\n.ap-layer button,\n.ap-layer input,\n.ap-layer textarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n color: inherit;\n margin: 0;\n box-sizing: border-box;\n}\n\n/* ----------------------------------------------------------------------------\n The button: the only thing visible when the tool is at rest.\n ---------------------------------------------------------------------------- */\n\n.ap-button {\n position: fixed;\n right: 16px;\n bottom: 16px;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 9px 14px;\n border: 1px solid var(--ap-border);\n border-radius: 999px;\n background: var(--ap-bg);\n color: var(--ap-text);\n box-shadow: var(--ap-shadow);\n cursor: pointer;\n pointer-events: auto;\n opacity: 0.92;\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ap-button:hover,\n.ap-button:focus-visible {\n opacity: 1;\n transform: translateY(-1px);\n}\n\n.ap-button:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n.ap-button[aria-pressed=\"true\"] {\n background: var(--ap-accent);\n border-color: var(--ap-accent);\n color: var(--ap-accent-text);\n opacity: 1;\n}\n\n.ap-button-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--ap-accent);\n flex: none;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-dot {\n background: var(--ap-accent-text);\n}\n\n.ap-button-count {\n padding: 1px 7px;\n border-radius: 999px;\n background: var(--ap-bg-raised);\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-count {\n background: rgba(255, 255, 255, 0.22);\n color: var(--ap-accent-text);\n}\n\n/* ----------------------------------------------------------------------------\n The pointing highlight.\n\n It is DRAWN HERE, from the coordinates of the element being pointed at.\n Nothing is put on the element itself: no class, no attribute, no style. So\n the site cannot move by a single pixel because of the pointing.\n ---------------------------------------------------------------------------- */\n\n.ap-highlight {\n position: fixed;\n border: 2px solid var(--ap-accent);\n border-radius: 3px;\n background: var(--ap-accent-veil);\n pointer-events: none;\n display: none;\n}\n\n.ap-highlight-label {\n position: fixed;\n max-width: 320px;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n display: none;\n box-shadow: var(--ap-shadow);\n}\n\n/* ----------------------------------------------------------------------------\n The markers: \"there are already notes here\".\n ---------------------------------------------------------------------------- */\n\n.ap-marker {\n position: fixed;\n min-width: 22px;\n height: 22px;\n padding: 0 6px;\n border: 2px solid var(--ap-bg);\n border-radius: 999px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n font-weight: 700;\n line-height: 18px;\n text-align: center;\n cursor: pointer;\n pointer-events: auto;\n box-shadow: var(--ap-shadow);\n}\n\n.ap-marker:focus-visible {\n outline: 2px solid var(--ap-accent-dark);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The panel.\n ---------------------------------------------------------------------------- */\n\n.ap-panel {\n position: fixed;\n top: 12px;\n right: 12px;\n bottom: 72px;\n width: 360px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n overflow: hidden;\n}\n\n.ap-panel.ap-open {\n display: flex;\n}\n\n.ap-panel-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n}\n\n.ap-panel-title {\n font-size: 15px;\n font-weight: 600;\n flex: 1 1 auto;\n}\n\n.ap-panel-instructions {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ap-border);\n color: var(--ap-text-soft);\n font-size: 13px;\n}\n\n.ap-panel-body {\n flex: 1 1 auto;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 4px 14px 14px;\n}\n\n.ap-panel-footer {\n padding: 8px 14px;\n border-top: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ap-section-title {\n margin: 14px 0 6px;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.ap-section-help {\n margin: 0 0 8px;\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-empty {\n margin: 16px 0;\n color: var(--ap-text-soft);\n}\n\n/* The standing mention of a public key. Deliberately NOT the alert colours:\n this is not a failure and it is on screen for ever -- an alarm that never\n goes away stops being read. It is a fact about the project, stated in the\n panel's own tone, and it stays at the top of every draw. */\n/* The public-key notice and the \"a newer client exists\" line are the same\n object on screen: a standing statement about what one is looking at, above\n the notes and above the failures. One rule, so they cannot drift apart. */\n.ap-public,\n.ap-upgrade {\n margin: 0 0 10px;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n line-height: 1.45;\n}\n\n/* ----------------------------------------------------------------------------\n A note, and its replies.\n ---------------------------------------------------------------------------- */\n\n.ap-note {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n}\n\n.ap-note.ap-orphan {\n background: var(--ap-bg-soft);\n}\n\n.ap-note.ap-focused {\n border-color: var(--ap-accent);\n box-shadow: 0 0 0 3px var(--ap-accent-veil);\n}\n\n.ap-note-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-note-author {\n font-weight: 600;\n}\n\n.ap-note-date {\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-note-target {\n margin: 4px 0 0;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-note-text {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.ap-note-actions {\n margin-top: 8px;\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-replies {\n margin: 8px 0 0;\n padding-left: 10px;\n border-left: 2px solid var(--ap-border);\n}\n\n.ap-reply {\n margin: 8px 0 0;\n}\n\n/* ----------------------------------------------------------------------------\n The form, anchored near the element pointed at.\n ---------------------------------------------------------------------------- */\n\n.ap-form {\n position: fixed;\n width: 340px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n gap: 8px;\n padding: 14px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n}\n\n.ap-form.ap-open {\n display: flex;\n}\n\n.ap-form-title {\n font-size: 15px;\n font-weight: 600;\n}\n\n.ap-form-target {\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-label {\n display: block;\n margin-bottom: 3px;\n font-size: 12px;\n font-weight: 600;\n color: var(--ap-text-soft);\n}\n\n.ap-help {\n margin: 3px 0 0;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n.ap-field,\n.ap-area {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n}\n\n.ap-field:focus,\n.ap-area:focus {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n.ap-area {\n min-height: 92px;\n resize: vertical;\n}\n\n.ap-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-counter {\n margin-left: auto;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n/* ----------------------------------------------------------------------------\n Buttons.\n ---------------------------------------------------------------------------- */\n\n.ap-primary,\n.ap-secondary,\n.ap-link {\n border-radius: 8px;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.ap-primary {\n padding: 8px 14px;\n border: 1px solid var(--ap-accent);\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-weight: 600;\n}\n\n.ap-primary:hover {\n background: var(--ap-accent-dark);\n border-color: var(--ap-accent-dark);\n}\n\n.ap-secondary {\n padding: 8px 14px;\n border: 1px solid var(--ap-border);\n background: var(--ap-bg);\n color: var(--ap-text);\n}\n\n.ap-secondary:hover {\n background: var(--ap-bg-raised);\n}\n\n.ap-link {\n padding: 2px 4px;\n border: 0;\n background: none;\n color: var(--ap-accent);\n text-decoration: underline;\n font-size: 13px;\n}\n\n.ap-primary:disabled,\n.ap-secondary:disabled,\n.ap-link:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.ap-primary:focus-visible,\n.ap-secondary:focus-visible,\n.ap-link:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The failures.\n\n They are RED, at the top of the block concerned, and carry the message the\n server returned as it stands: that is how a non-technical team learns that\n its remark is not saved, instead of believing it is.\n ---------------------------------------------------------------------------- */\n\n.ap-error {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-alert-border);\n border-radius: var(--ap-radius);\n background: var(--ap-alert-bg);\n color: var(--ap-alert-text);\n}\n\n.ap-error-title {\n font-weight: 700;\n margin-bottom: 4px;\n}\n\n.ap-error-detail {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: 13px;\n}\n\n.ap-error .ap-link {\n color: var(--ap-alert-text);\n}\n\n/* ----------------------------------------------------------------------------\n Narrow: the panel takes the full width, and so does the form.\n ---------------------------------------------------------------------------- */\n\n/* ----------------------------------------------------------------------------\n Narrow.\n\n DEFECT OBSERVED at 375 px wide: a panel taking the full height covers the\n whole page, and no element can be pointed at any more -- every click lands\n on the panel. So it becomes a bottom band, which leaves the top half of\n the viewport free; one scrolls the page there to bring the wanted element\n into view. The form, for its part, hides the panel while typing (see\n notes.js): on a screen that size, writing and reading the list at the same\n time does not hold.\n ---------------------------------------------------------------------------- */\n\n/* On a narrow screen the panel becomes a bottom band and the form takes the\n full width.\n\n THE WIDTH CEILING IS KEPT, and it comes from a measured defect: \"left: 8;\n right: 8\" sizes the element against its CONTAINING BLOCK, which the host\n site's horizontal overflow can make wider than the visible window.\n Measured, in mobile emulation at 390 px: the site overflows to 407 px\n (with the tool and without it), and the panel came out 391 px wide\n starting at 8, that is 9 px off screen. \"100vw\" is the window, not the\n containing block: the ceiling therefore does nothing when the site does\n not overflow, and pulls the width back when it does. */\n@media (max-width: 560px) {\n .ap-panel {\n top: auto;\n right: 8px;\n left: 8px;\n bottom: 66px;\n height: 52vh;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n\n .ap-form {\n left: 8px;\n right: 8px;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ap-button {\n transition: none;\n }\n}\n\n/* The failure shows without opening the panel: the button's dot changes\n colour. A team that does not click must be able to see that something is\n wrong. */\n.ap-button.ap-failed .ap-button-dot {\n background: var(--ap-alert-text);\n}\n\n.ap-button.ap-failed {\n border-color: var(--ap-alert-border);\n}\n\n/* Signature reminder, in the note form.\n The name was shown at the foot of the panel only: invisible at the moment\n one writes. A user reported not knowing which name they were writing\n under. */\n.ap-form-signature {\n display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;\n margin: 0 0 .6rem; font-size: .85rem; opacity: .8;\n}\n\n/* Resolution state, said on the card.\n Two cases NOT to be confused: resolved and online, resolved but not\n deployed yet. The second keeps the defect on the reviewer's screen; hiding\n it or announcing it as fixed would cost them their trust in the tool. */\n.ap-state-mark {\n display: inline-block; margin: 0 0 .5rem;\n padding: .15rem .55rem; border-radius: 4px;\n font-size: .75rem; font-weight: 600; letter-spacing: .02em;\n}\n.ap-note.ap-resolved { opacity: .72; }\n.ap-note.ap-resolved .ap-state-mark {\n color: #0f7a52; background: rgba(16, 185, 129, .14);\n}\n.ap-note.ap-resolved-pending .ap-state-mark {\n color: #8a5a00; background: rgba(245, 158, 11, .16);\n}\n/* The \"it is fixed\" / \"reopen\" block, opened under the card. Same shape as\n the reply block: it is the same gesture, one answers a remark. */\n.ap-resolve,\n.ap-reply-form {\n margin-top: .6rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n/* The question asked before the key is dropped, at the foot of the list.\n Framed like the resolution block -- it is the same shape of gesture, one\n answers before something changes -- and set apart from the notes above it,\n because it is not about a note. */\n.ap-forget {\n margin-top: 1rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n.ap-forget .ap-actions {\n margin-top: .5rem;\n}\n\n.ap-history-toggle {\n display: block; width: 100%; margin: 1rem 0 .25rem;\n padding: .5rem .75rem; border: 1px dashed currentColor; border-radius: 6px;\n background: none; color: inherit; font: inherit; opacity: .7; cursor: pointer;\n}\n.ap-history-toggle:hover { opacity: 1; }\n\n\n/* ----------------------------------------------------------------------------\n Setup and pasting the salt.\n\n These are the only screens where something is copied by hand. Everything\n there is SELECTABLE and monospaced: a 43-character salt copied wrong\n cannot be recovered, and nothing helps less than a font that confuses I, l\n and 1.\n ---------------------------------------------------------------------------- */\n\n.ap-panel-wide {\n width: 560px;\n}\n\n.ap-copy {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin: 0 0 12px;\n}\n\n.ap-code {\n flex: 1 1 auto;\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\",\n monospace;\n font-size: 12.5px;\n line-height: 1.5;\n resize: vertical;\n white-space: pre;\n overflow-x: auto;\n}\n\n.ap-code:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n@media (max-width: 560px) {\n .ap-panel-wide {\n width: auto;\n }\n\n .ap-copy {\n flex-direction: column;\n }\n}\n";
|
|
22
22
|
|
|
@@ -370,6 +370,14 @@
|
|
|
370
370
|
'button.notes_zero': '',
|
|
371
371
|
'button.notes_one': '1 note',
|
|
372
372
|
'button.notes_n': '{n} notes',
|
|
373
|
+
/* TWO NUMBERS, BECAUSE ONE OF THEM WAS THE WRONG ONE. The count said how
|
|
374
|
+
many notes the page carries, which on a page reviewed for a month is a
|
|
375
|
+
number nobody acts on -- and it kept growing as remarks were fixed. What
|
|
376
|
+
somebody opening the panel wants to know is how much is still in front
|
|
377
|
+
of them. The total stays beside it: it is the difference between "two
|
|
378
|
+
left" and "two, and nobody has looked yet". */
|
|
379
|
+
'button.notes_of': '{open} of {total}',
|
|
380
|
+
'button.help_counts': '{open} still to fix, {total} in all on this page',
|
|
373
381
|
|
|
374
382
|
/* -- The panel ----------------------------------------------------- */
|
|
375
383
|
'panel.title': 'Review notes',
|
|
@@ -858,6 +866,14 @@
|
|
|
858
866
|
let timer = null;
|
|
859
867
|
let rafPending = false;
|
|
860
868
|
|
|
869
|
+
/* The page is somebody else's, and it moves. `observer` watches it WHILE
|
|
870
|
+
annotation mode is on -- never outside it -- and its only job is to raise
|
|
871
|
+
`domDirty`. The work itself belongs to the animation frame in
|
|
872
|
+
refreshPositions: a page that mutates in a loop would otherwise pay for a
|
|
873
|
+
full re-anchoring on every single mutation. */
|
|
874
|
+
let observer = null;
|
|
875
|
+
let domDirty = false;
|
|
876
|
+
|
|
861
877
|
/* What we did NOT manage to read at the last load. We count it so we can say
|
|
862
878
|
it: a note skipped in silence is a remark that disappears. */
|
|
863
879
|
let skipped = { newer: 0, unreadable: 0, unknown: 0 };
|
|
@@ -1384,6 +1400,53 @@
|
|
|
1384
1400
|
}
|
|
1385
1401
|
};
|
|
1386
1402
|
|
|
1403
|
+
/* -- 12 bis. When the page redraws itself under the tool -------------------
|
|
1404
|
+
`anchor()` resolves the elements ONCE. A page that replaces a piece of its
|
|
1405
|
+
own DOM -- a click that re-renders a block, a framework that swaps a node
|
|
1406
|
+
for an equivalent one -- leaves us holding a node that is no longer in the
|
|
1407
|
+
document: it measures 0x0, and the badge does not move, it VANISHES.
|
|
1408
|
+
|
|
1409
|
+
The two functions below are what lets refreshPositions notice and repair
|
|
1410
|
+
that, without the panel paying for it. */
|
|
1411
|
+
|
|
1412
|
+
/** True as soon as one remembered element has left the document. This is the
|
|
1413
|
+
safety net, and it is deliberately independent of the MutationObserver:
|
|
1414
|
+
an observer can be missing, disconnected, or blind to a change made
|
|
1415
|
+
before it was hooked up -- a detached node cannot hide. */
|
|
1416
|
+
const anchorsStale = () => {
|
|
1417
|
+
for (let i = 0; i < anchored.length; i += 1) {
|
|
1418
|
+
if (!document.contains(anchored[i].element)) return true;
|
|
1419
|
+
}
|
|
1420
|
+
return false;
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* What the PANEL would show of the current anchoring, as a string.
|
|
1425
|
+
*
|
|
1426
|
+
* The panel is rebuilt from scratch by drawPanel(), which costs the reader
|
|
1427
|
+
* their scroll position, their focus, and any reply half typed. So it is
|
|
1428
|
+
* only ever redrawn when this string changes -- that is, when a note has
|
|
1429
|
+
* actually moved between "anchored" and "orphaned", or when the groups no
|
|
1430
|
+
* longer hold the same notes. An element swapped for an equivalent one
|
|
1431
|
+
* changes NOTHING here, and rightly: only the markers have to be redrawn.
|
|
1432
|
+
*
|
|
1433
|
+
* Note identifiers are used, never the elements: two different nodes
|
|
1434
|
+
* carrying the same notes are the same thing as far as the panel goes.
|
|
1435
|
+
*/
|
|
1436
|
+
const anchorSignature = () => {
|
|
1437
|
+
const parts = [];
|
|
1438
|
+
for (let i = 0; i < anchored.length; i += 1) {
|
|
1439
|
+
const ids = [];
|
|
1440
|
+
for (let j = 0; j < anchored[i].notes.length; j += 1) {
|
|
1441
|
+
ids.push(anchored[i].notes[j].id);
|
|
1442
|
+
}
|
|
1443
|
+
parts.push(ids.join(','));
|
|
1444
|
+
}
|
|
1445
|
+
const lost = [];
|
|
1446
|
+
for (let i = 0; i < orphans.length; i += 1) lost.push(orphans[i].id);
|
|
1447
|
+
return parts.join('|') + '/' + lost.join(',');
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1387
1450
|
/* ==== 60-ui.js ==== */
|
|
1388
1451
|
|
|
1389
1452
|
/* -- 13. Building the interface -------------------------------------------
|
|
@@ -1582,12 +1645,40 @@
|
|
|
1582
1645
|
}
|
|
1583
1646
|
};
|
|
1584
1647
|
|
|
1648
|
+
/**
|
|
1649
|
+
* Redoes the anchoring when -- and only when -- the page has moved under us.
|
|
1650
|
+
*
|
|
1651
|
+
* Two triggers, on purpose. `domDirty` is raised by the MutationObserver,
|
|
1652
|
+
* which sees everything including a swap that leaves the badge in exactly the
|
|
1653
|
+
* same place; `anchorsStale()` sees a detached node even when no observer is
|
|
1654
|
+
* running. Either one alone would have a blind spot.
|
|
1655
|
+
*
|
|
1656
|
+
* THE PANEL IS ONLY REDRAWN IF THE RESULT CHANGED. drawPanel() rebuilds the
|
|
1657
|
+
* list from nothing: called at every tick it would wipe the reader's scroll
|
|
1658
|
+
* position, the focus, and any reply being typed. The signature says whether
|
|
1659
|
+
* a note actually changed side; the markers, cheap and stateless, are redrawn
|
|
1660
|
+
* by the caller in any case.
|
|
1661
|
+
*/
|
|
1662
|
+
const reanchor = () => {
|
|
1663
|
+
if (!domDirty && !anchorsStale()) return;
|
|
1664
|
+
domDirty = false;
|
|
1665
|
+
const before = anchorSignature();
|
|
1666
|
+
anchor();
|
|
1667
|
+
/* A note can also come BACK from the orphan list here: the page had not
|
|
1668
|
+
finished rendering when we first looked, and now it has. That is a real
|
|
1669
|
+
change, and it is worth the redraw. */
|
|
1670
|
+
if (anchorSignature() !== before) drawPanel();
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1585
1673
|
const refreshPositions = () => {
|
|
1586
1674
|
if (rafPending) return;
|
|
1587
1675
|
rafPending = true;
|
|
1588
1676
|
window.requestAnimationFrame(() => {
|
|
1589
1677
|
rafPending = false;
|
|
1590
1678
|
if (!mode) return;
|
|
1679
|
+
// BEFORE drawing: the markers must be measured on the elements that
|
|
1680
|
+
// are in the document now, not on the ones that were.
|
|
1681
|
+
reanchor();
|
|
1591
1682
|
drawMarkers();
|
|
1592
1683
|
if (hovered && document.contains(hovered)) showHighlight(hovered);
|
|
1593
1684
|
if (target && document.contains(target)) positionForm(target);
|
|
@@ -2056,13 +2147,30 @@
|
|
|
2056
2147
|
ui.footer.appendChild(forget);
|
|
2057
2148
|
}
|
|
2058
2149
|
|
|
2150
|
+
/* WHAT IS STILL IN FRONT OF THE READER, AND HOW MANY THERE HAVE BEEN.
|
|
2151
|
+
The same test the panel uses to fold a note away, applied to the whole
|
|
2152
|
+
list: resolved AND online has done its job, anything else is still
|
|
2153
|
+
there. Orphans are counted with the rest -- they are shown, so they are
|
|
2154
|
+
in front of somebody.
|
|
2155
|
+
|
|
2156
|
+
The single number is kept while nothing has been fixed yet: "3 notes"
|
|
2157
|
+
says it better than "3 of 3", and that is the state a page spends its
|
|
2158
|
+
first day in. */
|
|
2059
2159
|
const total = notes.length;
|
|
2060
|
-
|
|
2061
|
-
|
|
2160
|
+
let open = 0;
|
|
2161
|
+
for (let i = 0; i < notes.length; i += 1) {
|
|
2162
|
+
const n = notes[i];
|
|
2163
|
+
if (!(n.resolved_at && alreadyDeployed(n.resolved_version))) open += 1;
|
|
2164
|
+
}
|
|
2165
|
+
ui.buttonCount.textContent = (total && open !== total)
|
|
2166
|
+
? T('button.notes_of', { open: open, total: total })
|
|
2167
|
+
: readableCount(total, 'button.notes_zero', 'button.notes_one', 'button.notes_n');
|
|
2062
2168
|
// The button carries the failure: someone who does not open it must be
|
|
2063
2169
|
// able to see, at a glance, that something is wrong.
|
|
2064
2170
|
ui.button.classList.toggle('ap-failed', !!currentFailure);
|
|
2065
|
-
ui.button.title = currentFailure ? currentFailure.title
|
|
2171
|
+
ui.button.title = currentFailure ? currentFailure.title
|
|
2172
|
+
: (total ? T('button.help_counts', { open: open, total: total })
|
|
2173
|
+
: T('button.help'));
|
|
2066
2174
|
};
|
|
2067
2175
|
|
|
2068
2176
|
/** Brings a note forward in the panel, without changing anything on the
|
|
@@ -2306,6 +2414,29 @@
|
|
|
2306
2414
|
// without emitting either scroll or resize.
|
|
2307
2415
|
timer = window.setInterval(refreshPositions, 500);
|
|
2308
2416
|
|
|
2417
|
+
/* THE PAGE REDRAWS ITSELF, AND THE BADGES MUST NOT DIE OF IT. A click on
|
|
2418
|
+
the site's own button can replace a whole block: the node we remembered
|
|
2419
|
+
is then detached, it measures 0x0, and the badge simply stops being
|
|
2420
|
+
drawn.
|
|
2421
|
+
|
|
2422
|
+
The callback raises a flag AND NOTHING ELSE. All the work -- redoing
|
|
2423
|
+
the anchoring, deciding whether the panel changed -- happens in the
|
|
2424
|
+
animation frame of refreshPositions, at most once per frame. A page
|
|
2425
|
+
that mutates in a loop would otherwise pay for a full re-anchoring on
|
|
2426
|
+
every mutation record, which is how an annotation layer turns a lively
|
|
2427
|
+
page into a slow one.
|
|
2428
|
+
|
|
2429
|
+
Observed on document.body, childList and subtree: an element swapped
|
|
2430
|
+
anywhere is what breaks us. It never crosses into the shadow root, so
|
|
2431
|
+
the tool cannot see -- or answer -- its own drawing. */
|
|
2432
|
+
if (typeof MutationObserver === 'function') {
|
|
2433
|
+
observer = new MutationObserver(() => {
|
|
2434
|
+
domDirty = true;
|
|
2435
|
+
});
|
|
2436
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2437
|
+
}
|
|
2438
|
+
domDirty = false;
|
|
2439
|
+
|
|
2309
2440
|
// The markers for what we ALREADY know, straight away; the server is
|
|
2310
2441
|
// asked next and will correct if there is anything new. Waiting for the
|
|
2311
2442
|
// network to show what is already on screen would suggest an empty page.
|
|
@@ -2330,6 +2461,13 @@
|
|
|
2330
2461
|
document.removeEventListener('keydown', onKey, true);
|
|
2331
2462
|
window.removeEventListener('scroll', refreshPositions, true);
|
|
2332
2463
|
window.removeEventListener('resize', refreshPositions);
|
|
2464
|
+
// Outside annotation mode the tool watches nothing: the site is the
|
|
2465
|
+
// site's again, and it pays nothing for us.
|
|
2466
|
+
if (observer) {
|
|
2467
|
+
observer.disconnect();
|
|
2468
|
+
observer = null;
|
|
2469
|
+
}
|
|
2470
|
+
domDirty = false;
|
|
2333
2471
|
if (timer) {
|
|
2334
2472
|
window.clearInterval(timer);
|
|
2335
2473
|
timer = null;
|
package/labels/fr.json
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
"button.notes_zero": "",
|
|
6
6
|
"button.notes_one": "1 note",
|
|
7
7
|
"button.notes_n": "{n} notes",
|
|
8
|
+
"button.notes_of": "{open} sur {total}",
|
|
9
|
+
"button.help_counts": "{open} encore à corriger, {total} en tout sur cette page",
|
|
8
10
|
"panel.title": "Notes de relecture",
|
|
9
11
|
"panel.close": "Fermer",
|
|
10
12
|
"panel.instructions": "Cliquez sur un élément de la page pour écrire une remarque à son sujet.",
|
package/package.json
CHANGED