Fossil SCM
Docs. Add a help tab and move the markup format reference link into there to save toolbar space.
Commit
205d80ed04941838750657e4d5de76f3bd9e39b3726ff25c2615d93c473bc7c9
Parent
354e82947a76efb…
2 files changed
+14
-1
+68
-42
+14
-1
| --- src/fossil.attach.js | ||
| +++ src/fossil.attach.js | ||
| @@ -417,15 +417,28 @@ | ||
| 417 | 417 | for(let r of this.#rows){ |
| 418 | 418 | if( r.file?.name===name ) return r; |
| 419 | 419 | } |
| 420 | 420 | } |
| 421 | 421 | |
| 422 | + /** | |
| 423 | + Injects the given File object as the attached content for the | |
| 424 | + given row. If the object's name collides with another row, | |
| 425 | + rowObj is removed from this widget and the old row is instead | |
| 426 | + re-populated with the new file. | |
| 427 | + | |
| 428 | + If rowObj.overrideName is set then the given file gets wrapped | |
| 429 | + with that name before attaching it, and that property is | |
| 430 | + removed from rowObj. This is intended only for communicating | |
| 431 | + auto-generated names for pasted data. | |
| 432 | + */ | |
| 422 | 433 | #injestBlob(rowObj, file){ |
| 423 | 434 | if( !file ) return; |
| 424 | 435 | const old = this.#rowMatchingName(file.name); |
| 425 | 436 | if( rowObj.overrideName ){ |
| 426 | - file = new File([file], rowObj.overrideName, {type: file.type}); | |
| 437 | + if( rowObj.overrideName !== file.name ){ | |
| 438 | + file = new File([file], rowObj.overrideName, {type: file.type}); | |
| 439 | + } | |
| 427 | 440 | rowObj.overrideName = undefined; |
| 428 | 441 | } |
| 429 | 442 | if( old && rowObj !== old ){ |
| 430 | 443 | /* |
| 431 | 444 | Fossil attachments treat the name as a unique-per-target |
| 432 | 445 |
| --- src/fossil.attach.js | |
| +++ src/fossil.attach.js | |
| @@ -417,15 +417,28 @@ | |
| 417 | for(let r of this.#rows){ |
| 418 | if( r.file?.name===name ) return r; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | #injestBlob(rowObj, file){ |
| 423 | if( !file ) return; |
| 424 | const old = this.#rowMatchingName(file.name); |
| 425 | if( rowObj.overrideName ){ |
| 426 | file = new File([file], rowObj.overrideName, {type: file.type}); |
| 427 | rowObj.overrideName = undefined; |
| 428 | } |
| 429 | if( old && rowObj !== old ){ |
| 430 | /* |
| 431 | Fossil attachments treat the name as a unique-per-target |
| 432 |
| --- src/fossil.attach.js | |
| +++ src/fossil.attach.js | |
| @@ -417,15 +417,28 @@ | |
| 417 | for(let r of this.#rows){ |
| 418 | if( r.file?.name===name ) return r; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | /** |
| 423 | Injects the given File object as the attached content for the |
| 424 | given row. If the object's name collides with another row, |
| 425 | rowObj is removed from this widget and the old row is instead |
| 426 | re-populated with the new file. |
| 427 | |
| 428 | If rowObj.overrideName is set then the given file gets wrapped |
| 429 | with that name before attaching it, and that property is |
| 430 | removed from rowObj. This is intended only for communicating |
| 431 | auto-generated names for pasted data. |
| 432 | */ |
| 433 | #injestBlob(rowObj, file){ |
| 434 | if( !file ) return; |
| 435 | const old = this.#rowMatchingName(file.name); |
| 436 | if( rowObj.overrideName ){ |
| 437 | if( rowObj.overrideName !== file.name ){ |
| 438 | file = new File([file], rowObj.overrideName, {type: file.type}); |
| 439 | } |
| 440 | rowObj.overrideName = undefined; |
| 441 | } |
| 442 | if( old && rowObj !== old ){ |
| 443 | /* |
| 444 | Fossil attachments treat the name as a unique-per-target |
| 445 |
+68
-42
| --- src/fossil.page.forumpost.js | ||
| +++ src/fossil.page.forumpost.js | ||
| @@ -53,10 +53,11 @@ | ||
| 53 | 53 | button: F.nu() |
| 54 | 54 | }); |
| 55 | 55 | const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor'); |
| 56 | 56 | D.clearElement(wrapper); |
| 57 | 57 | if( !opt.inReplyTo ){ |
| 58 | + /* Title... */ | |
| 58 | 59 | e.titleBar = D.addClass(D.div(),'titlebar'); |
| 59 | 60 | e.title = D.addClass(D.input('text'), 'title'); |
| 60 | 61 | e.title.setAttribute('maxlength', 125); |
| 61 | 62 | e.titleBar.append( |
| 62 | 63 | D.append(D.span(), "Title:"), |
| @@ -65,65 +66,82 @@ | ||
| 65 | 66 | if( opt.draftKey ){ |
| 66 | 67 | const key = opt.draftKey+'.title'; |
| 67 | 68 | e.title.addEventListener('blur', ()=>{ |
| 68 | 69 | F.storage.set(key, e.title.value) |
| 69 | 70 | }); |
| 70 | - e.title.value = F.storage.get(key,''); | |
| 71 | + e.title.value = opt.title || F.storage.get(key, ''); | |
| 72 | + }else if( opt.title ){ | |
| 73 | + e.title.value = opt.title; | |
| 71 | 74 | } |
| 72 | 75 | wrapper.append(e.titleBar); |
| 73 | 76 | } |
| 74 | 77 | |
| 75 | - { /* Mimetype bits... */ | |
| 78 | + { /* Mimetype... */ | |
| 76 | 79 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 77 | 80 | e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 78 | 81 | this.#toDisable.push(e.mimetype.select); |
| 79 | 82 | let i = 0; |
| 83 | + D.option(e.mimetype.select, '', 'Markdown format').disabled = true; | |
| 80 | 84 | for(const [k,v] of Object.entries({ |
| 81 | 85 | 'text/x-markdown': 'Markdown', |
| 82 | 86 | 'text/x-fossil-wiki': 'Fossil Wiki', |
| 83 | 87 | 'text/plain': 'Plain text' |
| 84 | 88 | })) { |
| 85 | 89 | const o = D.option(e.mimetype.select, k, v); |
| 86 | - if( !i++ ) o.setAttribute('selected', ''); | |
| 87 | - } | |
| 88 | - e.mimetype.label = D.span(); | |
| 89 | - e.mimetype.label.append( | |
| 90 | - D.a(F.repoUrl('markup_help'), 'Markup style'), | |
| 91 | - ':' | |
| 92 | - ); | |
| 93 | - e.mimetype.wrapper.append(e.mimetype.label, e.mimetype.select); | |
| 94 | - } | |
| 95 | - | |
| 96 | - e.button.preview = D.button("Preview", e=>this.#preview()); | |
| 97 | - e.button.submit = D.button("Submit"); | |
| 98 | - if( 1 ){ | |
| 99 | - F.confirmer(e.button.submit, { | |
| 100 | - confirmText: "Confirm submit...", | |
| 101 | - onconfirm: ()=>this.#submit() | |
| 102 | - }); | |
| 103 | - }else{ | |
| 104 | - e.button.submit.addEventListener('click', ()=>this.#submit()); | |
| 105 | - } | |
| 106 | - e.button.submit.setAttribute('disabled', ''); | |
| 107 | - e.buttons = D.addClass(D.div(), 'buttons'); | |
| 108 | - wrapper.append(e.buttons); | |
| 109 | - | |
| 110 | - e.err = D.addClass(D.div(), 'error', 'hidden'); | |
| 111 | - wrapper.append(e.err); | |
| 112 | - e.err.addEventListener('dblclick',()=>this.reportError()); | |
| 90 | + if( (opt.isNewThread && !i++) | |
| 91 | + || opt.mimetype===k ) o.setAttribute('selected', ''); | |
| 92 | + } | |
| 93 | + if( 0 ){ | |
| 94 | + e.mimetype.label = D.span(); | |
| 95 | + e.mimetype.label.append( | |
| 96 | + D.a(F.repoUrl('markup_help'), 'Markup style'), | |
| 97 | + ':' | |
| 98 | + ); | |
| 99 | + e.mimetype.wrapper.append(e.mimetype.label); | |
| 100 | + } | |
| 101 | + e.mimetype.wrapper.append(e.mimetype.select); | |
| 102 | + } | |
| 103 | + | |
| 104 | + { /* Preview/submit buttons... */ | |
| 105 | + e.button.preview = D.button("Preview", e=>this.#preview()); | |
| 106 | + e.button.submit = D.button("Submit"); | |
| 107 | + if( 1 ){ | |
| 108 | + F.confirmer(e.button.submit, { | |
| 109 | + confirmText: "Confirm submit...", | |
| 110 | + onconfirm: ()=>this.#submit() | |
| 111 | + }); | |
| 112 | + }else{ | |
| 113 | + e.button.submit.addEventListener('click', ()=>this.#submit()); | |
| 114 | + } | |
| 115 | + e.button.submit.setAttribute('disabled', ''); | |
| 116 | + e.buttons = D.addClass(D.div(), 'buttons'); | |
| 117 | + wrapper.append(e.buttons); | |
| 118 | + | |
| 119 | + e.error = D.addClass(D.div(), 'error', 'hidden'); | |
| 120 | + wrapper.append(e.error); | |
| 121 | + e.error.addEventListener('dblclick',()=>this.reportError()); | |
| 122 | + } | |
| 113 | 123 | |
| 114 | 124 | const idPrefix = 'FormPostEditor'+(++idCounter)/* TabManager requires IDs */; |
| 115 | 125 | { /* Main tabs... */ |
| 116 | 126 | e.tabs = D.attr( |
| 117 | 127 | D.addClass(D.div(), 'tab-container'), |
| 118 | 128 | 'id', idPrefix+'-tabs' |
| 119 | 129 | ); |
| 120 | 130 | this.#tabs = new F.TabManager(e.tabs); |
| 121 | 131 | this.#tabs.addEventListener('before-switch-to', (ev)=>{ |
| 122 | - this.#activeTab = ev.detail; | |
| 123 | - if( e.preview === this.#activeTab ){ | |
| 124 | - this.#e.button.preview.click(); | |
| 132 | + console.debug("Switching to tab",ev.detail); | |
| 133 | + switch( (this.#activeTab = ev.detail) ){ | |
| 134 | + case e.preview: | |
| 135 | + this.#e.button.preview.click(); | |
| 136 | + break; | |
| 137 | + case e.help: | |
| 138 | + if( e.help.$needsInit ){ | |
| 139 | + delete e.help.$needsInit; | |
| 140 | + this.#initHelpTab(); | |
| 141 | + } | |
| 142 | + break; | |
| 125 | 143 | } |
| 126 | 144 | }); |
| 127 | 145 | wrapper.append( e.tabs ); |
| 128 | 146 | |
| 129 | 147 | e.tabEdit = D.div(); |
| @@ -173,19 +191,10 @@ | ||
| 173 | 191 | the editor because people will open the editor, change the |
| 174 | 192 | status, and tap submit, resulting in a whole new, unedited |
| 175 | 193 | copy of the post, differing only in the new 'status' tag |
| 176 | 194 | added to it. |
| 177 | 195 | */ |
| 178 | - let i = 0; | |
| 179 | - for(const [k,v] of Object.entries({ | |
| 180 | - 'text/x-markdown': 'Markdown', | |
| 181 | - 'text/x-fossil-wiki': 'Fossil Wiki', | |
| 182 | - 'text/plain': 'Plain text' | |
| 183 | - })) { | |
| 184 | - const o = D.option(e.mimetype.select, k, v); | |
| 185 | - if( !i++ ) o.setAttribute('selected', ''); | |
| 186 | - } | |
| 187 | 196 | if( F.config.forumStatuses?.length>0 ){ |
| 188 | 197 | const sel = e.status = D.select(); |
| 189 | 198 | D.option(sel, "", "- Status -").disabled = true; |
| 190 | 199 | for( const status of F.config.forumStatuses ){ |
| 191 | 200 | D.option(sel, status.value, status.label); |
| @@ -209,10 +218,15 @@ | ||
| 209 | 218 | /* Reminder: we don't currently have a way to disable/enable |
| 210 | 219 | an Attacher's controls. */ |
| 211 | 220 | } |
| 212 | 221 | e.buttons.append(e.button.preview, e.button.submit); |
| 213 | 222 | this.#toDisable.push(e.button.preview); |
| 223 | + | |
| 224 | + e.help = D.attr(D.div(), 'id', idPrefix+'-help'); | |
| 225 | + e.help.$needsInit = true; | |
| 226 | + e.help.dataset.tabLabel = 'Help'; | |
| 227 | + this.#tabs.addTab(e.help); | |
| 214 | 228 | |
| 215 | 229 | if( opt.hiddenFields ){ |
| 216 | 230 | this.addHiddenFields( opt.hiddenFields ); |
| 217 | 231 | delete opt.hiddenFields; |
| 218 | 232 | } |
| @@ -304,11 +318,11 @@ | ||
| 304 | 318 | Reports an error by appending each argument to the error widget |
| 305 | 319 | and unhiding it. If passed no arugments, it clears and hides |
| 306 | 320 | the error widget. |
| 307 | 321 | */ |
| 308 | 322 | reportError(...msg){ |
| 309 | - const e = this.#e.err; | |
| 323 | + const e = this.#e.error; | |
| 310 | 324 | D.clearElement(e); |
| 311 | 325 | if( msg.length ){ |
| 312 | 326 | e.classList.remove('hidden'); |
| 313 | 327 | e.append(...msg); |
| 314 | 328 | console.error('ForumPostEditor:',...msg); |
| @@ -340,10 +354,21 @@ | ||
| 340 | 354 | } |
| 341 | 355 | |
| 342 | 356 | get title(){ |
| 343 | 357 | return this.#e.title.value; |
| 344 | 358 | } |
| 359 | + | |
| 360 | + #initHelpTab(){ | |
| 361 | + const eh = this.#e.help; | |
| 362 | + const list = D.ul(); | |
| 363 | + D.append( | |
| 364 | + D.li(list), | |
| 365 | + D.attr(D.a(F.repoUrl('markup_help'), 'Markup styles'), | |
| 366 | + 'target', '_new') | |
| 367 | + ); | |
| 368 | + eh.append(list); | |
| 369 | + } | |
| 345 | 370 | |
| 346 | 371 | #newFormData(addThisContent){ |
| 347 | 372 | const fd = new FormData; |
| 348 | 373 | for(const f of this.#extraFields){ |
| 349 | 374 | fd.append(f.name, f.value); |
| @@ -645,12 +670,13 @@ | ||
| 645 | 670 | if( eForumNew ){ |
| 646 | 671 | /* /forumnew */ |
| 647 | 672 | const fpe = new fossil.ForumPostEditor({ |
| 648 | 673 | draftKey: 'forumnew', |
| 649 | 674 | hiddenFields: eForumNew.querySelectorAll('input[type=hidden]') |
| 675 | + //mimetype: 'text/plain' | |
| 650 | 676 | }); |
| 651 | 677 | eForumNew.parentElement.insertBefore(fpe.widget, eForumNew); |
| 652 | 678 | eForumNew.remove(); |
| 653 | 679 | fossil.page.fpe = fpe /* for testing via the console */; |
| 654 | 680 | }/*eForumNew*/ |
| 655 | 681 | })/*F.onPageLoad callback*/; |
| 656 | 682 | })(window.fossil); |
| 657 | 683 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -53,10 +53,11 @@ | |
| 53 | button: F.nu() |
| 54 | }); |
| 55 | const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor'); |
| 56 | D.clearElement(wrapper); |
| 57 | if( !opt.inReplyTo ){ |
| 58 | e.titleBar = D.addClass(D.div(),'titlebar'); |
| 59 | e.title = D.addClass(D.input('text'), 'title'); |
| 60 | e.title.setAttribute('maxlength', 125); |
| 61 | e.titleBar.append( |
| 62 | D.append(D.span(), "Title:"), |
| @@ -65,65 +66,82 @@ | |
| 65 | if( opt.draftKey ){ |
| 66 | const key = opt.draftKey+'.title'; |
| 67 | e.title.addEventListener('blur', ()=>{ |
| 68 | F.storage.set(key, e.title.value) |
| 69 | }); |
| 70 | e.title.value = F.storage.get(key,''); |
| 71 | } |
| 72 | wrapper.append(e.titleBar); |
| 73 | } |
| 74 | |
| 75 | { /* Mimetype bits... */ |
| 76 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 77 | e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 78 | this.#toDisable.push(e.mimetype.select); |
| 79 | let i = 0; |
| 80 | for(const [k,v] of Object.entries({ |
| 81 | 'text/x-markdown': 'Markdown', |
| 82 | 'text/x-fossil-wiki': 'Fossil Wiki', |
| 83 | 'text/plain': 'Plain text' |
| 84 | })) { |
| 85 | const o = D.option(e.mimetype.select, k, v); |
| 86 | if( !i++ ) o.setAttribute('selected', ''); |
| 87 | } |
| 88 | e.mimetype.label = D.span(); |
| 89 | e.mimetype.label.append( |
| 90 | D.a(F.repoUrl('markup_help'), 'Markup style'), |
| 91 | ':' |
| 92 | ); |
| 93 | e.mimetype.wrapper.append(e.mimetype.label, e.mimetype.select); |
| 94 | } |
| 95 | |
| 96 | e.button.preview = D.button("Preview", e=>this.#preview()); |
| 97 | e.button.submit = D.button("Submit"); |
| 98 | if( 1 ){ |
| 99 | F.confirmer(e.button.submit, { |
| 100 | confirmText: "Confirm submit...", |
| 101 | onconfirm: ()=>this.#submit() |
| 102 | }); |
| 103 | }else{ |
| 104 | e.button.submit.addEventListener('click', ()=>this.#submit()); |
| 105 | } |
| 106 | e.button.submit.setAttribute('disabled', ''); |
| 107 | e.buttons = D.addClass(D.div(), 'buttons'); |
| 108 | wrapper.append(e.buttons); |
| 109 | |
| 110 | e.err = D.addClass(D.div(), 'error', 'hidden'); |
| 111 | wrapper.append(e.err); |
| 112 | e.err.addEventListener('dblclick',()=>this.reportError()); |
| 113 | |
| 114 | const idPrefix = 'FormPostEditor'+(++idCounter)/* TabManager requires IDs */; |
| 115 | { /* Main tabs... */ |
| 116 | e.tabs = D.attr( |
| 117 | D.addClass(D.div(), 'tab-container'), |
| 118 | 'id', idPrefix+'-tabs' |
| 119 | ); |
| 120 | this.#tabs = new F.TabManager(e.tabs); |
| 121 | this.#tabs.addEventListener('before-switch-to', (ev)=>{ |
| 122 | this.#activeTab = ev.detail; |
| 123 | if( e.preview === this.#activeTab ){ |
| 124 | this.#e.button.preview.click(); |
| 125 | } |
| 126 | }); |
| 127 | wrapper.append( e.tabs ); |
| 128 | |
| 129 | e.tabEdit = D.div(); |
| @@ -173,19 +191,10 @@ | |
| 173 | the editor because people will open the editor, change the |
| 174 | status, and tap submit, resulting in a whole new, unedited |
| 175 | copy of the post, differing only in the new 'status' tag |
| 176 | added to it. |
| 177 | */ |
| 178 | let i = 0; |
| 179 | for(const [k,v] of Object.entries({ |
| 180 | 'text/x-markdown': 'Markdown', |
| 181 | 'text/x-fossil-wiki': 'Fossil Wiki', |
| 182 | 'text/plain': 'Plain text' |
| 183 | })) { |
| 184 | const o = D.option(e.mimetype.select, k, v); |
| 185 | if( !i++ ) o.setAttribute('selected', ''); |
| 186 | } |
| 187 | if( F.config.forumStatuses?.length>0 ){ |
| 188 | const sel = e.status = D.select(); |
| 189 | D.option(sel, "", "- Status -").disabled = true; |
| 190 | for( const status of F.config.forumStatuses ){ |
| 191 | D.option(sel, status.value, status.label); |
| @@ -209,10 +218,15 @@ | |
| 209 | /* Reminder: we don't currently have a way to disable/enable |
| 210 | an Attacher's controls. */ |
| 211 | } |
| 212 | e.buttons.append(e.button.preview, e.button.submit); |
| 213 | this.#toDisable.push(e.button.preview); |
| 214 | |
| 215 | if( opt.hiddenFields ){ |
| 216 | this.addHiddenFields( opt.hiddenFields ); |
| 217 | delete opt.hiddenFields; |
| 218 | } |
| @@ -304,11 +318,11 @@ | |
| 304 | Reports an error by appending each argument to the error widget |
| 305 | and unhiding it. If passed no arugments, it clears and hides |
| 306 | the error widget. |
| 307 | */ |
| 308 | reportError(...msg){ |
| 309 | const e = this.#e.err; |
| 310 | D.clearElement(e); |
| 311 | if( msg.length ){ |
| 312 | e.classList.remove('hidden'); |
| 313 | e.append(...msg); |
| 314 | console.error('ForumPostEditor:',...msg); |
| @@ -340,10 +354,21 @@ | |
| 340 | } |
| 341 | |
| 342 | get title(){ |
| 343 | return this.#e.title.value; |
| 344 | } |
| 345 | |
| 346 | #newFormData(addThisContent){ |
| 347 | const fd = new FormData; |
| 348 | for(const f of this.#extraFields){ |
| 349 | fd.append(f.name, f.value); |
| @@ -645,12 +670,13 @@ | |
| 645 | if( eForumNew ){ |
| 646 | /* /forumnew */ |
| 647 | const fpe = new fossil.ForumPostEditor({ |
| 648 | draftKey: 'forumnew', |
| 649 | hiddenFields: eForumNew.querySelectorAll('input[type=hidden]') |
| 650 | }); |
| 651 | eForumNew.parentElement.insertBefore(fpe.widget, eForumNew); |
| 652 | eForumNew.remove(); |
| 653 | fossil.page.fpe = fpe /* for testing via the console */; |
| 654 | }/*eForumNew*/ |
| 655 | })/*F.onPageLoad callback*/; |
| 656 | })(window.fossil); |
| 657 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -53,10 +53,11 @@ | |
| 53 | button: F.nu() |
| 54 | }); |
| 55 | const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor'); |
| 56 | D.clearElement(wrapper); |
| 57 | if( !opt.inReplyTo ){ |
| 58 | /* Title... */ |
| 59 | e.titleBar = D.addClass(D.div(),'titlebar'); |
| 60 | e.title = D.addClass(D.input('text'), 'title'); |
| 61 | e.title.setAttribute('maxlength', 125); |
| 62 | e.titleBar.append( |
| 63 | D.append(D.span(), "Title:"), |
| @@ -65,65 +66,82 @@ | |
| 66 | if( opt.draftKey ){ |
| 67 | const key = opt.draftKey+'.title'; |
| 68 | e.title.addEventListener('blur', ()=>{ |
| 69 | F.storage.set(key, e.title.value) |
| 70 | }); |
| 71 | e.title.value = opt.title || F.storage.get(key, ''); |
| 72 | }else if( opt.title ){ |
| 73 | e.title.value = opt.title; |
| 74 | } |
| 75 | wrapper.append(e.titleBar); |
| 76 | } |
| 77 | |
| 78 | { /* Mimetype... */ |
| 79 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 80 | e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 81 | this.#toDisable.push(e.mimetype.select); |
| 82 | let i = 0; |
| 83 | D.option(e.mimetype.select, '', 'Markdown format').disabled = true; |
| 84 | for(const [k,v] of Object.entries({ |
| 85 | 'text/x-markdown': 'Markdown', |
| 86 | 'text/x-fossil-wiki': 'Fossil Wiki', |
| 87 | 'text/plain': 'Plain text' |
| 88 | })) { |
| 89 | const o = D.option(e.mimetype.select, k, v); |
| 90 | if( (opt.isNewThread && !i++) |
| 91 | || opt.mimetype===k ) o.setAttribute('selected', ''); |
| 92 | } |
| 93 | if( 0 ){ |
| 94 | e.mimetype.label = D.span(); |
| 95 | e.mimetype.label.append( |
| 96 | D.a(F.repoUrl('markup_help'), 'Markup style'), |
| 97 | ':' |
| 98 | ); |
| 99 | e.mimetype.wrapper.append(e.mimetype.label); |
| 100 | } |
| 101 | e.mimetype.wrapper.append(e.mimetype.select); |
| 102 | } |
| 103 | |
| 104 | { /* Preview/submit buttons... */ |
| 105 | e.button.preview = D.button("Preview", e=>this.#preview()); |
| 106 | e.button.submit = D.button("Submit"); |
| 107 | if( 1 ){ |
| 108 | F.confirmer(e.button.submit, { |
| 109 | confirmText: "Confirm submit...", |
| 110 | onconfirm: ()=>this.#submit() |
| 111 | }); |
| 112 | }else{ |
| 113 | e.button.submit.addEventListener('click', ()=>this.#submit()); |
| 114 | } |
| 115 | e.button.submit.setAttribute('disabled', ''); |
| 116 | e.buttons = D.addClass(D.div(), 'buttons'); |
| 117 | wrapper.append(e.buttons); |
| 118 | |
| 119 | e.error = D.addClass(D.div(), 'error', 'hidden'); |
| 120 | wrapper.append(e.error); |
| 121 | e.error.addEventListener('dblclick',()=>this.reportError()); |
| 122 | } |
| 123 | |
| 124 | const idPrefix = 'FormPostEditor'+(++idCounter)/* TabManager requires IDs */; |
| 125 | { /* Main tabs... */ |
| 126 | e.tabs = D.attr( |
| 127 | D.addClass(D.div(), 'tab-container'), |
| 128 | 'id', idPrefix+'-tabs' |
| 129 | ); |
| 130 | this.#tabs = new F.TabManager(e.tabs); |
| 131 | this.#tabs.addEventListener('before-switch-to', (ev)=>{ |
| 132 | console.debug("Switching to tab",ev.detail); |
| 133 | switch( (this.#activeTab = ev.detail) ){ |
| 134 | case e.preview: |
| 135 | this.#e.button.preview.click(); |
| 136 | break; |
| 137 | case e.help: |
| 138 | if( e.help.$needsInit ){ |
| 139 | delete e.help.$needsInit; |
| 140 | this.#initHelpTab(); |
| 141 | } |
| 142 | break; |
| 143 | } |
| 144 | }); |
| 145 | wrapper.append( e.tabs ); |
| 146 | |
| 147 | e.tabEdit = D.div(); |
| @@ -173,19 +191,10 @@ | |
| 191 | the editor because people will open the editor, change the |
| 192 | status, and tap submit, resulting in a whole new, unedited |
| 193 | copy of the post, differing only in the new 'status' tag |
| 194 | added to it. |
| 195 | */ |
| 196 | if( F.config.forumStatuses?.length>0 ){ |
| 197 | const sel = e.status = D.select(); |
| 198 | D.option(sel, "", "- Status -").disabled = true; |
| 199 | for( const status of F.config.forumStatuses ){ |
| 200 | D.option(sel, status.value, status.label); |
| @@ -209,10 +218,15 @@ | |
| 218 | /* Reminder: we don't currently have a way to disable/enable |
| 219 | an Attacher's controls. */ |
| 220 | } |
| 221 | e.buttons.append(e.button.preview, e.button.submit); |
| 222 | this.#toDisable.push(e.button.preview); |
| 223 | |
| 224 | e.help = D.attr(D.div(), 'id', idPrefix+'-help'); |
| 225 | e.help.$needsInit = true; |
| 226 | e.help.dataset.tabLabel = 'Help'; |
| 227 | this.#tabs.addTab(e.help); |
| 228 | |
| 229 | if( opt.hiddenFields ){ |
| 230 | this.addHiddenFields( opt.hiddenFields ); |
| 231 | delete opt.hiddenFields; |
| 232 | } |
| @@ -304,11 +318,11 @@ | |
| 318 | Reports an error by appending each argument to the error widget |
| 319 | and unhiding it. If passed no arugments, it clears and hides |
| 320 | the error widget. |
| 321 | */ |
| 322 | reportError(...msg){ |
| 323 | const e = this.#e.error; |
| 324 | D.clearElement(e); |
| 325 | if( msg.length ){ |
| 326 | e.classList.remove('hidden'); |
| 327 | e.append(...msg); |
| 328 | console.error('ForumPostEditor:',...msg); |
| @@ -340,10 +354,21 @@ | |
| 354 | } |
| 355 | |
| 356 | get title(){ |
| 357 | return this.#e.title.value; |
| 358 | } |
| 359 | |
| 360 | #initHelpTab(){ |
| 361 | const eh = this.#e.help; |
| 362 | const list = D.ul(); |
| 363 | D.append( |
| 364 | D.li(list), |
| 365 | D.attr(D.a(F.repoUrl('markup_help'), 'Markup styles'), |
| 366 | 'target', '_new') |
| 367 | ); |
| 368 | eh.append(list); |
| 369 | } |
| 370 | |
| 371 | #newFormData(addThisContent){ |
| 372 | const fd = new FormData; |
| 373 | for(const f of this.#extraFields){ |
| 374 | fd.append(f.name, f.value); |
| @@ -645,12 +670,13 @@ | |
| 670 | if( eForumNew ){ |
| 671 | /* /forumnew */ |
| 672 | const fpe = new fossil.ForumPostEditor({ |
| 673 | draftKey: 'forumnew', |
| 674 | hiddenFields: eForumNew.querySelectorAll('input[type=hidden]') |
| 675 | //mimetype: 'text/plain' |
| 676 | }); |
| 677 | eForumNew.parentElement.insertBefore(fpe.widget, eForumNew); |
| 678 | eForumNew.remove(); |
| 679 | fossil.page.fpe = fpe /* for testing via the console */; |
| 680 | }/*eForumNew*/ |
| 681 | })/*F.onPageLoad callback*/; |
| 682 | })(window.fossil); |
| 683 |