#160: Plugin-Block - Bearbeiten-Knopf nach Moduswechsel wieder da #161
@ -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