diff --git a/reviewboard/static/rb/js/reviews/index.ts b/reviewboard/static/rb/js/reviews/index.ts
index 6d84ad82187cc5a5f9e391ebe732e130c52f0708..0a271f474ff76980dedc744a5b196d3881505e08 100644
--- a/reviewboard/static/rb/js/reviews/index.ts
+++ b/reviewboard/static/rb/js/reviews/index.ts
@@ -68,6 +68,7 @@ export {
 } from './views/fileAttachmentThumbnailView';
 export { ImageReviewableView } from './views/imageReviewableView';
 export { MarkdownReviewableView } from './views/markdownReviewableView';
+export { PublishButtonView } from './views/publishButton';
 export { RegionCommentBlockView } from './views/regionCommentBlockView';
 export { ReviewDialogView } from './views/reviewDialogView';
 export { ReviewRequestEditorView } from './views/reviewRequestEditorView';
diff --git a/reviewboard/static/rb/js/reviews/views/publishButton.ts b/reviewboard/static/rb/js/reviews/views/publishButton.ts
new file mode 100644
index 0000000000000000000000000000000000000000..31fe9ecfdd6726d783d3271821ae2865f7d80f56
--- /dev/null
+++ b/reviewboard/static/rb/js/reviews/views/publishButton.ts
@@ -0,0 +1,131 @@
+/**
+ * The publish button for reviews.
+ *
+ * Version Added:
+ *      8.0
+ */
+
+import {
+    MenuButtonView,
+    MenuItem,
+    MenuItemType,
+    MenuItemsCollection,
+} from '@beanbag/ink';
+import {
+    type EventsHash,
+    BaseModel,
+    spina,
+} from '@beanbag/spina';
+import _ from 'underscore';
+
+
+import {
+    UserSession,
+} from 'reviewboard/common';
+import { ReviewRequestEditor } from '../models/reviewRequestEditorModel';
+
+/**
+ * The publish button.
+ *
+ * This is the standard publish button that provides the standard for
+ * publishing buttons across the website. It includes a dropdown checkbox list
+ * that allows the user to select the settings they prefer (emailing and
+ * archiving) when publishing. It is modular by nature, allowing for
+ * new options for the checkbox list in the future.
+ *
+ * Version Added:
+ *      8.0
+ */
+@spina
+export class PublishButtonView<TModel extends BaseModel>
+extends MenuButtonView<TModel> {
+    static modelEvents: EventsHash = {
+        'change:draftModes change:selectedDraftMode': '_update',
+    };
+
+    /**
+     * Initialize the publish button.
+     */
+    initialize() {
+        const reviewRequestEditor = this._getReviewRequestEditor();
+        const menuItems = new MenuItemsCollection();
+        let sendEmailItem: MenuItem | null = null;
+
+        if (reviewRequestEditor.get('showSendEmail')) {
+            sendEmailItem = new MenuItem({
+                checked: true,
+                label: _`Send E-Mail`,
+                type: MenuItemType.CHECKBOX_ITEM,
+            });
+            menuItems.add(sendEmailItem);
+        }
+
+        const publishAndArchiveItem: MenuItem = new MenuItem({
+            checked: UserSession.instance.get('publishAndArchive'),
+            label: _`Archive After Publishing`,
+            type: MenuItemType.CHECKBOX_ITEM,
+        });
+        menuItems.add(publishAndArchiveItem);
+
+        super.initialize({
+            dropdownButtonAriaLabel: _`Open publish options`,
+            hasActionButton: true,
+            label: _`Publish All`,
+            menuAriaLabel: _`Publish options`,
+            menuIconName: 'fa fa-gear',
+            menuItems: menuItems,
+            onActionButtonClick: () => {
+                this._action(publishAndArchiveItem, sendEmailItem);
+            },
+        });
+    }
+
+    /**
+     * Called when the component is initially rendered.
+     *
+     * This hook allows the view to perform any setup that depends on
+     * the component being in the DOM. In this case, it updates the
+     * state of the publish button based on the current model values.
+     */
+    protected onComponentInitialRender() {
+        super.onComponentInitialRender();
+        this._update();
+    }
+
+    /**
+     * Update the state of the publish button.
+     * 
+     * This should be implemented by subclasses.
+     */
+    protected _update() {
+        // Does nothing for now. Depends on Model used in subclasses.
+    }
+
+    /**
+     * Return the review request editor.
+     *
+     * Returns:
+     *      BaseModel
+     *      Used to access the showSendEmail setting.
+     */
+    protected _getReviewRequestEditor(): ReviewRequestEditor {
+        return this.model.get('reviewRequestEditor');
+    }
+
+    /**
+     * Trigger the publish operation.
+     *
+     * Args:
+     *      showSendEmailItem (MenuItem):
+     *          The checkbox object for emails.
+     */
+    protected _action(
+        publishAndArchiveItem: MenuItem,
+        sendEmailItem: MenuItem
+    ) {
+        this.trigger('publish', {
+            archive: publishAndArchiveItem.get('checked'),
+            trivial: sendEmailItem !== null && !sendEmailItem.get('checked'),
+        });
+    }
+}
diff --git a/reviewboard/static/rb/js/reviews/views/reviewDialogView.ts b/reviewboard/static/rb/js/reviews/views/reviewDialogView.ts
index eaa7f35cd07ff00f6789c3aca857dfdfd9e96fa5..fd366b68c8f33f5e8f57a563e699102738e4ecdb 100644
--- a/reviewboard/static/rb/js/reviews/views/reviewDialogView.ts
+++ b/reviewboard/static/rb/js/reviews/views/reviewDialogView.ts
@@ -4,12 +4,16 @@
 
 import {
     type MenuButtonView,
-    MenuItemsCollection,
+    MenuItem,
     craft,
     paint,
 } from '@beanbag/ink';
 import { BaseView, spina } from '@beanbag/spina';
 
