Fossil SCM

Wrap setup of the attachment-related controls into an onPageLoad() handler. Minor doc additions.

stephan 2026-06-06 08:13 UTC attach-v2
Commit feb95e6798275f93a063059903118c1acbbb2a9f05ac8f21d454a02712aa0252
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -504,88 +504,90 @@
504504
return i;
505505
}
506506
}/*Attacher*/;
507507
F.Attacher = Attacher;
508508
509
- const eAttachWrapper = document.querySelector('#attachadd-form-wrapper');
510
- if( eAttachWrapper ){
511
- /* This page is /attachadd v2 or a workalike. eAttachWrapper holds
512
- input[type=hidden] fields for use in attaching files and is
513
- where we inject a file attachment widget. */
514
- eAttachWrapper.classList.remove('hidden');
515
- const urlArgs = new URLSearchParams(window.location.search);
516
- let zTarget = urlArgs.get('target');
517
- let zTo = urlArgs.get('to') || urlArgs.get('from');
518
- const eBtnSubmit = D.button("Submit");
519
- eBtnSubmit.type = 'button';
520
- const updateBtnSubmit = (attacher)=>{
521
- if( attacher.isPopulated ){
522
- eBtnSubmit.removeAttribute('disabled');
523
- }else{
524
- eBtnSubmit.setAttribute('disabled', '');
525
- }
526
- };
527
- const cbAttacherChange = (ev)=>{
528
- const a = ev.detail.attacher;
529
- updateBtnSubmit(a);
530
- };
531
- const att = new Attacher({
532
- container: eAttachWrapper,
533
- startWith: 1,
534
- listener: cbAttacherChange,
535
- controls: [eBtnSubmit],
536
- description: true
537
- });
538
- eBtnSubmit.addEventListener('click', async (ev)=>{
539
- att.reportError();
540
- const li = att.collectState();
541
- if( !li.length ) return;
542
- if( eBtnSubmit.dataset.submitted ) return;
543
- eBtnSubmit.dataset.submitted = 1;
544
- D.disable(eBtnSubmit);
545
- const fd = new FormData();
546
- att.populateFormData(fd);
547
- for( const eIn of eAttachWrapper.querySelectorAll(
548
- 'input[type="hidden"]'
549
- ) ){
550
- /* Copy over hidden input fields emitted by the server. */
551
- if( eIn.name==='target' ){
552
- zTarget = eIn.value;
553
- }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){
554
- zTo = eIn.value;
555
- }
556
- fd.append(eIn.name, eIn.value)
557
- }
558
- if( att.isDryRun ){
559
- fd.append('dryrun', '1');
560
- }
561
- let err;
562
- const resp = await window.fetch(F.repoUrl('attachadd_ajax_post'), {
563
- method: 'POST',
564
- body: fd
565
- }).catch((e)=>{
566
- err = e;
567
- });
568
- D.enable(eBtnSubmit);
569
- delete eBtnSubmit.dataset.submitted;
570
- const jr = err ? undefined : await resp.json().catch(()=>{});
571
- if( err || jr?.error || !resp.ok ){
572
- const msg = err ? err.message : (jr?.error || resp.statusText);
573
- att.reportError("Attaching failed: ", msg);
574
- }else{
575
- att.clear();
576
- let to = zTo || jr?.redirect;
577
- if( to ){
578
- if( '/'!==to[0] ){
579
- to = F.repoUrl(to);
580
- }
581
- window.location = to;
582
- }else if( zTarget ){
583
- window.location = '?target='+zTarget+'&'+Date.now();
584
- }
585
- }
586
- })/*submit handler*/;
587
- updateBtnSubmit(att);
588
- F.page.attacher = att /* only for testing via dev console */;
589
- }/* /attachadd */
509
+ F.onPageLoad(function(){
510
+ const eAttachWrapper = document.querySelector('#attachadd-form-wrapper');
511
+ if( eAttachWrapper ){
512
+ /* This page is /attachadd v2 or a workalike. eAttachWrapper holds
513
+ input[type=hidden] fields for use in attaching files and is
514
+ where we inject a file attachment widget. */
515
+ eAttachWrapper.classList.remove('hidden');
516
+ const urlArgs = new URLSearchParams(window.location.search);
517
+ let zTarget = urlArgs.get('target');
518
+ let zTo = urlArgs.get('to') || urlArgs.get('from');
519
+ const eBtnSubmit = D.button("Submit");
520
+ eBtnSubmit.type = 'button';
521
+ const updateBtnSubmit = (attacher)=>{
522
+ if( attacher.isPopulated ){
523
+ eBtnSubmit.removeAttribute('disabled');
524
+ }else{
525
+ eBtnSubmit.setAttribute('disabled', '');
526
+ }
527
+ };
528
+ const cbAttacherChange = (ev)=>{
529
+ const a = ev.detail.attacher;
530
+ updateBtnSubmit(a);
531
+ };
532
+ const att = new Attacher({
533
+ container: eAttachWrapper,
534
+ startWith: 1,
535
+ listener: cbAttacherChange,
536
+ controls: [eBtnSubmit],
537
+ description: true
538
+ });
539
+ eBtnSubmit.addEventListener('click', async (ev)=>{
540
+ att.reportError();
541
+ const li = att.collectState();
542
+ if( !li.length ) return;
543
+ if( eBtnSubmit.dataset.submitted ) return;
544
+ eBtnSubmit.dataset.submitted = 1;
545
+ D.disable(eBtnSubmit);
546
+ const fd = new FormData();
547
+ att.populateFormData(fd);
548
+ for( const eIn of eAttachWrapper.querySelectorAll(
549
+ 'input[type="hidden"]'
550
+ ) ){
551
+ /* Copy over hidden input fields emitted by the server. */
552
+ if( eIn.name==='target' ){
553
+ zTarget = eIn.value;
554
+ }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){
555
+ zTo = eIn.value;
556
+ }
557
+ fd.append(eIn.name, eIn.value)
558
+ }
559
+ if( att.isDryRun ){
560
+ fd.append('dryrun', '1');
561
+ }
562
+ let err;
563
+ const resp = await window.fetch(F.repoUrl('attachadd_ajax_post'), {
564
+ method: 'POST',
565
+ body: fd
566
+ }).catch((e)=>{
567
+ err = e;
568
+ });
569
+ D.enable(eBtnSubmit);
570
+ delete eBtnSubmit.dataset.submitted;
571
+ const jr = err ? undefined : await resp.json().catch(()=>{});
572
+ if( err || jr?.error || !resp.ok ){
573
+ const msg = err ? err.message : (jr?.error || resp.statusText);
574
+ att.reportError("Attaching failed: ", msg);
575
+ }else{
576
+ att.clear();
577
+ let to = zTo || jr?.redirect;
578
+ if( to ){
579
+ if( '/'!==to[0] ){
580
+ to = F.repoUrl(to);
581
+ }
582
+ window.location = to;
583
+ }else if( zTarget ){
584
+ window.location = '?target='+zTarget+'&'+Date.now();
585
+ }
586
+ }
587
+ })/*submit handler*/;
588
+ updateBtnSubmit(att);
589
+ F.page.attacher = att /* only for testing via dev console */;
590
+ }/* /attachadd */
591
+ })/*onPageLoad()*/;
590592
591593
})(window.fossil);
592594
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -504,88 +504,90 @@
504 return i;
505 }
506 }/*Attacher*/;
507 F.Attacher = Attacher;
508
509 const eAttachWrapper = document.querySelector('#attachadd-form-wrapper');
510 if( eAttachWrapper ){
511 /* This page is /attachadd v2 or a workalike. eAttachWrapper holds
512 input[type=hidden] fields for use in attaching files and is
513 where we inject a file attachment widget. */
514 eAttachWrapper.classList.remove('hidden');
515 const urlArgs = new URLSearchParams(window.location.search);
516 let zTarget = urlArgs.get('target');
517 let zTo = urlArgs.get('to') || urlArgs.get('from');
518 const eBtnSubmit = D.button("Submit");
519 eBtnSubmit.type = 'button';
520 const updateBtnSubmit = (attacher)=>{
521 if( attacher.isPopulated ){
522 eBtnSubmit.removeAttribute('disabled');
523 }else{
524 eBtnSubmit.setAttribute('disabled', '');
525 }
526 };
527 const cbAttacherChange = (ev)=>{
528 const a = ev.detail.attacher;
529 updateBtnSubmit(a);
530 };
531 const att = new Attacher({
532 container: eAttachWrapper,
533 startWith: 1,
534 listener: cbAttacherChange,
535 controls: [eBtnSubmit],
536 description: true
537 });
538 eBtnSubmit.addEventListener('click', async (ev)=>{
539 att.reportError();
540 const li = att.collectState();
541 if( !li.length ) return;
542 if( eBtnSubmit.dataset.submitted ) return;
543 eBtnSubmit.dataset.submitted = 1;
544 D.disable(eBtnSubmit);
545 const fd = new FormData();
546 att.populateFormData(fd);
547 for( const eIn of eAttachWrapper.querySelectorAll(
548 'input[type="hidden"]'
549 ) ){
550 /* Copy over hidden input fields emitted by the server. */
551 if( eIn.name==='target' ){
552 zTarget = eIn.value;
553 }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){
554 zTo = eIn.value;
555 }
556 fd.append(eIn.name, eIn.value)
557 }
558 if( att.isDryRun ){
559 fd.append('dryrun', '1');
560 }
561 let err;
562 const resp = await window.fetch(F.repoUrl('attachadd_ajax_post'), {
563 method: 'POST',
564 body: fd
565 }).catch((e)=>{
566 err = e;
567 });
568 D.enable(eBtnSubmit);
569 delete eBtnSubmit.dataset.submitted;
570 const jr = err ? undefined : await resp.json().catch(()=>{});
571 if( err || jr?.error || !resp.ok ){
572 const msg = err ? err.message : (jr?.error || resp.statusText);
573 att.reportError("Attaching failed: ", msg);
574 }else{
575 att.clear();
576 let to = zTo || jr?.redirect;
577 if( to ){
578 if( '/'!==to[0] ){
579 to = F.repoUrl(to);
580 }
581 window.location = to;
582 }else if( zTarget ){
583 window.location = '?target='+zTarget+'&'+Date.now();
584 }
585 }
586 })/*submit handler*/;
587 updateBtnSubmit(att);
588 F.page.attacher = att /* only for testing via dev console */;
589 }/* /attachadd */
 
 
590
591 })(window.fossil);
592
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -504,88 +504,90 @@
504 return i;
505 }
506 }/*Attacher*/;
507 F.Attacher = Attacher;
508
509 F.onPageLoad(function(){
510 const eAttachWrapper = document.querySelector('#attachadd-form-wrapper');
511 if( eAttachWrapper ){
512 /* This page is /attachadd v2 or a workalike. eAttachWrapper holds
513 input[type=hidden] fields for use in attaching files and is
514 where we inject a file attachment widget. */
515 eAttachWrapper.classList.remove('hidden');
516 const urlArgs = new URLSearchParams(window.location.search);
517 let zTarget = urlArgs.get('target');
518 let zTo = urlArgs.get('to') || urlArgs.get('from');
519 const eBtnSubmit = D.button("Submit");
520 eBtnSubmit.type = 'button';
521 const updateBtnSubmit = (attacher)=>{
522 if( attacher.isPopulated ){
523 eBtnSubmit.removeAttribute('disabled');
524 }else{
525 eBtnSubmit.setAttribute('disabled', '');
526 }
527 };
528 const cbAttacherChange = (ev)=>{
529 const a = ev.detail.attacher;
530 updateBtnSubmit(a);
531 };
532 const att = new Attacher({
533 container: eAttachWrapper,
534 startWith: 1,
535 listener: cbAttacherChange,
536 controls: [eBtnSubmit],
537 description: true
538 });
539 eBtnSubmit.addEventListener('click', async (ev)=>{
540 att.reportError();
541 const li = att.collectState();
542 if( !li.length ) return;
543 if( eBtnSubmit.dataset.submitted ) return;
544 eBtnSubmit.dataset.submitted = 1;
545 D.disable(eBtnSubmit);
546 const fd = new FormData();
547 att.populateFormData(fd);
548 for( const eIn of eAttachWrapper.querySelectorAll(
549 'input[type="hidden"]'
550 ) ){
551 /* Copy over hidden input fields emitted by the server. */
552 if( eIn.name==='target' ){
553 zTarget = eIn.value;
554 }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){
555 zTo = eIn.value;
556 }
557 fd.append(eIn.name, eIn.value)
558 }
559 if( att.isDryRun ){
560 fd.append('dryrun', '1');
561 }
562 let err;
563 const resp = await window.fetch(F.repoUrl('attachadd_ajax_post'), {
564 method: 'POST',
565 body: fd
566 }).catch((e)=>{
567 err = e;
568 });
569 D.enable(eBtnSubmit);
570 delete eBtnSubmit.dataset.submitted;
571 const jr = err ? undefined : await resp.json().catch(()=>{});
572 if( err || jr?.error || !resp.ok ){
573 const msg = err ? err.message : (jr?.error || resp.statusText);
574 att.reportError("Attaching failed: ", msg);
575 }else{
576 att.clear();
577 let to = zTo || jr?.redirect;
578 if( to ){
579 if( '/'!==to[0] ){
580 to = F.repoUrl(to);
581 }
582 window.location = to;
583 }else if( zTarget ){
584 window.location = '?target='+zTarget+'&'+Date.now();
585 }
586 }
587 })/*submit handler*/;
588 updateBtnSubmit(att);
589 F.page.attacher = att /* only for testing via dev console */;
590 }/* /attachadd */
591 })/*onPageLoad()*/;
592
593 })(window.fossil);
594
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -131,11 +131,14 @@
131131
browser's cancel button while waiting, they'll be stuck with
132132
an unsubmittable form. */
133133
setTimeout(()=>{delete form.dataset.submitted}, 7000);
134134
return;
135135
};
136
+
136137
document.querySelectorAll("form").forEach(function(form){
138
+ /* Set up controls for closing posts and setting thread
139
+ status. */
137140
form.addEventListener('submit', formSubmitted);
138141
form
139142
.querySelectorAll("input.action-close, input.action-reopen")
140143
.forEach(function(e){
141144
e.classList.remove('hidden');
@@ -149,11 +152,13 @@
149152
form
150153
.querySelectorAll("input[type='button'].action-status")
151154
.forEach(function(btn){
152155
btn.classList.remove('hidden');
153156
const sel = btn.previousElementSibling;
154
- const updateAble = ()=>{
157
+ const updateButton = ()=>{
158
+ /* Enable btn only when the status has been locally
159
+ modified. */
155160
if( sel.dataset.initialValue ){
156161
if( sel.dataset.initialValue===sel.value ){
157162
btn.setAttribute('disabled','');
158163
}else{
159164
btn.removeAttribute('disabled');
@@ -164,12 +169,12 @@
164169
}else{
165170
btn.removeAttribute('disabled');
166171
}
167172
}
168173
};
169
- sel.addEventListener('change', updateAble, true);
170
- updateAble();
174
+ sel.addEventListener('change', updateButton, true);
175
+ updateButton();
171176
F.confirmer(btn, {
172177
confirmText: "Confirm status change",
173178
onconfirm: ()=>form.submit()
174179
});
175180
});
176181
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -131,11 +131,14 @@
131 browser's cancel button while waiting, they'll be stuck with
132 an unsubmittable form. */
133 setTimeout(()=>{delete form.dataset.submitted}, 7000);
134 return;
135 };
 
136 document.querySelectorAll("form").forEach(function(form){
 
 
137 form.addEventListener('submit', formSubmitted);
138 form
139 .querySelectorAll("input.action-close, input.action-reopen")
140 .forEach(function(e){
141 e.classList.remove('hidden');
@@ -149,11 +152,13 @@
149 form
150 .querySelectorAll("input[type='button'].action-status")
151 .forEach(function(btn){
152 btn.classList.remove('hidden');
153 const sel = btn.previousElementSibling;
154 const updateAble = ()=>{
 
 
155 if( sel.dataset.initialValue ){
156 if( sel.dataset.initialValue===sel.value ){
157 btn.setAttribute('disabled','');
158 }else{
159 btn.removeAttribute('disabled');
@@ -164,12 +169,12 @@
164 }else{
165 btn.removeAttribute('disabled');
166 }
167 }
168 };
169 sel.addEventListener('change', updateAble, true);
170 updateAble();
171 F.confirmer(btn, {
172 confirmText: "Confirm status change",
173 onconfirm: ()=>form.submit()
174 });
175 });
176
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -131,11 +131,14 @@
131 browser's cancel button while waiting, they'll be stuck with
132 an unsubmittable form. */
133 setTimeout(()=>{delete form.dataset.submitted}, 7000);
134 return;
135 };
136
137 document.querySelectorAll("form").forEach(function(form){
138 /* Set up controls for closing posts and setting thread
139 status. */
140 form.addEventListener('submit', formSubmitted);
141 form
142 .querySelectorAll("input.action-close, input.action-reopen")
143 .forEach(function(e){
144 e.classList.remove('hidden');
@@ -149,11 +152,13 @@
152 form
153 .querySelectorAll("input[type='button'].action-status")
154 .forEach(function(btn){
155 btn.classList.remove('hidden');
156 const sel = btn.previousElementSibling;
157 const updateButton = ()=>{
158 /* Enable btn only when the status has been locally
159 modified. */
160 if( sel.dataset.initialValue ){
161 if( sel.dataset.initialValue===sel.value ){
162 btn.setAttribute('disabled','');
163 }else{
164 btn.removeAttribute('disabled');
@@ -164,12 +169,12 @@
169 }else{
170 btn.removeAttribute('disabled');
171 }
172 }
173 };
174 sel.addEventListener('change', updateButton, true);
175 updateButton();
176 F.confirmer(btn, {
177 confirmText: "Confirm status change",
178 onconfirm: ()=>form.submit()
179 });
180 });
181

Keyboard Shortcuts

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