Signal drop!
Relay (operand.online) is unreachable.
Usually, a dropped signal means an upgrade is happening. Hold on!
Sorry, no connección.
Hang in there while we get back on track
gram: docs
> ./packages/core/src/prosemirror/imageCommit.ts
/**
* Image resize / drag-move PM commits shared by the React and Vue adapters.
*
* The float-vs-inline fork is identical across adapters; only the DOM lookups
* differ (which `.layout-page-content` the drop landed in; how the inline drop
* position is hit-tested). Those stay in each adapter, which passes the
* resolved EMU offsets (float) or drop position (inline) into the pure commits
* here. Each commit dispatches and returns the PM position the caller should
* re-select as a NodeSelection (or null on no-op / failure).
*/
import type { Node as PMNode } from 'prosemirror-model';
import type { EditorView } from 'prosemirror-view';
/**
* An image resize handle: the 4 corners ('nw'/'ne'/'se'/'sw') resize both axes
* and keep the aspect ratio; the 4 edge midpoints ('n'/'s'/'e'/'w') resize a
* single axis so the user can deliberately stretch the image (break aspect).
*/
export type ImageResizeHandle = 'nw' | 'ne' | 'se' | 'sw' | 'n' | 's' | 'e' | 'w';
const MIN_IMAGE_PX = 20;
const MAX_IMAGE_PX = 2000;
/**
* New image dimensions for a resize drag, shared by the React and Vue overlays
* (issue #266). Corner handles drive both axes (aspect-locked unless
* `lockAspect` is false, e.g. Shift held); edge handles drive one axis and
* never lock. The non-driven axis is returned unchanged. Driven axes are
* clamped to a sane pixel range.
*/
export function calculateResizedImageDimensions(
handle: ImageResizeHandle,
deltaX: number,
deltaY: number,
startWidth: number,
startHeight: number,
lockAspect: boolean
): { width: number; height: number } {
const drivesWidth = handle.includes('w') || handle.includes('e');
const drivesHeight = handle.includes('n') || handle.includes('s');
const isCorner = drivesWidth && drivesHeight;
const signX = handle.includes('w') ? -1 : 1;
const signY = handle.includes('n') ? -1 : 1;
let newWidth = drivesWidth ? startWidth + deltaX * signX : startWidth;
let newHeight = drivesHeight ? startHeight + deltaY * signY : startHeight;
if (isCorner && lockAspect) {
const scale = Math.max(newWidth / startWidth, newHeight / startHeight);
newWidth = startWidth * scale;
newHeight = startHeight * scale;
}
const clamp = (n: number) => Math.max(MIN_IMAGE_PX, Math.min(MAX_IMAGE_PX, n));
return {
width: drivesWidth ? clamp(newWidth) : startWidth,
height: drivesHeight ? clamp(newHeight) : startHeight,
};
}
/** True when the image is floating (anchored) rather than inline. */
export function isFloatingImage(node: PMNode): boolean {
const wrapType = node.attrs.wrapType as string | undefined;
return (
node.attrs.displayMode === 'float' ||
(wrapType ? ['square', 'tight', 'through'].includes(wrapType) : false)
);
}
/** Resolve the image node at `pmPos`, or null if it isn't an image. */
function imageNodeAt(view: EditorView, pmPos: number): PMNode | null {
const node = view.state.doc.nodeAt(pmPos);
if (!node || node.type.name !== 'image') return null;
return node;
}
/**
* Resize commit: set the image node's `width`/`height`. Returns `pmPos` to
* re-select, or null if the position no longer holds an image.
*/
export function commitImageResize(
view: EditorView,
pmPos: number,
newWidth: number,
newHeight: number
): number | null {
try {
const node = imageNodeAt(view, pmPos);
if (!node) return null;
view.dispatch(
view.state.tr.setNodeMarkup(pmPos, undefined, {
...node.attrs,
width: newWidth,
height: newHeight,
})
);
return pmPos;
} catch {
// Position may have shifted during resize.
return null;
}
}
/**
* Floating drag commit: rewrite the anchor's margin-relative `position`
* offsets (in EMU) so the image lands at the drop point while staying
* floating. Returns `pmPos` to re-select, or null on no-op / failure.
*/
export function commitImageFloatMove(
view: EditorView,
pmPos: number,
hOffsetEmu: number,
vOffsetEmu: number
): number | null {
try {
const node = imageNodeAt(view, pmPos);
if (!node) return null;
const newPosition = {
horizontal: { posOffset: hOffsetEmu, relativeTo: 'margin' },
vertical: { posOffset: vOffsetEmu, relativeTo: 'margin' },
};
view.dispatch(
view.state.tr.setNodeMarkup(pmPos, undefined, { ...node.attrs, position: newPosition })
);
return pmPos;
} catch {
return null;
}
}
/**
* Inline drag commit: move the image node to `dropPos` via a delete + insert
* pair. Returns the PM position to re-select, or null when the drop is a
* no-op (same slot) or fails.
*/
export function commitImageInlineMove(
view: EditorView,
pmPos: number,
dropPos: number
): number | null {
try {
const node = imageNodeAt(view, pmPos);
if (!node) return null;
if (dropPos === pmPos || dropPos === pmPos + 1) return null;
let tr = view.state.tr;
if (dropPos <= pmPos) {
tr = tr.delete(pmPos, pmPos + node.nodeSize);
tr = tr.insert(dropPos, node);
view.dispatch(tr);
return dropPos;
}
tr = tr.delete(pmPos, pmPos + node.nodeSize);
const adjusted = Math.min(dropPos - node.nodeSize, tr.doc.content.size);
tr = tr.insert(adjusted, node);
view.dispatch(tr);
return Math.min(adjusted, view.state.doc.content.size - 1);
} catch {
// Position may have shifted between the drag's frames.
return null;
}
}