Fossil SCM

When attaching a given file name a second time, replace the first instance, as uploading two copies of the same name would not have the desired effect (it's treated as two revs of the same attachment).

stephan 2026-06-02 15:38 UTC attach-v2
Commit 98c65d8dec21d53497628c9b0a3e02a9a4a9d321e8c876afdb1dfd20be42b19b
--- src/default.css
+++ src/default.css
@@ -2045,10 +2045,11 @@
20452045
}
20462046
.attach-container > .attach-row > .attach-dropzone.populated {
20472047
background-color: #f1f8e9;
20482048
border-color: #8bc34a;
20492049
border-style: solid;
2050
+ text-align: left;
20502051
}
20512052
.attach-container > .attach-row .attach-row-info {
20522053
font-family: monospace;
20532054
font-size: 0.9em;
20542055
color: #555;
20552056
--- src/default.css
+++ src/default.css
@@ -2045,10 +2045,11 @@
2045 }
2046 .attach-container > .attach-row > .attach-dropzone.populated {
2047 background-color: #f1f8e9;
2048 border-color: #8bc34a;
2049 border-style: solid;
 
2050 }
2051 .attach-container > .attach-row .attach-row-info {
2052 font-family: monospace;
2053 font-size: 0.9em;
2054 color: #555;
2055
--- src/default.css
+++ src/default.css
@@ -2045,10 +2045,11 @@
2045 }
2046 .attach-container > .attach-row > .attach-dropzone.populated {
2047 background-color: #f1f8e9;
2048 border-color: #8bc34a;
2049 border-style: solid;
2050 text-align: left;
2051 }
2052 .attach-container > .attach-row .attach-row-info {
2053 font-family: monospace;
2054 font-size: 0.9em;
2055 color: #555;
2056
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -33,14 +33,19 @@
3333
D.button(this.#opt.addButtonLabel || 'Add attachment',
3434
()=>this.#addRow()),
3535
'attach-add-button'
3636
);
3737
eBtnAdd.type = 'button';
38
- this.#e.list = D.addClass(D.div(), 'attach-container')
38
+ this.#e.list = D.addClass(D.div(), 'attach-container');
3939
opt.container.appendChild(this.#e.list);
4040
this.#e.list.appendChild(eBtnAdd);
4141
}
42
+
43
+ #removeRow(rowObj){
44
+ rowObj.eRow.remove();
45
+ this.#rows = this.#rows.filter(v=>v!==rowObj);
46
+ }
4247
4348
#addRow(){
4449
const id = ++idCounter;
4550
const rowObj = {
4651
id, file: null, mimeType: ''
@@ -63,12 +68,11 @@
6368
'hidden', 'attach-desc'
6469
);
6570
const eRemove = D.addClass(
6671
D.button('Remove', (ev)=>{
6772
ev.stopPropagation();
68
- eRow.remove();
69
- this.#rows = this.#rows.filter(v=>v!==rowObj);
73
+ this.#removeRow(rowObj);
7074
}),
7175
'attach-row-remove'
7276
);
7377
eRemove.type = 'button';
7478
@@ -117,20 +121,27 @@
117121
});
118122
D.append(eRow, eDropzone, eDesc);
119123
rowObj.eDropzone = eDropzone;
120124
rowObj.eInfo = eInfo;
121125
rowObj.eDesc = eDesc;
126
+ rowObj.eRow = eRow;
122127
this.#rows.push( rowObj );
123
- this.#e.list.append(eRow, this.#e.btnAdd);
128
+ this.#e.list.append(eRow, this.#e.btnAdd)/*move to the end*/;
124129
if( 0 ){
125130
/* To allow immediate ctrl-v, we need a trick...
126131
But don't do this because it will interfere with, e.g.,
127132
the forum editor. */
128133
D.attr(eRow, 'tabindex', '-1');
129134
eRow.focus();
130135
}
131136
}
137
+
138
+ #rowMatchingName(name){
139
+ for(let r of this.#rows){
140
+ if( r.file?.name===name ) return r;
141
+ }
142
+ }
132143
133144
#injestBlob(rowObj, file){
134145
if( !file ) return;
135146
if( file.name === 'image.png' ){
136147
/* Workaround to attempt to avoid name collisions when
@@ -138,10 +149,23 @@
138149
distinguish a ctrl-v of bitmap data vs a ctrl-v of an
139150
image file using a desktop file manager. */
140151
file = new File([file], `pasted-image-${rowObj.id}.png`,
141152
{type: file.type});
142153
}
154
+ /*
155
+ Fossil attachments treat the name as a unique-per-target key,
156
+ with the newest one being the primary. If a name is given
157
+ twice, replace the prior entry before adding the new
158
+ one. There are conceivable, but also unlikely, cases where
159
+ this will have unintended side-effects, but that seems like a
160
+ lesser evil than attaching the same file N times, leading to N
161
+ attachment artifacts.
162
+ */
163
+ const old = this.#rowMatchingName(file.name);
164
+ if( old && rowObj !== old){
165
+ this.#removeRow(old);
166
+ }
143167
rowObj.file = file;
144168
rowObj.mimeType = file.type || 'application/octet-stream';
145169
146170
const lbl = file.name || 'Pasted Content';
147171
let szLbl;
148172
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -33,14 +33,19 @@
33 D.button(this.#opt.addButtonLabel || 'Add attachment',
34 ()=>this.#addRow()),
35 'attach-add-button'
36 );
37 eBtnAdd.type = 'button';
38 this.#e.list = D.addClass(D.div(), 'attach-container')
39 opt.container.appendChild(this.#e.list);
40 this.#e.list.appendChild(eBtnAdd);
41 }
 
 
 
 
 
