annotepage-client 2.7.0 → 2.9.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 +2 -2
- package/dist/HASHES.txt +2 -0
- package/dist/annotepage.js +123 -5
- package/labels/fr.json +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Load the client once, on a page of the site, with `data-setup` and **without**
|
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
36
|
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
37
|
-
integrity="sha384-
|
|
37
|
+
integrity="sha384-AoKEUi40eHALo8q+pBcFmYeJx5+6baVWGfpwR5xyCcw4bfUaP3lRKavPbClxuueS"
|
|
38
38
|
crossorigin="anonymous"
|
|
39
39
|
data-server="https://<your-server>/annotepage/api.php"
|
|
40
40
|
data-setup
|
|
@@ -55,7 +55,7 @@ on the server. No network request is made at that point.
|
|
|
55
55
|
|
|
56
56
|
```html
|
|
57
57
|
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
58
|
-
integrity="sha384-
|
|
58
|
+
integrity="sha384-AoKEUi40eHALo8q+pBcFmYeJx5+6baVWGfpwR5xyCcw4bfUaP3lRKavPbClxuueS"
|
|
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.9.0 sha384-AoKEUi40eHALo8q+pBcFmYeJx5+6baVWGfpwR5xyCcw4bfUaP3lRKavPbClxuueS 183961 bytes
|
|
2
|
+
2.8.0 sha384-OJwckEoVZ2bTAdnTOon5j4gOXCZiHF6VQtf0efutPWJq4YOyLgweYrrntZfRmaum 182813 bytes
|
|
1
3
|
2.7.0 sha384-i7lpLUYLSVSOFMi+9W+EMhMbYDe7Nff/TF2vUuyQvVQ+XHph8rGJDXn2s4aNPSOT 175916 bytes
|
|
2
4
|
2.6.0 sha384-PVE6tjzC72/VHyRLdxHLpwMLIp9ji+VUBapD+j3vmoi69rpkXn0h+ZicxwORUdQt 167714 bytes
|
|
3
5
|
2.5.0 sha384-8bHU9VMghgfLL4PSwE3XJZXcDYqhd5SlUE31yMgaI/2ZNhH3Z18he6X7BsF7kvx1 167714 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.9.0
|
|
5
5
|
Format version : 2 (see FORMAT.md)
|
|
6
6
|
Licence : MIT
|
|
7
7
|
|
|
@@ -16,9 +16,9 @@
|
|
|
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.9.0";
|
|
20
20
|
const FORMAT = 2;
|
|
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, the \"a newer client exists\" line and the \"this\n server speaks another format\" line are the same object on screen: a\n standing statement about what one is looking at, above the notes and above\n the failures. One rule, so they cannot drift apart -- and the third one\n joins it rather than inventing a colour, precisely because a protocol\n disagreement is a fact about the project, not an incident. */\n.ap-public,\n.ap-upgrade,\n.ap-format {\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";
|
|
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/* The other side. Two positions, right and left, and no third one: in\n annotation mode the layer takes every click, so every pixel of panel is a\n pixel of page that can no longer be pointed at. A floating panel would\n only move that loss around.\n\n \"bottom\" IS NOT REDECLARED, on purpose: the panel keeps the same 72 px of\n clearance above the floating button either way, and that clearance is the\n same on both sides because the button moves WITH it.\n\n AND THE BUTTON MOVES WITH IT, which was decided the other way first. The\n argument for leaving it was that it is the tool's only visible trace at\n rest, so an anchor that moved would stop being one. The owner's answer,\n and it is the better one: \"move left\" is asked of the TOOL, not of half of\n it. A panel on one edge and the control that opens it on the other makes\n the eye cross the whole window for one gesture, and somebody who set the\n side set it for the tool.\n\n TWO THINGS THIS RULE DEPENDS ON, both fragile:\n - it is written with ONE class, so it weighs exactly what \".ap-panel\"\n weighs;\n - it is written BEFORE the narrow block below.\n Reverse either and it wins under 560 px, where the panel is a bottom band\n spanning the width -- and a panel pinned to the left there is the full-\n height panel whose defect at 375 px is described in that block. */\n.ap-left {\n right: auto;\n left: 12px;\n}\n/* The button follows, and it is put on the LAYER and not on the panel: the\n button is not inside the panel, so the side has to be readable from a\n common ancestor. Same offset mirrored, same bottom -- only the edge\n changes. */\n.ap-layer.ap-left-side .ap-button {\n right: auto;\n left: 16px;\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, the \"a newer client exists\" line and the \"this\n server speaks another format\" line are the same object on screen: a\n standing statement about what one is looking at, above the notes and above\n the failures. One rule, so they cannot drift apart -- and the third one\n joins it rather than inventing a colour, precisely because a protocol\n disagreement is a fact about the project, not an incident. */\n.ap-public,\n.ap-upgrade,\n.ap-format {\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 /* A band spanning the width has no side, so the choice is inert here and\n the button says so by not being there. Offering a control that visibly\n does nothing is worse than offering none. The stored side is untouched:\n it is waiting for the wide screen it was chosen on. */\n .ap-side-toggle {\n display: none;\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
|
|
|
23
23
|
/* ==== 00-preamble.js ==== */
|
|
24
24
|
|
|
@@ -382,6 +382,11 @@
|
|
|
382
382
|
/* -- The panel ----------------------------------------------------- */
|
|
383
383
|
'panel.title': 'Review notes',
|
|
384
384
|
'panel.close': 'Close',
|
|
385
|
+
/* The panel has two positions and the button says where it is GOING, not
|
|
386
|
+
where it is: a label that named the current side would read as a
|
|
387
|
+
statement and be pressed by nobody. */
|
|
388
|
+
'panel.move_left': 'Move left',
|
|
389
|
+
'panel.move_right': 'Move right',
|
|
385
390
|
'panel.instructions': 'Click an element of the page to write a remark about it.',
|
|
386
391
|
'panel.escape': 'Press Escape to stop.',
|
|
387
392
|
'panel.empty': 'Nobody has written a note on this page yet.',
|
|
@@ -894,6 +899,7 @@
|
|
|
894
899
|
let hovered = null; // element under the pointer
|
|
895
900
|
let currentFailure = null; // { title, detail } shown in the panel
|
|
896
901
|
let author = ''; // read at startup: see 90-boot
|
|
902
|
+
let side = 'right'; // which edge the panel sits on: see readSide
|
|
897
903
|
let timer = null;
|
|
898
904
|
let rafPending = false;
|
|
899
905
|
|
|
@@ -928,6 +934,37 @@
|
|
|
928
934
|
// the name is there to know who to talk to, not to prove who one is.
|
|
929
935
|
const AUTHOR_KEY = 'annotepage/author';
|
|
930
936
|
|
|
937
|
+
/* The side the panel sits on. GLOBAL, exactly like the name above and
|
|
938
|
+
deliberately NOT under the project id: which edge a panel should sit on is
|
|
939
|
+
a fact about the screen and the hand in front of it, not about the project
|
|
940
|
+
being reviewed. Asking again on the next project would be asking the same
|
|
941
|
+
person the same question twice.
|
|
942
|
+
|
|
943
|
+
Two values and no third one. Annotation mode makes the layer take every
|
|
944
|
+
click, so every pixel of panel is a pixel of page that can no longer be
|
|
945
|
+
pointed at -- a panel that could sit anywhere would only move that loss
|
|
946
|
+
around, and would have to be moved back. */
|
|
947
|
+
const SIDE_KEY = 'annotepage/side';
|
|
948
|
+
|
|
949
|
+
function readSide() {
|
|
950
|
+
try {
|
|
951
|
+
return window.localStorage.getItem(SIDE_KEY) === 'left' ? 'left' : 'right';
|
|
952
|
+
} catch (e) {
|
|
953
|
+
// Without storage the panel starts on the right every visit: the
|
|
954
|
+
// choice still works, it is just not remembered.
|
|
955
|
+
return 'right';
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function writeSide(value) {
|
|
960
|
+
side = value === 'left' ? 'left' : 'right';
|
|
961
|
+
try {
|
|
962
|
+
window.localStorage.setItem(SIDE_KEY, side);
|
|
963
|
+
} catch (e) {
|
|
964
|
+
// No consequence: only the memory of the side is lost.
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
931
968
|
/* The key is stored UNDER THE PROJECT ID. That naming is not cosmetic: two
|
|
932
969
|
projects reviewed from the same browser must not overwrite each other.
|
|
933
970
|
|
|
@@ -1677,7 +1714,18 @@
|
|
|
1677
1714
|
const close = create('button', 'ap-link', T('panel.close'));
|
|
1678
1715
|
close.type = 'button';
|
|
1679
1716
|
close.addEventListener('click', () => leaveMode());
|
|
1717
|
+
/* A REAL BUTTON, in the header, beside Close -- not a draggable title
|
|
1718
|
+
bar. A title bar reads as a title and not as a handle; without
|
|
1719
|
+
snapping, a dragged panel ends up crooked; and under a finger the drag
|
|
1720
|
+
fights the page's own scrolling. Being a button, it is reachable from
|
|
1721
|
+
the keyboard for free, which is why there is no shortcut for this:
|
|
1722
|
+
onKey is a capturing document listener and would fire while somebody
|
|
1723
|
+
is typing their remark in the textarea. */
|
|
1724
|
+
const sideToggle = create('button', 'ap-link ap-side-toggle');
|
|
1725
|
+
sideToggle.type = 'button';
|
|
1726
|
+
sideToggle.addEventListener('click', () => moveSide());
|
|
1680
1727
|
header.appendChild(title);
|
|
1728
|
+
header.appendChild(sideToggle);
|
|
1681
1729
|
header.appendChild(close);
|
|
1682
1730
|
const instructions = create('div', 'ap-panel-instructions');
|
|
1683
1731
|
instructions.appendChild(create('div', null, T('panel.instructions')));
|
|
@@ -1703,10 +1751,43 @@
|
|
|
1703
1751
|
label: label,
|
|
1704
1752
|
markers: markers,
|
|
1705
1753
|
panel: panel,
|
|
1754
|
+
sideToggle: sideToggle,
|
|
1706
1755
|
body: body,
|
|
1707
1756
|
footer: footer,
|
|
1708
1757
|
form: form
|
|
1709
1758
|
};
|
|
1759
|
+
|
|
1760
|
+
applySide();
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
/* -- The side the panel sits on ------------------------------------------
|
|
1764
|
+
The class carries ONE name, deliberately: ".ap-left" weighs exactly as
|
|
1765
|
+
much as ".ap-panel", so the narrow block further down the stylesheet --
|
|
1766
|
+
where the panel is a bottom band and the side means nothing -- wins over
|
|
1767
|
+
it by being written later. */
|
|
1768
|
+
|
|
1769
|
+
const applySide = () => {
|
|
1770
|
+
if (!ui) return;
|
|
1771
|
+
if (side === 'left') ui.panel.classList.add('ap-left');
|
|
1772
|
+
else ui.panel.classList.remove('ap-left');
|
|
1773
|
+
/* AND THE FLOATING BUTTON GOES WITH IT. It is not inside the panel, so
|
|
1774
|
+
the side is carried by the layer, which is the ancestor they share.
|
|
1775
|
+
Moving only the panel would put the control that opens it on the
|
|
1776
|
+
opposite edge from the thing it opens. */
|
|
1777
|
+
if (side === 'left') ui.layer.classList.add('ap-left-side');
|
|
1778
|
+
else ui.layer.classList.remove('ap-left-side');
|
|
1779
|
+
ui.sideToggle.textContent = side === 'left'
|
|
1780
|
+
? T('panel.move_right') : T('panel.move_left');
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
const moveSide = () => {
|
|
1784
|
+
writeSide(side === 'left' ? 'right' : 'left');
|
|
1785
|
+
applySide();
|
|
1786
|
+
/* The form is placed against the band the panel occupies, so the band
|
|
1787
|
+
having moved, it has to be measured again -- otherwise the form stays
|
|
1788
|
+
clamped away from an edge that is now free, and towards the one that
|
|
1789
|
+
is not. */
|
|
1790
|
+
if (target && document.contains(target)) positionForm(target);
|
|
1710
1791
|
};
|
|
1711
1792
|
|
|
1712
1793
|
/* -- 14. Highlight and markers ------------------------------------------- */
|
|
@@ -2348,14 +2429,48 @@
|
|
|
2348
2429
|
}
|
|
2349
2430
|
const width = form.offsetWidth || 340;
|
|
2350
2431
|
const height = form.offsetHeight || 260;
|
|
2351
|
-
|
|
2352
|
-
|
|
2432
|
+
|
|
2433
|
+
/* THE PANEL IS AN OBSTACLE, not a neighbour. In annotation mode the
|
|
2434
|
+
layer takes every click, so a form drawn under the panel is a form
|
|
2435
|
+
whose Send button cannot be pressed.
|
|
2436
|
+
|
|
2437
|
+
This is a defect that PREDATES the left/right choice, and it was
|
|
2438
|
+
already there on the right: the only clamp was "window.innerWidth -
|
|
2439
|
+
width - 12", which is a point INSIDE a panel sitting at right: 12.
|
|
2440
|
+
Point at anything near the right edge and the form landed under the
|
|
2441
|
+
panel. Moving the panel to the left would simply have mirrored it. So
|
|
2442
|
+
the form is now clamped against the free strip beside the panel, on
|
|
2443
|
+
whichever side that is. */
|
|
2444
|
+
const band = panelBand();
|
|
2445
|
+
let low = 8;
|
|
2446
|
+
let high = window.innerWidth - width - 12;
|
|
2447
|
+
if (band) {
|
|
2448
|
+
if (side === 'left') low = Math.max(low, band.right + 8);
|
|
2449
|
+
else high = Math.min(high, band.left - 8 - width);
|
|
2450
|
+
}
|
|
2451
|
+
let left = Math.min(Math.max(r.left, low), high);
|
|
2452
|
+
/* Between 560 px -- where the panel becomes a band and this whole branch
|
|
2453
|
+
stops running -- and roughly 720, no strip is wide enough for the
|
|
2454
|
+
form. It then starts at the free edge, which keeps its beginning
|
|
2455
|
+
reachable; there is no placement that does better on that width. */
|
|
2456
|
+
if (high < low) left = low;
|
|
2353
2457
|
let top = r.bottom + 8;
|
|
2354
2458
|
if (top + height > window.innerHeight - 12) top = Math.max(8, r.top - height - 8);
|
|
2355
2459
|
form.style.left = Math.max(8, left) + 'px';
|
|
2356
2460
|
form.style.top = Math.max(8, top) + 'px';
|
|
2357
2461
|
};
|
|
2358
2462
|
|
|
2463
|
+
/** The horizontal strip the panel takes, or null when it is not showing.
|
|
2464
|
+
Measured rather than computed from the stylesheet: the width is capped by
|
|
2465
|
+
max-width on a narrow window, and a value read from the CSS would be the
|
|
2466
|
+
width the panel would have had. */
|
|
2467
|
+
const panelBand = () => {
|
|
2468
|
+
if (!ui || !ui.panel.classList.contains('ap-open')) return null;
|
|
2469
|
+
const r = ui.panel.getBoundingClientRect();
|
|
2470
|
+
if (r.width === 0) return null;
|
|
2471
|
+
return { left: r.left, right: r.right };
|
|
2472
|
+
};
|
|
2473
|
+
|
|
2359
2474
|
/** True on the screens where the panel and the form do not fit side by
|
|
2360
2475
|
side. The threshold is the stylesheet's. */
|
|
2361
2476
|
const narrowScreen = () => window.innerWidth <= 560;
|
|
@@ -3280,6 +3395,9 @@
|
|
|
3280
3395
|
|
|
3281
3396
|
const start = () => {
|
|
3282
3397
|
author = readAuthor();
|
|
3398
|
+
// Read ONCE, here, and never again: the side is asked of the storage at
|
|
3399
|
+
// startup like the name, not at every draw.
|
|
3400
|
+
side = readSide();
|
|
3283
3401
|
|
|
3284
3402
|
// Outside the project's scope: silence. So the tag can live in a
|
|
3285
3403
|
// template shared by the whole site.
|
package/labels/fr.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"button.help_counts": "{open} encore à corriger, {total} en tout sur cette page",
|
|
10
10
|
"panel.title": "Notes de relecture",
|
|
11
11
|
"panel.close": "Fermer",
|
|
12
|
+
"panel.move_left": "Déplacer à gauche",
|
|
13
|
+
"panel.move_right": "Déplacer à droite",
|
|
12
14
|
"panel.instructions": "Cliquez sur un élément de la page pour écrire une remarque à son sujet.",
|
|
13
15
|
"panel.escape": "Touche Échap pour arrêter.",
|
|
14
16
|
"panel.empty": "Personne n’a encore écrit de note sur cette page.",
|
package/package.json
CHANGED