@erdemtuna/doc-review 0.7.0 → 0.8.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/src/serialize.js CHANGED
@@ -9,20 +9,6 @@
9
9
  export const UI_ATTR = "data-eh-ui";
10
10
  export const MARK_ATTR = "data-eh-mark";
11
11
 
12
- /** Keep a hydrated app editable even if its framework removes the attribute. */
13
- export function keepBodyEditable(body) {
14
- const enforce = () => {
15
- if (body.getAttribute("contenteditable") !== "true") {
16
- body.setAttribute("contenteditable", "true");
17
- }
18
- };
19
- enforce();
20
- const Observer = body.ownerDocument.defaultView.MutationObserver;
21
- const observer = new Observer(enforce);
22
- observer.observe(body, { attributes: true, attributeFilter: ["contenteditable"] });
23
- return observer;
24
- }
25
-
26
12
  /** Serialize a document to the HTML that should live on disk. */
27
13
  export function serializeDocument(doc) {
28
14
  const clone = doc.documentElement.cloneNode(true);
package/src/server.js CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import crypto from "node:crypto";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { atomicWrite, Store, resolveAsset } from "./state.js";
7
+ import { normalizeCommentAnchor } from "./comment-anchor.js";
7
8
  import { injectSdk, stripSdk } from "./html-transform.js";
8
9
  import { isMarkdown, renderMarkdownPage } from "./markdown.js";
9
10
  import { canonicalTarget, ensureStateDir, localUrl, SERVER_PROTOCOL, serverPath, stateDir, targetKey } from "./paths.js";
@@ -274,7 +275,7 @@ export function createServer({ store: suppliedStore, storeOptions, owner = null,
274
275
  id: c.id,
275
276
  kind: c.kind,
276
277
  quote: c.quote,
277
- anchor: c.anchor,
278
+ anchor: c.anchor == null ? c.anchor : normalizeCommentAnchor(c.kind, c.anchor),
278
279
  feedback: c.feedback,
279
280
  ...(c.correction ? { correction: true, correction_of: c.correctionOf } : {}),
280
281
  })),
@@ -562,6 +563,10 @@ export function createServer({ store: suppliedStore, storeOptions, owner = null,
562
563
  if (route === "/chrome.css") return serveFile(res, path.join(here, "chrome.css"));
563
564
  if (route === "/chrome.js") return serveFile(res, path.join(here, "chrome-client.js"));
564
565
  if (route === "/chrome-session.js") return serveFile(res, path.join(here, "chrome-session.js"));
566
+ if (route === "/icons.js") return serveFile(res, path.join(here, "icons.js"), opaqueModuleCors(req));
567
+ if (route === "/positioning.js") return serveFile(res, path.join(here, "positioning.js"), opaqueModuleCors(req));
568
+ if (route === "/review-mode.js") return serveFile(res, path.join(here, "review-mode.js"), opaqueModuleCors(req));
569
+ if (route === "/comment-target.js") return serveFile(res, path.join(here, "comment-target.js"), opaqueModuleCors(req));
565
570
  if (route === "/sdk.js") return serveFile(res, path.join(here, "sdk.js"), opaqueModuleCors(req));
566
571
  if (route === "/editing.js") return serveFile(res, path.join(here, "editing.js"), opaqueModuleCors(req));
567
572
  if (route === "/anchor-text.js") return serveFile(res, path.join(here, "anchor-text.js"), opaqueModuleCors(req));
@@ -829,15 +834,21 @@ export function createServer({ store: suppliedStore, storeOptions, owner = null,
829
834
 
830
835
  if (action === "comment" && req.method === "POST") {
831
836
  const body = await readBody(req);
837
+ const kind = body.kind === "element" ? "element" : "selection";
838
+ const feedback = String(body.feedback || "").trim();
832
839
  const comment = {
833
840
  id: uid("c"),
834
- kind: body.kind === "element" ? "element" : "selection",
841
+ kind,
835
842
  quote: String(body.quote || ""),
836
- anchor: body.anchor || null,
837
- feedback: String(body.feedback || ""),
843
+ anchor: normalizeCommentAnchor(
844
+ kind,
845
+ body.anchor || (kind === "selection" ? { quote: String(body.quote || "") } : null)
846
+ ),
847
+ feedback,
838
848
  createdAt: Date.now(),
839
849
  };
840
850
  if (!comment.feedback) return json(res, 400, { error: "empty feedback" });
851
+ if (!comment.anchor) return json(res, 400, { error: "invalid comment anchor" });
841
852
  store.addComment(key, comment);
842
853
  return json(res, 200, { comment, page: pageState(key) });
843
854
  }
package/src/state.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { normalizeCommentAnchor } from "./comment-anchor.js";
4
5
  import { canonicalTarget, ensureStateDir, pageKey, realFile, statePath, targetKey } from "./paths.js";
5
6
 
6
7
  /** Anything untouched this long is review debris, not work in progress. */
@@ -46,6 +47,17 @@ function normalizeState(parsed, makeBatchId) {
46
47
  receipts: parsed.receipts && typeof parsed.receipts === "object" ? parsed.receipts : {},
47
48
  };
48
49
  let changed = !parsed.batches || !parsed.receipts;
50
+ const normalizeAnchor = (comment) => {
51
+ if (!comment || comment.anchor == null) return;
52
+ const normalized = normalizeCommentAnchor(comment.kind === "element" ? "element" : "selection", comment.anchor);
53
+ if (JSON.stringify(normalized) !== JSON.stringify(comment.anchor)) {
54
+ comment.anchor = normalized;
55
+ changed = true;
56
+ }
57
+ };
58
+ for (const page of Object.values(data.pages)) {
59
+ for (const comment of page?.comments || []) normalizeAnchor(comment);
60
+ }
49
61
  for (const record of Object.values(data.batches)) {
50
62
  if (!record || typeof record !== "object" || !record.batch || !Array.isArray(record.cleanup)) {
51
63
  throw new Error("Invalid doc-review state: malformed feedback batch.");
@@ -73,6 +85,9 @@ function normalizeState(parsed, makeBatchId) {
73
85
  if (!DELIVERY_STATES.has(record.delivery_state)) {
74
86
  throw new Error(`Invalid doc-review state: unknown delivery state ${record.delivery_state}.`);
75
87
  }
88
+ for (const page of record.batch.pages || []) {
89
+ for (const comment of page.comments || []) normalizeAnchor(comment);
90
+ }
76
91
  }
77
92
  return { data, changed };
78
93
  }
@@ -234,8 +249,12 @@ export class Store {
234
249
  }
235
250
 
236
251
  addComment(key, comment) {
252
+ const anchor = comment.anchor == null
253
+ ? comment.anchor
254
+ : normalizeCommentAnchor(comment.kind === "element" ? "element" : "selection", comment.anchor);
255
+ if (comment.anchor != null && !anchor) throw new Error("Invalid comment anchor.");
237
256
  return this.update(key, (page) => {
238
- page.comments.push(comment);
257
+ page.comments.push({ ...comment, ...(comment.anchor !== undefined ? { anchor } : {}) });
239
258
  });
240
259
  }
241
260