diff rich_editor/rich_editor.js @ 206:240337164a80

[Seobeo] SSL should be used for large file as well lol.
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 11:41:53 -0800
parents e5aed6c36672
children 5d3e116dd745
line wrap: on
line diff
--- a/rich_editor/rich_editor.js	Sun Feb 15 11:07:52 2026 -0800
+++ b/rich_editor/rich_editor.js	Sun Feb 15 11:41:53 2026 -0800
@@ -160,6 +160,83 @@
         height: auto;
         border-radius: 4px;
         margin: 8px 0;
+        cursor: pointer;
+      }
+
+      .rich-editor-content img.selected {
+        outline: 2px solid #0078ff;
+        outline-offset: 2px;
+      }
+
+      .rich-editor-resize-wrapper {
+        position: relative;
+        display: inline-block;
+        margin: 8px 0;
+      }
+
+      .rich-editor-resize-handle {
+        position: absolute;
+        width: 12px;
+        height: 12px;
+        background: #0078ff;
+        border: 2px solid white;
+        border-radius: 50%;
+        cursor: nwse-resize;
+        z-index: 10;
+      }
+
+      .rich-editor-resize-handle.nw {
+        top: -6px;
+        left: -6px;
+        cursor: nwse-resize;
+      }
+
+      .rich-editor-resize-handle.ne {
+        top: -6px;
+        right: -6px;
+        cursor: nesw-resize;
+      }
+
+      .rich-editor-resize-handle.sw {
+        bottom: -6px;
+        left: -6px;
+        cursor: nesw-resize;
+      }
+
+      .rich-editor-resize-handle.se {
+        bottom: -6px;
+        right: -6px;
+        cursor: nwse-resize;
+      }
+
+      .rich-editor-size-input {
+        position: absolute;
+        top: -35px;
+        left: 50%;
+        transform: translateX(-50%);
+        background: white;
+        border: 1px solid #ccc;
+        padding: 4px 8px;
+        border-radius: 4px;
+        font-size: 12px;
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+        z-index: 11;
+      }
+
+      .rich-editor-size-input input {
+        width: 60px;
+        padding: 2px 4px;
+        border: 1px solid #ccc;
+        border-radius: 2px;
+        font-size: 12px;
+      }
+
+      .rich-editor-size-input label {
+        font-size: 11px;
+        color: #666;
       }
 
       .rich-editor-content a {
@@ -242,7 +319,11 @@
     constructor(elementId, options) {
       this.options = { ...DEFAULT_OPTIONS, ...options };
       this.container = document.getElementById(elementId);
-      this.state = { readOnly: false };
+      this.state = {
+        readOnly: false,
+        selectedImage: null,
+        resizing: false
+      };
 
       if (!this.container) {
         throw new Error(`Element with id "${elementId}" not found`);
@@ -391,6 +472,23 @@
           }
         }
       });
