annotepage-client 2.0.0 → 2.0.2

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 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.0.0/dist/annotepage.js"
37
- integrity="sha384-A5Wrzv2mtFVnn8Mt0xC7BglTbxbb75unG3CNt5YHUNQ6X5QcwfFPI0OWtANjN2V/"
36
+ <script src="https://<your-cdn>/annotepage-client@2.0.2/dist/annotepage.js"
37
+ integrity="sha384-aSkx1XQLhcYpDQrpoD8yUn0Wu00BHbi1KktWiXqMQufioNtReQw+tm804/ZzNfo8"
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.0.0/dist/annotepage.js"
58
- integrity="sha384-A5Wrzv2mtFVnn8Mt0xC7BglTbxbb75unG3CNt5YHUNQ6X5QcwfFPI0OWtANjN2V/"
57
+ <script src="https://<your-cdn>/annotepage-client@2.0.2/dist/annotepage.js"
58
+ integrity="sha384-aSkx1XQLhcYpDQrpoD8yUn0Wu00BHbi1KktWiXqMQufioNtReQw+tm804/ZzNfo8"
59
59
  crossorigin="anonymous"
60
60
  data-server="https://<your-server>/annotepage/api.php"
61
61
  data-project="7Qb1kZ3xNvA9dLpEqKf2Zt"
@@ -200,6 +200,21 @@ variables nor the theme of the host site.
200
200
  `textContent` everywhere, `innerHTML` nowhere: the text of a note is typed by a
201
201
  human and is never interpreted as markup.
202
202
 
203
+ ## The interface says "key", the format says "salt"
204
+
205
+ Since 2.0.2 every string a person reads says **key** -- "The key of this
206
+ project is needed", "The project key (43 characters)". Cryptographically that
207
+ is what it is: 32 bytes of key material, from which HKDF derives the project
208
+ id, the AES-256-GCM key and the blind-index key. A salt, by definition, is
209
+ public; this is the only secret there is, and the word invited people to treat
210
+ it as if it were not.
211
+
212
+ What did NOT change, deliberately: the label KEYS are still `salt.title`,
213
+ `salt.help` and so on -- renaming them would silently break every translation
214
+ file already written against them -- and `FORMAT.md`, the storage key
215
+ `annotepage/salt/<project>`, and the MCP's `salt` configuration field are
216
+ unchanged, because those are a format and a contract rather than prose.
217
+
203
218
  ## Translating, or changing a word
204
219
 
205
220
  Every text shown is in `src/15-labels.js`, in a flat object, in English. Two
package/dist/HASHES.txt CHANGED
@@ -1 +1,3 @@
1
- 2.0.0 sha384-A5Wrzv2mtFVnn8Mt0xC7BglTbxbb75unG3CNt5YHUNQ6X5QcwfFPI0OWtANjN2V/ 132399 bytes
1
+ 2.0.2 sha384-ozz/RzNqWwcqwcKVLVrrJht2k97gwDYfvH4AkZyUAxW0lPbKiip8wj9hDvD6PNhy 134184 bytes
2
+ 2.0.1 sha384-aSkx1XQLhcYpDQrpoD8yUn0Wu00BHbi1KktWiXqMQufioNtReQw+tm804/ZzNfo8 134184 bytes
3
+ 2.0.0 sha384-hqDI4neEKI/hhkIKK5P4GVNRQrJ5dFYvBu9JovxWccyK9irmi+ouvhrgrumlXnXl 134205 bytes
@@ -1,7 +1,7 @@
1
1
  /* ============================================================================
2
2
  annotepage -- the annotation layer, browser side.
3
3
 
4
- Package version : 2.0.0
4
+ Package version : 2.0.2
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.0.0";
19
+ const TOOL_VERSION = "2.0.2";
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/* ----------------------------------------------------------------------------\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.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
 
@@ -397,6 +397,12 @@
397
397
  'note.resolved': 'Resolved on {date} by {by}',
398
398
  'note.resolved_pending': 'Resolved, waiting to be deployed',
399
399
  'note.resolved_version': 'Fix shipped in version {version}',
400
+ 'setup.localhost': 'You are on a local machine, so three of the values above '
401
+ + 'need care. The origins line names an origin every developer shares -- '
402
+ + 'never put it in a relay configuration others use. The tag points at '
403
+ + 'this host, which will not exist once the site moves. And a note '
404
+ + 'written here lands on any page with the same path, staging and '
405
+ + 'production included: the index is the path alone.',
400
406
 
401
407
  /* -- Failures. They are shown, they are never kept quiet ----------- */