42
43 #addRow(){
44 const id = ++idCounter;
45 const rowObj = {
46 id, file: null, mimeType: ''
@@ -63,12 +68,11 @@
63 'hidden', 'attach-desc'
64 );
65 const eRemove = D.addClass(
66 D.button('Remove', (ev)=>{
67 ev.stopPropagation();
68 eRow.remove();
69 this.#rows = this.#rows.filter(v=>v!==rowObj);
70 }),
71 'attach-row-remove'
72 );
73 eRemove.type = 'button';
74
@@ -117,20 +121,27 @@
117 });
118 D.append(eRow, eDropzone, eDesc);
119 rowObj.eDropzone = eDropzone;
120 rowObj.eInfo = eInfo;
121 rowObj.eDesc = eDesc;
 
122 this.#rows.push( rowObj );
123 this.#e.list.append(eRow, this.#e.btnAdd);
124 if( 0 ){
125 /* To allow immediate ctrl-v, we need a trick...
126 But don't do this because it will interfere with, e.g.,
127 the forum editor. */
128 D.attr(eRow, 'tabindex', '-1');
129 eRow.focus();
130 }
131 }
 
 
 
 
 
 
132
133 #injestBlob(rowObj, file){
134 if( !file ) return;
135 if( file.name === 'image.png' ){
136 /* Workaround to attempt to avoid name collisions when
@@ -138,10 +149,23 @@
138 distinguish a ctrl-v of bitmap data vs a ctrl-v of an
139 image file using a desktop file manager. */
140 file = new File([file], `pasted-image-${rowObj.id}.png`,
141 {type: file.type});
142 }
 
 
 
 
 
 
 
 
 
 
 
 
 
143 rowObj.file = file;
144 rowObj.mimeType = file.type || 'application/octet-stream';
145
146 const lbl = file.name || 'Pasted Content';
147 let szLbl;
148
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -33,14 +33,19 @@
33 D.button(this.#opt.addButtonLabel || 'Add attachment',
34 ()=>this.#addRow()),
35 'attach-add-button'
36 );
37 eBtnAdd.type = 'button';
38 this.#e.list = D.addClass(D.div(), 'attach-container');
39 opt.container.appendChild(this.#e.list);
40 this.#e.list.appendChild(eBtnAdd);
41 }
42
43 #removeRow(rowObj){
44 rowObj.eRow.remove();
45 this.#rows = this.#rows.filter(v=>v!==rowObj);
46 }
47
48 #addRow(){
49 const id = ++idCounter;
50 const rowObj = {
51 id, file: null, mimeType: ''
@@ -63,12 +68,11 @@
68 'hidden', 'attach-desc'
69 );
70 const eRemove = D.addClass(
71 D.button('Remove', (ev)=>{
72 ev.stopPropagation();
73 this.#removeRow(rowObj);
 
74 }),
75 'attach-row-remove'
76 );
77 eRemove.type = 'button';
78
@@ -117,20 +121,27 @@
121 });
122 D.append(eRow, eDropzone, eDesc);
123 rowObj.eDropzone = eDropzone;
124 rowObj.eInfo = eInfo;
125 rowObj.eDesc = eDesc;
126 rowObj.eRow = eRow;
127 this.#rows.push( rowObj );
128 this.#e.list.append(eRow, this.#e.btnAdd)/*move to the end*/;
129 if( 0 ){
130 /* To allow immediate ctrl-v, we need a trick...
131 But don't do this because it will interfere with, e.g.,
132 the forum editor. */
133 D.attr(eRow, 'tabindex', '-1');
134 eRow.focus();
135 }
136 }
137
138 #rowMatchingName(name){
139 for(let r of this.#rows){
140 if( r.file?.name===name ) return r;
141 }
142 }
143
144 #injestBlob(rowObj, file){
145 if( !file ) return;
146 if( file.name === 'image.png' ){
147 /* Workaround to attempt to avoid name collisions when
@@ -138,10 +149,23 @@
149 distinguish a ctrl-v of bitmap data vs a ctrl-v of an
150 image file using a desktop file manager. */
151 file = new File([file], `pasted-image-${rowObj.id}.png`,
152 {type: file.type});
153 }
154 /*
155 Fossil attachments treat the name as a unique-per-target key,
156 with the newest one being the primary. If a name is given
157 twice, replace the prior entry before adding the new
158 one. There are conceivable, but also unlikely, cases where
159 this will have unintended side-effects, but that seems like a
160 lesser evil than attaching the same file N times, leading to N
161 attachment artifacts.
162 */
163 const old = this.#rowMatchingName(file.name);
164 if( old && rowObj !== old){
165 this.#removeRow(old);
166 }
167 rowObj.file = file;
168 rowObj.mimeType = file.type || 'application/octet-stream';
169
170 const lbl = file.name || 'Pasted Content';
171 let szLbl;
172

Keyboard Shortcuts

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