#160: Plugin-Block — Bearbeiten-Knopf nach Moduswechsel wieder da
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 1m29s
CI / Auth e2e pack (pull_request) Successful in 7m22s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Release / Build release images and notes (push) Successful in 1m9s
CI / Lint, typecheck, test (push) Successful in 4m48s
CI / Build container images (push) Has been skipped
Release / Release-candidate operations QA (push) Successful in 52s
Prod deploy / Deploy the released images to Prod (push) Successful in 18s
CI / Auth e2e pack (push) Successful in 7m2s
CI / Import/export fidelity gate (push) Successful in 54s
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 1m29s
CI / Auth e2e pack (pull_request) Successful in 7m22s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Release / Build release images and notes (push) Successful in 1m9s
CI / Lint, typecheck, test (push) Successful in 4m48s
CI / Build container images (push) Has been skipped
Release / Release-candidate operations QA (push) Successful in 52s
Prod deploy / Deploy the released images to Prod (push) Successful in 18s
CI / Auth e2e pack (push) Successful in 7m2s
CI / Import/export fidelity gate (push) Successful in 54s
Die NodeView las editor.isEditable nur beim Mount. Die Seite mountet immer im Lesemodus, und der Moduswechsel läuft über setEditable() — das emittiert in TipTap nur ein update-Event, aber keine Transaction, weshalb React-NodeViews nie neu rendern (geprüft in @tiptap/react 3.27.1: updateProps feuert nur bei Node-Änderung und Selektions- Wechsel). Folge: die Block-Leiste blieb ohne Bearbeiten-Knopf, für alle Block-Plugins (ChordPro, Mermaid, Excalidraw, draw.io). Fix: useEditorEditable abonniert das update-Event und liest isEditable reaktiv; verliert die Seite die Editierbarkeit, während die Editier-UI des Plugins offen ist, fällt der Block auf render zurück (der Lesemodus blendet die Leiste aus, es gäbe sonst keinen Weg mehr heraus). Damit stimmt auch die setData-Schreibrecht-Prüfung (editableRef) wieder. Regressionstest im plugin-blocks-Pack: Block existiert bereits, Seite lädt im Lesemodus, Wechsel in den Edit-Modus zeigt den Knopf (fiel ohne Fix reproduzierbar durch); Rückweg Lesemodus→render mitgeprüft. Die bisherigen Tests fügten Blöcke immer erst nach dem Moduswechsel ein und konnten den Fall nicht sehen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a879561ec7
commit
db0e563f95
@ -115,6 +115,47 @@ test('a block round-trips: insert → edit data → reload → render', async ({
|
|||||||
await admin.close();
|
await admin.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the edit affordance appears when edit mode starts after load (issue #160)', async ({
|
||||||
|
browser,
|
||||||
|
}) => {
|
||||||
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
||||||
|
await installAsRequired(admin);
|
||||||
|
const pond = await personalPond(admin);
|
||||||
|
const created = await createPage(admin, pond.id, `E2E Block Stale Editable ${Date.now()}`);
|
||||||
|
|
||||||
|
// Seed a page that already carries a block (the edit round-trip persists
|
||||||
|
// "+e" into the document before the reload below).
|
||||||
|
const seeded = await openEditor(admin, pond.slug, created.slug);
|
||||||
|
await insertBlock(seeded);
|
||||||
|
await seeded.locator('.plugin-block__bar button').click();
|
||||||
|
await expect(frameBody(seeded)).toHaveText('editing:+e');
|
||||||
|
await seeded.locator('.plugin-block__bar button').click();
|
||||||
|
await expect(frameBody(seeded)).toHaveText('block:+e');
|
||||||
|
|
||||||
|
// A fresh load mounts the node view in read mode. Entering edit mode goes
|
||||||
|
// through `setEditable`, which dispatches no transaction — the bug (#160)
|
||||||
|
// was a stale `editable` from mount keeping the edit button hidden.
|
||||||
|
await seeded.reload();
|
||||||
|
await expect(frameBody(seeded)).toHaveText('block:+e', { timeout: 15000 });
|
||||||
|
await expect(seeded.locator('.plugin-block__surface')).toHaveAttribute('data-state', 'ready');
|
||||||
|
|
||||||
|
await seeded.locator('.editor-page__mode-toggle').click();
|
||||||
|
await expect(seeded.locator('.ProseMirror')).toHaveAttribute('contenteditable', 'true');
|
||||||
|
const editButton = seeded.locator('.plugin-block__bar button');
|
||||||
|
await expect(editButton).toBeVisible();
|
||||||
|
|
||||||
|
// The button is functional, not just painted.
|
||||||
|
await editButton.click();
|
||||||
|
await expect(frameBody(seeded)).toHaveText('editing:+e+e');
|
||||||
|
|
||||||
|
// Leaving page edit mode while the plugin's edit UI is open must drop the
|
||||||
|
// frame back to render — read mode has no bar to leave edit mode with.
|
||||||
|
await seeded.locator('.editor-page__mode-toggle').click();
|
||||||
|
await expect(frameBody(seeded)).toHaveText('block:+e+e');
|
||||||
|
|
||||||
|
await admin.close();
|
||||||
|
});
|
||||||
|
|
||||||
test('two collaborating users see block-data changes live', async ({ browser }) => {
|
test('two collaborating users see block-data changes live', async ({ browser }) => {
|
||||||
const owner = await contextForUser(browser, BASE_URL, 'fixture-user');
|
const owner = await contextForUser(browser, BASE_URL, 'fixture-user');
|
||||||
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
||||||
|
|||||||
@ -27,6 +27,26 @@ declare module '@tiptap/core' {
|
|||||||
|
|
||||||
type BlockMode = 'render' | 'edit';
|
type BlockMode = 'render' | 'edit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `editor.isEditable`, kept live (issue #160): the page's read/edit toggle
|
||||||
|
* flips editability through `setEditable`, which emits `update` but dispatches
|
||||||
|
* no transaction — so React node views never re-render on their own and a
|
||||||
|
* plain `editor.isEditable` read goes stale at mount (the page always mounts
|
||||||
|
* in read mode, hiding the block's edit button for the whole session).
|
||||||
|
*/
|
||||||
|
function useEditorEditable(editor: NodeViewProps['editor']): boolean {
|
||||||
|
const [editable, setEditable] = useState(editor.isEditable);
|
||||||
|
useEffect(() => {
|
||||||
|
const sync = (): void => setEditable(editor.isEditable);
|
||||||
|
sync();
|
||||||
|
editor.on('update', sync);
|
||||||
|
return () => {
|
||||||
|
editor.off('update', sync);
|
||||||
|
};
|
||||||
|
}, [editor]);
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The live surface of one plugin block (issue #76): a sandboxed iframe (#73)
|
* The live surface of one plugin block (issue #76): a sandboxed iframe (#73)
|
||||||
* driven through the plugin's `render`/`edit` lifecycle. Data flows two ways —
|
* driven through the plugin's `render`/`edit` lifecycle. Data flows two ways —
|
||||||
@ -133,6 +153,13 @@ function ActivePluginBlock({
|
|||||||
.catch(() => undefined);
|
.catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Losing editability while the plugin's edit UI is open (the page toggled
|
||||||
|
// back to read mode) must drop the frame back to render — read mode shows
|
||||||
|
// no bar to leave edit mode with (issue #160).
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editable && modeRef.current === 'edit') void switchMode('render');
|
||||||
|
}, [editable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="plugin-block__surface" data-state={state} data-mode={mode}>
|
<div className="plugin-block__surface" data-state={state} data-mode={mode}>
|
||||||
<div className="plugin-block__bar" contentEditable={false}>
|
<div className="plugin-block__bar" contentEditable={false}>
|
||||||
@ -208,6 +235,7 @@ function PluginBlockView({
|
|||||||
const blockType = node.attrs.blockType as string;
|
const blockType = node.attrs.blockType as string;
|
||||||
const plugins = usePondPlugins(scope.pondId);
|
const plugins = usePondPlugins(scope.pondId);
|
||||||
const plugin = plugins.data?.find((entry) => entry.id === pluginId && entry.kind === 'code');
|
const plugin = plugins.data?.find((entry) => entry.id === pluginId && entry.kind === 'code');
|
||||||
|
const editable = useEditorEditable(editor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper
|
<NodeViewWrapper
|
||||||
@ -219,7 +247,7 @@ function PluginBlockView({
|
|||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
blockType={blockType}
|
blockType={blockType}
|
||||||
data={node.attrs.data}
|
data={node.attrs.data}
|
||||||
editable={editor.isEditable}
|
editable={editable}
|
||||||
updateData={(data) => updateAttributes({ data })}
|
updateData={(data) => updateAttributes({ data })}
|
||||||
/>
|
/>
|
||||||
) : plugins.isSuccess ? (
|
) : plugins.isSuccess ? (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user