+
+      // Handle image clicks for resizing
+      this.content.addEventListener('click', (e) => {
+        if (e.target.tagName === 'IMG') {
+          e.preventDefault();
+          this.selectImage(e.target);
+        } else if (!e.target.closest('.rich-editor-resize-wrapper')) {
+          this.deselectImage();
+        }
+      });
+
+      // Deselect image when clicking outside
+      document.addEventListener('click', (e) => {
+        if (!this.content.contains(e.target)) {
+          this.deselectImage();
+        }
+      });
     }
 
     showDropOverlay() {
@@ -468,6 +566,13 @@
             const img = document.createElement('img');
             img.src = result.url;
             img.alt = file.name;
+
+            // Store original dimensions when image loads
+            img.onload = () => {
+              img.dataset.originalWidth = img.naturalWidth;
+              img.dataset.originalHeight = img.naturalHeight;
+            };
+
             placeholder.replaceWith(img);
           } else {
             const link = document.createElement('a');
@@ -636,6 +741,152 @@
     focus() {
       this.content.focus();
     }
+
+    selectImage(img) {
+      if (this.state.readOnly) return;
+
+      // Deselect previous image
+      this.deselectImage();
+
+      this.state.selectedImage = img;
+      img.classList.add('selected');
+
+      // Wrap image in resize container if not already wrapped
+      if (!img.parentElement.classList.contains('rich-editor-resize-wrapper')) {
+        const wrapper = document.createElement('div');
+        wrapper.className = 'rich-editor-resize-wrapper';
+        wrapper.contentEditable = false;
+
+        // Store original dimensions if not set
+        if (!img.dataset.originalWidth) {
+          img.dataset.originalWidth = img.naturalWidth;
+          img.dataset.originalHeight = img.naturalHeight;
+        }
+
+        img.parentNode.insertBefore(wrapper, img);
+        wrapper.appendChild(img);
+      }
+
+      const wrapper = img.parentElement;
+
+      // Add resize handles
+      const corners = ['nw', 'ne', 'sw', 'se'];
+      corners.forEach(corner => {
+        const handle = document.createElement('div');
+        handle.className = `rich-editor-resize-handle ${corner}`;
+        handle.dataset.corner = corner;
+
+        handle.addEventListener('mousedown', (e) => {
+          e.preventDefault();
+          e.stopPropagation();
+          this.startResize(e, img, corner);
+        });
+
+        wrapper.appendChild(handle);
+      });
+
+      // Add size input
+      const sizeInput = document.createElement('div');
+      sizeInput.className = 'rich-editor-size-input';
+      sizeInput.innerHTML = `
+        <label>Width:</label>
+        <input type="number" class="width-input" value="${img.width}" min="50" max="2000">
+        <label>px</label>
+      `;
+
+      const widthInput = sizeInput.querySelector('.width-input');
+      widthInput.addEventListener('input', (e) => {
+        const newWidth = parseInt(e.target.value);
+        if (newWidth && newWidth > 0) {
+          this.resizeImage(img, newWidth);
+        }
+      });
+
+      wrapper.appendChild(sizeInput);
+    }
+
+    deselectImage() {
+      if (!this.state.selectedImage) return;
+
+      const img = this.state.selectedImage;
+      img.classList.remove('selected');
+
+      const wrapper = img.parentElement;
+      if (wrapper && wrapper.classList.contains('rich-editor-resize-wrapper')) {
+        // Remove resize handles and size input
+        wrapper.querySelectorAll('.rich-editor-resize-handle, .rich-editor-size-input').forEach(el => el.remove());
+
+        // Unwrap image
+        wrapper.parentNode.insertBefore(img, wrapper);
+        wrapper.remove();
+      }
+
+      this.state.selectedImage = null;
+    }
+
+    startResize(e, img, corner) {
+      this.state.resizing = true;
+
+      const startX = e.clientX;
+      const startY = e.clientY;
+      const startWidth = img.width;
+      const startHeight = img.height;
+      const aspectRatio = startWidth / startHeight;
+
+      const onMouseMove = (e) => {
+        if (!this.state.resizing) return;
+
+        let deltaX = e.clientX - startX;
+        let deltaY = e.clientY - startY;
+
+        // Adjust delta based on corner
+        if (corner === 'nw' || corner === 'sw') {
+          deltaX = -deltaX;
+        }
+        if (corner === 'nw' || corner === 'ne') {
+          deltaY = -deltaY;
+        }
+
+        // Use the larger delta to maintain aspect ratio
+        const delta = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY;
+        const newWidth = Math.max(50, startWidth + delta);
+
+        this.resizeImage(img, newWidth, aspectRatio);
+      };
+
+      const onMouseUp = () => {
+        this.state.resizing = false;
+        document.removeEventListener('mousemove', onMouseMove);
+        document.removeEventListener('mouseup', onMouseUp);
+
+        // Update width input
+        const wrapper = img.parentElement;
+        const widthInput = wrapper.querySelector('.width-input');
+        if (widthInput) {
+          widthInput.value = img.width;
+        }
+
+        if (this.debouncedSave) this.debouncedSave();
+      };
+
+      document.addEventListener('mousemove', onMouseMove);
+      document.addEventListener('mouseup', onMouseUp);
+    }
+
+    resizeImage(img, newWidth, aspectRatio = null) {
+      if (!aspectRatio) {
+        const originalWidth = parseInt(img.dataset.originalWidth) || img.naturalWidth;
+        const originalHeight = parseInt(img.dataset.originalHeight) || img.naturalHeight;
+        aspectRatio = originalWidth / originalHeight;
+      }
+
+      const newHeight = newWidth / aspectRatio;
+
+      img.width = newWidth;
+      img.height = newHeight;
+      img.style.width = newWidth + 'px';
+      img.style.height = newHeight + 'px';
+    }
   }
 
   return {