402
408
  'error.title': 'Your remark has NOT been saved',
@@ -446,10 +452,10 @@
446
452
  '{n} notes were written by a more recent version of annotepage and '
447
453
  + 'could not be read.',
448
454
  'read.unreadable_one':
449
- '1 note could not be decrypted. The salt in this browser may not be '
455
+ '1 note could not be decrypted. The key in this browser may not be '
450
456
  + 'the one it was written with.',
451
457
  'read.unreadable_n':
452
- '{n} notes could not be decrypted. The salt in this browser may not be '
458
+ '{n} notes could not be decrypted. The key in this browser may not be '
453
459
  + 'the one they were written with.',
454
460
  'read.unknown_one':
455
461
  '1 note is written in a mode this tool does not know, and was not '
@@ -464,52 +470,52 @@
464
470
  'marker.n': '{n} notes here',
465
471
 
466
472
  /* -- The salt: the only secret, and it cannot be recovered ---------- */
467
- 'salt.title': 'The salt of this project is needed',
473
+ 'salt.title': 'The key of this project is needed',
468
474
  'salt.help':
469
475
  'The notes of this project are encrypted in your browser. Without the '
470
- + 'project salt, this browser can neither read them nor write any. Ask '
476
+ + 'project key, this browser can neither read them nor write any. Ask '
471
477
  + 'whoever installed the tool for it, and paste it below. It will be '
472
478
  + 'remembered by this browser, for this site.',
473
- 'salt.label': 'The project salt (43 characters)',
474
- 'salt.confirm': 'Use this salt',
475
- 'salt.empty': 'Paste the salt before confirming.',
479
+ 'salt.label': 'The project key (43 characters)',
480
+ 'salt.confirm': 'Use this key',
481
+ 'salt.empty': 'Paste the key before confirming.',
476
482
  'salt.shape':
477
- 'This is not a salt: 43 characters are expected, from A-Z a-z 0-9 - _, '
483
+ 'This is not a key: 43 characters are expected, from A-Z a-z 0-9 - _, '
478
484
  + 'with no space and no decorative dash. Copy it in one block.',
479
485
  'salt.wrong':
480
- 'This salt is not the one for this project. Nothing was sent, nothing '
481
- + 'was decrypted. Check that you are pasting the salt of the right '
486
+ 'This key is not the one for this project. Nothing was sent, nothing '
487
+ + 'was decrypted. Check that you are pasting the key of the right '
482
488
  + 'project.',
483
489
  'salt.origin_changed':
484
- 'This salt is remembered per browser AND per domain. The day staging '
490
+ 'This key is remembered per browser AND per domain. The day staging '
485
491
  + 'becomes production, it has to be pasted once more on the new domain '
486
492
  + '-- the notes themselves do not move.',
487
493
  'salt.not_kept':
488
- 'This browser refuses to remember the salt (private browsing, or '
489
- + 'storage blocked). The tool works for this page, but the salt will '
494
+ 'This browser refuses to remember the key (private browsing, or '
495
+ + 'storage blocked). The tool works for this page, but the key will '
490
496
  + 'have to be pasted again on the next load.',
491
- 'salt.replace': 'Paste another salt',
492
- 'salt.forget': 'Forget the salt on this browser',
497
+ 'salt.replace': 'Paste another key',
498
+ 'salt.forget': 'Forget the key on this browser',
493
499
 
494
500
  /* -- Setup --------------------------------------------------------- */
495
501
  'setup.title': 'Install annotepage on this site',
496
- 'setup.generate': 'Generate a salt and create the project',
502
+ 'setup.generate': 'Generate a key and create the project',
497
503
  'setup.warning_title': 'Read this before continuing',
