Fossil SCM

Fix display of attached filename when an attachment slot is re-selected. When pasting image data in the description field, attach that image (this is easier than first tapping the narrow border then ctrl-v).

stephan 2026-06-04 19:10 UTC attach-v2
Commit 69f1fe3ece563ff9d59146b3296ad1e123c5bda11c6ae2f4891b11b858b6704b
1 file changed +42 -21
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -291,26 +291,43 @@
291291
ev.preventDefault();
292292
eDropzone.classList.remove('dragover');
293293
if( ev.dataTransfer.files.length ){
294294
this.#injestBlob(rowObj, ev.dataTransfer.files[0]);
295295
}
296
+ });
297
+ const pasteImage = (event, item)=>{
298
+ if( item.type.indexOf('image') === 0 ) {
299
+ event.preventDefault();
300
+ const blob = item.getAsFile();
301
+ this.#injestBlob(rowObj, blob);
302
+ return true;
303
+ }
304
+ return false;
305
+ };
306
+ eDesc?.addEventListener?.('paste', (e) => {
307
+ e.stopPropagation();
308
+ const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
309
+ if( !items ) return;
310
+ for( let i = 0; i < items.length; ++i ){
311
+ const item = items[i];
312
+ if( pasteImage(e, item) ){
313
+ break;
314
+ }
315
+ }
296316
});
297317
eRow.addEventListener('paste', (e) => {
298318
const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
299319
if( !items ) return;
300320
for( let i = 0; i < items.length; ++i ){
301321
const item = items[i];
302
- if( item.type.indexOf('image') === 0 ) {
303
- e.preventDefault();
304
- const blob = item.getAsFile();
305
- this.#injestBlob(rowObj, blob);
322
+ if( pasteImage(e, item) ){
306323
break;
307324
}else if( item.type === 'text/plain' ){
308325
e.preventDefault();
309326
item.getAsString((text) => {
310
- rowObj.name = `pasted-text-${Date.now()}.txt`;
311
- const blob = new File([text], rowObj.name,
327
+ rowObj.overrideName = `pasted-text-${Date.now()}.txt`;
328
+ const blob = new File([text], rowObj.overrideName,
312329
{type: 'text/plain'});
313330
this.#injestBlob(rowObj, blob);
314331
});
315332
break;
316333
}else if(0){
@@ -364,25 +381,12 @@
364381
if( file.name === 'image.png' ){
365382
/* Workaround to attempt to avoid name collisions when pasting
366383
multiple images. We cannot, at this level, unambiguously
367384
distinguish a ctrl-v of bitmap data vs a ctrl-v of an image
368385
file copied via a desktop file manager. */
369
- rowObj.name = `pasted-image-${Date.now()}.png`;
370
- }
371
- if( rowObj.name && rowObj.name!==file.name ){
372
- file = new File([file], rowObj.name, {type: file.type});
373
- }
374
-
375
- let szLbl;
376
- if( file.size < 500000 ){
377
- szLbl = file.size + ' bytes';
378
- }else if( file.size < 1000000 ){
379
- szLbl = (file.size / 1024).toFixed(2)+' KB';
380
- }else{
381
- szLbl = (file.size / (1024 * 1024)).toFixed(2)+' MB';
382
- }
383
- this.#rowError(rowObj);
386
+ rowObj.overrideName = `pasted-image-${Date.now()}.png`;
387
+ }
384388
const old = this.#rowMatchingName(file.name);
385389
if( old && rowObj !== old ){
386390
/*
387391
Fossil attachments treat the name as a unique-per-target
388392
key, with the newest one being the primary. If a name is
@@ -395,11 +399,28 @@
395399
*/
396400
/* recycle `old` instead to avoid UI flicker. */
397401
this.#rowError(old);
398402
this.#removeRow(rowObj);
399403
rowObj.e = old.e;
404
+ //if( rowObj.e.eDesc ) rowObj.e.eDesc.value = '';
405
+ }
406
+ console.warn("rowObj, old",rowObj, old);
407
+ if( rowObj.overrideName ){
408
+ console.warn("Renaming file to",rowObj.overrideName);
409
+ file = new File([file], rowObj.overrideName, {type: file.type});
410
+ rowObj.overrideName = undefined;
411
+ }
412
+
413
+ let szLbl;
414
+ if( file.size < 500000 ){
415
+ szLbl = file.size + ' bytes';
416
+ }else if( file.size < 1000000 ){
417
+ szLbl = (file.size / 1024).toFixed(2)+' KB';
418
+ }else{
419
+ szLbl = (file.size / (1024 * 1024)).toFixed(2)+' MB';
400420
}
421
+ this.#rowError(rowObj);
401422
rowObj.file = file;
402423
rowObj.mimeType = file.type || 'application/octet-stream';
403424
D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content');
404425
D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || '');
405426
rowObj.e.dropzone.classList.add('populated');
406427
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -291,26 +291,43 @@
291 ev.preventDefault();
292 eDropzone.classList.remove('dragover');
293 if( ev.dataTransfer.files.length ){
294 this.#injestBlob(rowObj, ev.dataTransfer.files[0]);
295 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296 });
297 eRow.addEventListener('paste', (e) => {
298 const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
299 if( !items ) return;
300 for( let i = 0; i < items.length; ++i ){
301 const item = items[i];
302 if( item.type.indexOf('image') === 0 ) {
303 e.preventDefault();
304 const blob = item.getAsFile();
305 this.#injestBlob(rowObj, blob);
306 break;
307 }else if( item.type === 'text/plain' ){
308 e.preventDefault();
309 item.getAsString((text) => {
310 rowObj.name = `pasted-text-${Date.now()}.txt`;
311 const blob = new File([text], rowObj.name,
312 {type: 'text/plain'});
313 this.#injestBlob(rowObj, blob);
314 });
315 break;
316 }else if(0){
@@ -364,25 +381,12 @@
364 if( file.name === 'image.png' ){
365 /* Workaround to attempt to avoid name collisions when pasting
366 multiple images. We cannot, at this level, unambiguously
367 distinguish a ctrl-v of bitmap data vs a ctrl-v of an image
368 file copied via a desktop file manager. */
369 rowObj.name = `pasted-image-${Date.now()}.png`;
370 }
371 if( rowObj.name && rowObj.name!==file.name ){
372 file = new File([file], rowObj.name, {type: file.type});
373 }
374
375 let szLbl;
376 if( file.size < 500000 ){
377 szLbl = file.size + ' bytes';
378 }else if( file.size < 1000000 ){
379 szLbl = (file.size / 1024).toFixed(2)+' KB';
380 }else{
381 szLbl = (file.size / (1024 * 1024)).toFixed(2)+' MB';
382 }
383 this.#rowError(rowObj);
384 const old = this.#rowMatchingName(file.name);
385 if( old && rowObj !== old ){
386 /*
387 Fossil attachments treat the name as a unique-per-target
388 key, with the newest one being the primary. If a name is
@@ -395,11 +399,28 @@
395 */
396 /* recycle `old` instead to avoid UI flicker. */
397 this.#rowError(old);
398 this.#removeRow(rowObj);
399 rowObj.e = old.e;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400 }
 
401 rowObj.file = file;
402 rowObj.mimeType = file.type || 'application/octet-stream';
403 D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content');
404 D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || '');
405 rowObj.e.dropzone.classList.add('populated');
406
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -291,26 +291,43 @@
291 ev.preventDefault();
292 eDropzone.classList.remove('dragover');
293 if( ev.dataTransfer.files.length ){
294 this.#injestBlob(rowObj, ev.dataTransfer.files[0]);
295 }
296 });
297 const pasteImage = (event, item)=>{
298 if( item.type.indexOf('image') === 0 ) {
299 event.preventDefault();
300 const blob = item.getAsFile();
301 this.#injestBlob(rowObj, blob);
302 return true;
303 }
304 return false;
305 };
306 eDesc?.addEventListener?.('paste', (e) => {
307 e.stopPropagation();
308 const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
309 if( !items ) return;
310 for( let i = 0; i < items.length; ++i ){
311 const item = items[i];
312 if( pasteImage(e, item) ){
313 break;
314 }
315 }
316 });
317 eRow.addEventListener('paste', (e) => {
318 const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
319 if( !items ) return;
320 for( let i = 0; i < items.length; ++i ){
321 const item = items[i];
322 if( pasteImage(e, item) ){
 
 
 
323 break;
324 }else if( item.type === 'text/plain' ){
325 e.preventDefault();
326 item.getAsString((text) => {
327 rowObj.overrideName = `pasted-text-${Date.now()}.txt`;
328 const blob = new File([text], rowObj.overrideName,
329 {type: 'text/plain'});
330 this.#injestBlob(rowObj, blob);
331 });
332 break;
333 }else if(0){
@@ -364,25 +381,12 @@
381 if( file.name === 'image.png' ){
382 /* Workaround to attempt to avoid name collisions when pasting
383 multiple images. We cannot, at this level, unambiguously
384 distinguish a ctrl-v of bitmap data vs a ctrl-v of an image
385 file copied via a desktop file manager. */
386 rowObj.overrideName = `pasted-image-${Date.now()}.png`;
387 }
 
 
 
 
 
 
 
 
 
 
 
 
 
388 const old = this.#rowMatchingName(file.name);
389 if( old && rowObj !== old ){
390 /*
391 Fossil attachments treat the name as a unique-per-target
392 key, with the newest one being the primary. If a name is
@@ -395,11 +399,28 @@
399 */
400 /* recycle `old` instead to avoid UI flicker. */
401 this.#rowError(old);
402 this.#removeRow(rowObj);
403 rowObj.e = old.e;
404 //if( rowObj.e.eDesc ) rowObj.e.eDesc.value = '';
405 }
406 console.warn("rowObj, old",rowObj, old);
407 if( rowObj.overrideName ){
408 console.warn("Renaming file to",rowObj.overrideName);
409 file = new File([file], rowObj.overrideName, {type: file.type});
410 rowObj.overrideName = undefined;
411 }
412
413 let szLbl;
414 if( file.size < 500000 ){
415 szLbl = file.size + ' bytes';
416 }else if( file.size < 1000000 ){
417 szLbl = (file.size / 1024).toFixed(2)+' KB';
418 }else{
419 szLbl = (file.size / (1024 * 1024)).toFixed(2)+' MB';
420 }
421 this.#rowError(rowObj);
422 rowObj.file = file;
423 rowObj.mimeType = file.type || 'application/octet-stream';
424 D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content');
425 D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || '');
426 rowObj.e.dropzone.classList.add('populated');
427

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button