+import {
+    PublishButtonView,
+} from './publishButton';
+
 import {
     type Review,
     ClientCommChannel,
@@ -905,8 +909,8 @@ class HeaderFooterCommentView extends BaseView<
      */
     _getRawValueFieldsName(): string {
         return UserSession.instance.get('defaultUseRichText')
-               ? 'markdownTextFields'
-               : 'rawTextFields';
+            ? 'markdownTextFields'
+            : 'rawTextFields';
     }
 }
 
@@ -1058,6 +1062,47 @@ interface ReviewDialogViewCreationOptions extends ReviewDialogViewOptions {
 }
 
 
+/**
+ * The publish button.
+ *
+ * Version Added:
+ *     TODO
+ */
+@spina
+class ReviewDialogPublishButtonView extends
+    PublishButtonView<ReviewRequestEditor> {
+    /**
+     * Return the review request editor.
+     *
+     * Returns:
+     *      ReviewRequestEditor:
+     *      Used to access the sendEmail and publishAndArchive
+     *      settings.
+     */
+    protected _getReviewRequestEditor(): ReviewRequestEditor {
+        return this.model;
+    }
+
+    /**
+     * Call the '_saveReview' function as it's action.
+     *
+     * Args:
+     *      showSendEmailItem (MenuItem):
+     *          The checkbox object for emails.
+     */
+    protected _action(
+        publishAndArchiveItem: MenuItem,
+        showSendEmailItem: MenuItem
+    ) {
+        this.trigger('_saveReview', true, {
+            archive: publishAndArchiveItem.get('checked'),
+            trivial: (showSendEmailItem === null ||
+                      !showSendEmailItem.get('checked')),
+        });
+    }
+}
+
+
 /**
  * Creates a dialog for modifying a draft review.
  *
@@ -1125,6 +1170,7 @@ export class ReviewDialogView extends BaseView<
             model: options.review,
             reviewRequestEditor: options.reviewRequestEditor,
         });
+
         this.instance = dialog;
 
         dialog.render();
@@ -1506,7 +1552,7 @@ export class ReviewDialogView extends BaseView<
             this._handleEmptyReview();
 
             this.trigger('loadCommentsDone');
-        } catch(err) {
+        } catch (err) {
             alert(err.message); // TODO: provide better output.
         }
     }
@@ -1576,34 +1622,11 @@ export class ReviewDialogView extends BaseView<
             </div>
         `;
 
-        const menuItems = new MenuItemsCollection([
-            {
-                label: _`... and only e-mail the owner`,
-                onClick: () => {
-                    this._saveReview(true, {
-                        publishToOwnerOnly: true,
-                    });
-                },
-            },
-            {
-                label: _`... and archive the review request`,
-                onClick: () => {
-                    this._saveReview(true, {
-                        publishAndArchive: true,
-                    });
-                },
-            },
-        ]);
-
-        const publishButton = craft<MenuButtonView>`
-            <Ink.MenuButton
-              hasActionButton
-              label="${_`Publish Review`}"
-              menuAriaLabel="${_`More publishing options`}"
-              menuItems=${menuItems}
-              onActionButtonClick=${() => this._saveReview(true)}
-              type="primary"/>
-        `;
+        const publishButton = new ReviewDialogPublishButtonView({
+            model: this.options.reviewRequestEditor,
+        });
+        this.listenTo(publishButton, '_saveReview', this._saveReview);
+        publishButton.render();
 
         const rightButtonsEl = craft<HTMLElement>`
             <div class="review-dialog-buttons-right">
@@ -1728,6 +1751,11 @@ export class ReviewDialogView extends BaseView<
      *     options (object):
      *         Options for the model save operation.
      *
+     * Option Args:
+     *     arhive (boolean):
+     *         Whether or not the review should be automatically archived.
+     *     Whether the publish is "trivial" (if true, no e-mail
+     *         notifications will be sent).
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
@@ -1735,15 +1763,15 @@ export class ReviewDialogView extends BaseView<
     async _saveReview(
         publish: boolean,
         options: {
-            publishAndArchive?: boolean;
-            publishToOwnerOnly?: boolean;
+            archive?: boolean;
+            trivial?: boolean;
         } = {},
     ): Promise<void> {
-        if (publish && options.publishToOwnerOnly) {
+        if (publish && options.trivial) {
             this.model.set('publishToOwnerOnly', true);
         }
 
-        if (publish && options.publishAndArchive) {
+        if (publish && options.archive) {
             this.model.set('publishAndArchive', true);
         }
 
@@ -1822,7 +1850,7 @@ export class ReviewDialogView extends BaseView<
         model.set({
             forceTextType: 'html',
             includeTextTypes: this.#defaultUseRichText
-                              ? 'raw,markdown' : 'raw',
+                ? 'raw,markdown' : 'raw',
         });
     }
 
diff --git a/reviewboard/static/rb/js/reviews/views/tests/index.ts b/reviewboard/static/rb/js/reviews/views/tests/index.ts
index 2683fa2834b195753767a81691b9aa6db691f9a7..2814b0e449ee48427846645956bb6c69bc678a10 100644
--- a/reviewboard/static/rb/js/reviews/views/tests/index.ts
+++ b/reviewboard/static/rb/js/reviews/views/tests/index.ts
@@ -4,6 +4,7 @@ import './diffFragmentViewTests';
 import './diffReviewableViewTests';
 import './diffViewerPageViewTests';
 import './fileAttachmentThumbnailViewTests';
+import './publishButtonViewTests';
 import './reviewDialogViewTests';
 import './reviewRequestEditorViewTests';
 import './reviewRequestFieldViewsTests';
diff --git a/reviewboard/static/rb/js/reviews/views/tests/publishButtonViewTests.ts b/reviewboard/static/rb/js/reviews/views/tests/publishButtonViewTests.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5cfe7cc2321812d9a36e542ec635ae03c6186964
--- /dev/null
+++ b/reviewboard/static/rb/js/reviews/views/tests/publishButtonViewTests.ts
@@ -0,0 +1,253 @@
+import { suite } from '@beanbag/jasmine-suites';
+import {
+    afterEach,
+    beforeEach,
+    describe,
+    expect,
+    it,
+    spyOn,
+    jasmine,
+} from 'jasmine-core';
+
+import {
+    ReviewRequest,
+    UserSession,
+} from 'reviewboard/common';
+import {
+    PublishButtonView,
+    ReviewRequestEditor,
+    UnifiedBanner,
+} from 'reviewboard/reviews';
+
+suite('rb/views/PublishButtonView', function () {
+    let model: UnifiedBanner;
+    let publishButtonView: PublishButtonView | null;
+
+    afterEach(function () {
+        publishButtonView.remove();
+    });
+
+    describe('initialization with both options enabled', function () {
+        beforeEach(function () {
+            UserSession.instance.set('publishAndArchive', true);
+
+            const reviewRequest = new ReviewRequest({
+                id: 123,
+                'public': true,
+                state: ReviewRequest.PENDING,
+            });
+            const reviewRequestEditor = new ReviewRequestEditor({
+                commentIssueManager: new RB.CommentIssueManager(),
+                mutableByUser: true,
+                reviewRequest: reviewRequest,
+                showSendEmail: true,
+                statusMutableByUser: true,
+            });
+            const pendingReview = reviewRequest.createReview();
+            spyOn(reviewRequest, 'ready').and.resolveTo();
+            spyOn(reviewRequest.draft, 'ready').and.resolveTo();
+            spyOn(pendingReview, 'ready').and.resolveTo();
+
+            model = new UnifiedBanner({
+                pendingReview: pendingReview,
+                reviewRequest: reviewRequest,
+                reviewRequestEditor: reviewRequestEditor,
+            });
+
+            publishButtonView = new PublishButtonView({
+                model: model,
+            });
+
+            publishButtonView.render();
+        });
+
+        it('should initialize both email and archive as checked', function () {
+            const menuItems = publishButtonView.menuView.menuItems;
+            expect(menuItems.length).toBe(2);
+
+            const sendEmailItem = menuItems.at(0);
+            expect(sendEmailItem.get('label')).toBe('Send E-Mail');
+            expect(sendEmailItem.get('checked')).toBe(true);
+
+            const archiveItem = menuItems.at(1);
+            expect(archiveItem.get('label')).toBe('Archive After Publishing');
+            expect(archiveItem.get('checked')).toBe(true);
+        });
+
+        it('should initialize archive as not checked', function () {
+            UserSession.instance.set('publishAndArchive', false);
+
+            publishButtonView.remove();
+            publishButtonView = new PublishButtonView({
+                model: model,
+            });
+            publishButtonView.render();
+
+            const menuItems = publishButtonView.menuView.menuItems;
+            expect(menuItems.length).toBe(2);
+
+            const sendEmailItem = menuItems.at(0);
+            expect(sendEmailItem.get('label')).toBe('Send E-Mail');
+            expect(sendEmailItem.get('checked')).toBe(true);
+
+            const archiveItem = menuItems.at(1);
+            expect(archiveItem.get('label')).toBe('Archive After Publishing');
+            expect(archiveItem.get('checked')).toBe(false);
+        });
+    });
+
+    describe('initialization without emails enabled', function () {
+        beforeEach(function () {
+            UserSession.instance.set('publishAndArchive', true);
+
+            const reviewRequest = new ReviewRequest({
+                id: 123,
+                'public': true,
+                state: ReviewRequest.PENDING,
+            });
+            const reviewRequestEditor = new ReviewRequestEditor({
+                commentIssueManager: new RB.CommentIssueManager(),
+                mutableByUser: true,
+                reviewRequest: reviewRequest,
+                showSendEmail: false,
+                statusMutableByUser: true,
+            });
+            const pendingReview = reviewRequest.createReview();
+            spyOn(reviewRequest, 'ready').and.resolveTo();
+            spyOn(reviewRequest.draft, 'ready').and.resolveTo();
+            spyOn(pendingReview, 'ready').and.resolveTo();
+
+            model = new UnifiedBanner({
+                pendingReview: pendingReview,
+                reviewRequest: reviewRequest,
+                reviewRequestEditor: reviewRequestEditor,
+            });
+
+            publishButtonView = new PublishButtonView({
+                model: model,
+            });
+
+            publishButtonView.render();
+        });
+
+        it('should initialize only archive as checked', function () {
+            const menuItems = publishButtonView.menuView.menuItems;
+            expect(menuItems.length).toBe(1);
+
+            const archiveItem = menuItems.at(0);
+            expect(archiveItem.get('label')).toBe('Archive After Publishing');
+            expect(archiveItem.get('checked')).toBe(true);
+        });
+
+        it('should initialize only archive as not checked', function () {
+            UserSession.instance.set('publishAndArchive', false);
+
+            publishButtonView.remove();
+            publishButtonView = new PublishButtonView({
+                model: model,
+            });
+            publishButtonView.render();
+
+            const menuItems = publishButtonView.menuView.menuItems;
+            expect(menuItems.length).toBe(1);
+
+            const archiveItem = menuItems.at(0);
+            expect(archiveItem.get('label')).toBe('Archive After Publishing');
+            expect(archiveItem.get('checked')).toBe(false);
+        });
+    });
+
+    describe('Publish action', function () {
+        it('should trigger the publish event handler when publishAndArchive' +
+           'is true', function () {
+            UserSession.instance.set('publishAndArchive', true);
+
+            const reviewRequest = new ReviewRequest({
+                id: 123,
+                'public': true,
+                state: ReviewRequest.PENDING,
+            });
+            const reviewRequestEditor = new ReviewRequestEditor({
+                commentIssueManager: new RB.CommentIssueManager(),
+                mutableByUser: true,
+                reviewRequest: reviewRequest,
+                showSendEmail: true,
+                statusMutableByUser: true,
+            });
+            const pendingReview = reviewRequest.createReview();
+            spyOn(reviewRequest, 'ready').and.resolveTo();
+            spyOn(reviewRequest.draft, 'ready').and.resolveTo();
+            spyOn(pendingReview, 'ready').and.resolveTo();
+
+            model = new UnifiedBanner({
+                pendingReview: pendingReview,
+                reviewRequest: reviewRequest,
+                reviewRequestEditor: reviewRequestEditor,
+            });
+
+            publishButtonView = new PublishButtonView({
+                id: 'my-button',
+                model: model,
+            });
+
+            publishButtonView.render().$el.appendTo($testsScratch);
+
+            const onMyEvent = jasmine.createSpy('onClick');
+
+            publishButtonView.on('publish', onMyEvent);
+            publishButtonView.actionButtonView.$el.click();
+
+            expect(onMyEvent).toHaveBeenCalledWith({
+                archive: true,
+                trivial: false,
+            });
+        });
+
+        it('should trigger the publish event handler when publishAndArchive' + 
+           ' is false', function () {
+            UserSession.instance.set('publishAndArchive', false);
+
+            const reviewRequest = new ReviewRequest({
+                id: 123,
+                'public': true,
+                state: ReviewRequest.PENDING,
+            });
+            const reviewRequestEditor = new ReviewRequestEditor({
+                commentIssueManager: new RB.CommentIssueManager(),
+                mutableByUser: true,
+                reviewRequest: reviewRequest,
+                showSendEmail: true,
+                statusMutableByUser: true,
+            });
+            const pendingReview = reviewRequest.createReview();
+            spyOn(reviewRequest, 'ready').and.resolveTo();
+            spyOn(reviewRequest.draft, 'ready').and.resolveTo();
+            spyOn(pendingReview, 'ready').and.resolveTo();
+
+            model = new UnifiedBanner({
+                pendingReview: pendingReview,
+                reviewRequest: reviewRequest,
+                reviewRequestEditor: reviewRequestEditor,
+            });
+
+            publishButtonView = new PublishButtonView({
+                id: 'my-button',
+                model: model,
+            });
+            publishButtonView.render().$el.appendTo($testsScratch);
+
+            publishButtonView.menuView.menuItems.at(0).set('checked', false);
+
+
+            const onMyEvent = jasmine.createSpy('onClick');
+
+            publishButtonView.on('publish', onMyEvent);
+            publishButtonView.actionButtonView.$el.click();
+
+            expect(onMyEvent).toHaveBeenCalledWith({
+                archive: false,
+                trivial: true,
+            });
+        });
+    });
+});
\ No newline at end of file
diff --git a/reviewboard/static/rb/js/reviews/views/unifiedBannerView.ts b/reviewboard/static/rb/js/reviews/views/unifiedBannerView.ts
index 16a67d70c99f1a03a46f201f4efeaf3d135156d5..c05a97662282777cbf22611bb8fffed4f2e6f4e7 100644
--- a/reviewboard/static/rb/js/reviews/views/unifiedBannerView.ts
+++ b/reviewboard/static/rb/js/reviews/views/unifiedBannerView.ts
@@ -5,8 +5,6 @@ import {
     type MenuLabelView,
     MenuButtonView,
     MenuItem,
-    MenuItemType,
-    MenuItemsCollection,
     MenuView,
     craft,
     paint,
@@ -37,6 +35,7 @@ import {
     UnifiedBanner,
 } from '../models/unifiedBannerModel';
 import { type ReviewRequestEditorView } from './reviewRequestEditorView';
+import { PublishButtonView } from './publishButton';
 import { ChangeDescriptionFieldView } from './reviewRequestFieldViews';
 
 
@@ -230,7 +229,7 @@ class DraftModeMenu extends BaseView<UnifiedBanner> {
                               aria-hidden="true"/>
                         ${text}
                     `,
-                    {empty: true});
+                    { empty: true });
             } else {
                 newMenuItems.push(new MenuItem({
                     label: text,
@@ -250,75 +249,14 @@ class DraftModeMenu extends BaseView<UnifiedBanner> {
  * The publish button.
  *
  * Version Added:
- *     6.0
+ *     TODO
  */
 @spina