498
504
  'setup.warning':
499
- 'The salt below is the ONLY secret of the project, and nobody else has '
505
+ 'The key below is the ONLY secret of the project, and nobody else has '
500
506
  + 'it: not the server, not the author of the tool, nobody you can ask. '
501
- + 'SALT LOST = NOTES LOST, for good, with no recovery. Put it away now, '
507
+ + 'KEY LOST = NOTES LOST, for good, with no recovery. Put it away now, '
502
508
  + 'where your team keeps its passwords, before continuing.',
503
- 'setup.salt': 'The project salt -- keep it',
509
+ 'setup.salt': 'The project key -- keep it',
504
510
  'setup.project': 'The project id -- public, it goes into the page',
505
511
  'setup.tag': 'The tag to paste at the end of <body>, on the pages to annotate',
506
512
  'setup.server': 'To declare in the server configuration',
507
513
  'setup.copy': 'Copy',
508
514
  'setup.copied': 'Copied',
509
515
  'setup.copy_failed': 'Select the text and copy it by hand.',
510
- 'setup.continue': 'I have put the salt away, continue',
516
+ 'setup.continue': 'I have put the key away, continue',
511
517
  'setup.done':
512
- 'The salt is remembered by this browser. Paste the tag above into the '
518
+ 'The key is remembered by this browser. Paste the tag above into the '
513
519
  + 'pages, declare the project on the server, then reload this page: the '
514
520
  + 'tool takes over.',
515
521
  'setup.no_server':
@@ -2269,6 +2275,23 @@
2269
2275
  return t;
2270
2276
  };
2271
2277
 
2278
+ /* Is this page served from a local development machine?
2279
+
2280
+ It matters because the setup screen is about to hand over three things that
2281
+ are all WRONG when it is: an `origins` line naming an origin every developer
2282
+ on earth shares, a tag whose src points at a host that will not exist
2283
+ tomorrow, and a project whose notes will land on staging and production too
2284
+ -- the page index is the PATH ALONE (FORMAT.md section 4), so nothing about
2285
+ where a note was written is recorded anywhere.
2286
+
2287
+ `*.localhost` is included: it resolves to the loopback by RFC 6761 and dev
2288
+ servers hand it out for subdomains. */
2289
+ const isLocalHost = () => {
2290
+ const h = location.hostname;
2291
+ return h === 'localhost' || h === '127.0.0.1' || h === '[::1]' || h === '::1'
2292
+ || /\.localhost$/.test(h);
2293
+ };
2294
+
2272
2295
  const serverConfig = (id) =>
2273
2296
  'project ' + id + '\n'
2274
2297
  + ' origins ' + location.origin + '\n'
@@ -2368,6 +2391,14 @@
2368
2391
  copyBlock(screen.body, T('setup.tag'), tagToPaste(derived.id));
2369
2392
  copyBlock(screen.body, T('setup.server'), serverConfig(derived.id));
2370
2393
 
2394
+ /* Only on a local machine, and only here. Not a runtime badge: a
2395
+ permanent notice on every page load of every developer's app is
2396
+ noise, and localhost is not an error. It is said once, at the
2397
+ moment the three wrong values are handed over. */
2398
+ if (isLocalHost()) {
2399
+ screen.body.appendChild(create('p', 'ap-help', T('setup.localhost')));
2400
+ }
2401
+
2371
2402
  const actions = create('div', 'ap-actions');
2372
2403
  const proceed = create('button', 'ap-primary', T('setup.continue'));
2373
2404
  proceed.type = 'button';
package/labels/fr.json CHANGED
@@ -50,6 +50,7 @@
50
50
  "note.resolved": "Corrigée le {date} par {by}",
51
51
  "note.resolved_pending": "Corrigée, en attente de déploiement",
52
52
  "note.resolved_version": "Correctif livre en version {version}",
