Fossil SCM
Add options to limit the number of selected attachments and to optionally start with a selection widget visible.
Commit
675a722a2425a7ae7139702acd2b7b42e7da130b8a0c843b0a2e5ce2bb2fbf60
Parent
639e2cf0b7c6861…
2 files changed
-1
+49
-6
-1
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -2050,11 +2050,10 @@ | ||
| 2050 | 2050 | text-align: left; |
| 2051 | 2051 | } |
| 2052 | 2052 | .attach-container > .attach-row .attach-row-info { |
| 2053 | 2053 | font-family: monospace; |
| 2054 | 2054 | font-size: 0.9em; |
| 2055 | - color: #555; | |
| 2056 | 2055 | flex-grow: 1; |
| 2057 | 2056 | } |
| 2058 | 2057 | .attach-container > .attach-row .attach-desc { |
| 2059 | 2058 | max-width: initial; |
| 2060 | 2059 | width: 100%; |
| 2061 | 2060 |
| --- 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 |
+49
-6
| --- src/fossil.attach.js | ||
| +++ src/fossil.attach.js | ||
| @@ -1,7 +1,10 @@ | ||
| 1 | 1 | "use strict"; |
| 2 | 2 | /** |
| 3 | + Utility for interactive file attachment. Supports attachment | |
| 4 | + selection from a file dialog, from the clipboard, or drag/drop. | |
| 5 | + | |
| 3 | 6 | Requires that window.fossil has already been set up. |
| 4 | 7 | Depends on fossil.dom. |
| 5 | 8 | */ |
| 6 | 9 | (function(namespace){ |
| 7 | 10 | "use strict"; |
| @@ -24,27 +27,66 @@ | ||
| 24 | 27 | |
| 25 | 28 | opt.container: DOM element to wrap this object in. |
| 26 | 29 | |
| 27 | 30 | opt.addButtonLabel: optional label for the "add attachment" |
| 28 | 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. | |
| 29 | 39 | */ |
| 30 | 40 | 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); | |
| 32 | 46 | const eBtnAdd = this.#e.btnAdd = D.addClass( |
| 33 | 47 | D.button(this.#opt.addButtonLabel || 'Add attachment', |
| 34 | 48 | ()=>this.#addRow()), |
| 35 | 49 | 'attach-add-button' |
| 36 | 50 | ); |
| 37 | 51 | eBtnAdd.type = 'button'; |
| 38 | 52 | this.#e.list = D.addClass(D.div(), 'attach-container'); |
| 39 | 53 | opt.container.appendChild(this.#e.list); |
| 40 | 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 | + } | |
| 41 | 60 | } |
| 42 | 61 | |
| 43 | 62 | #removeRow(rowObj){ |
| 44 | 63 | rowObj.eRow.remove(); |
| 45 | 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 | + } | |
| 46 | 88 | } |
| 47 | 89 | |
| 48 | 90 | #addRow(){ |
| 49 | 91 | const id = ++idCounter; |
| 50 | 92 | const rowObj = { |
| @@ -122,12 +164,13 @@ | ||
| 122 | 164 | D.append(eRow, eDropzone, eDesc); |
| 123 | 165 | rowObj.eDropzone = eDropzone; |
| 124 | 166 | rowObj.eInfo = eInfo; |
| 125 | 167 | rowObj.eDesc = eDesc; |
| 126 | 168 | rowObj.eRow = eRow; |
| 169 | + this.#e.list.append(eRow); | |
| 127 | 170 | this.#rows.push( rowObj ); |
| 128 | - this.#e.list.append(eRow, this.#e.btnAdd)/*move to the end*/; | |
| 171 | + this.#updateBtnAdd(); | |
| 129 | 172 | if( 0 ){ |
| 130 | 173 | /* To allow immediate ctrl-v, we need a trick... |
| 131 | 174 | But don't do this because it will interfere with, e.g., |
| 132 | 175 | the forum editor. */ |
| 133 | 176 | D.attr(eRow, 'tabindex', '-1'); |
| @@ -142,14 +185,14 @@ | ||
| 142 | 185 | } |
| 143 | 186 | |
| 144 | 187 | #injestBlob(rowObj, file){ |
| 145 | 188 | if( !file ) return; |
| 146 | 189 | 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. */ | |
| 151 | 194 | file = new File([file], `pasted-image-${rowObj.id}.png`, |
| 152 | 195 | {type: file.type}); |
| 153 | 196 | } |
| 154 | 197 | /* |
| 155 | 198 | Fossil attachments treat the name as a unique-per-target key, |
| 156 | 199 |
| --- 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 |