0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-05-06 09:10:06 +00:00

Lexical: Made image resize handles functional

This commit is contained in:
Dan Brown 2024-06-05 17:18:58 +01:00
parent ba871ec46a
commit e959c468f6
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
3 changed files with 80 additions and 31 deletions
resources
js/wysiwyg
nodes
ui/decorators
sass

View file

@ -80,7 +80,6 @@ export class ImageNode extends DecoratorNode<EditorDecoratorAdapter> {
setWidth(width: number): void { setWidth(width: number): void {
const self = this.getWritable(); const self = this.getWritable();
self.__width = width; self.__width = width;
console.log('widrg', width)
} }
getWidth(): number { getWidth(): number {

View file

@ -7,48 +7,65 @@ import {ImageNode} from "../../nodes/image";
export class ImageDecorator extends EditorDecorator { export class ImageDecorator extends EditorDecorator {
protected dom: HTMLElement|null = null; protected dom: HTMLElement|null = null;
protected dragLastMouseUp: number = 0;
buildDOM(context: EditorUiContext) { buildDOM(context: EditorUiContext) {
const handleClasses = ['nw', 'ne', 'se', 'sw']; let handleElems: HTMLElement[] = [];
const handleEls = handleClasses.map(c => {
return el('div', {class: `editor-image-decorator-handle ${c}`});
});
const decorateEl = el('div', { const decorateEl = el('div', {
class: 'editor-image-decorator', class: 'editor-image-decorator',
}, handleEls); }, []);
let selected = false;
const windowClick = (event: MouseEvent) => { const windowClick = (event: MouseEvent) => {
if (!decorateEl.contains(event.target as Node)) { if (!decorateEl.contains(event.target as Node) && (Date.now() - this.dragLastMouseUp > 100)) {
unselect(); unselect();
} }
}; };
const mouseDown = (event: MouseEvent) => {
const handle = (event.target as HTMLElement).closest('.editor-image-decorator-handle') as HTMLElement|null;
if (handle) {
// handlingResize = true;
this.startHandlingResize(handle, event, context);
}
};
const select = () => { const select = () => {
if (selected) {
return;
}
selected = true;
decorateEl.classList.add('selected'); decorateEl.classList.add('selected');
window.addEventListener('click', windowClick); window.addEventListener('click', windowClick);
};
const unselect = () => { const handleClasses = ['nw', 'ne', 'se', 'sw'];
decorateEl.classList.remove('selected'); handleElems = handleClasses.map(c => {
window.removeEventListener('click', windowClick); return el('div', {class: `editor-image-decorator-handle ${c}`});
}; });
decorateEl.append(...handleElems);
decorateEl.addEventListener('mousedown', mouseDown);
decorateEl.addEventListener('click', (event) => {
context.editor.update(() => { context.editor.update(() => {
const nodeSelection = $createNodeSelection(); const nodeSelection = $createNodeSelection();
nodeSelection.add(this.getNode().getKey()); nodeSelection.add(this.getNode().getKey());
$setSelection(nodeSelection); $setSelection(nodeSelection);
}); });
};
select(); const unselect = () => {
}); selected = false;
// handlingResize = false;
decorateEl.addEventListener('mousedown', (event: MouseEvent) => { decorateEl.classList.remove('selected');
const handle = (event.target as Element).closest('.editor-image-decorator-handle'); window.removeEventListener('click', windowClick);
if (handle) { decorateEl.removeEventListener('mousedown', mouseDown);
this.startHandlingResize(handle, event, context); for (const el of handleElems) {
el.remove();
} }
};
decorateEl.addEventListener('click', (event) => {
select();
}); });
return decorateEl; return decorateEl;
@ -63,26 +80,56 @@ export class ImageDecorator extends EditorDecorator {
return this.dom; return this.dom;
} }
startHandlingResize(element: Node, event: MouseEvent, context: EditorUiContext) { startHandlingResize(element: HTMLElement, event: MouseEvent, context: EditorUiContext) {
const startingX = event.screenX; const startingX = event.screenX;
const startingY = event.screenY; const startingY = event.screenY;
const node = this.getNode() as ImageNode;
let startingWidth = element.clientWidth;
let startingHeight = element.clientHeight;
let startingRatio = startingWidth / startingHeight;
let hasHeight = false;
context.editor.getEditorState().read(() => {
startingWidth = node.getWidth() || startingWidth;
startingHeight = node.getHeight() || startingHeight;
if (node.getHeight()) {
hasHeight = true;
}
startingRatio = startingWidth / startingHeight;
});
const flipXChange = element.classList.contains('nw') || element.classList.contains('sw');
const flipYChange = element.classList.contains('nw') || element.classList.contains('ne');
const mouseMoveListener = (event: MouseEvent) => { const mouseMoveListener = (event: MouseEvent) => {
const xChange = event.screenX - startingX; let xChange = event.screenX - startingX;
const yChange = event.screenY - startingY; if (flipXChange) {
console.log({ xChange, yChange }); xChange = 0 - xChange;
}
let yChange = event.screenY - startingY;
if (flipYChange) {
yChange = 0 - yChange;
}
const balancedChange = Math.sqrt(Math.pow(xChange, 2) + Math.pow(yChange, 2));
const increase = xChange + yChange > 0;
const directedChange = increase ? balancedChange : 0-balancedChange;
const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
let newHeight = 0;
if (hasHeight) {
newHeight = newWidth * startingRatio;
}
context.editor.update(() => { context.editor.update(() => {
const node = this.getNode() as ImageNode; const node = this.getNode() as ImageNode;
node.setWidth(node.getWidth() + xChange); node.setWidth(newWidth);
node.setHeight(node.getHeight() + yChange); node.setHeight(newHeight);
}); });
}; };
const mouseUpListener = (event: MouseEvent) => { const mouseUpListener = (event: MouseEvent) => {
window.removeEventListener('mousemove', mouseMoveListener); window.removeEventListener('mousemove', mouseMoveListener);
window.removeEventListener('mouseup', mouseUpListener); window.removeEventListener('mouseup', mouseUpListener);
} this.dragLastMouseUp = Date.now();
};
window.addEventListener('mousemove', mouseMoveListener); window.addEventListener('mousemove', mouseMoveListener);
window.addEventListener('mouseup', mouseUpListener); window.addEventListener('mouseup', mouseUpListener);

View file

@ -85,20 +85,23 @@
display: inline-flex; display: inline-flex;
} }
.editor-image-decorator { .editor-image-decorator {
display: inline-block;
position: absolute; position: absolute;
border: 1px solid var(--editor-color-primary);
left: 0; left: 0;
right: 0; right: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: inline-block;
&.selected {
border: 1px dashed var(--editor-color-primary);
}
} }
.editor-image-decorator-handle { .editor-image-decorator-handle {
position: absolute; position: absolute;
display: block; display: block;
width: 10px; width: 10px;
height: 10px; height: 10px;
background-color: var(--editor-color-primary); border: 2px solid var(--editor-color-primary);
background-color: #FFF;
user-select: none; user-select: none;
&.nw { &.nw {
inset-inline-start: -5px; inset-inline-start: -5px;