53
+ "setup.localhost": "Vous etes sur une machine locale : trois des valeurs ci-dessus demandent attention. La ligne origins nomme une origine que tous les developpeurs partagent -- ne la mettez jamais dans la configuration d'un relais utilise par d'autres. La balise pointe vers cet hote, qui n'existera plus quand le site bougera. Et une note ecrite ici apparaitra sur toute page de meme chemin, preproduction et production comprises : l'index est le chemin seul.",
53
54
  "error.title": "Votre remarque n’a PAS été enregistrée",
54
55
  "error.title_read": "Les notes n’ont pas pu être relues",
55
56
  "error.title_resolution": "L’état de la remarque n’a PAS été changé",
@@ -64,40 +65,40 @@
64
65
  "error.hide": "Masquer",
65
66
  "read.newer_one": "1 note a été écrite par une version plus récente d’annotepage et n’a pas pu être lue.",
66
67
  "read.newer_n": "{n} notes ont été écrites par une version plus récente d’annotepage et n’ont pas pu être lues.",
67
- "read.unreadable_one": "1 note n’a pas pu être déchiffrée. Le sel de ce navigateur n’est peut-être pas celui avec lequel elle a été écrite.",
68
- "read.unreadable_n": "{n} notes n’ont pas pu être déchiffrées. Le sel de ce navigateur n’est peut-être pas celui avec lequel elles ont été écrites.",
68
+ "read.unreadable_one": "1 note n’a pas pu être déchiffrée. La clé de ce navigateur n’est peut-être pas celle avec laquelle elle a été écrite.",
69
+ "read.unreadable_n": "{n} notes n’ont pas pu être déchiffrées. La clé de ce navigateur n’est peut-être pas celle avec laquelle elles ont été écrites.",
69
70
  "read.unknown_one": "1 note est écrite dans un mode que cet outil ne connaît pas et n’a pas été lue.",
70
71
  "read.unknown_n": "{n} notes sont écrites dans un mode que cet outil ne connaît pas et n’ont pas été lues.",
71
72
  "read.title_partial": "Certaines notes n’ont pas pu être lues",
72
73
  "marker.one": "1 note ici",
73
74
  "marker.n": "{n} notes ici",
74
- "salt.title": "Le sel de ce projet est nécessaire",
75
- "salt.help": "Les notes de ce projet sont chiffrées dans votre navigateur. Sans le sel du projet, ce navigateur ne peut ni les lire, ni en écrire. Demandez-le à la personne qui a installé l’outil, et collez-le ci-dessous. Il sera retenu par ce navigateur, pour ce site.",
76
- "salt.label": "Le sel du projet (43 caractères)",
77
- "salt.confirm": "Utiliser ce sel",
78
- "salt.empty": "Collez le sel avant de valider.",
79
- "salt.shape": "Ce n’est pas un sel : on attend 43 caractères parmi A-Z a-z 0-9 - _, sans espace ni tiret décoratif. Recopiez-le d’un seul bloc.",
80
- "salt.wrong": "Ce sel n’est pas celui de ce projet. Rien n’a été envoyé, rien n’a été déchiffré. Vérifiez que vous collez le sel du bon projet.",
81
- "salt.origin_changed": "Ce sel est retenu par navigateur ET par domaine. Le jour où la préproduction devient la production, il faut le recoller une fois sur le nouveau domaine — les notes, elles, ne bougent pas.",
82
- "salt.not_kept": "Ce navigateur refuse de retenir le sel (navigation privée, ou stockage bloqué). L’outil fonctionne pour cette page, mais le sel sera à recoller au prochain chargement.",
83
- "salt.replace": "Coller un autre sel",
84
- "salt.forget": "Oublier le sel sur ce navigateur",
75
+ "salt.title": "La clé de ce projet est nécessaire",
76
+ "salt.help": "Les notes de ce projet sont chiffrées dans votre navigateur. Sans la clé du projet, ce navigateur ne peut ni les lire, ni en écrire. Demandez-la à la personne qui a installé l’outil, et collez-la ci-dessous. Elle sera retenue par ce navigateur, pour ce site.",
77
+ "salt.label": "La clé du projet (43 caractères)",
78
+ "salt.confirm": "Utiliser cette clé",
79
+ "salt.empty": "Collez la clé avant de valider.",
80
+ "salt.shape": "Ce n’est pas une clé : on attend 43 caractères parmi A-Z a-z 0-9 - _, sans espace ni tiret décoratif. Recopiez-la d’un seul bloc.",
81
+ "salt.wrong": "Cette clé n’est pas celle de ce projet. Rien n’a été envoyé, rien n’a été déchiffré. Vérifiez que vous collez la clé du bon projet.",
82
+ "salt.origin_changed": "Cette clé est retenue par navigateur ET par domaine. Le jour où la préproduction devient la production, il faut la recoller une fois sur le nouveau domaine — les notes, elles, ne bougent pas.",
83
+ "salt.not_kept": "Ce navigateur refuse de retenir la clé (navigation privée, ou stockage bloqué). L’outil fonctionne pour cette page, mais la clé sera à recoller au prochain chargement.",
84
+ "salt.replace": "Coller une autre clé",
85
+ "salt.forget": "Oublier la clé sur ce navigateur",
85
86
  "setup.title": "Installer annotepage sur ce site",
