Fossil SCM
Minor internal cleanups. Add some tooltip help. Add a Stash button which closes a forum editor widget but retains any local edits and stays on the page. It cannot be called Close because there's a very different Close button already associated with posts.
Commit
2e788c488bdc332a6c34aab2d4a075682e99d5d10324b0aee990686e71e275b6
Parent
0313a31ed14d6c8…
1 file changed
+48
-20
+48
-20
| --- src/fossil.page.forumpost.js | ||
| +++ src/fossil.page.forumpost.js | ||
| @@ -138,10 +138,11 @@ | ||
| 138 | 138 | } |
| 139 | 139 | |
| 140 | 140 | { /* Mimetype... */ |
| 141 | 141 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 142 | 142 | const sel = e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 143 | + sel.setAttribute('title', 'Markup format for this post.'); | |
| 143 | 144 | this.#toDisable.push(sel); |
| 144 | 145 | let i = 0; |
| 145 | 146 | D.option(sel, '', '- Markup format -').disabled = true; |
| 146 | 147 | for(const [k,v] of Object.entries({ |
| 147 | 148 | 'text/x-markdown': 'Markdown', |
| @@ -163,14 +164,35 @@ | ||
| 163 | 164 | e.mimetype.wrapper.append(sel); |
| 164 | 165 | } |
| 165 | 166 | |
| 166 | 167 | e.buttons = D.addClass(D.div(), 'buttons'); |
| 167 | 168 | { /* Preview/submit buttons... */ |
| 168 | - e.button.preview = D.button("Preview", e=>this.#preview()); | |
| 169 | - e.button.submit = D.button("Submit"); | |
| 169 | + e.button.preview = D.attr( | |
| 170 | + D.button("Preview", e=>this.#preview()), | |
| 171 | + 'title', | |
| 172 | + 'Preview your edits.' | |
| 173 | + ); | |
| 174 | + e.button.submit = D.attr( | |
| 175 | + D.button("Submit"), | |
| 176 | + 'title', | |
| 177 | + 'Save any edits to the server. Not permitted until Preview has been used.' | |
| 178 | + ); | |
| 179 | + e.button.stash = D.attr( | |
| 180 | + D.button( | |
| 181 | + "Stash", e=>this.close() | |
| 182 | + /* This could be called Close, but that would semantically | |
| 183 | + collide with the Close [this post] button. All "Stash" | |
| 184 | + does is close the widget. */ | |
| 185 | + ), | |
| 186 | + 'title', "Close this editor and stash any edits locally." | |
| 187 | + ); | |
| 170 | 188 | if( opt.ondiscard instanceof Function ){ |
| 171 | - e.button.discard = D.button('Discard'); | |
| 189 | + e.button.discard = D.attr( | |
| 190 | + D.button('Discard'), | |
| 191 | + 'title', | |
| 192 | + 'Close the editor and discard all local edits.' | |
| 193 | + ); | |
| 172 | 194 | } |
| 173 | 195 | if( 1 ){ |
| 174 | 196 | F.confirmer(e.button.submit, { |
| 175 | 197 | confirmText: "Confirm submit...", |
| 176 | 198 | onconfirm: ()=>this.#submit() |
| @@ -270,10 +292,11 @@ | ||
| 270 | 292 | |
| 271 | 293 | if( opt.edit |
| 272 | 294 | && !opt.inReplyTo |
| 273 | 295 | && F.config.forumStatuses?.length>0 ){ |
| 274 | 296 | const sel = e.status = D.select(); |
| 297 | + sel.setAttribute('title', 'The status tag value for this post.'); | |
| 275 | 298 | D.option(sel, "", "- Status -").disabled = true; |
| 276 | 299 | for( const status of F.config.forumStatuses ){ |
| 277 | 300 | D.option(sel, status.value, status.label); |
| 278 | 301 | } |
| 279 | 302 | e.buttons.append(sel); |
| @@ -302,11 +325,11 @@ | ||
| 302 | 325 | e.tabAttach.dataset.tabLabel = 'Attachments'; |
| 303 | 326 | this.#tabs.addTab(e.tabAttach); |
| 304 | 327 | /* Reminder: we don't currently have a way to disable/enable |
| 305 | 328 | an Attacher's controls during ajax traffic. */ |
| 306 | 329 | } |
| 307 | - e.buttons.append(e.button.preview, e.button.submit); | |
| 330 | + e.buttons.append(e.button.preview, e.button.submit, e.button.stash); | |
| 308 | 331 | if( e.button.discard ){ |
| 309 | 332 | e.buttons.append(e.button.discard); |
| 310 | 333 | this.#toDisable.push(e.button.discard); |
| 311 | 334 | } |
| 312 | 335 | |
| @@ -329,11 +352,16 @@ | ||
| 329 | 352 | 'and edit modes, which is generally useful but some ', |
| 330 | 353 | 'software keyboards misinteract with it. If the preview ', |
| 331 | 354 | 'starts when tapping Enter, turn this setting off.' |
| 332 | 355 | ].join('') |
| 333 | 356 | ); |
| 334 | - eCb.checked = F.storage.getBool('edit-shift-enter-preview', true); | |
| 357 | + eCb.checked = F.storage.getBool( | |
| 358 | + 'edit-shift-enter-preview', | |
| 359 | + true | |
| 360 | + /* Maintenance reminder: this setting is shared across | |
| 361 | + several apps, like /chat, /wikiedit, and /fileedit. */ | |
| 362 | + ); | |
| 335 | 363 | eCb.addEventListener('change', (ev)=>{ |
| 336 | 364 | F.storage.set('edit-shift-enter-preview', eCb.checked); |
| 337 | 365 | }); |
| 338 | 366 | F.helpButtonlets.setup(eHelp); |
| 339 | 367 | eLbl.append("Shift-enter toggles preview?", eCb, eHelp); |
| @@ -351,13 +379,12 @@ | ||
| 351 | 379 | https://fossil-scm.org/forum/forumpost/dbd5b68366147ce8 |
| 352 | 380 | */ |
| 353 | 381 | if(!isShiftEnter(ev)) return; |
| 354 | 382 | ev.preventDefault(); |
| 355 | 383 | ev.stopPropagation(); |
| 356 | - e.editor.blur(/*force change event, if needed*/); | |
| 384 | + e.editor.blur(/*force draft update if needed*/); | |
| 357 | 385 | this.#tabs.switchToTab(e.preview); |
| 358 | - this.#preview(); | |
| 359 | 386 | }, false); |
| 360 | 387 | // If we're in the preview tab, have ctrl-enter switch back to the editor. |
| 361 | 388 | document.body.addEventListener('keydown',(ev)=>{ |
| 362 | 389 | if(!isShiftEnter(ev)) return; |
| 363 | 390 | if(this.#activeTab !== e.tabEdit){ |
| @@ -983,21 +1010,21 @@ | ||
| 983 | 1010 | } |
| 984 | 1011 | |
| 985 | 1012 | /* Apply page-specific tweaks for ForumPostEditor instance fpe |
| 986 | 1013 | then plug it into the UI at the end of ePost. */ |
| 987 | 1014 | const initFPEWidget = (fpe, ePost)=>{ |
| 988 | - const w = fpe.widget; | |
| 989 | - fpe.eUnhideThenWhenDone = [ | |
| 1015 | + ePost.eUnhideThenWhenDone = [ | |
| 990 | 1016 | /* List of elements to hide while editing/replying and reveal |
| 991 | 1017 | when discarding or saving. */ |
| 992 | 1018 | ]; |
| 993 | 1019 | for( const ee of ePost.querySelectorAll( |
| 994 | 1020 | '.forumpost-single-controls, fieldset.forum-status-selection' |
| 995 | 1021 | ) ){ |
| 996 | 1022 | ee.hidden = true; |
| 997 | - fpe.eUnhideThenWhenDone.push(ee); | |
| 1023 | + ePost.eUnhideThenWhenDone.push(ee); | |
| 998 | 1024 | } |
| 1025 | + const w = fpe.widget; | |
| 999 | 1026 | w.classList.add('animate-entrance'); |
| 1000 | 1027 | ePost.append(w); |
| 1001 | 1028 | requestAnimationFrame(() => { |
| 1002 | 1029 | w.scrollIntoView({ |
| 1003 | 1030 | behavior: 'smooth', |
| @@ -1070,22 +1097,23 @@ | ||
| 1070 | 1097 | ePost.style.marginLeft = 'initial'; |
| 1071 | 1098 | eButton.dataset.originalLabel = eButton.innerText; |
| 1072 | 1099 | }; |
| 1073 | 1100 | |
| 1074 | 1101 | /** Undoes the damage done by setupEditReplyElement(). */ |
| 1075 | - const restoreEditReplyElement = (ePost, eButton, fpe)=>{ | |
| 1102 | + const restoreEditReplyElement = (ePost, eButton)=>{ | |
| 1076 | 1103 | if( ePost.dataset.originalMarginLeft ){ |
| 1077 | 1104 | ePost.style.marginLeft = ePost.dataset.originalMarginLeft; |
| 1078 | 1105 | delete ePost.dataset.originalMarginLeft; |
| 1079 | 1106 | } |
| 1080 | 1107 | if( eButton.dataset.originalLabel ){ |
| 1081 | 1108 | eButton.innerText = eButton.dataset.originalLabel; |
| 1082 | 1109 | delete eButton.dataset.originalLabel; |
| 1083 | 1110 | } |
| 1084 | - for(const ee of (fpe?.eUnhideThenWhenDone || [])){ | |
| 1111 | + for(const ee of (ePost.eUnhideThenWhenDone || [])){ | |
| 1085 | 1112 | ee.removeAttribute('hidden'); |
| 1086 | 1113 | } |
| 1114 | + ePost.eUnhideThenWhenDone = undefined; | |
| 1087 | 1115 | }; |
| 1088 | 1116 | |
| 1089 | 1117 | /** |
| 1090 | 1118 | Reports an error regarding the forum post element |
| 1091 | 1119 | ePost, appending each entry in msg to a wrapper |
| @@ -1151,31 +1179,31 @@ | ||
| 1151 | 1179 | |
| 1152 | 1180 | setupEditReplyElement(ePost, eBtnReply); |
| 1153 | 1181 | eBtnReply.innerText = "Replying..."; |
| 1154 | 1182 | const ondone = (fpe, response)=>{ |
| 1155 | 1183 | /* onsubmit() and ondiscard() callback */ |
| 1156 | - restoreEditReplyElement(ePost, eBtnReply, fpe); | |
| 1184 | + restoreEditReplyElement(ePost, eBtnReply); | |
| 1157 | 1185 | //console.debug("ondiscard/onsubmit", fpe, artifact); |
| 1158 | 1186 | if( response/*onsubmit()*/ ){ |
| 1159 | 1187 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1160 | 1188 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1161 | - }else{/*ondiscard()*/ | |
| 1189 | + }else{/*ondiscard() or onclose()*/ | |
| 1162 | 1190 | } |
| 1163 | 1191 | }; |
| 1164 | 1192 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1165 | 1193 | hiddenFields: form.querySelectorAll( |
| 1166 | 1194 | 'input[type=hidden][name=csrf]' |
| 1167 | 1195 | /* Do not inherit the fpid field, else this will become |
| 1168 | 1196 | an edit to that post rather than a response. */ |
| 1169 | 1197 | ), |
| 1170 | - ondiscard: ondone, | |
| 1171 | 1198 | onsubmit: ondone, |
| 1172 | 1199 | onclose: ()=>{ |
| 1173 | 1200 | if( releaseLock ){ |
| 1174 | 1201 | releaseLock(); |
| 1175 | 1202 | releaseLock = null; |
| 1176 | 1203 | } |
| 1204 | + ondone(); | |
| 1177 | 1205 | }, |
| 1178 | 1206 | inReplyTo: fpid, |
| 1179 | 1207 | draftKey |
| 1180 | 1208 | })); |
| 1181 | 1209 | initFPEWidget(fpe, ePost); |
| @@ -1231,32 +1259,32 @@ | ||
| 1231 | 1259 | if( fpid === response.uuid |
| 1232 | 1260 | && !response.statusModified |
| 1233 | 1261 | && 0===response.attachedCount ){ |
| 1234 | 1262 | fpe.reportError("No changes made."); |
| 1235 | 1263 | }else{ |
| 1236 | - restoreEditReplyElement(ePost, eBtnEdit, fpe); | |
| 1264 | + restoreEditReplyElement(ePost, eBtnEdit); | |
| 1237 | 1265 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1238 | 1266 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1239 | 1267 | } |
| 1240 | 1268 | }else{ |
| 1241 | - /*ondiscard()*/ | |
| 1242 | - restoreEditReplyElement(ePost, eBtnEdit, fpe); | |
| 1269 | + /*ondiscard() or onclose()*/ | |
| 1270 | + restoreEditReplyElement(ePost, eBtnEdit); | |
| 1243 | 1271 | } |
| 1244 | 1272 | }; |
| 1245 | 1273 | const eStatusSelect = ePost.querySelector( |
| 1246 | 1274 | ':scope > fieldset.forum-status-selection select[name=status]' |
| 1247 | 1275 | ); |
| 1248 | 1276 | |
| 1249 | 1277 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1250 | 1278 | hiddenFields: form.querySelectorAll('input[type=hidden]'), |
| 1251 | - ondiscard: ondone, | |
| 1252 | 1279 | onsubmit: ondone, |
| 1253 | 1280 | onclose: ()=>{ |
| 1254 | 1281 | if(releaseLock){ |
| 1255 | 1282 | releaseLock(); |
| 1256 | 1283 | releaseLock = null; |
| 1257 | 1284 | } |
| 1285 | + ondone(); | |
| 1258 | 1286 | }, |
| 1259 | 1287 | draftKey, |
| 1260 | 1288 | edit: artifact, |
| 1261 | 1289 | status: eStatusSelect?.value, |
| 1262 | 1290 | inReplyTo: firt |
| @@ -1266,11 +1294,11 @@ | ||
| 1266 | 1294 | .catch(err=>{ |
| 1267 | 1295 | if( releaseLock ){ |
| 1268 | 1296 | releaseLock(); |
| 1269 | 1297 | releaseLock = null; |
| 1270 | 1298 | } |
| 1271 | - restoreEditReplyElement(ePost, eBtnEdit, null); | |
| 1299 | + restoreEditReplyElement(ePost, eBtnEdit); | |
| 1272 | 1300 | console.error("Error fetching post:", err); |
| 1273 | 1301 | reportFPEError(ePost, "Error fetching post: ", err.message); |
| 1274 | 1302 | }); |
| 1275 | 1303 | }/*editClicked()*/; |
| 1276 | 1304 | |
| 1277 | 1305 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -138,10 +138,11 @@ | |
| 138 | } |
| 139 | |
| 140 | { /* Mimetype... */ |
| 141 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 142 | const sel = e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 143 | this.#toDisable.push(sel); |
| 144 | let i = 0; |
| 145 | D.option(sel, '', '- Markup format -').disabled = true; |
| 146 | for(const [k,v] of Object.entries({ |
| 147 | 'text/x-markdown': 'Markdown', |
| @@ -163,14 +164,35 @@ | |
| 163 | e.mimetype.wrapper.append(sel); |
| 164 | } |
| 165 | |
| 166 | e.buttons = D.addClass(D.div(), 'buttons'); |
| 167 | { /* Preview/submit buttons... */ |
| 168 | e.button.preview = D.button("Preview", e=>this.#preview()); |
| 169 | e.button.submit = D.button("Submit"); |
| 170 | if( opt.ondiscard instanceof Function ){ |
| 171 | e.button.discard = D.button('Discard'); |
| 172 | } |
| 173 | if( 1 ){ |
| 174 | F.confirmer(e.button.submit, { |
| 175 | confirmText: "Confirm submit...", |
| 176 | onconfirm: ()=>this.#submit() |
| @@ -270,10 +292,11 @@ | |
| 270 | |
| 271 | if( opt.edit |
| 272 | && !opt.inReplyTo |
| 273 | && F.config.forumStatuses?.length>0 ){ |
| 274 | const sel = e.status = D.select(); |
| 275 | D.option(sel, "", "- Status -").disabled = true; |
| 276 | for( const status of F.config.forumStatuses ){ |
| 277 | D.option(sel, status.value, status.label); |
| 278 | } |
| 279 | e.buttons.append(sel); |
| @@ -302,11 +325,11 @@ | |
| 302 | e.tabAttach.dataset.tabLabel = 'Attachments'; |
| 303 | this.#tabs.addTab(e.tabAttach); |
| 304 | /* Reminder: we don't currently have a way to disable/enable |
| 305 | an Attacher's controls during ajax traffic. */ |
| 306 | } |
| 307 | e.buttons.append(e.button.preview, e.button.submit); |
| 308 | if( e.button.discard ){ |
| 309 | e.buttons.append(e.button.discard); |
| 310 | this.#toDisable.push(e.button.discard); |
| 311 | } |
| 312 | |
| @@ -329,11 +352,16 @@ | |
| 329 | 'and edit modes, which is generally useful but some ', |
| 330 | 'software keyboards misinteract with it. If the preview ', |
| 331 | 'starts when tapping Enter, turn this setting off.' |
| 332 | ].join('') |
| 333 | ); |
| 334 | eCb.checked = F.storage.getBool('edit-shift-enter-preview', true); |
| 335 | eCb.addEventListener('change', (ev)=>{ |
| 336 | F.storage.set('edit-shift-enter-preview', eCb.checked); |
| 337 | }); |
| 338 | F.helpButtonlets.setup(eHelp); |
| 339 | eLbl.append("Shift-enter toggles preview?", eCb, eHelp); |
| @@ -351,13 +379,12 @@ | |
| 351 | https://fossil-scm.org/forum/forumpost/dbd5b68366147ce8 |
| 352 | */ |
| 353 | if(!isShiftEnter(ev)) return; |
| 354 | ev.preventDefault(); |
| 355 | ev.stopPropagation(); |
| 356 | e.editor.blur(/*force change event, if needed*/); |
| 357 | this.#tabs.switchToTab(e.preview); |
| 358 | this.#preview(); |
| 359 | }, false); |
| 360 | // If we're in the preview tab, have ctrl-enter switch back to the editor. |
| 361 | document.body.addEventListener('keydown',(ev)=>{ |
| 362 | if(!isShiftEnter(ev)) return; |
| 363 | if(this.#activeTab !== e.tabEdit){ |
| @@ -983,21 +1010,21 @@ | |
| 983 | } |
| 984 | |
| 985 | /* Apply page-specific tweaks for ForumPostEditor instance fpe |
| 986 | then plug it into the UI at the end of ePost. */ |
| 987 | const initFPEWidget = (fpe, ePost)=>{ |
| 988 | const w = fpe.widget; |
| 989 | fpe.eUnhideThenWhenDone = [ |
| 990 | /* List of elements to hide while editing/replying and reveal |
| 991 | when discarding or saving. */ |
| 992 | ]; |
| 993 | for( const ee of ePost.querySelectorAll( |
| 994 | '.forumpost-single-controls, fieldset.forum-status-selection' |
| 995 | ) ){ |
| 996 | ee.hidden = true; |
| 997 | fpe.eUnhideThenWhenDone.push(ee); |
| 998 | } |
| 999 | w.classList.add('animate-entrance'); |
| 1000 | ePost.append(w); |
| 1001 | requestAnimationFrame(() => { |
| 1002 | w.scrollIntoView({ |
| 1003 | behavior: 'smooth', |
| @@ -1070,22 +1097,23 @@ | |
| 1070 | ePost.style.marginLeft = 'initial'; |
| 1071 | eButton.dataset.originalLabel = eButton.innerText; |
| 1072 | }; |
| 1073 | |
| 1074 | /** Undoes the damage done by setupEditReplyElement(). */ |
| 1075 | const restoreEditReplyElement = (ePost, eButton, fpe)=>{ |
| 1076 | if( ePost.dataset.originalMarginLeft ){ |
| 1077 | ePost.style.marginLeft = ePost.dataset.originalMarginLeft; |
| 1078 | delete ePost.dataset.originalMarginLeft; |
| 1079 | } |
| 1080 | if( eButton.dataset.originalLabel ){ |
| 1081 | eButton.innerText = eButton.dataset.originalLabel; |
| 1082 | delete eButton.dataset.originalLabel; |
| 1083 | } |
| 1084 | for(const ee of (fpe?.eUnhideThenWhenDone || [])){ |
| 1085 | ee.removeAttribute('hidden'); |
| 1086 | } |
| 1087 | }; |
| 1088 | |
| 1089 | /** |
| 1090 | Reports an error regarding the forum post element |
| 1091 | ePost, appending each entry in msg to a wrapper |
| @@ -1151,31 +1179,31 @@ | |
| 1151 | |
| 1152 | setupEditReplyElement(ePost, eBtnReply); |
| 1153 | eBtnReply.innerText = "Replying..."; |
| 1154 | const ondone = (fpe, response)=>{ |
| 1155 | /* onsubmit() and ondiscard() callback */ |
| 1156 | restoreEditReplyElement(ePost, eBtnReply, fpe); |
| 1157 | //console.debug("ondiscard/onsubmit", fpe, artifact); |
| 1158 | if( response/*onsubmit()*/ ){ |
| 1159 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1160 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1161 | }else{/*ondiscard()*/ |
| 1162 | } |
| 1163 | }; |
| 1164 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1165 | hiddenFields: form.querySelectorAll( |
| 1166 | 'input[type=hidden][name=csrf]' |
| 1167 | /* Do not inherit the fpid field, else this will become |
| 1168 | an edit to that post rather than a response. */ |
| 1169 | ), |
| 1170 | ondiscard: ondone, |
| 1171 | onsubmit: ondone, |
| 1172 | onclose: ()=>{ |
| 1173 | if( releaseLock ){ |
| 1174 | releaseLock(); |
| 1175 | releaseLock = null; |
| 1176 | } |
| 1177 | }, |
| 1178 | inReplyTo: fpid, |
| 1179 | draftKey |
| 1180 | })); |
| 1181 | initFPEWidget(fpe, ePost); |
| @@ -1231,32 +1259,32 @@ | |
| 1231 | if( fpid === response.uuid |
| 1232 | && !response.statusModified |
| 1233 | && 0===response.attachedCount ){ |
| 1234 | fpe.reportError("No changes made."); |
| 1235 | }else{ |
| 1236 | restoreEditReplyElement(ePost, eBtnEdit, fpe); |
| 1237 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1238 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1239 | } |
| 1240 | }else{ |
| 1241 | /*ondiscard()*/ |
| 1242 | restoreEditReplyElement(ePost, eBtnEdit, fpe); |
| 1243 | } |
| 1244 | }; |
| 1245 | const eStatusSelect = ePost.querySelector( |
| 1246 | ':scope > fieldset.forum-status-selection select[name=status]' |
| 1247 | ); |
| 1248 | |
| 1249 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1250 | hiddenFields: form.querySelectorAll('input[type=hidden]'), |
| 1251 | ondiscard: ondone, |
| 1252 | onsubmit: ondone, |
| 1253 | onclose: ()=>{ |
| 1254 | if(releaseLock){ |
| 1255 | releaseLock(); |
| 1256 | releaseLock = null; |
| 1257 | } |
| 1258 | }, |
| 1259 | draftKey, |
| 1260 | edit: artifact, |
| 1261 | status: eStatusSelect?.value, |
| 1262 | inReplyTo: firt |
| @@ -1266,11 +1294,11 @@ | |
| 1266 | .catch(err=>{ |
| 1267 | if( releaseLock ){ |
| 1268 | releaseLock(); |
| 1269 | releaseLock = null; |
| 1270 | } |
| 1271 | restoreEditReplyElement(ePost, eBtnEdit, null); |
| 1272 | console.error("Error fetching post:", err); |
| 1273 | reportFPEError(ePost, "Error fetching post: ", err.message); |
| 1274 | }); |
| 1275 | }/*editClicked()*/; |
| 1276 | |
| 1277 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -138,10 +138,11 @@ | |
| 138 | } |
| 139 | |
| 140 | { /* Mimetype... */ |
| 141 | e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper'); |
| 142 | const sel = e.mimetype.select = D.addClass(D.select(), 'mimetype-select'); |
| 143 | sel.setAttribute('title', 'Markup format for this post.'); |
| 144 | this.#toDisable.push(sel); |
| 145 | let i = 0; |
| 146 | D.option(sel, '', '- Markup format -').disabled = true; |
| 147 | for(const [k,v] of Object.entries({ |
| 148 | 'text/x-markdown': 'Markdown', |
| @@ -163,14 +164,35 @@ | |
| 164 | e.mimetype.wrapper.append(sel); |
| 165 | } |
| 166 | |
| 167 | e.buttons = D.addClass(D.div(), 'buttons'); |
| 168 | { /* Preview/submit buttons... */ |
| 169 | e.button.preview = D.attr( |
| 170 | D.button("Preview", e=>this.#preview()), |
| 171 | 'title', |
| 172 | 'Preview your edits.' |
| 173 | ); |
| 174 | e.button.submit = D.attr( |
| 175 | D.button("Submit"), |
| 176 | 'title', |
| 177 | 'Save any edits to the server. Not permitted until Preview has been used.' |
| 178 | ); |
| 179 | e.button.stash = D.attr( |
| 180 | D.button( |
| 181 | "Stash", e=>this.close() |
| 182 | /* This could be called Close, but that would semantically |
| 183 | collide with the Close [this post] button. All "Stash" |
| 184 | does is close the widget. */ |
| 185 | ), |
| 186 | 'title', "Close this editor and stash any edits locally." |
| 187 | ); |
| 188 | if( opt.ondiscard instanceof Function ){ |
| 189 | e.button.discard = D.attr( |
| 190 | D.button('Discard'), |
| 191 | 'title', |
| 192 | 'Close the editor and discard all local edits.' |
| 193 | ); |
| 194 | } |
| 195 | if( 1 ){ |
| 196 | F.confirmer(e.button.submit, { |
| 197 | confirmText: "Confirm submit...", |
| 198 | onconfirm: ()=>this.#submit() |
| @@ -270,10 +292,11 @@ | |
| 292 | |
| 293 | if( opt.edit |
| 294 | && !opt.inReplyTo |
| 295 | && F.config.forumStatuses?.length>0 ){ |
| 296 | const sel = e.status = D.select(); |
| 297 | sel.setAttribute('title', 'The status tag value for this post.'); |
| 298 | D.option(sel, "", "- Status -").disabled = true; |
| 299 | for( const status of F.config.forumStatuses ){ |
| 300 | D.option(sel, status.value, status.label); |
| 301 | } |
| 302 | e.buttons.append(sel); |
| @@ -302,11 +325,11 @@ | |
| 325 | e.tabAttach.dataset.tabLabel = 'Attachments'; |
| 326 | this.#tabs.addTab(e.tabAttach); |
| 327 | /* Reminder: we don't currently have a way to disable/enable |
| 328 | an Attacher's controls during ajax traffic. */ |
| 329 | } |
| 330 | e.buttons.append(e.button.preview, e.button.submit, e.button.stash); |
| 331 | if( e.button.discard ){ |
| 332 | e.buttons.append(e.button.discard); |
| 333 | this.#toDisable.push(e.button.discard); |
| 334 | } |
| 335 | |
| @@ -329,11 +352,16 @@ | |
| 352 | 'and edit modes, which is generally useful but some ', |
| 353 | 'software keyboards misinteract with it. If the preview ', |
| 354 | 'starts when tapping Enter, turn this setting off.' |
| 355 | ].join('') |
| 356 | ); |
| 357 | eCb.checked = F.storage.getBool( |
| 358 | 'edit-shift-enter-preview', |
| 359 | true |
| 360 | /* Maintenance reminder: this setting is shared across |
| 361 | several apps, like /chat, /wikiedit, and /fileedit. */ |
| 362 | ); |
| 363 | eCb.addEventListener('change', (ev)=>{ |
| 364 | F.storage.set('edit-shift-enter-preview', eCb.checked); |
| 365 | }); |
| 366 | F.helpButtonlets.setup(eHelp); |
| 367 | eLbl.append("Shift-enter toggles preview?", eCb, eHelp); |
| @@ -351,13 +379,12 @@ | |
| 379 | https://fossil-scm.org/forum/forumpost/dbd5b68366147ce8 |
| 380 | */ |
| 381 | if(!isShiftEnter(ev)) return; |
| 382 | ev.preventDefault(); |
| 383 | ev.stopPropagation(); |
| 384 | e.editor.blur(/*force draft update if needed*/); |
| 385 | this.#tabs.switchToTab(e.preview); |
| 386 | }, false); |
| 387 | // If we're in the preview tab, have ctrl-enter switch back to the editor. |
| 388 | document.body.addEventListener('keydown',(ev)=>{ |
| 389 | if(!isShiftEnter(ev)) return; |
| 390 | if(this.#activeTab !== e.tabEdit){ |
| @@ -983,21 +1010,21 @@ | |
| 1010 | } |
| 1011 | |
| 1012 | /* Apply page-specific tweaks for ForumPostEditor instance fpe |
| 1013 | then plug it into the UI at the end of ePost. */ |
| 1014 | const initFPEWidget = (fpe, ePost)=>{ |
| 1015 | ePost.eUnhideThenWhenDone = [ |
| 1016 | /* List of elements to hide while editing/replying and reveal |
| 1017 | when discarding or saving. */ |
| 1018 | ]; |
| 1019 | for( const ee of ePost.querySelectorAll( |
| 1020 | '.forumpost-single-controls, fieldset.forum-status-selection' |
| 1021 | ) ){ |
| 1022 | ee.hidden = true; |
| 1023 | ePost.eUnhideThenWhenDone.push(ee); |
| 1024 | } |
| 1025 | const w = fpe.widget; |
| 1026 | w.classList.add('animate-entrance'); |
| 1027 | ePost.append(w); |
| 1028 | requestAnimationFrame(() => { |
| 1029 | w.scrollIntoView({ |
| 1030 | behavior: 'smooth', |
| @@ -1070,22 +1097,23 @@ | |
| 1097 | ePost.style.marginLeft = 'initial'; |
| 1098 | eButton.dataset.originalLabel = eButton.innerText; |
| 1099 | }; |
| 1100 | |
| 1101 | /** Undoes the damage done by setupEditReplyElement(). */ |
| 1102 | const restoreEditReplyElement = (ePost, eButton)=>{ |
| 1103 | if( ePost.dataset.originalMarginLeft ){ |
| 1104 | ePost.style.marginLeft = ePost.dataset.originalMarginLeft; |
| 1105 | delete ePost.dataset.originalMarginLeft; |
| 1106 | } |
| 1107 | if( eButton.dataset.originalLabel ){ |
| 1108 | eButton.innerText = eButton.dataset.originalLabel; |
| 1109 | delete eButton.dataset.originalLabel; |
| 1110 | } |
| 1111 | for(const ee of (ePost.eUnhideThenWhenDone || [])){ |
| 1112 | ee.removeAttribute('hidden'); |
| 1113 | } |
| 1114 | ePost.eUnhideThenWhenDone = undefined; |
| 1115 | }; |
| 1116 | |
| 1117 | /** |
| 1118 | Reports an error regarding the forum post element |
| 1119 | ePost, appending each entry in msg to a wrapper |
| @@ -1151,31 +1179,31 @@ | |
| 1179 | |
| 1180 | setupEditReplyElement(ePost, eBtnReply); |
| 1181 | eBtnReply.innerText = "Replying..."; |
| 1182 | const ondone = (fpe, response)=>{ |
| 1183 | /* onsubmit() and ondiscard() callback */ |
| 1184 | restoreEditReplyElement(ePost, eBtnReply); |
| 1185 | //console.debug("ondiscard/onsubmit", fpe, artifact); |
| 1186 | if( response/*onsubmit()*/ ){ |
| 1187 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1188 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1189 | }else{/*ondiscard() or onclose()*/ |
| 1190 | } |
| 1191 | }; |
| 1192 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1193 | hiddenFields: form.querySelectorAll( |
| 1194 | 'input[type=hidden][name=csrf]' |
| 1195 | /* Do not inherit the fpid field, else this will become |
| 1196 | an edit to that post rather than a response. */ |
| 1197 | ), |
| 1198 | onsubmit: ondone, |
| 1199 | onclose: ()=>{ |
| 1200 | if( releaseLock ){ |
| 1201 | releaseLock(); |
| 1202 | releaseLock = null; |
| 1203 | } |
| 1204 | ondone(); |
| 1205 | }, |
| 1206 | inReplyTo: fpid, |
| 1207 | draftKey |
| 1208 | })); |
| 1209 | initFPEWidget(fpe, ePost); |
| @@ -1231,32 +1259,32 @@ | |
| 1259 | if( fpid === response.uuid |
| 1260 | && !response.statusModified |
| 1261 | && 0===response.attachedCount ){ |
| 1262 | fpe.reportError("No changes made."); |
| 1263 | }else{ |
| 1264 | restoreEditReplyElement(ePost, eBtnEdit); |
| 1265 | window.location = F.repoUrl('forumpost/'+response.uuid); |
| 1266 | setTimeout(()=>fpe.close(), 500/*just in case not redirected*/); |
| 1267 | } |
| 1268 | }else{ |
| 1269 | /*ondiscard() or onclose()*/ |
| 1270 | restoreEditReplyElement(ePost, eBtnEdit); |
| 1271 | } |
| 1272 | }; |
| 1273 | const eStatusSelect = ePost.querySelector( |
| 1274 | ':scope > fieldset.forum-status-selection select[name=status]' |
| 1275 | ); |
| 1276 | |
| 1277 | const fpe = new F.ForumPostEditor(F.nu({ |
| 1278 | hiddenFields: form.querySelectorAll('input[type=hidden]'), |
| 1279 | onsubmit: ondone, |
| 1280 | onclose: ()=>{ |
| 1281 | if(releaseLock){ |
| 1282 | releaseLock(); |
| 1283 | releaseLock = null; |
| 1284 | } |
| 1285 | ondone(); |
| 1286 | }, |
| 1287 | draftKey, |
| 1288 | edit: artifact, |
| 1289 | status: eStatusSelect?.value, |
| 1290 | inReplyTo: firt |
| @@ -1266,11 +1294,11 @@ | |
| 1294 | .catch(err=>{ |
| 1295 | if( releaseLock ){ |
| 1296 | releaseLock(); |
| 1297 | releaseLock = null; |
| 1298 | } |
| 1299 | restoreEditReplyElement(ePost, eBtnEdit); |
| 1300 | console.error("Error fetching post:", err); |
| 1301 | reportFPEError(ePost, "Error fetching post: ", err.message); |
| 1302 | }); |
| 1303 | }/*editClicked()*/; |
| 1304 | |
| 1305 |