-class PublishButtonView extends MenuButtonView<UnifiedBanner> {
-    static modelEvents = {
-        'change:draftModes change:selectedDraftMode': '_update',
-    };
-
-    /**********************
-     * Instance variables *
-     **********************/
-
-    #$archiveCheckbox: JQuery;
-    #$trivialCheckbox: JQuery;
-
-    /**
-     * Initialize the view.
-     */
-    initialize() {
-        const reviewRequestEditor = this.model.get('reviewRequestEditor');
-        const menuItems = new MenuItemsCollection();
-
-        let showSendEmailItem: MenuItem | null = null;
-
-        if (reviewRequestEditor.get('showSendEmail')) {
-            showSendEmailItem = new MenuItem({
-                checked: true,
-                label: _`Send E-Mail`,
-                type: MenuItemType.CHECKBOX_ITEM,
-            });
-            menuItems.add(showSendEmailItem);
-        }
-
-        const archiveItem = new MenuItem({
-            label: _`Archive after publishing`,
-            type: MenuItemType.CHECKBOX_ITEM,
-        });
-        menuItems.add(archiveItem);
-
-        super.initialize({
-            dropdownButtonAriaLabel: _`Open publish options`,
-            hasActionButton: true,
-            label: _`Publish All`,
-            menuAriaLabel: _`Publish options`,
-            menuIconName: 'fa fa-gear',
-            menuItems: menuItems,
-            onActionButtonClick: () => {
-                this.trigger('publish', {
-                    archive: archiveItem.get('checked'),
-                    trivial: showSendEmailItem === null ||
-                             !showSendEmailItem.get('checked'),
-                });
-            },
-        });
-    }
-
-    /**
-     * Handle the initial rendering of the menu button.
-     */
-    protected onComponentInitialRender() {
-        super.onComponentInitialRender();
-
-        this._update()
-    }
-
+class BannerPublishButtonView extends PublishButtonView<UnifiedBanner> {
     /**
-     * Update the state of the publish button.
+     * Update the state of the publish button based on the UnifiedBanner model.
      */
-    private _update() {
+    protected _update() {
         const model = this.model;
         const draftModes = model.get('draftModes');
         const selectedDraftMode = model.get('selectedDraftMode');
@@ -427,8 +365,8 @@ export class UnifiedBannerView extends FloatingBannerView<
     /** The draft mode menu. */
     #modeMenu: DraftModeMenu;
 
-    /** The publish button. */
-    #publishButton: PublishButtonView;
+    /** The publish button view. */
+    #publishButton: BannerPublishButtonView;
 
     /** The review request editor view. */
     #reviewRequestEditorView: ReviewRequestEditorView;
@@ -534,7 +472,7 @@ export class UnifiedBannerView extends FloatingBannerView<
         });
         this.#modeMenu.renderInto(this.#$modeSelector);
 
-        this.#publishButton = new PublishButtonView({
+        this.#publishButton = new BannerPublishButtonView({
             model: model,
         });
         this.#publishButton.$el.prependTo(this.#$draftActions);
@@ -971,12 +909,34 @@ export class UnifiedBannerView extends FloatingBannerView<
             const reviewReplyDrafts = model.get('reviewReplyDrafts');
             const reply = reviewReplyDrafts[draftMode.singleReviewReply];
 
-            await showConfirmDialog({
-                isDangerous: true,
-                title: _`Are you sure you want to discard this reply?`,
+                await reply.destroy();
+            } else {
+                console.error('Discard reached with no active drafts.');
+            }
+        } catch (err) {
+            alert(err.xhr.errorText);
+        }
+    }
 
-                body: _`
-                    If you discard this reply, all unpublished comments
+    /**
+     * Ask the user to confirm a discard operation.
+     *
+     * Args:
+     *     draftMode (DraftMode):
+     *         The current draft mode being discarded.
+     *
+     * Returns:
+     *     Promise:
+     *     A promise which resolves to either ``true`` (proceed) or ``false``
+     *     (cancel).
+     */
+    private _confirmDiscard(
+        draftMode: DraftMode,
+    ): Promise<boolean> {
+        return new Promise(resolve => {
+            const text = draftMode.hasReview
+                ? _`
+                    If you discard this review, all unpublished comments
                     will be deleted.
                 `,
                 confirmButtonText: _`Discard the reply`,
