comparison rich_editor/rich_editor.js @ 208:5d3e116dd745

[MrJuneJune] made it more mobile friendly.
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 12:33:54 -0800
parents 240337164a80
children
comparison
equal deleted inserted replaced
207:58d9b64d8dca 208:5d3e116dd745
85 }); 85 });
86 86
87 return toolbar; 87 return toolbar;
88 } 88 }
89 89
90 function ensureViewportMeta() {
91 // Check if viewport meta tag exists
92 let viewport = document.querySelector('meta[name="viewport"]');
93 if (!viewport) {
94 viewport = document.createElement('meta');
95 viewport.name = 'viewport';
96 viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0';
97 document.head.appendChild(viewport);
98 }
99 }
100
90 function createStyles() { 101 function createStyles() {
91 if (document.getElementById('rich-editor-styles')) return; 102 if (document.getElementById('rich-editor-styles')) return;
103
104 ensureViewportMeta();
92 105
93 const style = document.createElement('style'); 106 const style = document.createElement('style');
94 style.id = 'rich-editor-styles'; 107 style.id = 'rich-editor-styles';
95 style.textContent = ` 108 style.textContent = `
96 .rich-editor-container { 109 .rich-editor-container {
292 justify-content: center; 305 justify-content: center;
293 font-size: 18px; 306 font-size: 18px;
294 color: #0078ff; 307 color: #0078ff;
295 pointer-events: none; 308 pointer-events: none;
296 z-index: 10; 309 z-index: 10;
310 padding: 20px;
311 text-align: center;
297 } 312 }
298 313
299 .rich-editor-status { 314 .rich-editor-status {
300 padding: 4px 8px; 315 padding: 4px 8px;
301 font-size: 12px; 316 font-size: 12px;
308 display: none; 323 display: none;
309 } 324 }
310 325
311 ol,ul { 326 ol,ul {
312 padding: 16px; 327 padding: 16px;
328 }
329
330 /* Mobile optimizations */
331 @media (max-width: 720px) {
332 .rich-editor-toolbar {
333 padding: 6px;
334 gap: 3px;
335 overflow-x: auto;
336 -webkit-overflow-scrolling: touch;
337 }
338
339 .rich-editor-btn {
340 min-width: 44px;
341 min-height: 44px;
342 padding: 8px;
343 flex-shrink: 0;
344 }
345
346 .rich-editor-content {
347 min-height: 200px;
348 padding: 12px;
349 font-size: 16px; /* Prevents iOS zoom */
350 line-height: 1.6;
351 }
352
353 .rich-editor-content h1 {
354 font-size: 1.75em;
355 }
356
357 .rich-editor-content h2 {
358 font-size: 1.5em;
359 }
360
361 .rich-editor-content h3 {
362 font-size: 1.25em;
363 }
364
365 .rich-editor-status {
366 font-size: 11px;
367 padding: 6px 8px;
368 }
369
370 /* Mobile-friendly image resizing */
371 .rich-editor-resize-handle {
372 width: 20px;
373 height: 20px;
374 border-width: 3px;
375 }
376
377 .rich-editor-resize-handle.nw {
378 top: -10px;
379 left: -10px;
380 }
381
382 .rich-editor-resize-handle.ne {
383 top: -10px;
384 right: -10px;
385 }
386
387 .rich-editor-resize-handle.sw {
388 bottom: -10px;
389 left: -10px;
390 }
391
392 .rich-editor-resize-handle.se {
393 bottom: -10px;
394 right: -10px;
395 }
396
397 .rich-editor-size-input {
398 top: -40px;
399 padding: 6px 10px;
400 }
401
402 .rich-editor-size-input input {
403 width: 70px;
404 padding: 4px 6px;
405 font-size: 16px; /* Prevents iOS zoom */
406 }
407
408 /* Better touch targets for images */
409 .rich-editor-content img {
410 margin: 12px 0;
411 }
412
413 /* Hide drop overlay text on small screens */
414 .rich-editor-upload-overlay {
415 font-size: 16px;
416 }
417
418 /* Make link/note dialogs mobile-friendly */
419 .rich-editor-content a {
420 padding: 2px 0;
421 }
422 }
423
424 /* Extra small devices */
425 @media (max-width: 480px) {
426 .rich-editor-toolbar {
427 padding: 4px;
428 gap: 2px;
429 }
430
431 .rich-editor-btn {
432 min-width: 40px;
433 min-height: 40px;
434 padding: 6px;
435 }
436
437 .rich-editor-content {
438 padding: 10px;
439 font-size: 16px;
440 }
441
442 .rich-editor-size-input {
443 font-size: 11px;
444 }
445
446 .rich-editor-size-input input {
447 width: 60px;
448 font-size: 14px;
449 }
313 } 450 }
314 `; 451 `;
315 document.head.appendChild(style); 452 document.head.appendChild(style);
316 } 453 }
317 454
774 corners.forEach(corner => { 911 corners.forEach(corner => {
775 const handle = document.createElement('div'); 912 const handle = document.createElement('div');
776 handle.className = `rich-editor-resize-handle ${corner}`; 913 handle.className = `rich-editor-resize-handle ${corner}`;
777 handle.dataset.corner = corner; 914 handle.dataset.corner = corner;
778 915
916 // Mouse events
779 handle.addEventListener('mousedown', (e) => { 917 handle.addEventListener('mousedown', (e) => {
780 e.preventDefault(); 918 e.preventDefault();
781 e.stopPropagation(); 919 e.stopPropagation();
782 this.startResize(e, img, corner); 920 this.startResize(e, img, corner);
921 });
922
923 // Touch events for mobile
924 handle.addEventListener('touchstart', (e) => {
925 e.preventDefault();
926 e.stopPropagation();
927 const touch = e.touches[0];
928 this.startResize(touch, img, corner, true);
783 }); 929 });
784 930
785 wrapper.appendChild(handle); 931 wrapper.appendChild(handle);
786 }); 932 });
787 933
822 } 968 }
823 969
824 this.state.selectedImage = null; 970 this.state.selectedImage = null;
825 } 971 }
826 972
827 startResize(e, img, corner) { 973 startResize(e, img, corner, isTouch = false) {
828 this.state.resizing = true; 974 this.state.resizing = true;
829 975
830 const startX = e.clientX; 976 const startX = e.clientX;
831 const startY = e.clientY; 977 const startY = e.clientY;
832 const startWidth = img.width; 978 const startWidth = img.width;
833 const startHeight = img.height; 979 const startHeight = img.height;
834 const aspectRatio = startWidth / startHeight; 980 const aspectRatio = startWidth / startHeight;
835 981
836 const onMouseMove = (e) => { 982 const onMove = (e) => {
837 if (!this.state.resizing) return; 983 if (!this.state.resizing) return;
838 984
839 let deltaX = e.clientX - startX; 985 const clientX = isTouch ? e.touches[0].clientX : e.clientX;
840 let deltaY = e.clientY - startY; 986 const clientY = isTouch ? e.touches[0].clientY : e.clientY;
987
988 let deltaX = clientX - startX;
989 let deltaY = clientY - startY;
841 990
842 // Adjust delta based on corner 991 // Adjust delta based on corner
843 if (corner === 'nw' || corner === 'sw') { 992 if (corner === 'nw' || corner === 'sw') {
844 deltaX = -deltaX; 993 deltaX = -deltaX;
845 } 994 }
852 const newWidth = Math.max(50, startWidth + delta); 1001 const newWidth = Math.max(50, startWidth + delta);
853 1002
854 this.resizeImage(img, newWidth, aspectRatio); 1003 this.resizeImage(img, newWidth, aspectRatio);
855 }; 1004 };
856 1005
857 const onMouseUp = () => { 1006 const onEnd = () => {
858 this.state.resizing = false; 1007 this.state.resizing = false;
859 document.removeEventListener('mousemove', onMouseMove); 1008
860 document.removeEventListener('mouseup', onMouseUp); 1009 if (isTouch) {
1010 document.removeEventListener('touchmove', onMove);
1011 document.removeEventListener('touchend', onEnd);
1012 } else {
1013 document.removeEventListener('mousemove', onMove);
1014 document.removeEventListener('mouseup', onEnd);
1015 }
861 1016
862 // Update width input 1017 // Update width input
863 const wrapper = img.parentElement; 1018 const wrapper = img.parentElement;
864 const widthInput = wrapper.querySelector('.width-input'); 1019 const widthInput = wrapper.querySelector('.width-input');
865 if (widthInput) { 1020 if (widthInput) {
867 } 1022 }
868 1023
869 if (this.debouncedSave) this.debouncedSave(); 1024 if (this.debouncedSave) this.debouncedSave();
870 }; 1025 };
871 1026
872 document.addEventListener('mousemove', onMouseMove); 1027 if (isTouch) {
873 document.addEventListener('mouseup', onMouseUp); 1028 document.addEventListener('touchmove', onMove, { passive: false });
1029 document.addEventListener('touchend', onEnd);
1030 } else {
1031 document.addEventListener('mousemove', onMove);
1032 document.addEventListener('mouseup', onEnd);
1033 }
874 } 1034 }
875 1035
876 resizeImage(img, newWidth, aspectRatio = null) { 1036 resizeImage(img, newWidth, aspectRatio = null) {
877 if (!aspectRatio) { 1037 if (!aspectRatio) {
878 const originalWidth = parseInt(img.dataset.originalWidth) || img.naturalWidth; 1038 const originalWidth = parseInt(img.dataset.originalWidth) || img.naturalWidth;