Changeset feced2c in Klonkt for src/assets/js/reply-editor.js
- Timestamp:
- 07/19/2026 05:25:26 PM (7 weeks ago)
- Branches:
- main
- Children:
- 5190152
- Parents:
- 33e1dbd
- git-author:
- Robin <roboburr@…> (07/19/2026 05:24:58 PM)
- git-committer:
- Robin <roboburr@…> (07/19/2026 05:25:26 PM)
- File:
-
- 1 edited
-
src/assets/js/reply-editor.js (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/reply-editor.js
r33e1dbd rfeced2c 32 32 if (lang) lang.hidden = false; 33 33 34 // ── Media attachments: picker (📎), drag/drop and paste ────────────── 35 var attWrap = form.querySelector('.re-attachments'); 36 var attField = form.querySelector('input[name="attachments"]'); 37 var fileInput = form.querySelector('.re-file'); 38 var attachments = []; 39 40 function syncAtt() { 41 attField.value = attachments.length ? JSON.stringify(attachments) : ''; 42 attWrap.hidden = attachments.length === 0; 43 } 44 function addChip(a) { 45 var chip = document.createElement('span'); 46 chip.className = 're-att'; 47 if (a.mediaType.indexOf('image/') === 0) { 48 var img = document.createElement('img'); 49 img.src = a.url; img.alt = a.name || ''; 50 chip.appendChild(img); 51 } else { 52 chip.appendChild(document.createTextNode((a.mediaType.indexOf('audio/') === 0 ? '🎵 ' : '🎬 ') + (a.name || a.mediaType))); 53 } 54 var del = document.createElement('button'); 55 del.type = 'button'; del.className = 're-att-del'; del.textContent = '×'; 56 del.addEventListener('click', function () { 57 attachments = attachments.filter(function (x) { return x !== a; }); 58 chip.remove(); syncAtt(); 59 }); 60 chip.appendChild(del); 61 attWrap.appendChild(chip); 62 } 63 function uploadFiles(files) { 64 Array.prototype.forEach.call(files, function (file) { 65 if (!/^(image|audio|video)\//.test(file.type) || attachments.length >= 4) return; 66 var chip = document.createElement('span'); 67 chip.className = 're-att re-att-busy'; 68 chip.textContent = '⏳ ' + file.name; 69 attWrap.hidden = false; 70 attWrap.appendChild(chip); 71 var fd = new FormData(); 72 fd.append('media', file); 73 fetch(form.getAttribute('data-upload'), { method: 'POST', body: fd }) 74 .then(function (r) { return r.json().then(function (j) { return r.ok ? j : Promise.reject(j); }); }) 75 .then(function (j) { 76 chip.remove(); 77 var a = { url: j.url, mediaType: j.mediaType, name: j.name || file.name }; 78 attachments.push(a); addChip(a); syncAtt(); 79 }) 80 .catch(function (err) { 81 chip.className = 're-att re-att-err'; 82 chip.textContent = (form.getAttribute('data-upload-err') || 'Upload failed') + (err && err.error ? ': ' + err.error : ''); 83 setTimeout(function () { chip.remove(); syncAtt(); }, 5000); 84 }); 85 }); 86 } 87 34 88 // Toolbar commands (execCommand is deprecated-but-universal; same approach 35 89 // as the post editor). … … 38 92 if (!btn) return; 39 93 e.preventDefault(); 94 var cmd = btn.getAttribute('data-cmd'); 95 if (cmd === 'attach') { fileInput.click(); return; } // no editor focus: keeps the picker usable on mobile 40 96 ed.focus(); 41 var cmd = btn.getAttribute('data-cmd');42 97 if (cmd === 'bold') document.execCommand('bold'); 43 98 else if (cmd === 'italic') document.execCommand('italic'); … … 49 104 } 50 105 }); 106 fileInput.addEventListener('change', function () { 107 uploadFiles(fileInput.files); 108 fileInput.value = ''; 109 }); 51 110 52 // Paste as plain text (rich paste becomes messy HTML; formatting is what53 // the toolbar is for). Media paste/drop lands in the media phase.111 // Paste: files become attachments; text pastes as plain text (rich paste 112 // becomes messy HTML; formatting is what the toolbar is for). 54 113 ed.addEventListener('paste', function (e) { 55 var txt = (e.clipboardData || window.clipboardData).getData('text/plain'); 114 var cd = e.clipboardData || window.clipboardData; 115 if (cd.files && cd.files.length) { 116 e.preventDefault(); 117 uploadFiles(cd.files); 118 return; 119 } 120 var txt = cd.getData('text/plain'); 56 121 if (!txt) return; 57 122 e.preventDefault(); 58 123 document.execCommand('insertText', false, txt); 124 }); 125 126 // Drag/drop media onto the editor. 127 ed.addEventListener('dragover', function (e) { 128 if (e.dataTransfer && Array.prototype.some.call(e.dataTransfer.types || [], function (t) { return t === 'Files'; })) { 129 e.preventDefault(); 130 ed.classList.add('re-drop'); 131 } 132 }); 133 ed.addEventListener('dragleave', function () { ed.classList.remove('re-drop'); }); 134 ed.addEventListener('drop', function (e) { 135 ed.classList.remove('re-drop'); 136 if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) { 137 e.preventDefault(); 138 uploadFiles(e.dataTransfer.files); 139 } 59 140 }); 60 141 … … 72 153 if (cancel) cancel.addEventListener('click', function () { setFull(false); }); 73 154 74 // Serialize on submit; block truly empty replies .155 // Serialize on submit; block truly empty replies (media-only is fine). 75 156 form.addEventListener('submit', function (e) { 76 157 var html = ed.innerHTML.trim(); 77 158 var plain = (ed.innerText || '').replace(/ /g, ' ').trim(); 78 if (!plain ) { e.preventDefault(); ed.focus(); return; }79 hidden.value = html;159 if (!plain && !attachments.length) { e.preventDefault(); ed.focus(); return; } 160 hidden.value = plain ? html : ''; 80 161 ta.value = plain; 81 162 document.documentElement.classList.remove('re-lock');
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)