diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts
index c312919db..64b59492b 100644
--- a/resources/js/wysiwyg/index.ts
+++ b/resources/js/wysiwyg/index.ts
@@ -13,7 +13,7 @@ import {registerTaskListHandler} from "./ui/framework/helpers/task-list-handler"
 import {registerTableSelectionHandler} from "./ui/framework/helpers/table-selection-handler";
 import {el} from "./utils/dom";
 import {registerShortcuts} from "./services/shortcuts";
-import {registerImageResizer} from "./ui/framework/helpers/image-resizer";
+import {registerNodeResizer} from "./ui/framework/helpers/image-resizer";
 
 export function createPageEditorInstance(container: HTMLElement, htmlContent: string, options: Record<string, any> = {}): SimpleWysiwygEditorInterface {
     const config: CreateEditorArgs = {
@@ -56,7 +56,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
         registerTableSelectionHandler(editor),
         registerTaskListHandler(editor, editArea),
         registerDropPasteHandling(context),
-        registerImageResizer(context),
+        registerNodeResizer(context),
     );
 
     listenToCommonEvents(editor);
diff --git a/resources/js/wysiwyg/nodes/_common.ts b/resources/js/wysiwyg/nodes/_common.ts
index cc45dc910..ff957f953 100644
--- a/resources/js/wysiwyg/nodes/_common.ts
+++ b/resources/js/wysiwyg/nodes/_common.ts
@@ -63,4 +63,11 @@ export function updateElementWithCommonBlockProps(element: HTMLElement, node: Co
     if (node.__alignment) {
         element.classList.add('align-' + node.__alignment);
     }
+}
+
+export interface NodeHasSize {
+    setHeight(height: number): void;
+    setWidth(width: number): void;
+    getHeight(): number;
+    getWidth(): number;
 }
\ No newline at end of file
diff --git a/resources/js/wysiwyg/nodes/callout.ts b/resources/js/wysiwyg/nodes/callout.ts
index 8018190c8..ededc0f29 100644
--- a/resources/js/wysiwyg/nodes/callout.ts
+++ b/resources/js/wysiwyg/nodes/callout.ts
@@ -34,6 +34,7 @@ export class CalloutNode extends ElementNode {
     static clone(node: CalloutNode) {
         const newNode = new CalloutNode(node.__category, node.__key);
         newNode.__id = node.__id;
+        newNode.__alignment = node.__alignment;
         return newNode;
     }
 
diff --git a/resources/js/wysiwyg/nodes/media.ts b/resources/js/wysiwyg/nodes/media.ts
index 4159cd457..5b3c1b9c2 100644
--- a/resources/js/wysiwyg/nodes/media.ts
+++ b/resources/js/wysiwyg/nodes/media.ts
@@ -1,6 +1,6 @@
 import {
     DOMConversion,
-    DOMConversionMap, DOMConversionOutput,
+    DOMConversionMap, DOMConversionOutput, DOMExportOutput,
     ElementNode,
     LexicalEditor,
     LexicalNode,
@@ -8,7 +8,7 @@ import {
 } from 'lexical';
 import type {EditorConfig} from "lexical/LexicalEditor";
 
-import {el} from "../utils/dom";
+import {el, sizeToPixels} from "../utils/dom";
 import {
     CommonBlockAlignment,
     SerializedCommonBlockNode,
@@ -16,6 +16,7 @@ import {
     updateElementWithCommonBlockProps
 } from "./_common";
 import {elem} from "../../services/dom";
+import {$selectSingleNode} from "../utils/selection";
 
 export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio';
 export type MediaNodeSource = {
@@ -89,6 +90,8 @@ export class MediaNode extends ElementNode {
         const newNode = new MediaNode(node.__tag, node.__key);
         newNode.__attributes = Object.assign({}, node.__attributes);
         newNode.__sources = node.__sources.map(s => Object.assign({}, s));
+        newNode.__id = node.__id;
+        newNode.__alignment = node.__alignment;
         return newNode;
     }
 
@@ -166,7 +169,35 @@ export class MediaNode extends ElementNode {
         return self.__alignment;
     }
 
-    createDOM(_config: EditorConfig, _editor: LexicalEditor) {
+    setHeight(height: number): void {
+        if (!height) {
+            return;
+        }
+
+        const attrs = Object.assign({}, this.getAttributes(), {height});
+        this.setAttributes(attrs);
+    }
+
+    getHeight(): number {
+        const self = this.getLatest();
+        return sizeToPixels(self.__attributes.height || '0');
+    }
+
+    setWidth(width: number): void {
+        const attrs = Object.assign({}, this.getAttributes(), {width});
+        this.setAttributes(attrs);
+    }
+
+    getWidth(): number {
+        const self = this.getLatest();
+        return sizeToPixels(self.__attributes.width || '0');
+    }
+
+    isInline(): boolean {
+        return true;
+    }
+
+    createInnerDOM() {
         const sources = (this.__tag === 'video' || this.__tag === 'audio') ? this.__sources : [];
         const sourceEls = sources.map(source => el('source', source));
         const element = el(this.__tag, this.__attributes, sourceEls);
@@ -174,6 +205,19 @@ export class MediaNode extends ElementNode {
         return element;
     }
 
+    createDOM(_config: EditorConfig, _editor: LexicalEditor) {
+        const media = this.createInnerDOM();
+        const wrap = el('span', {
+            class: media.className + ' editor-media-wrap',
+        }, [media]);
+
+        wrap.addEventListener('click', e => {
+            _editor.update(() => $selectSingleNode(this));
+        });
+
+        return wrap;
+    }
+
     updateDOM(prevNode: unknown, dom: HTMLElement) {
         return true;
     }
@@ -202,6 +246,11 @@ export class MediaNode extends ElementNode {
         };
     }
 
+    exportDOM(editor: LexicalEditor): DOMExportOutput {
+        const element = this.createInnerDOM();
+        return { element };
+    }
+
     exportJSON(): SerializedMediaNode {
         return {
             ...super.exportJSON(),
diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md
index 064d65e4c..92042295c 100644
--- a/resources/js/wysiwyg/todo.md
+++ b/resources/js/wysiwyg/todo.md
@@ -6,7 +6,6 @@
 
 ## Main Todo
 
-- Media resize support (like images)
 - Mac: Shortcut support via command.
 
 ## Secondary Todo
@@ -17,9 +16,6 @@
 
 ## Bugs
 
-- Can't select iframe embeds by themselves. (click enters iframe)
 - Removing link around image via button deletes image, not just link 
 - `SELECTION_CHANGE_COMMAND` not fired when clicking out of a table cell. Prevents toolbar hiding on table unselect.
-- Template drag/drop not handled when outside core editor area (ignored in margin area).
-- Table row copy/paste does not handle merged cells
-  - TinyMCE fills gaps with the  cells that would be visually in the row
\ No newline at end of file
+- Template drag/drop not handled when outside core editor area (ignored in margin area).
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/framework/helpers/image-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/image-resizer.ts
index cceb58b6b..c8105eafc 100644
--- a/resources/js/wysiwyg/ui/framework/helpers/image-resizer.ts
+++ b/resources/js/wysiwyg/ui/framework/helpers/image-resizer.ts
@@ -1,10 +1,16 @@
-import {BaseSelection,} from "lexical";
+import {BaseSelection, LexicalNode,} from "lexical";
 import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
 import {el} from "../../../utils/dom";
-import {$isImageNode, ImageNode} from "../../../nodes/image";
+import {$isImageNode} from "../../../nodes/image";
 import {EditorUiContext} from "../core";
+import {NodeHasSize} from "../../../nodes/_common";
+import {$isMediaNode} from "../../../nodes/media";
 
-class ImageResizer {
+function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode {
+    return $isImageNode(node) || $isMediaNode(node);
+}
+
+class NodeResizer {
     protected context: EditorUiContext;
     protected dom: HTMLElement|null = null;
     protected scrollContainer: HTMLElement;
@@ -26,13 +32,17 @@ class ImageResizer {
             this.hide();
         }
 
-        if (nodes.length === 1 && $isImageNode(nodes[0])) {
-            const imageNode = nodes[0];
-            const nodeKey = imageNode.getKey();
-            const imageDOM = this.context.editor.getElementByKey(nodeKey);
+        if (nodes.length === 1 && isNodeWithSize(nodes[0])) {
+            const node = nodes[0];
+            const nodeKey = node.getKey();
+            let nodeDOM = this.context.editor.getElementByKey(nodeKey);
 
-            if (imageDOM) {
-                this.showForImage(imageNode, imageDOM);
+            if (nodeDOM && nodeDOM.nodeName === 'SPAN') {
+                nodeDOM = nodeDOM.firstElementChild as HTMLElement;
+            }
+
+            if (nodeDOM) {
+                this.showForNode(node, nodeDOM);
             }
         }
     }
@@ -42,10 +52,13 @@ class ImageResizer {
         this.hide();
     }
 
-    protected showForImage(node: ImageNode, dom: HTMLElement) {
+    protected showForNode(node: NodeHasSize&LexicalNode, dom: HTMLElement) {
         this.dom = this.buildDOM();
 
-        const ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-image-resizer-ghost'});
+        let ghost = el('span', {class: 'editor-node-resizer-ghost'});
+        if ($isImageNode(node)) {
+            ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-node-resizer-ghost'});
+        }
         this.dom.append(ghost);
 
         this.context.scrollDOM.append(this.dom);
@@ -55,16 +68,16 @@ class ImageResizer {
         this.activeSelection = node.getKey();
     }
 
-    protected updateDOMPosition(imageDOM: HTMLElement) {
+    protected updateDOMPosition(nodeDOM: HTMLElement) {
         if (!this.dom) {
             return;
         }
 
-        const imageBounds = imageDOM.getBoundingClientRect();
-        this.dom.style.left = imageDOM.offsetLeft + 'px';
-        this.dom.style.top = imageDOM.offsetTop + 'px';
-        this.dom.style.width = imageBounds.width + 'px';
-        this.dom.style.height = imageBounds.height + 'px';
+        const nodeDOMBounds = nodeDOM.getBoundingClientRect();
+        this.dom.style.left = nodeDOM.offsetLeft + 'px';
+        this.dom.style.top = nodeDOM.offsetTop + 'px';
+        this.dom.style.width = nodeDOMBounds.width + 'px';
+        this.dom.style.height = nodeDOMBounds.height + 'px';
     }
 
     protected updateDOMSize(width: number, height: number): void {
@@ -85,15 +98,15 @@ class ImageResizer {
     protected buildDOM() {
         const handleClasses = ['nw', 'ne', 'se', 'sw'];
         const handleElems = handleClasses.map(c => {
-            return el('div', {class: `editor-image-resizer-handle ${c}`});
+            return el('div', {class: `editor-node-resizer-handle ${c}`});
         });
 
         return el('div', {
-            class: 'editor-image-resizer',
+            class: 'editor-node-resizer',
         }, handleElems);
     }
 
-    setupTracker(container: HTMLElement, node: ImageNode, imageDOM: HTMLElement): MouseDragTracker {
+    setupTracker(container: HTMLElement, node: NodeHasSize, nodeDOM: HTMLElement): MouseDragTracker {
         let startingWidth: number = 0;
         let startingHeight: number = 0;
         let startingRatio: number = 0;
@@ -116,22 +129,22 @@ class ImageResizer {
             const increase = xChange + yChange > 0;
             const directedChange = increase ? balancedChange : 0-balancedChange;
             const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
-            const newHeight = newWidth * startingRatio;
+            const newHeight = Math.round(newWidth * startingRatio);
 
             return {width: newWidth, height: newHeight};
         };
 
-        return new MouseDragTracker(container, '.editor-image-resizer-handle', {
+        return new MouseDragTracker(container, '.editor-node-resizer-handle', {
             down(event: MouseEvent, handle: HTMLElement) {
                 _this.dom?.classList.add('active');
                 _this.context.editor.getEditorState().read(() => {
-                    const imageRect = imageDOM.getBoundingClientRect();
-                    startingWidth = node.getWidth() || imageRect.width;
-                    startingHeight = node.getHeight() || imageRect.height;
+                    const domRect = nodeDOM.getBoundingClientRect();
+                    startingWidth = node.getWidth() || domRect.width;
+                    startingHeight = node.getHeight() || domRect.height;
                     if (node.getHeight()) {
                         hasHeight = true;
                     }
-                    startingRatio = startingWidth / startingHeight;
+                    startingRatio = startingHeight / startingWidth;
                 });
 
                 flipXChange = handle.classList.contains('nw') || handle.classList.contains('sw');
@@ -148,7 +161,7 @@ class ImageResizer {
                     node.setHeight(hasHeight ? size.height : 0);
                     _this.context.manager.triggerLayoutUpdate();
                     requestAnimationFrame(() => {
-                        _this.updateDOMPosition(imageDOM);
+                        _this.updateDOMPosition(nodeDOM);
                     })
                 });
                 _this.dom?.classList.remove('active');
@@ -158,8 +171,8 @@ class ImageResizer {
 }
 
 
-export function registerImageResizer(context: EditorUiContext): (() => void) {
-    const resizer = new ImageResizer(context);
+export function registerNodeResizer(context: EditorUiContext): (() => void) {
+    const resizer = new NodeResizer(context);
 
     return () => {
         resizer.teardown();
diff --git a/resources/js/wysiwyg/utils/dom.ts b/resources/js/wysiwyg/utils/dom.ts
index a307bdd75..d5c63a816 100644
--- a/resources/js/wysiwyg/utils/dom.ts
+++ b/resources/js/wysiwyg/utils/dom.ts
@@ -31,6 +31,22 @@ export function formatSizeValue(size: number | string, defaultSuffix: string = '
     return size;
 }
 
+export function sizeToPixels(size: string): number {
+    if (/^-?\d+$/.test(size)) {
+        return Number(size);
+    }
+
+    if (/^-?\d+\.\d+$/.test(size)) {
+        return Math.round(Number(size));
+    }
+
+    if (/^-?\d+px\s*$/.test(size)) {
+        return Number(size.trim().replace('px', ''));
+    }
+
+    return 0;
+}
+
 export type StyleMap = Map<string, string>;
 
 /**
diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss
index 80633df94..31ce564be 100644
--- a/resources/sass/_editor.scss
+++ b/resources/sass/_editor.scss
@@ -288,14 +288,14 @@ body.editor-is-fullscreen {
   position: relative;
   display: inline-flex;
 }
-.editor-image-resizer {
+.editor-node-resizer {
   position: absolute;
   left: 0;
   right: 0;
   display: inline-block;
   outline: 2px dashed var(--editor-color-primary);
 }
-.editor-image-resizer-handle {
+.editor-node-resizer-handle {
   position: absolute;
   display: block;
   width: 10px;
@@ -325,7 +325,7 @@ body.editor-is-fullscreen {
     cursor: sw-resize;
   }
 }
-.editor-image-resizer-ghost {
+.editor-node-resizer-ghost {
   opacity: 0.5;
   display: none;
   position: absolute;
@@ -335,8 +335,9 @@ body.editor-is-fullscreen {
   height: 100%;
   z-index: 2;
   pointer-events: none;
+  background-color: var(--editor-color-primary);
 }
-.editor-image-resizer.active .editor-image-resizer-ghost {
+.editor-node-resizer.active .editor-node-resizer-ghost {
   display: block;
 }
 
@@ -372,6 +373,13 @@ body.editor-is-fullscreen {
   outline: 2px dashed var(--editor-color-primary);
 }
 
+.editor-media-wrap {
+  cursor: not-allowed;
+  iframe {
+    pointer-events: none;
+  }
+}
+
 /**
  * Fake task list checkboxes
  */