|
@@ -12,10 +12,14 @@ class WYSIWYGEditor {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
this.selectedImages = [];
|
|
this.selectedImages = [];
|
|
|
|
|
+ this.resizingImage = null;
|
|
|
|
|
+ this.resizeData = null;
|
|
|
|
|
+ this.aspectRatioLocked = false;
|
|
|
|
|
|
|
|
this.createEditor();
|
|
this.createEditor();
|
|
|
this.bindEvents();
|
|
this.bindEvents();
|
|
|
this.setupImageUpload();
|
|
this.setupImageUpload();
|
|
|
|
|
+ this.setupImageResizing();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
createEditor() {
|
|
createEditor() {
|
|
@@ -365,6 +369,222 @@ class WYSIWYGEditor {
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ setupImageResizing() {
|
|
|
|
|
+ this.content.addEventListener('click', (e) => {
|
|
|
|
|
+ if (e.target.tagName === 'IMG') {
|
|
|
|
|
+ this.selectImageForResize(e.target);
|
|
|
|
|
+ } else if (e.target.closest('.resize-container')) {
|
|
|
|
|
+ this.selectImageForResize(e.target.closest('.resize-container').querySelector('img'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.deselectImageForResize();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Handle resize handle mouse events
|
|
|
|
|
+ this.content.addEventListener('mousedown', (e) => {
|
|
|
|
|
+ if (e.target.classList.contains('resize-handle')) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ this.startResize(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Handle global mouse events for resizing
|
|
|
|
|
+ document.addEventListener('mousemove', (e) => {
|
|
|
|
|
+ if (this.resizingImage) {
|
|
|
|
|
+ this.handleResize(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('mouseup', () => {
|
|
|
|
|
+ if (this.resizingImage) {
|
|
|
|
|
+ this.stopResize();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ selectImageForResize(img) {
|
|
|
|
|
+ this.deselectImageForResize();
|
|
|
|
|
+
|
|
|
|
|
+ // Wrap image in resize container if not already wrapped
|
|
|
|
|
+ if (!img.closest('.resize-container')) {
|
|
|
|
|
+ const container = document.createElement('div');
|
|
|
|
|
+ container.className = 'resize-container';
|
|
|
|
|
+ img.parentNode.insertBefore(container, img);
|
|
|
|
|
+ container.appendChild(img);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const container = img.closest('.resize-container');
|
|
|
|
|
+ container.classList.add('resizing');
|
|
|
|
|
+
|
|
|
|
|
+ // Add resize handles
|
|
|
|
|
+ this.addResizeHandles(container);
|
|
|
|
|
+
|
|
|
|
|
+ // Add aspect ratio toggle
|
|
|
|
|
+ this.addAspectRatioToggle(container);
|
|
|
|
|
+
|
|
|
|
|
+ // Add size display
|
|
|
|
|
+ this.addSizeDisplay(container);
|
|
|
|
|
+
|
|
|
|
|
+ this.updateSizeDisplay(container);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ deselectImageForResize() {
|
|
|
|
|
+ // Remove all resize containers and handles
|
|
|
|
|
+ this.content.querySelectorAll('.resize-container').forEach(container => {
|
|
|
|
|
+ const img = container.querySelector('img');
|
|
|
|
|
+ if (img) {
|
|
|
|
|
+ container.parentNode.insertBefore(img, container);
|
|
|
|
|
+ }
|
|
|
|
|
+ container.remove();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.resizingImage = null;
|
|
|
|
|
+ this.resizeData = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ addResizeHandles(container) {
|
|
|
|
|
+ const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e'];
|
|
|
|
|
+
|
|
|
|
|
+ handles.forEach(position => {
|
|
|
|
|
+ const handle = document.createElement('div');
|
|
|
|
|
+ handle.className = `resize-handle ${position}`;
|
|
|
|
|
+ handle.dataset.position = position;
|
|
|
|
|
+ container.appendChild(handle);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ addAspectRatioToggle(container) {
|
|
|
|
|
+ const toggle = document.createElement('button');
|
|
|
|
|
+ toggle.className = 'aspect-ratio-toggle';
|
|
|
|
|
+ toggle.textContent = this.aspectRatioLocked ? 'Locked' : 'Free';
|
|
|
|
|
+ toggle.title = 'Toggle aspect ratio lock';
|
|
|
|
|
+
|
|
|
|
|
+ toggle.addEventListener('click', (e) => {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ this.aspectRatioLocked = !this.aspectRatioLocked;
|
|
|
|
|
+ toggle.textContent = this.aspectRatioLocked ? 'Locked' : 'Free';
|
|
|
|
|
+ toggle.classList.toggle('locked', this.aspectRatioLocked);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ container.appendChild(toggle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ addSizeDisplay(container) {
|
|
|
|
|
+ const display = document.createElement('div');
|
|
|
|
|
+ display.className = 'size-display';
|
|
|
|
|
+ container.appendChild(display);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ updateSizeDisplay(container) {
|
|
|
|
|
+ const img = container.querySelector('img');
|
|
|
|
|
+ const display = container.querySelector('.size-display');
|
|
|
|
|
+ if (img && display) {
|
|
|
|
|
+ display.textContent = `${img.offsetWidth} × ${img.offsetHeight}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ startResize(e) {
|
|
|
|
|
+ const handle = e.target;
|
|
|
|
|
+ const container = handle.closest('.resize-container');
|
|
|
|
|
+ const img = container.querySelector('img');
|
|
|
|
|
+
|
|
|
|
|
+ this.resizingImage = img;
|
|
|
|
|
+ this.resizeData = {
|
|
|
|
|
+ container: container,
|
|
|
|
|
+ handle: handle,
|
|
|
|
|
+ position: handle.dataset.position,
|
|
|
|
|
+ startX: e.clientX,
|
|
|
|
|
+ startY: e.clientY,
|
|
|
|
|
+ startWidth: img.offsetWidth,
|
|
|
|
|
+ startHeight: img.offsetHeight,
|
|
|
|
|
+ aspectRatio: img.offsetWidth / img.offsetHeight
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ handleResize(e) {
|
|
|
|
|
+ if (!this.resizeData) return;
|
|
|
|
|
+
|
|
|
|
|
+ const deltaX = e.clientX - this.resizeData.startX;
|
|
|
|
|
+ const deltaY = e.clientY - this.resizeData.startY;
|
|
|
|
|
+ const position = this.resizeData.position;
|
|
|
|
|
+
|
|
|
|
|
+ let newWidth = this.resizeData.startWidth;
|
|
|
|
|
+ let newHeight = this.resizeData.startHeight;
|
|
|
|
|
+
|
|
|
|
|
+ switch (position) {
|
|
|
|
|
+ case 'se':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth + deltaX;
|
|
|
|
|
+ newHeight = this.aspectRatioLocked
|
|
|
|
|
+ ? newWidth / this.resizeData.aspectRatio
|
|
|
|
|
+ : this.resizeData.startHeight + deltaY;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'sw':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth - deltaX;
|
|
|
|
|
+ newHeight = this.aspectRatioLocked
|
|
|
|
|
+ ? newWidth / this.resizeData.aspectRatio
|
|
|
|
|
+ : this.resizeData.startHeight + deltaY;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'ne':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth + deltaX;
|
|
|
|
|
+ newHeight = this.aspectRatioLocked
|
|
|
|
|
+ ? newWidth / this.resizeData.aspectRatio
|
|
|
|
|
+ : this.resizeData.startHeight - deltaY;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'nw':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth - deltaX;
|
|
|
|
|
+ newHeight = this.aspectRatioLocked
|
|
|
|
|
+ ? newWidth / this.resizeData.aspectRatio
|
|
|
|
|
+ : this.resizeData.startHeight - deltaY;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'n':
|
|
|
|
|
+ newHeight = this.resizeData.startHeight - deltaY;
|
|
|
|
|
+ if (this.aspectRatioLocked) {
|
|
|
|
|
+ newWidth = newHeight * this.resizeData.aspectRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 's':
|
|
|
|
|
+ newHeight = this.resizeData.startHeight + deltaY;
|
|
|
|
|
+ if (this.aspectRatioLocked) {
|
|
|
|
|
+ newWidth = newHeight * this.resizeData.aspectRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'w':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth - deltaX;
|
|
|
|
|
+ if (this.aspectRatioLocked) {
|
|
|
|
|
+ newHeight = newWidth / this.resizeData.aspectRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'e':
|
|
|
|
|
+ newWidth = this.resizeData.startWidth + deltaX;
|
|
|
|
|
+ if (this.aspectRatioLocked) {
|
|
|
|
|
+ newHeight = newWidth / this.resizeData.aspectRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Apply minimum size constraints
|
|
|
|
|
+ newWidth = Math.max(50, newWidth);
|
|
|
|
|
+ newHeight = Math.max(50, newHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // Apply new dimensions
|
|
|
|
|
+ this.resizingImage.style.width = newWidth + 'px';
|
|
|
|
|
+ this.resizingImage.style.height = newHeight + 'px';
|
|
|
|
|
+
|
|
|
|
|
+ // Update size display
|
|
|
|
|
+ this.updateSizeDisplay(this.resizeData.container);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ stopResize() {
|
|
|
|
|
+ if (this.resizeData) {
|
|
|
|
|
+ // Update textarea content
|
|
|
|
|
+ this.textarea.value = this.content.innerHTML;
|
|
|
|
|
+ this.updateCharCount();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.resizingImage = null;
|
|
|
|
|
+ this.resizeData = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
formatHeading(tag) {
|
|
formatHeading(tag) {
|
|
|
const selection = window.getSelection();
|
|
const selection = window.getSelection();
|
|
|
const range = selection.getRangeAt(0);
|
|
const range = selection.getRangeAt(0);
|