From e7d646d29e2e04c2acf09ae2029423201a09d1f1 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 21 May 2026 22:50:26 +1200 Subject: [PATCH 1/4] Show success notice when a custom post is saved to the site Post a Notice toast on every HTTP write to the site from the Custom Post editor (save draft, update, publish) and from the standalone settings sheet reached from the Custom Posts list. The notice's title reflects the action: "Draft saved" only on the first save of a brand-new post, "Post updated" for any save of an existing post, and "Post published" via the publishing sheet. The post title is the notice's subtitle (omitted when empty). Localized strings live in a shared CustomPostNotice enum. --- .../CustomPostListViewModel.swift | 26 +++++++++++ .../CustomPostEditorViewController.swift | 44 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift index 8e27233c641d..3977c9b166b0 100644 --- a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift @@ -417,6 +417,11 @@ final class CustomPostListViewModel: ObservableObject { wpService: service ) let viewModel = CustomPostSettingsViewModel(editorService: editorService, blog: blog, isStandalone: true) + viewModel.onEditorPostSaved = { [editorService] in + let postTitle = editorService.post?.title?.raw + let subtitle = (postTitle?.isEmpty == false) ? postTitle : nil + Notice(title: Strings.CustomPostNotice.postUpdated, message: subtitle, feedbackType: .success).post() + } let settingsVC = PostSettingsViewController(viewModel: viewModel) let nav = UINavigationController(rootViewController: settingsVC) vc.present(nav, animated: true) @@ -941,6 +946,27 @@ private enum Strings { value: "Page successfully updated", comment: "Notice shown after successfully changing a page's role (homepage, posts page, or regular page)" ) + + /// Localized titles for the success notices emitted when a custom post is + /// persisted to the server. Shared between the editor and the standalone + /// settings sheet (from the Custom Posts list). + enum CustomPostNotice { + static let draftSaved = NSLocalizedString( + "customPost.notice.draftSaved", + value: "Draft saved", + comment: "Success notice shown after a new draft custom post is created on the server." + ) + static let postUpdated = NSLocalizedString( + "customPost.notice.postUpdated", + value: "Post updated", + comment: "Success notice shown after an existing custom post is updated on the server." + ) + static let postPublished = NSLocalizedString( + "customPost.notice.postPublished", + value: "Post published", + comment: "Success notice shown after a custom post is published on the server." + ) + } } /// Represents the WordPress "Your homepage displays" setting. diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift index 86c2b9e83b8f..9e271a7d2cf8 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift @@ -231,6 +231,10 @@ private extension CustomPostEditorViewController { guard let self else { return } switch result { case .published: + let postTitle = editorService.post?.title?.raw + let subtitle = (postTitle?.isEmpty == false) ? postTitle : nil + Notice(title: Strings.CustomPostNotice.postPublished, message: subtitle, feedbackType: .success) + .post() completion() case .cancelled: break @@ -251,6 +255,12 @@ private extension CustomPostEditorViewController { func save(publish: Bool) async { SVProgressHUD.show() + // Capture whether this is a brand-new post (not yet on the server) so the + // notice can distinguish "Draft saved" (just created) from "Post updated" + // (edited an existing post). Any save of an already-existing post — draft + // or published — reads as an update from the user's perspective. + let isNewPost = post == nil + do { let data = try await editorViewController.getTitleAndContent() try await editorService.save( @@ -260,6 +270,17 @@ private extension CustomPostEditorViewController { dismissHUDWithSuccess() + let title: String + if publish { + title = Strings.CustomPostNotice.postPublished + } else if isNewPost { + title = Strings.CustomPostNotice.draftSaved + } else { + title = Strings.CustomPostNotice.postUpdated + } + let subtitle = data.title.isEmpty ? nil : data.title + Notice(title: title, message: subtitle, feedbackType: .success).post() + if publish { completion() } @@ -280,3 +301,26 @@ extension CustomPostEditorViewController: CustomPostEditorServiceDelegate { return EditorContent(title: result.title, content: result.content) } } + +private enum Strings { + /// Localized titles for the success notices emitted when a custom post is + /// persisted to the server. Shared between the editor and the standalone + /// settings sheet (from the Custom Posts list). + enum CustomPostNotice { + static let draftSaved = NSLocalizedString( + "customPost.notice.draftSaved", + value: "Draft saved", + comment: "Success notice shown after a new draft custom post is created on the server." + ) + static let postUpdated = NSLocalizedString( + "customPost.notice.postUpdated", + value: "Post updated", + comment: "Success notice shown after an existing custom post is updated on the server." + ) + static let postPublished = NSLocalizedString( + "customPost.notice.postPublished", + value: "Post published", + comment: "Success notice shown after a custom post is published on the server." + ) + } +} From 693ccb91635d9180aaa0f1014c38d9a166443369 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 16 Jun 2026 12:15:47 +1200 Subject: [PATCH 2/4] Reformat PublishPostViewController with swift-format --- .../PublishPostViewController.swift | 89 +++++++++++++++---- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift index 8eb452320142..de9503a55004 100644 --- a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift @@ -221,23 +221,82 @@ private typealias Strings = PrepublishingSheetStrings enum PrepublishingSheetStrings { static let title = NSLocalizedString("prepublishing.title", value: "Publishing", comment: "Navigation title") - static let publishingTo = NSLocalizedString("prepublishing.publishingTo", value: "Publishing to", comment: "Label in the header in the pre-publishing sheet") - static let publish = NSLocalizedString("prepublishing.publish", value: "Publish", comment: "Primary button label in the pre-publishing sheet") - static let schedule = NSLocalizedString("prepublishing.schedule", value: "Schedule", comment: "Primary button label in the pre-publishing shee") - static let publishDate = NSLocalizedString("prepublishing.publishDate", value: "Publish Date", comment: "Label for a cell in the pre-publishing sheet") - static let visibility = NSLocalizedString("prepublishing.visibility", value: "Visibility", comment: "Label for a cell in the pre-publishing sheet") - static let categories = NSLocalizedString("prepublishing.categories", value: "Categories", comment: "Label for a cell in the pre-publishing sheet") - static let tags = NSLocalizedString("prepublishing.tags", value: "Tags", comment: "Label for a cell in the pre-publishing sheet") - static let jetpackSocial = NSLocalizedString("prepublishing.jetpackSocial", value: "Jetpack Social", comment: "Label for a cell in the pre-publishing sheet") - static let immediately = NSLocalizedString("prepublishing.publishDateImmediately", value: "Immediately", comment: "Placeholder value for a publishing date in the prepublishing sheet when the date is not selected") - static let uploadingMedia = NSLocalizedString("prepublishing.uploadingMedia", value: "Uploading media", comment: "Title for a publish button state in the pre-publishing sheet") - private static let uploadMediaOneItemRemaining = NSLocalizedString("prepublishing.uploadMediaOneItemRemaining", value: "%@ item remaining", comment: "Details label for a publish button state in the pre-publishing sheet") - private static let uploadMediaManyItemsRemaining = NSLocalizedString("prepublishing.uploadMediaManyItemsRemaining", value: "%@ items remaining", comment: "Details label for a publish button state in the pre-publishing sheet") + static let publishingTo = NSLocalizedString( + "prepublishing.publishingTo", + value: "Publishing to", + comment: "Label in the header in the pre-publishing sheet" + ) + static let publish = NSLocalizedString( + "prepublishing.publish", + value: "Publish", + comment: "Primary button label in the pre-publishing sheet" + ) + static let schedule = NSLocalizedString( + "prepublishing.schedule", + value: "Schedule", + comment: "Primary button label in the pre-publishing shee" + ) + static let publishDate = NSLocalizedString( + "prepublishing.publishDate", + value: "Publish Date", + comment: "Label for a cell in the pre-publishing sheet" + ) + static let visibility = NSLocalizedString( + "prepublishing.visibility", + value: "Visibility", + comment: "Label for a cell in the pre-publishing sheet" + ) + static let categories = NSLocalizedString( + "prepublishing.categories", + value: "Categories", + comment: "Label for a cell in the pre-publishing sheet" + ) + static let tags = NSLocalizedString( + "prepublishing.tags", + value: "Tags", + comment: "Label for a cell in the pre-publishing sheet" + ) + static let jetpackSocial = NSLocalizedString( + "prepublishing.jetpackSocial", + value: "Jetpack Social", + comment: "Label for a cell in the pre-publishing sheet" + ) + static let immediately = NSLocalizedString( + "prepublishing.publishDateImmediately", + value: "Immediately", + comment: "Placeholder value for a publishing date in the prepublishing sheet when the date is not selected" + ) + static let uploadingMedia = NSLocalizedString( + "prepublishing.uploadingMedia", + value: "Uploading media", + comment: "Title for a publish button state in the pre-publishing sheet" + ) + private static let uploadMediaOneItemRemaining = NSLocalizedString( + "prepublishing.uploadMediaOneItemRemaining", + value: "%@ item remaining", + comment: "Details label for a publish button state in the pre-publishing sheet" + ) + private static let uploadMediaManyItemsRemaining = NSLocalizedString( + "prepublishing.uploadMediaManyItemsRemaining", + value: "%@ items remaining", + comment: "Details label for a publish button state in the pre-publishing sheet" + ) static func uploadMediaRemaining(count: Int) -> String { - String(format: count == 1 ? Strings.uploadMediaOneItemRemaining : Strings.uploadMediaManyItemsRemaining, count.description) + String( + format: count == 1 ? Strings.uploadMediaOneItemRemaining : Strings.uploadMediaManyItemsRemaining, + count.description + ) } - static let mediaUploadFailedTitle = NSLocalizedString("prepublishing.mediaUploadFailedTitle", value: "Failed to upload media", comment: "Title for a publish button state in the pre-publishing sheet") - static let mediaUploadFailedDetailsMultipleFailures = NSLocalizedString("prepublishing.mediaUploadFailedDetails", value: "%@ items failed to upload", comment: "Details for a publish button state in the pre-publishing sheet; count as a parameter") + static let mediaUploadFailedTitle = NSLocalizedString( + "prepublishing.mediaUploadFailedTitle", + value: "Failed to upload media", + comment: "Title for a publish button state in the pre-publishing sheet" + ) + static let mediaUploadFailedDetailsMultipleFailures = NSLocalizedString( + "prepublishing.mediaUploadFailedDetails", + value: "%@ items failed to upload", + comment: "Details for a publish button state in the pre-publishing sheet; count as a parameter" + ) static let discardChangesTitle = NSLocalizedString( "prepublishing.discardChanges.title", From 01ff98c2fc21a3157848190c110df5dec33c70f6 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 16 Jun 2026 12:26:07 +1200 Subject: [PATCH 3/4] Show the published notice for both custom post publishing flows Move the success notice into PublishPostViewController.show(editorService:blog:from:completion:) so both the custom post editor and the Custom Posts list show it after publishing through the sheet. Consolidate the duplicated notice strings into a shared CustomPostNoticeStrings type. --- .../CustomPostListViewModel.swift | 23 +---------- .../CustomPostNoticeStrings.swift | 22 ++++++++++ .../CustomPostEditorViewController.swift | 41 +++---------------- .../PublishPostViewController.swift | 9 +++- 4 files changed, 36 insertions(+), 59 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostNoticeStrings.swift diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift index 3977c9b166b0..817f3b27ab29 100644 --- a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListViewModel.swift @@ -420,7 +420,7 @@ final class CustomPostListViewModel: ObservableObject { viewModel.onEditorPostSaved = { [editorService] in let postTitle = editorService.post?.title?.raw let subtitle = (postTitle?.isEmpty == false) ? postTitle : nil - Notice(title: Strings.CustomPostNotice.postUpdated, message: subtitle, feedbackType: .success).post() + Notice(title: CustomPostNoticeStrings.postUpdated, message: subtitle, feedbackType: .success).post() } let settingsVC = PostSettingsViewController(viewModel: viewModel) let nav = UINavigationController(rootViewController: settingsVC) @@ -946,27 +946,6 @@ private enum Strings { value: "Page successfully updated", comment: "Notice shown after successfully changing a page's role (homepage, posts page, or regular page)" ) - - /// Localized titles for the success notices emitted when a custom post is - /// persisted to the server. Shared between the editor and the standalone - /// settings sheet (from the Custom Posts list). - enum CustomPostNotice { - static let draftSaved = NSLocalizedString( - "customPost.notice.draftSaved", - value: "Draft saved", - comment: "Success notice shown after a new draft custom post is created on the server." - ) - static let postUpdated = NSLocalizedString( - "customPost.notice.postUpdated", - value: "Post updated", - comment: "Success notice shown after an existing custom post is updated on the server." - ) - static let postPublished = NSLocalizedString( - "customPost.notice.postPublished", - value: "Post published", - comment: "Success notice shown after a custom post is published on the server." - ) - } } /// Represents the WordPress "Your homepage displays" setting. diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostNoticeStrings.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostNoticeStrings.swift new file mode 100644 index 000000000000..ddec38725b20 --- /dev/null +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostNoticeStrings.swift @@ -0,0 +1,22 @@ +import Foundation + +/// Localized titles for the success notices emitted when a custom post is +/// persisted to the server. Shared between the editor, the publishing sheet, +/// and the Custom Posts list. +enum CustomPostNoticeStrings { + static let draftSaved = NSLocalizedString( + "customPost.notice.draftSaved", + value: "Draft saved", + comment: "Success notice shown after a new draft custom post is created on the server." + ) + static let postUpdated = NSLocalizedString( + "customPost.notice.postUpdated", + value: "Post updated", + comment: "Success notice shown after an existing custom post is updated on the server." + ) + static let postPublished = NSLocalizedString( + "customPost.notice.postPublished", + value: "Post published", + comment: "Success notice shown after a custom post is published on the server." + ) +} diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift index 9e271a7d2cf8..f741ef1b93df 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift @@ -228,16 +228,8 @@ private extension CustomPostEditorViewController { blog: blog, from: self, completion: { [weak self] result in - guard let self else { return } - switch result { - case .published: - let postTitle = editorService.post?.title?.raw - let subtitle = (postTitle?.isEmpty == false) ? postTitle : nil - Notice(title: Strings.CustomPostNotice.postPublished, message: subtitle, feedbackType: .success) - .post() - completion() - case .cancelled: - break + if case .published = result { + self?.completion() } } ) @@ -272,11 +264,11 @@ private extension CustomPostEditorViewController { let title: String if publish { - title = Strings.CustomPostNotice.postPublished + title = CustomPostNoticeStrings.postPublished } else if isNewPost { - title = Strings.CustomPostNotice.draftSaved + title = CustomPostNoticeStrings.draftSaved } else { - title = Strings.CustomPostNotice.postUpdated + title = CustomPostNoticeStrings.postUpdated } let subtitle = data.title.isEmpty ? nil : data.title Notice(title: title, message: subtitle, feedbackType: .success).post() @@ -301,26 +293,3 @@ extension CustomPostEditorViewController: CustomPostEditorServiceDelegate { return EditorContent(title: result.title, content: result.content) } } - -private enum Strings { - /// Localized titles for the success notices emitted when a custom post is - /// persisted to the server. Shared between the editor and the standalone - /// settings sheet (from the Custom Posts list). - enum CustomPostNotice { - static let draftSaved = NSLocalizedString( - "customPost.notice.draftSaved", - value: "Draft saved", - comment: "Success notice shown after a new draft custom post is created on the server." - ) - static let postUpdated = NSLocalizedString( - "customPost.notice.postUpdated", - value: "Post updated", - comment: "Success notice shown after an existing custom post is updated on the server." - ) - static let postPublished = NSLocalizedString( - "customPost.notice.postPublished", - value: "Post published", - comment: "Success notice shown after a custom post is published on the server." - ) - } -} diff --git a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift index de9503a55004..92e4a35b415a 100644 --- a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift @@ -81,7 +81,14 @@ final class PublishPostViewController: editorService: editorService, blog: blog ) - publishVC.onCompletion = completion + publishVC.onCompletion = { result in + if case .published = result { + let postTitle = editorService.post?.title?.raw + let subtitle = (postTitle?.isEmpty == false) ? postTitle : nil + Notice(title: CustomPostNoticeStrings.postPublished, message: subtitle, feedbackType: .success).post() + } + completion(result) + } let navigationVC = UINavigationController(rootViewController: publishVC) navigationVC.sheetPresentationController?.detents = [ .custom(identifier: .medium, resolver: { _ in 526 }), From a198936ffebcdb5c96ef2858b67f4907f31d193a Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 16 Jun 2026 12:32:48 +1200 Subject: [PATCH 4/4] Remove the unused publish argument from CustomPostEditorViewController.save The method is only ever called to persist a draft or update, so drop the always-false publish parameter and the dead publish-only branches. --- .../CustomPostEditorViewController.swift | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift index f741ef1b93df..0e5b1d209c9c 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift @@ -143,7 +143,7 @@ private extension CustomPostEditorViewController { style: .default, handler: { [weak self] _ in Task { - await self?.save(publish: false) + await self?.save() } } ) @@ -170,7 +170,7 @@ private extension CustomPostEditorViewController { attributes: enabled ? [] : [.disabled] ) { [weak self] _ in Task { - await self?.save(publish: false) + await self?.save() } } resolve([saveDraft]) @@ -197,7 +197,7 @@ private extension CustomPostEditorViewController { } else { return UIAction(title: PostEditorStrings.update) { [weak self] _ in Task { - await self?.save(publish: false) + await self?.save() } } } @@ -244,38 +244,27 @@ private extension CustomPostEditorViewController { private extension CustomPostEditorViewController { - func save(publish: Bool) async { + func save() async { SVProgressHUD.show() // Capture whether this is a brand-new post (not yet on the server) so the // notice can distinguish "Draft saved" (just created) from "Post updated" - // (edited an existing post). Any save of an already-existing post — draft - // or published — reads as an update from the user's perspective. + // (edited an existing post). Any save of an already-existing post (draft + // or published) reads as an update from the user's perspective. let isNewPost = post == nil do { let data = try await editorViewController.getTitleAndContent() try await editorService.save( content: EditorContent(title: data.title, content: data.content), - publish: publish + publish: false ) dismissHUDWithSuccess() - let title: String - if publish { - title = CustomPostNoticeStrings.postPublished - } else if isNewPost { - title = CustomPostNoticeStrings.draftSaved - } else { - title = CustomPostNoticeStrings.postUpdated - } + let title = isNewPost ? CustomPostNoticeStrings.draftSaved : CustomPostNoticeStrings.postUpdated let subtitle = data.title.isEmpty ? nil : data.title Notice(title: title, message: subtitle, feedbackType: .success).post() - - if publish { - completion() - } } catch { SVProgressHUD.showError(withStatus: error.localizedDescription) }