Public sharing (#218): - Bind public-share content to the requested shareId. getSharedPage now enforces dto.shareId (forwarded from /share/:shareId/p/:slug): the page must be reachable THROUGH that exact share (its own share, or an includeSubPages ancestor that contains it). A forged/mismatched shareId 404s instead of rendering off the slug alone and no longer leaks the real canonical key via redirect. A request with no shareId keeps the legacy slug-capability path. - Trim /shares/page-info: drop internal metadata (creatorId, spaceId, workspaceId, contributorIds, lastUpdated*, parent/position, lock/template flags, timestamps) from the anonymous payload. - Default share-to-web includeSubPages to false (opt-in), so enabling a share no longer silently exposes the whole sub-tree (#216). Editor (#218): - Harden the new-page pre-sync window: the body editor is kept read-only until the collab provider is Connected and synced, so early keystrokes can't land only in local ProseMirror and then be clobbered by the server's empty doc. - Surface a "Connecting… (read-only)" affordance during the static phase so input isn't silently swallowed. Other: - Breadcrumb: resolve from the page's own ancestor data (/pages/breadcrumbs) instead of waiting for the lazily-built sidebar tree, so deep pages don't render a blank breadcrumb for seconds. - Pasting GitHub `> [!type]` callouts now converts to a callout node instead of a literal blockquote (new marked extension wired into markdownToHtml). Tests: editor-sync-state gate (client), getSharedPage share-binding (server), github-callout markdown conversion (editor-ext). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { WebSocketStatus } from "@hocuspocus/provider";
|
|
import { isCollabSynced, isBodyEditable } from "./editor-sync-state";
|
|
|
|
describe("isCollabSynced", () => {
|
|
it("is true only when Connected and synced", () => {
|
|
expect(isCollabSynced(WebSocketStatus.Connected, true)).toBe(true);
|
|
});
|
|
|
|
it("is false while connecting or not yet synced", () => {
|
|
expect(isCollabSynced(WebSocketStatus.Connecting, true)).toBe(false);
|
|
expect(isCollabSynced(WebSocketStatus.Connected, false)).toBe(false);
|
|
expect(isCollabSynced(WebSocketStatus.Disconnected, true)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("isBodyEditable (pre-sync data-loss gate, #218)", () => {
|
|
const base = { editable: true, inEditMode: true, showStatic: false };
|
|
|
|
it("allows editing only after the static (pre-sync) phase ends", () => {
|
|
expect(isBodyEditable(base)).toBe(true);
|
|
});
|
|
|
|
it("never editable while the static read-only editor is shown", () => {
|
|
expect(isBodyEditable({ ...base, showStatic: true })).toBe(false);
|
|
});
|
|
|
|
it("honors read-only and view mode", () => {
|
|
expect(isBodyEditable({ ...base, editable: false })).toBe(false);
|
|
expect(isBodyEditable({ ...base, inEditMode: false })).toBe(false);
|
|
});
|
|
});
|