Fossil SCM

Add options to limit the number of selected attachments and to optionally start with a selection widget visible.

stephan 2026-06-02 16:42 UTC attach-v2
Commit 675a722a2425a7ae7139702acd2b7b42e7da130b8a0c843b0a2e5ce2bb2fbf60
--- src/default.css
+++ src/default.css
@@ -2050,11 +2050,10 @@
20502050
text-align: left;
20512051
}
20522052
.attach-container > .attach-row .attach-row-info {
20532053
font-family: monospace;
20542054
font-size: 0.9em;
2055
- color: #555;
20562055
flex-grow: 1;
20572056
}
20582057
.attach-container > .attach-row .attach-desc {
20592058
max-width: initial;
20602059
width: 100%;
20612060
--- src/default.css
+++ src/default.css
@@ -2050,11 +2050,10 @@
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 flex-grow: 1;
2057 }
2058 .attach-container > .attach-row .attach-desc {
2059 max-width: initial;
2060 width: 100%;
2061
--- src/default.css
+++ src/default.css
@@ -2050,11 +2050,10 @@
2050 text-align: left;
2051 }
2052 .attach-container > .attach-row .attach-row-info {
2053 font-family: monospace;
2054 font-size: 0.9em;
 
2055 flex-grow: 1;
2056 }
2057 .attach-container > .attach-row .attach-desc {
2058 max-width: initial;
2059 width: 100%;
2060
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -1,7 +1,10 @@
11
"use strict";
22
/**
3
+ Utility for interactive file attachment. Supports attachment
4
+ selection from a file dialog, from the clipboard, or drag/drop.
5
+
36
Requires that window.fossil has already been set up.
47
Depends on fossil.dom.
58
*/
69
(function(namespace){
710
"use strict";
@@ -24,27 +27,66 @@
2427
2528
opt.container: DOM element to wrap this object in.
2629
2730
opt.addButtonLabel: optional label for the "add attachment"
2831
button, defaulting to something generic.
32
+
33
+ opt.limit: optional max number of attachments to allow. This
34
+ defaults to "some sensible value".
35
+
36
+ opt.startWith[=0]: if >0 then that many file selection widgets
37
+ are automatically activated, as if the user had tapped the Add
38
+ button that many times.
2939
*/
3040
constructor(opt){
31
- this.#opt = opt = Object.assign(Object.create(null),opt);
41
+ this.#opt = opt = Object.assign(Object.create(null),{
42
+ addButtonLabel: false,
43
+ startWith: 0,
44
+ limit: 7
45
+ }, opt);
3246
const eBtnAdd = this.#e.btnAdd = D.addClass(
3347
D.button(this.#opt.addButtonLabel || 'Add attachment',
3448
()=>this.#addRow()),
3549
'attach-add-button'
3650
);
3751
eBtnAdd.type = 'button';
3852
this.#e.list = D.addClass(D.div(), 'attach-container');
3953
opt.container.appendChild(this.#e.list);
4054
this.#e.list.appendChild(eBtnAdd);
55
+ if( opt.startWith > 0 ){
56
+ for(let i = 0; i < opt.startWith; ++i ){
57
+ this.#addRow();
58
+ }
59
+ }
4160
}
4261
4362
#removeRow(rowObj){
4463
rowObj.eRow.remove();
4564
this.#rows = this.#rows.filter(v=>v!==rowObj);
65
+ this.#updateBtnAdd();
66
+ if( 0===this.#rows.length
67
+ && 1===this.#opt.limit
68
+ && 1===this.#opt.startWith ){
69
+ /* Intended primarily for /addattach. */
70
+ this.#addRow();
71
+ }
72
+ }
73
+
74
+ /**
75
+ Hide or show the Add button, as appropriate.
76
+ */
77
+ #updateBtnAdd(){
78
+ const b = this.#e.btnAdd;
79
+ if( this.#opt.limit>0 && this.#rows.length >= this.#opt.limit ){
80
+ b.classList.add('hidden');
81
+ //b.setAttribute('disabled','');
82
+ //F.toast.warning("Attachment form limit reached.");
83
+ }else{
84
+ b.classList.remove('hidden');
85
+ //b.removeAttribute('disabled');
86
+ this.#e.list.append(b/*move to the end*/);
87
+ }
4688
}
4789
4890
#addRow(){
4991
const id = ++idCounter;
5092
const rowObj = {
@@ -122,12 +164,13 @@
122164
D.append(eRow, eDropzone, eDesc);
123165
rowObj.eDropzone = eDropzone;
124166
rowObj.eInfo = eInfo;
125167
rowObj.eDesc = eDesc;
126168
rowObj.eRow = eRow;
169
+ this.#e.list.append(eRow);
127170
this.#rows.push( rowObj );
128
- this.#e.list.append(eRow, this.#e.btnAdd)/*move to the end*/;
171
+ this.#updateBtnAdd();
129172
if( 0 ){
130173
/* To allow immediate ctrl-v, we need a trick...
131174
But don't do this because it will interfere with, e.g.,
132175
the forum editor. */
133176
D.attr(eRow, 'tabindex', '-1');
@@ -142,14 +185,14 @@
142185
}
143186
144187
#injestBlob(rowObj, file){
145188
if( !file ) return;
146189
if( file.name === 'image.png' ){
147
- /* Workaround to attempt to avoid name collisions when
148
- pasting multiple images. We cannot, at this level, unambiguously
149
- distinguish a ctrl-v of bitmap data vs a ctrl-v of an
150
- image file using a desktop file manager. */
190
+ /* Workaround to attempt to avoid name collisions when pasting
191
+ multiple images. We cannot, at this level, unambiguously
192
+ distinguish a ctrl-v of bitmap data vs a ctrl-v of an image
193
+ file copied via a desktop file manager. */
151194
file = new File([file], `pasted-image-${rowObj.id}.png`,
152195
{type: file.type});
153196
}
154197
/*
155198
Fossil attachments treat the name as a unique-per-target key,
156199
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -1,7 +1,10 @@
1 "use strict";
2 /**
 
 
 
3 Requires that window.fossil has already been set up.
4 Depends on fossil.dom.
5 */
6 (function(namespace){
7 "use strict";
@@ -24,27 +27,66 @@
24
25 opt.container: DOM element to wrap this object in.
26
27 opt.addButtonLabel: optional label for the "add attachment"
28 button, defaulting to something generic.
 
 
 
 
 
 
 
29 */
30 constructor(opt){
31 this.#opt = opt = Object.assign(Object.create(null),opt);
 
 
 
 
32 const eBtnAdd = this.#e.btnAdd = D.addClass(
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 = {
@@ -122,12 +164,13 @@
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');
@@ -142,14 +185,14 @@
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
148 pasting multiple images. We cannot, at this level, unambiguously
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
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -1,7 +1,10 @@
1 "use strict";
2 /**
3 Utility for interactive file attachment. Supports attachment
4 selection from a file dialog, from the clipboard, or drag/drop.
5
6 Requires that window.fossil has already been set up.
7 Depends on fossil.dom.
8 */
9 (function(namespace){
10 "use strict";
@@ -24,27 +27,66 @@
27
28 opt.container: DOM element to wrap this object in.
29
30 opt.addButtonLabel: optional label for the "add attachment"
31 button, defaulting to something generic.
32
33 opt.limit: optional max number of attachments to allow. This
34 defaults to "some sensible value".
35
36 opt.startWith[=0]: if >0 then that many file selection widgets
37 are automatically activated, as if the user had tapped the Add
38 button that many times.
39 */
40 constructor(opt){
41 this.#opt = opt = Object.assign(Object.create(null),{
42 addButtonLabel: false,
43 startWith: 0,
44 limit: 7
45 }, opt);
46 const eBtnAdd = this.#e.btnAdd = D.addClass(
47 D.button(this.#opt.addButtonLabel || 'Add attachment',
48 ()=>this.#addRow()),
49 'attach-add-button'
50 );
51 eBtnAdd.type = 'button';
52 this.#e.list = D.addClass(D.div(), 'attach-container');
53 opt.container.appendChild(this.#e.list);
54 this.#e.list.appendChild(eBtnAdd);
55 if( opt.startWith > 0 ){
56 for(let i = 0; i < opt.startWith; ++i ){
57 this.#addRow();
58 }
59 }
60 }
61
62 #removeRow(rowObj){
63 rowObj.eRow.remove();
64 this.#rows = this.#rows.filter(v=>v!==rowObj);
65 this.#updateBtnAdd();
66 if( 0===this.#rows.length
67 && 1===this.#opt.limit
68 && 1===this.#opt.startWith ){
69 /* Intended primarily for /addattach. */
70 this.#addRow();
71 }
72 }
73
74 /**
75 Hide or show the Add button, as appropriate.
76 */
77 #updateBtnAdd(){
78 const b = this.#e.btnAdd;
79 if( this.#opt.limit>0 && this.#rows.length >= this.#opt.limit ){
80 b.classList.add('hidden');
81 //b.setAttribute('disabled','');
82 //F.toast.warning("Attachment form limit reached.");
83 }else{
84 b.classList.remove('hidden');
85 //b.removeAttribute('disabled');
86 this.#e.list.append(b/*move to the end*/);
87 }
88 }
89
90 #addRow(){
91 const id = ++idCounter;
92 const rowObj = {
@@ -122,12 +164,13 @@
164 D.append(eRow, eDropzone, eDesc);
165 rowObj.eDropzone = eDropzone;
166 rowObj.eInfo = eInfo;
167 rowObj.eDesc = eDesc;
168 rowObj.eRow = eRow;
169 this.#e.list.append(eRow);
170 this.#rows.push( rowObj );
171 this.#updateBtnAdd();
172 if( 0 ){
173 /* To allow immediate ctrl-v, we need a trick...
174 But don't do this because it will interfere with, e.g.,
175 the forum editor. */
176 D.attr(eRow, 'tabindex', '-1');
@@ -142,14 +185,14 @@
185 }
186
187 #injestBlob(rowObj, file){
188 if( !file ) return;
189 if( file.name === 'image.png' ){
190 /* Workaround to attempt to avoid name collisions when pasting
191 multiple images. We cannot, at this level, unambiguously
192 distinguish a ctrl-v of bitmap data vs a ctrl-v of an image
193 file copied via a desktop file manager. */
194 file = new File([file], `pasted-image-${rowObj.id}.png`,
195 {type: file.type});
196 }
197 /*
198 Fossil attachments treat the name as a unique-per-target key,
199

Keyboard Shortcuts

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