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).
Commit
98c65d8dec21d53497628c9b0a3e02a9a4a9d321e8c876afdb1dfd20be42b19b
Parent
c51c545faf13042…
2 files changed
+1
+28
-4
+1
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -2045,10 +2045,11 @@ | ||
| 2045 | 2045 | } |
| 2046 | 2046 | .attach-container > .attach-row > .attach-dropzone.populated { |
| 2047 | 2047 | background-color: #f1f8e9; |
| 2048 | 2048 | border-color: #8bc34a; |
| 2049 | 2049 | border-style: solid; |
| 2050 | + text-align: left; | |
| 2050 | 2051 | } |
| 2051 | 2052 | .attach-container > .attach-row .attach-row-info { |
| 2052 | 2053 | font-family: monospace; |
| 2053 | 2054 | font-size: 0.9em; |
| 2054 | 2055 | color: #555; |
| 2055 | 2056 |
| --- 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 |
+28
-4
| --- src/fossil.attach.js | ||
| +++ src/fossil.attach.js | ||
| @@ -33,14 +33,19 @@ | ||
| 33 | 33 | D.button(this.#opt.addButtonLabel || 'Add attachment', |
| 34 | 34 | ()=>this.#addRow()), |
| 35 | 35 | 'attach-add-button' |
| 36 | 36 | ); |
| 37 | 37 | eBtnAdd.type = 'button'; |
| 38 | - this.#e.list = D.addClass(D.div(), 'attach-container') | |
| 38 | + this.#e.list = D.addClass(D.div(), 'attach-container'); | |
| 39 | 39 | opt.container.appendChild(this.#e.list); |
| 40 | 40 | this.#e.list.appendChild(eBtnAdd); |
| 41 | 41 | } |
| 42 | + | |
| 43 | + #removeRow(rowObj){ | |
| 44 | + rowObj.eRow.remove(); | |
| 45 | + this.#rows = this.#rows.filter(v=>v!==rowObj); | |
| 46 | + } | |
| 42 | 47 | |
| 43 | 48 | #addRow(){ |
| 44 | 49 | const id = ++idCounter; |
| 45 | 50 | const rowObj = { |
| 46 | 51 | id, file: null, mimeType: '' |
| @@ -63,12 +68,11 @@ | ||
| 63 | 68 | 'hidden', 'attach-desc' |
| 64 | 69 | ); |
| 65 | 70 | const eRemove = D.addClass( |
| 66 | 71 | D.button('Remove', (ev)=>{ |
| 67 | 72 | ev.stopPropagation(); |
| 68 | - eRow.remove(); | |
| 69 | - this.#rows = this.#rows.filter(v=>v!==rowObj); | |
| 73 | + this.#removeRow(rowObj); | |
| 70 | 74 | }), |
| 71 | 75 | 'attach-row-remove' |
| 72 | 76 | ); |
| 73 | 77 | eRemove.type = 'button'; |
| 74 | 78 | |
| @@ -117,20 +121,27 @@ | ||
| 117 | 121 | }); |
| 118 | 122 | D.append(eRow, eDropzone, eDesc); |
| 119 | 123 | rowObj.eDropzone = eDropzone; |
| 120 | 124 | rowObj.eInfo = eInfo; |
| 121 | 125 | rowObj.eDesc = eDesc; |
| 126 | + rowObj.eRow = eRow; | |
| 122 | 127 | 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*/; | |
| 124 | 129 | if( 0 ){ |
| 125 | 130 | /* To allow immediate ctrl-v, we need a trick... |
| 126 | 131 | But don't do this because it will interfere with, e.g., |
| 127 | 132 | the forum editor. */ |
| 128 | 133 | D.attr(eRow, 'tabindex', '-1'); |
| 129 | 134 | eRow.focus(); |
| 130 | 135 | } |
| 131 | 136 | } |
| 137 | + | |
| 138 | + #rowMatchingName(name){ | |
| 139 | + for(let r of this.#rows){ | |
| 140 | + if( r.file?.name===name ) return r; | |
| 141 | + } | |
| 142 | + } | |
| 132 | 143 | |
| 133 | 144 | #injestBlob(rowObj, file){ |
| 134 | 145 | if( !file ) return; |
| 135 | 146 | if( file.name === 'image.png' ){ |
| 136 | 147 | /* Workaround to attempt to avoid name collisions when |
| @@ -138,10 +149,23 @@ | ||
| 138 | 149 | distinguish a ctrl-v of bitmap data vs a ctrl-v of an |
| 139 | 150 | image file using a desktop file manager. */ |
| 140 | 151 | file = new File([file], `pasted-image-${rowObj.id}.png`, |
| 141 | 152 | {type: file.type}); |
| 142 | 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 | + } | |
| 143 | 167 | rowObj.file = file; |
| 144 | 168 | rowObj.mimeType = file.type || 'application/octet-stream'; |
| 145 | 169 | |
| 146 | 170 | const lbl = file.name || 'Pasted Content'; |
| 147 | 171 | let szLbl; |
| 148 | 172 |
| --- 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 |