comparison 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
comparison
equal deleted inserted replaced
205:e07b4b5a66bb 206:240337164a80
158 .rich-editor-content img { 158 .rich-editor-content img {
159 max-width: 100%; 159 max-width: 100%;
160 height: auto; 160 height: auto;
161 border-radius: 4px; 161 border-radius: 4px;
162 margin: 8px 0; 162 margin: 8px 0;
163 cursor: pointer;
164 }
165
166 .rich-editor-content img.selected {
167 outline: 2px solid #0078ff;
168 outline-offset: 2px;
169 }
170
171 .rich-editor-resize-wrapper {
172 position: relative;
173 display: inline-block;
174 margin: 8px 0;
175 }
176
177 .rich-editor-resize-handle {
178 position: absolute;
179 width: 12px;
180 height: 12px;
181 background: #0078ff;
182 border: 2px solid white;
183 border-radius: 50%;
184 cursor: nwse-resize;
185 z-index: 10;
186 }
187
188 .rich-editor-resize-handle.nw {
189 top: -6px;
190 left: -6px;
191 cursor: nwse-resize;
192 }
193
194 .rich-editor-resize-handle.ne {
195 top: -6px;
196 right: -6px;
197 cursor: nesw-resize;
198 }
199
200 .rich-editor-resize-handle.sw {
201 bottom: -6px;
202 left: -6px;
203 cursor: nesw-resize;
204 }
205
206 .rich-editor-resize-handle.se {
207 bottom: -6px;
208 right: -6px;
209 cursor: nwse-resize;
210 }
211
212 .rich-editor-size-input {
213 position: absolute;
214 top: -35px;
215 left: 50%;
216 transform: translateX(-50%);
217 background: white;
218 border: 1px solid #ccc;
219 padding: 4px 8px;
220 border-radius: 4px;
221 font-size: 12px;
222 display: flex;
223 gap: 8px;
224 align-items: center;
225 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
226 z-index: 11;
227 }
228
229 .rich-editor-size-input input {
230 width: 60px;
231 padding: 2px 4px;
232 border: 1px solid #ccc;
233 border-radius: 2px;
234 font-size: 12px;
235 }
236
237 .rich-editor-size-input label {
238 font-size: 11px;
239 color: #666;
163 } 240 }
164 241
165 .rich-editor-content a { 242 .rich-editor-content a {
166 color: #0078ff; 243 color: #0078ff;
167 text-decoration: none; 244 text-decoration: none;
240 317
241 class Editor { 318 class Editor {
242 constructor(elementId, options) { 319 constructor(elementId, options) {
243 this.options = { ...DEFAULT_OPTIONS, ...options }; 320 this.options = { ...DEFAULT_OPTIONS, ...options };
244 this.container = document.getElementById(elementId); 321 this.container = document.getElementById(elementId);
245 this.state = { readOnly: false }; 322 this.state = {
323 readOnly: false,
324 selectedImage: null,
325 resizing: false
326 };
246 327
247 if (!this.container) { 328 if (!this.container) {
248 throw new Error(`Element with id "${elementId}" not found`); 329 throw new Error(`Element with id "${elementId}" not found`);
249 } 330 }
250 331
389 this.save(); 470 this.save();
390 break; 471 break;
391 } 472 }
392 } 473 }
393 }); 474 });
475
476 // Handle image clicks for resizing
477 this.content.addEventListener('click', (e) => {
478 if (e.target.tagName === 'IMG') {
479 e.preventDefault();
480 this.selectImage(e.target);
481 } else if (!e.target.closest('.rich-editor-resize-wrapper')) {
482 this.deselectImage();
483 }
484 });
485
486 // Deselect image when clicking outside
487 document.addEventListener('click', (e) => {
488 if (!this.content.contains(e.target)) {
489 this.deselectImage();
490 }
491 });
394 } 492 }
395 493
396 showDropOverlay() { 494 showDropOverlay() {
397 if (this.dropOverlay) return; 495 if (this.dropOverlay) return;
398 496
466 // Replace placeholder with actual content 564 // Replace placeholder with actual content
467 if (file.type.startsWith('image/')) { 565 if (file.type.startsWith('image/')) {
468 const img = document.createElement('img'); 566 const img = document.createElement('img');
469 img.src = result.url; 567 img.src = result.url;
470 img.alt = file.name; 568 img.alt = file.name;
569
570 // Store original dimensions when image loads
571 img.onload = () => {
572 img.dataset.originalWidth = img.naturalWidth;
573 img.dataset.originalHeight = img.naturalHeight;
574 };
575
471 placeholder.replaceWith(img); 576 placeholder.replaceWith(img);
472 } else { 577 } else {
473 const link = document.createElement('a'); 578 const link = document.createElement('a');
474 link.href = result.url; 579 link.href = result.url;
475 link.textContent = file.name; 580 link.textContent = file.name;
634 } 739 }
635 740
636 focus() { 741 focus() {
637 this.content.focus(); 742 this.content.focus();
638 } 743 }
744
745 selectImage(img) {
746 if (this.state.readOnly) return;
747
748 // Deselect previous image
749 this.deselectImage();
750
751 this.state.selectedImage = img;
752 img.classList.add('selected');
753
754 // Wrap image in resize container if not already wrapped
755 if (!img.parentElement.classList.contains('rich-editor-resize-wrapper')) {
756 const wrapper = document.createElement('div');
757 wrapper.className = 'rich-editor-resize-wrapper';
758 wrapper.contentEditable = false;
759
760 // Store original dimensions if not set
761 if (!img.dataset.originalWidth) {
762 img.dataset.originalWidth = img.naturalWidth;
763 img.dataset.originalHeight = img.naturalHeight;
764 }
765
766 img.parentNode.insertBefore(wrapper, img);
767 wrapper.appendChild(img);
768 }
769
770 const wrapper = img.parentElement;
771
772 // Add resize handles
773 const corners = ['nw', 'ne', 'sw', 'se'];
774 corners.forEach(corner => {
775 const handle = document.createElement('div');
776 handle.className = `rich-editor-resize-handle ${corner}`;
777 handle.dataset.corner = corner;
778
779 handle.addEventListener('mousedown', (e) => {
780 e.preventDefault();
781 e.stopPropagation();
782 this.startResize(e, img, corner);
783 });
784
785 wrapper.appendChild(handle);
786 });
787
788 // Add size input
789 const sizeInput = document.createElement('div');
790 sizeInput.className = 'rich-editor-size-input';
791 sizeInput.innerHTML = `
792 <label>Width:</label>
793 <input type="number" class="width-input" value="${img.width}" min="50" max="2000">
794 <label>px</label>
795 `;
796
797 const widthInput = sizeInput.querySelector('.width-input');
798 widthInput.addEventListener('input', (e) => {
799 const newWidth = parseInt(e.target.value);
800 if (newWidth && newWidth > 0) {
801 this.resizeImage(img, newWidth);
802 }
803 });
804
805 wrapper.appendChild(sizeInput);
806 }
807
808 deselectImage() {
809 if (!this.state.selectedImage) return;
810
811 const img = this.state.selectedImage;
812 img.classList.remove('selected');
813
814 const wrapper = img.parentElement;
815 if (wrapper && wrapper.classList.contains('rich-editor-resize-wrapper')) {
816 // Remove resize handles and size input
817 wrapper.querySelectorAll('.rich-editor-resize-handle, .rich-editor-size-input').forEach(el => el.remove());
818
819 // Unwrap image
820 wrapper.parentNode.insertBefore(img, wrapper);
821 wrapper.remove();
822 }
823
824 this.state.selectedImage = null;
825 }
826
827 startResize(e, img, corner) {
828 this.state.resizing = true;
829
830 const startX = e.clientX;
831 const startY = e.clientY;
832 const startWidth = img.width;
833 const startHeight = img.height;
834 const aspectRatio = startWidth / startHeight;
835
836 const onMouseMove = (e) => {
837 if (!this.state.resizing) return;
838
839 let deltaX = e.clientX - startX;
840 let deltaY = e.clientY - startY;
841
842 // Adjust delta based on corner
843 if (corner === 'nw' || corner === 'sw') {
844 deltaX = -deltaX;
845 }
846 if (corner === 'nw' || corner === 'ne') {
847 deltaY = -deltaY;
848 }
849
850 // Use the larger delta to maintain aspect ratio
851 const delta = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY;
852 const newWidth = Math.max(50, startWidth + delta);
853
854 this.resizeImage(img, newWidth, aspectRatio);
855 };
856
857 const onMouseUp = () => {
858 this.state.resizing = false;
859 document.removeEventListener('mousemove', onMouseMove);
860 document.removeEventListener('mouseup', onMouseUp);
861
862 // Update width input
863 const wrapper = img.parentElement;
864 const widthInput = wrapper.querySelector('.width-input');
865 if (widthInput) {
866 widthInput.value = img.width;
867 }
868
869 if (this.debouncedSave) this.debouncedSave();
870 };
871
872 document.addEventListener('mousemove', onMouseMove);
873 document.addEventListener('mouseup', onMouseUp);
874 }
875
876 resizeImage(img, newWidth, aspectRatio = null) {
877 if (!aspectRatio) {
878 const originalWidth = parseInt(img.dataset.originalWidth) || img.naturalWidth;
879 const originalHeight = parseInt(img.dataset.originalHeight) || img.naturalHeight;
880 aspectRatio = originalWidth / originalHeight;
881 }
882
883 const newHeight = newWidth / aspectRatio;
884
885 img.width = newWidth;
886 img.height = newHeight;
887 img.style.width = newWidth + 'px';
888 img.style.height = newHeight + 'px';
889 }
639 } 890 }
640 891
641 return { 892 return {
642 init: function(elementId, options) { 893 init: function(elementId, options) {
643 return new Editor(elementId, options); 894 return new Editor(elementId, options);