86
- "setup.generate": "Engendrer un sel et créer le projet",
87
+ "setup.generate": "Engendrer une clé et créer le projet",
87
88
  "setup.warning_title": "À lire avant de continuer",
88
- "setup.warning": "Le sel ci-dessous est le SEUL secret du projet, et personne d’autre ne l’a : ni le serveur, ni l’auteur de l’outil, ni personne à qui le demander. SEL PERDU = NOTES PERDUES, définitivement, sans récupération possible. Rangez-le maintenant, là où votre équipe range ses mots de passe, avant de continuer.",
89
- "setup.salt": "Le sel du projet — à conserver",
89
+ "setup.warning": "La clé ci-dessous est le SEUL secret du projet, et personne d’autre ne l’a : ni le serveur, ni l’auteur de l’outil, ni personne à qui la demander. CLÉ PERDUE = NOTES PERDUES, définitivement, sans recours. Rangez-la maintenant, là où votre équipe range ses mots de passe, avant de continuer.",
90
+ "setup.salt": "La clé du projet — à conserver",
90
91
  "setup.project": "L’identifiant du projet — public, il va dans la page",
91
92
  "setup.tag": "La balise à coller en fin de <body>, sur les pages à annoter",
92
93
  "setup.server": "À déclarer dans la configuration du serveur",
93
94
  "setup.copy": "Copier",
94
95
  "setup.copied": "Copié",
95
96
  "setup.copy_failed": "Sélectionnez le texte et copiez-le à la main.",
96
- "setup.continue": "J’ai rangé le sel, continuer",
97
- "setup.done": "Le sel est retenu par ce navigateur. Collez la balise ci-dessus dans les pages, déclarez le projet côté serveur, puis rechargez cette page : l’outil prend la suite.",
97
+ "setup.continue": "J’ai rangé la clé, continuer",
98
+ "setup.done": "La clé est retenue par ce navigateur. Collez la balise ci-dessus dans les pages, déclarez le projet côté serveur, puis rechargez cette page : l’outil prend la main.",
98
99
  "setup.no_server": "Aucune adresse de serveur n’est déclarée sur la balise (data-server), et le client ne vient pas du site : il ne peut pas deviner où écrire. Ajoutez data-server à la balise.",
99
100
  "setup.plain_mode": "Ce projet est déclaré en mode CLAIR : le serveur lira les remarques, les noms et les chemins. Ce mode n’est acceptable que si le serveur est le site lui-même, derrière la même restriction d’accès. Un relais le refusera.",
100
101
  "context.title": "annotepage ne peut pas fonctionner sur cette page",
101
102
  "context.help": "Le chiffrement des notes et le regroupement par page reposent sur WebCrypto, que le navigateur ne fournit que dans un contexte sûr : https, ou localhost. Cette page n’en est pas un. Rien ne peut être écrit ni relu ici tant qu’elle est servie ainsi.",
102
103
  "date.unknown": "date inconnue"
103
- }
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "annotepage-client",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "The annotepage annotation layer: click an element of a page, leave a remark on it, it is encrypted in the browser.",
5
5
  "license": "MIT",
6
6
  "repository": {