F3.6 — insertImage reducer + command:
- engine/operations.ts: insertImage(state, options) — atomic insert
(URL required, optional alt/caption/width/height/align/status).
Replaces an empty paragraph cursor lands on, otherwise inserts after.
Caret lands on the new paragraph following the image (image is
atomic — no caret inside the figure).
- engine/commands.ts: 'insertImage' case added to WordsCommand union +
applyWordsCommand switch. Public command surface: provider can call
applyWordsCommand(state, { type: 'insertImage', src, alt?, ... }).
F3.9 — eidos CSS for image block:
- figure[data-words-block='image']: centered flex column with border,
bg, padding, gap.
- img: max-inline-size 100%, auto height, user-select none.
- figcaption: italic, centered, status-color, smaller font.
- data-words-image-align='left'/'right': flex alignment + auto margins.
- data-words-image-status='pending': opacity + grayscale + "Uploading…"
::after pseudo-text.
- data-words-image-status='error': threat border + "Upload failed"
::after pseudo-text.
- :focus / :focus-within: 2px accent outline so the atomic block has
a visible selection state.
F3.10 — imageExtension stub:
- extensions/image/image-extension.ts: WordsExtension shape with
name='image', nodeTypes=['image'], factories.image=createImage,
commandNames.insertImage='insertImage'. Hooks intentionally absent
(engine still hardcodes branches today; stub publishes the public
shape parallel to tableExtension).
- extensions/index.ts exports imageExtension alongside tableExtension.
Morfo:
- data-block + data-words-node value sets include 'image'.
- New optional attrs declared: data-words-image-align (left/center/
right), data-words-image-status (pending/error). Now the eidos CSS
selectors validate against the morfo contract.
211/211 tests pass in words soma + morfo scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
parent
08bef77f4b
commit
2a51adc00f
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Image extension — entrypoint that materialises the `WordsExtension`
|
||||
* contract for the `image` node.
|
||||
*
|
||||
* **F3.10 stub stage**: ships the three static facets we can publish
|
||||
* without touching the engine's hardcoded branches:
|
||||
*
|
||||
* 1. `nodeTypes` — `['image']` is the only node this extension owns.
|
||||
* 2. `factories` — `createImage` adapted to the contract's erased
|
||||
* signature.
|
||||
* 3. `commandNames` — public command `insertImage` mapped to its
|
||||
* internal operation type.
|
||||
*
|
||||
* The dynamic hooks (`render`, `normalize`, `serialize`, `path`,
|
||||
* `keyboard`, `commands`, `events`) are intentionally absent — the
|
||||
* engine handles them via hardcoded branches today. They migrate to
|
||||
* this extension as the registry takes over (parallel to the table
|
||||
* extension migration plan).
|
||||
*/
|
||||
|
||||
import type { WordsExtension, WordsExtensionNode } from '../extension-types';
|
||||
import { createImage } from './factories';
|
||||
|
||||
type ErasedFactory = (...args: ReadonlyArray<unknown>) => WordsExtensionNode;
|
||||
|
||||
const imageFactory = ((...args: ReadonlyArray<unknown>) =>
|
||||
createImage(...(args as Parameters<typeof createImage>))) as ErasedFactory;
|
||||
|
||||
export const imageExtension: WordsExtension = {
|
||||
name: 'image',
|
||||
version: '1.0.0',
|
||||
nodeTypes: ['image'],
|
||||
factories: {
|
||||
image: imageFactory
|
||||
},
|
||||
commandNames: {
|
||||
// Public name → internal operation type. 1:1 today.
|
||||
insertImage: 'insertImage'
|
||||
}
|
||||
// render / normalize / serialize / path / keyboard / commands /
|
||||
// events / dispose intentionally absent — see file header.
|
||||
} satisfies WordsExtension;
|
||||
Loading…
Reference in new issue