diff --git a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift index 31b92c55cd21..b5f9bb119bf7 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift @@ -12,8 +12,8 @@ struct CustomPostEditorServiceTests { // MARK: - New Post Tests - @Test("applyLocally updates PostCreateParams for new posts") - func applyLocallyUpdatesCreateParamsForNewPost() throws { + @Test("applyLocally updates settings for new posts") + func applyLocallyUpdatesSettingsForNewPost() throws { // Given let context = ContextManager.forTesting().mainContext let blog = BlogBuilder(context).build() @@ -151,29 +151,20 @@ struct CustomPostEditorServiceTests { #expect(service.hasSettingsChanges == true) } - // MARK: - initialParams Tests + // MARK: - initialSettings Tests - @Test("init with initialParams uses provided params instead of defaults") - func initWithInitialParamsUsesProvidedParams() throws { + @Test("init with initialSettings uses provided settings instead of defaults") + func initWithInitialSettingsUsesProvidedSettings() throws { let context = ContextManager.forTesting().mainContext let blog = BlogBuilder(context).build() - var params = PostCreateParams(meta: nil) - params.status = .draft - params.title = "Copied Title" - params.content = "Copied Content" - params.categories = [TermId(5)] + var settings = PostSettings() + settings.categoryIDs = [5] - let service = try makeService(blog: blog, post: nil, initialParams: params) + let service = try makeService(blog: blog, post: nil, initialSettings: settings) - // PostSettings does not store title/content (those are managed by the - // Gutenberg editor), so verify via categoryIDs which PostSettings does map. #expect(service.settings.categoryIDs == [5]) - // Also verify via the test-only inspection method that the full params - // are stored, including title and content. - let storedParams = service.inspectCreateParams() - #expect(storedParams?.title == "Copied Title") - #expect(storedParams?.content == "Copied Content") + #expect(service.inspectNewPostSettings()?.categoryIDs == [5]) } } @@ -182,7 +173,7 @@ struct CustomPostEditorServiceTests { private func makeService( blog: Blog, post: AnyPostWithEditContext?, - initialParams: PostCreateParams? = nil + initialSettings: PostSettings? = nil ) throws -> CustomPostEditorService { let api = try WordPressAPI( urlSession: .shared, @@ -204,7 +195,7 @@ private func makeService( details: makePostTypeDetails(), client: client, wpService: wpService, - initialParams: initialParams + initialSettings: initialSettings ) } diff --git a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift index aee5f7a1b4a7..69311111d009 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift @@ -869,7 +869,7 @@ struct PostSettingsTests { #expect(term1 != term3, "Same id 0, different name — different unresolved term") } - // MARK: - PostCreateParams Custom Terms Tests + // MARK: - makeCreateParameters Tests @Test("makeCreateParameters includes custom taxonomy terms in additionalFields") func testMakeCreateParametersIncludesCustomTerms() { @@ -877,9 +877,7 @@ struct PostSettingsTests { let taxonomies = [ SiteTaxonomy.makeTaxonomy(slug: "genre", restBase: "genre") ] - let existing = PostCreateParams(meta: nil) - - var settings = PostSettings(from: existing, taxonomies: taxonomies) + var settings = PostSettings() settings.otherTerms = [ "genre": [ PostSettings.Term(id: 10, name: "fiction"), @@ -888,123 +886,47 @@ struct PostSettingsTests { ] // When - let params = settings.makeCreateParameters(from: existing, taxonomies: taxonomies) + let params = settings.makeCreateParameters(taxonomies: taxonomies) // Then let termIds = params.additionalFields?.termIdsForKey(key: "genre") ?? [] #expect(Set(termIds) == Set([TermId(10), TermId(20)])) } - @Test("init(from: PostCreateParams) populates otherTerms from additionalFields") - func testInitFromCreateParamsReadsCustomTerms() { - // Given - let taxonomies = [ - SiteTaxonomy.makeTaxonomy(slug: "genre", restBase: "genre") - ] - let termMap: [String: [TermId]] = ["genre": [TermId(10), TermId(20)]] - let params = PostCreateParams( - meta: nil, - additionalFields: WpAdditionalFields.fromTermIdMap(map: termMap) - ) - - // When - let settings = PostSettings(from: params, taxonomies: taxonomies) - - // Then - #expect( - settings.otherTerms["genre"] == [ - PostSettings.Term(id: 10, name: ""), - PostSettings.Term(id: 20, name: "") - ] - ) - } - - @Test("init(from: PostCreateParams) populates parentPageID") - func testInitFromCreateParamsReadsParent() { - // Given - var params = PostCreateParams(meta: nil) - params.parent = PostId(42) - - // When - let settings = PostSettings(from: params) - - // Then - #expect(settings.parentPageID == 42) - } - - @Test("PostCreateParams parent survives round-trip through PostSettings") - func testParentRoundTripThroughPostSettings() { - // Given - var params = PostCreateParams(meta: nil) - params.parent = PostId(42) - - // When - let settings = PostSettings(from: params) - let outputParams = settings.makeCreateParameters(from: .init(meta: nil), taxonomies: []) + // MARK: - defaults(from: Blog) Tests - // Then - #expect(outputParams.parent == PostId(42)) - } - - @Test("init(from: PostCreateParams) defaults status to draft when nil") - func testInitFromCreateParamsDefaultsStatusToDraft() { - // Given - let params = PostCreateParams(meta: nil) - - // When - let settings = PostSettings(from: params) - - // Then - #expect(settings.status == .draft) - #expect(settings.publishDate == nil) - } - - @Test("init(from: PostCreateParams) reads scheduled status and date") - func testInitFromCreateParamsReadsScheduledStatus() { - // Given - var params = PostCreateParams(meta: nil) - params.status = .future - let scheduledDate = Date(timeIntervalSince1970: 2_000_000_000) - params.dateGmt = scheduledDate + @Test("defaults inherits site discussion defaults (closed)") + func testDefaultsInheritsClosedDiscussion() { + let context = ContextManager.forTesting().mainContext + let blog = BlogBuilder(context).with(siteName: "Test").build() + blog.settings?.commentsAllowed = false + blog.settings?.pingbackInboundEnabled = false - // When - let settings = PostSettings(from: params) + let settings = PostSettings.defaults(from: blog) + let params = settings.makeCreateParameters(taxonomies: []) - // Then - #expect(settings.status == .scheduled) - #expect(settings.publishDate == scheduledDate) + #expect(!settings.allowComments) + #expect(!settings.allowPings) + #expect(params.commentStatus == .closed) + #expect(params.pingStatus == .closed) } - @Test("init(from: PostCreateParams) reads pending status with nil date") - func testInitFromCreateParamsReadsPendingStatus() { - // Given - var params = PostCreateParams(meta: nil) - params.status = .pending + @Test("defaults inherits site discussion defaults (open)") + func testDefaultsInheritsOpenDiscussion() { + let context = ContextManager.forTesting().mainContext + let blog = BlogBuilder(context).with(siteName: "Test").build() + blog.settings?.commentsAllowed = true + blog.settings?.pingbackInboundEnabled = true - // When - let settings = PostSettings(from: params) + let settings = PostSettings.defaults(from: blog) + let params = settings.makeCreateParameters(taxonomies: []) - // Then - #expect(settings.status == .pending) - #expect(settings.publishDate == nil) + #expect(settings.allowComments) + #expect(settings.allowPings) + #expect(params.commentStatus == .open) + #expect(params.pingStatus == .open) } - @Test("PostCreateParams status and date survive round-trip through PostSettings") - func testStatusAndDateRoundTripThroughPostSettings() { - // Given - var params = PostCreateParams(meta: nil) - params.status = .future - let scheduledDate = Date(timeIntervalSince1970: 2_000_000_000) - params.dateGmt = scheduledDate - - // When - let settings = PostSettings(from: params) - let outputParams = settings.makeCreateParameters(from: .init(meta: nil), taxonomies: []) - - // Then - #expect(outputParams.status == .future) - #expect(outputParams.dateGmt == scheduledDate) - } } // MARK: - Test Helpers diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditor.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditor.swift index 157b681e1e63..1f52e9d2fc26 100644 --- a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditor.swift +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditor.swift @@ -11,7 +11,8 @@ struct CustomPostEditor: View { let post: AnyPostWithEditContext? let details: PostTypeDetailsWithEditContext let blog: Blog - let initialParams: PostCreateParams? + let initialSettings: PostSettings? + let initialContent: EditorContent? var body: some View { ViewControllerWrapper( @@ -20,7 +21,8 @@ struct CustomPostEditor: View { post: post, details: details, blog: blog, - initialParams: initialParams + initialSettings: initialSettings, + initialContent: initialContent ) .ignoresSafeArea() } @@ -32,7 +34,8 @@ private struct ViewControllerWrapper: UIViewControllerRepresentable { let post: AnyPostWithEditContext? let details: PostTypeDetailsWithEditContext let blog: Blog - let initialParams: PostCreateParams? + let initialSettings: PostSettings? + let initialContent: EditorContent? @Environment(\.dismiss) var dismiss: DismissAction @@ -44,7 +47,8 @@ private struct ViewControllerWrapper: UIViewControllerRepresentable { client: client, post: post, details: details, - initialParams: initialParams + initialSettings: initialSettings, + initialContent: initialContent ) { dismiss() } diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditorService.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditorService.swift index 7aff6a9ee07a..b080aaf66df2 100644 --- a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditorService.swift +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostEditorService.swift @@ -18,7 +18,7 @@ protocol CustomPostEditorServiceDelegate: AnyObject { class CustomPostEditorService { private enum State { - case newPost(PostCreateParams) + case newPost(PostSettings) /// - Parameter pending: Settings applied locally from Post Settings /// but not yet saved to the server. case existingPost(AnyPostWithEditContext, pending: PostSettings? = nil) @@ -44,8 +44,8 @@ class CustomPostEditorService { var settings: PostSettings { switch state { - case let .newPost(params): - return PostSettings(from: params, taxonomies: taxonomies) + case let .newPost(settings): + return settings case let .existingPost(post, pending): return pending ?? PostSettings(from: post, taxonomies: taxonomies) } @@ -55,9 +55,8 @@ class CustomPostEditorService { /// Term resolution is deferred to the actual save. func applyLocally(settings: PostSettings) { switch state { - case .newPost(let existing): - let params = settings.makeCreateParameters(from: existing, taxonomies: taxonomies) - state = .newPost(params) + case .newPost: + state = .newPost(settings) case .existingPost(let post, _): state = .existingPost(post, pending: settings) } @@ -69,15 +68,8 @@ class CustomPostEditorService { details: PostTypeDetailsWithEditContext, client: WordPressClient, wpService: WpService, - initialParams: PostCreateParams? = nil + initialSettings: PostSettings? = nil ) { - if let post { - self.state = .existingPost(post) - } else if let initialParams { - self.state = .newPost(initialParams) - } else { - self.state = .newPost(PostCreateParams.defaultParams(from: blog)) - } self.details = details self.client = client self.wpService = wpService @@ -85,15 +77,18 @@ class CustomPostEditorService { let capabilities = PostSettingsCapabilities(from: details) // At the moment, category & tags are separated from custom taxonomies. We can unify them as taxonomies later, // by which point we won't need this filter logic. - self.taxonomies = (try? blog.taxonomies - .filter { capabilities.customTaxonomySlugs.contains($0.slug) } - .sorted(using: KeyPathComparator(\.name))) ?? [] + self.taxonomies = + (try? blog.taxonomies + .filter { capabilities.customTaxonomySlugs.contains($0.slug) } + .sorted(using: KeyPathComparator(\.name))) ?? [] - switch self.state { - case let .newPost(params): - self.initialSettings = PostSettings(from: params, taxonomies: taxonomies) - case let .existingPost(post, pending): - self.initialSettings = pending ?? PostSettings(from: post, taxonomies: taxonomies) + if let post { + self.state = .existingPost(post) + self.initialSettings = PostSettings(from: post, taxonomies: self.taxonomies) + } else { + let settings = initialSettings ?? .defaults(from: blog) + self.state = .newPost(settings) + self.initialSettings = settings } } @@ -103,9 +98,10 @@ class CustomPostEditorService { TermResolutionService(taxonomyService: AnyTermService(client: client, endpoint: endpoint)) } - /// Saves or publishes from post settings. Handles term resolution, optional - /// publish status override with editor content injection, and create-or-update branching. - func save(settings: PostSettings, publish: Bool) async throws { + /// Resolves any unresolved (`id == 0`) tags and custom-taxonomy terms by + /// looking them up on the server or creating them. Required before turning + /// settings into create/update parameters, which filter to `id > 0`. + private func resolveTerms(in settings: PostSettings) async throws -> PostSettings { var settings = settings settings.tags = try await makeTermResolutionService(endpoint: .tags).resolveIDs(for: settings.tags) for taxonomy in taxonomies { @@ -113,15 +109,21 @@ class CustomPostEditorService { settings.otherTerms[taxonomy.slug] = try await makeTermResolutionService(endpoint: taxonomy.endpoint) .resolveIDs(for: slugTerms) } + return settings + } + + /// Saves or publishes from post settings. Handles term resolution, optional + /// publish status override with editor content injection, and create-or-update branching. + func save(settings: PostSettings, publish: Bool) async throws { + let settings = try await resolveTerms(in: settings) switch (state, publish) { - case (.newPost(let existing), false): + case (.newPost, false): // Store settings locally so the editor can create the post later. - let params = settings.makeCreateParameters(from: existing, taxonomies: taxonomies) - state = .newPost(params) + state = .newPost(settings) - case (.newPost(let existing), true): - var params = settings.makeCreateParameters(from: existing, taxonomies: taxonomies) + case (.newPost, true): + var params = settings.makeCreateParameters(taxonomies: taxonomies) // Update content if let delegate { @@ -160,8 +162,11 @@ class CustomPostEditorService { let hasTitle = details.supports.map[.title] == .bool(true) switch state { - case .newPost(let existing): - var params = existing + case .newPost(let settings): + // Resolve any new tags / custom terms (id == 0) before they get + // filtered out by `makeCreateParameters`. + let resolved = try await resolveTerms(in: settings) + var params = resolved.makeCreateParameters(taxonomies: taxonomies) params.status = publish ? .publish : .draft params.title = hasTitle ? content.title : nil params.content = content.content @@ -169,14 +174,9 @@ class CustomPostEditorService { case .existingPost(let post, let pending): var params: PostUpdateParams - if var pending { - pending.tags = try await makeTermResolutionService(endpoint: .tags).resolveIDs(for: pending.tags) - for taxonomy in taxonomies { - guard let slugTerms = pending.otherTerms[taxonomy.slug] else { continue } - pending.otherTerms[taxonomy.slug] = try await makeTermResolutionService(endpoint: taxonomy.endpoint) - .resolveIDs(for: slugTerms) - } - params = pending.makeUpdateParameters(from: post, taxonomies: taxonomies) + if let pending { + let resolved = try await resolveTerms(in: pending) + params = resolved.makeUpdateParameters(from: post, taxonomies: taxonomies) } else { params = PostUpdateParams(meta: nil) } @@ -195,7 +195,8 @@ class CustomPostEditorService { guard try await !hasBeenModified(post: post) else { throw PostUpdateError.conflicts } let endpoint = details.toPostEndpointType() - let updatedPost = try await wpService.posts().updatePost(endpointType: endpoint, postId: post.id, params: params) + let updatedPost = try await wpService.posts() + .updatePost(endpointType: endpoint, postId: post.id, params: params) state = .existingPost(updatedPost) initialSettings = settings @@ -238,9 +239,9 @@ extension CustomPostEditorService { } // Used in unit tests. - func inspectCreateParams() -> PostCreateParams? { - if case .newPost(let params) = state { - return params + func inspectNewPostSettings() -> PostSettings? { + if case .newPost(let settings) = state { + return settings } return nil } @@ -257,24 +258,3 @@ enum PostUpdateError: LocalizedError { ) } } - -extension PostCreateParams { - /// Creates default parameters for a new post, equivalent to `Blog.createPost()`. - static func defaultParams(from blog: Blog) -> PostCreateParams { - var params = PostCreateParams(meta: nil) - params.status = .draft - - if let categoryID = blog.settings?.defaultCategoryID, - categoryID != PostCategory.uncategorized { - params.categories = [TermId(categoryID.int64Value)] - } - - params.format = blog.settings?.defaultPostFormat.flatMap { PostFormat.from(slug: $0) } - - if let userID = blog.userID { - params.author = UserId(userID.int64Value) - } - - return params - } -} diff --git a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostTabView.swift b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostTabView.swift index 550610bd5707..d776e07a0751 100644 --- a/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostTabView.swift +++ b/WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostTabView.swift @@ -55,48 +55,58 @@ struct CustomPostTabView: View { self.blog = blog self.presentingViewController = presentingViewController - _allViewModel = State(initialValue: CustomPostListViewModel( - client: client, - service: service, - details: details, - filter: CustomPostListFilter(tab: .all), - blog: blog, - showsHierarchyIfApplicable: true, - presentingViewController: presentingViewController - )) - _publishedViewModel = State(initialValue: CustomPostListViewModel( - client: client, - service: service, - details: details, - filter: CustomPostListFilter(tab: .published), - blog: blog, - showsHierarchyIfApplicable: true, - presentingViewController: presentingViewController - )) - _draftsViewModel = State(initialValue: CustomPostListViewModel( - client: client, - service: service, - details: details, - filter: CustomPostListFilter(tab: .drafts), - blog: blog, - presentingViewController: presentingViewController - )) - _scheduledViewModel = State(initialValue: CustomPostListViewModel( - client: client, - service: service, - details: details, - filter: CustomPostListFilter(tab: .scheduled), - blog: blog, - presentingViewController: presentingViewController - )) - _trashViewModel = State(initialValue: CustomPostListViewModel( - client: client, - service: service, - details: details, - filter: CustomPostListFilter(tab: .trash), - blog: blog, - presentingViewController: presentingViewController - )) + _allViewModel = State( + initialValue: CustomPostListViewModel( + client: client, + service: service, + details: details, + filter: CustomPostListFilter(tab: .all), + blog: blog, + showsHierarchyIfApplicable: true, + presentingViewController: presentingViewController + ) + ) + _publishedViewModel = State( + initialValue: CustomPostListViewModel( + client: client, + service: service, + details: details, + filter: CustomPostListFilter(tab: .published), + blog: blog, + showsHierarchyIfApplicable: true, + presentingViewController: presentingViewController + ) + ) + _draftsViewModel = State( + initialValue: CustomPostListViewModel( + client: client, + service: service, + details: details, + filter: CustomPostListFilter(tab: .drafts), + blog: blog, + presentingViewController: presentingViewController + ) + ) + _scheduledViewModel = State( + initialValue: CustomPostListViewModel( + client: client, + service: service, + details: details, + filter: CustomPostListFilter(tab: .scheduled), + blog: blog, + presentingViewController: presentingViewController + ) + ) + _trashViewModel = State( + initialValue: CustomPostListViewModel( + client: client, + service: service, + details: details, + filter: CustomPostListFilter(tab: .trash), + blog: blog, + presentingViewController: presentingViewController + ) + ) _authorFilter = .authorFilter(for: TaggedManagedObjectID(blog)) self.applyAuthorFilter() @@ -175,7 +185,8 @@ struct CustomPostTabView: View { post: presentation.post, details: details, blog: blog, - initialParams: presentation.initialParams + initialSettings: presentation.initialSettings, + initialContent: presentation.initialContent ) } .onChange(of: authorFilter, applyAuthorFilter) @@ -204,8 +215,9 @@ struct CustomPostTabView: View { private var currentUserAvatarURL: URL? { guard let userID = blog.userID, - let author = blog.getAuthorWith(id: userID), - let urlString = author.avatarURL else { + let author = blog.getAuthorWith(id: userID), + let urlString = author.avatarURL + else { return nil } return URL(string: urlString) @@ -232,16 +244,21 @@ struct CustomPostTabView: View { private func duplicatePost(_ post: AnyPostWithEditContext) { let capabilities = PostSettingsCapabilities(from: details) - var params = PostCreateParams.defaultParams(from: blog) - params.title = post.title?.raw - params.content = post.content.raw + var settings = PostSettings.defaults(from: blog) if capabilities.supportsCategories, let categories = post.categories { - params.categories = categories + settings.categoryIDs = Set(categories.map { Int($0) }) } if capabilities.supportsPostFormats { - params.format = post.format + // Assign unconditionally — when the source post has no format, + // clear the blog default so the duplicate matches the source's + // "no format" state rather than snapshotting today's blog default. + settings.postFormat = post.format?.id } - editorPresentation = .duplicatePost(params) + let content = EditorContent( + title: post.title?.raw ?? "", + content: post.content.raw ?? "" + ) + editorPresentation = .duplicatePost(settings: settings, content: content) WPAnalytics.track(.postListDuplicateAction, withProperties: ["post_type": details.slug]) } @@ -359,7 +376,7 @@ private struct SubmitFeedbackViewRepresentable: UIViewControllerRepresentable { private enum EditorPresentation: Identifiable { case newPost case editPost(AnyPostWithEditContext) - case duplicatePost(PostCreateParams) + case duplicatePost(settings: PostSettings, content: EditorContent) var id: String { switch self { @@ -376,11 +393,14 @@ private enum EditorPresentation: Identifiable { } } - var initialParams: PostCreateParams? { - switch self { - case .duplicatePost(let params): return params - default: return nil - } + var initialSettings: PostSettings? { + if case .duplicatePost(let settings, _) = self { return settings } + return nil + } + + var initialContent: EditorContent? { + if case .duplicatePost(_, let content) = self { return content } + return nil } } @@ -418,6 +438,7 @@ private enum Strings { static let betaBadge = NSLocalizedString( "customPostType.navigation.betaBadge", value: "BETA", - comment: "Badge label indicating that custom post type support is a beta feature. Displayed next to the navigation title." + comment: + "Badge label indicating that custom post type support is a beta feature. Displayed next to the navigation title." ) } diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift index 4912472b9729..86c2b9e83b8f 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/CustomPostEditorViewController.swift @@ -18,11 +18,17 @@ class CustomPostEditorViewController: PostGBKEditorViewController { let editorService: CustomPostEditorService private lazy var primarySaveButton = UIBarButtonItem(primaryAction: savePostAction()) - private lazy var redoButton = UIBarButtonItem(systemItem: .redo, primaryAction: UIAction { - [weak editorViewController] _ in editorViewController?.redo() } + private lazy var redoButton = UIBarButtonItem( + systemItem: .redo, + primaryAction: UIAction { + [weak editorViewController] _ in editorViewController?.redo() + } ) - private lazy var undoButton = UIBarButtonItem(systemItem: .undo, primaryAction: UIAction { - [weak editorViewController] _ in editorViewController?.undo() } + private lazy var undoButton = UIBarButtonItem( + systemItem: .undo, + primaryAction: UIAction { + [weak editorViewController] _ in editorViewController?.undo() + } ) init( @@ -31,7 +37,8 @@ class CustomPostEditorViewController: PostGBKEditorViewController { client: WordPressClient, post: AnyPostWithEditContext?, details: PostTypeDetailsWithEditContext, - initialParams: PostCreateParams? = nil, + initialSettings: PostSettings? = nil, + initialContent: EditorContent? = nil, completion: @escaping () -> Void ) { self.client = client @@ -39,8 +46,12 @@ class CustomPostEditorViewController: PostGBKEditorViewController { self.completion = completion self.editorService = CustomPostEditorService( - blog: blog, post: post, details: details, client: client, wpService: wpService, - initialParams: initialParams + blog: blog, + post: post, + details: details, + client: client, + wpService: wpService, + initialSettings: initialSettings ) let postTypeDetails = PostTypeDetails( @@ -48,14 +59,15 @@ class CustomPostEditorViewController: PostGBKEditorViewController { restBase: details.restBase, restNamespace: details.restNamespace ) - super.init( - postId: post.map { Int($0.id) }, - postType: postTypeDetails, - title: post?.title?.raw ?? initialParams?.title, - content: post?.content.raw ?? initialParams?.content, - status: (post?.status ?? .draft).description, - blog: blog - ) + super + .init( + postId: post.map { Int($0.id) }, + postType: postTypeDetails, + title: post?.title?.raw ?? initialContent?.title, + content: post?.content.raw ?? initialContent?.content, + status: (post?.status ?? .draft).description, + blog: blog + ) } required init?(coder aDecoder: NSCoder) { @@ -66,13 +78,20 @@ class CustomPostEditorViewController: PostGBKEditorViewController { super.viewDidLoad() editorService.delegate = self - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(closeButtonAction)) + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .close, + target: self, + action: #selector(closeButtonAction) + ) navigationItem.rightBarButtonItems = rightBarButtonItems() redoButton.isEnabled = false undoButton.isEnabled = false } - override func editor(_ viewController: GutenbergKit.EditorViewController, didUpdateHistoryState state: EditorState) { + override func editor( + _ viewController: GutenbergKit.EditorViewController, + didUpdateHistoryState state: EditorState + ) { redoButton.isEnabled = state.hasRedo undoButton.isEnabled = state.hasUndo } @@ -118,11 +137,17 @@ private extension CustomPostEditorViewController { self?.navigationController?.dismiss(animated: true) } if post?.status ?? .draft == .draft { - alert.addAction(UIAlertAction(title: PostEditorStrings.saveDraft, style: .default, handler: { [weak self] _ in - Task { - await self?.save(publish: false) - } - })) + alert.addAction( + UIAlertAction( + title: PostEditorStrings.saveDraft, + style: .default, + handler: { [weak self] _ in + Task { + await self?.save(publish: false) + } + } + ) + ) } alert.popoverPresentationController?.barButtonItem = self.navigationItem.leftBarButtonItem @@ -142,11 +167,12 @@ private extension CustomPostEditorViewController { let saveDraft = UIAction( title: PostEditorStrings.saveDraft, image: UIImage(systemName: "doc"), - attributes: enabled ? [] : [.disabled]) { [weak self] _ in - Task { - await self?.save(publish: false) - } + attributes: enabled ? [] : [.disabled] + ) { [weak self] _ in + Task { + await self?.save(publish: false) } + } resolve([saveDraft]) } } @@ -188,7 +214,7 @@ private extension CustomPostEditorViewController { func showPostSettings() { let viewModel = CustomPostSettingsViewModel(editorService: editorService, blog: blog) - viewModel.onEditorPostSaved = { /* No-op: shared editorService is already up-to-date */ } + viewModel.onEditorPostSaved = { /* No-op: shared editorService is already up-to-date */ } let settingsVC = PostSettingsViewController(viewModel: viewModel) let navigation = UINavigationController(rootViewController: settingsVC) present(navigation, animated: true) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettings.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettings.swift index 641c1cccbaf2..b768b4115867 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettings.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettings.swift @@ -73,64 +73,15 @@ struct PostSettings: Hashable { // MARK: - Initialization - /// Creates settings for a new post from optional stored create parameters. + /// Creates settings with sensible defaults for a brand-new post. /// - /// When `params` is nil (first open), all fields use sensible defaults. - /// When non-nil, the stored values from a previous Post Settings session - /// are applied on top of the defaults. - init(from params: PostCreateParams, taxonomies: [SiteTaxonomy] = []) { - excerpt = params.excerpt ?? "" - slug = params.slug ?? "" - if let paramStatus = params.status { - status = BasePost.Status(paramStatus) - } else { - status = .draft - } - if status == .draft || status == .pending { - publishDate = nil - } else { - publishDate = params.dateGmt - } - password = params.password - metadata = PostMetadata(from: .init()) - - if let author = params.author { - self.author = Author(id: Int(author), displayName: "–", avatarURL: nil) - } - if let featuredMedia = params.featuredMedia, featuredMedia > 0 { - featuredImageID = Int(featuredMedia) - } - if let commentStatus = params.commentStatus { - allowComments = commentStatus == .open - } - if let pingStatus = params.pingStatus { - allowPings = pingStatus == .open - } - if let format = params.format { - postFormat = format.id - } - if let sticky = params.sticky { - isStickyPost = sticky - } - if let parent = params.parent, parent > 0 { - parentPageID = Int(parent) - } - if !params.categories.isEmpty { - categoryIDs = Set(params.categories.map { Int($0) }) - } - if !params.tags.isEmpty { - tags = params.tags.map { Term(id: Int($0), name: "") } - } - - // Custom taxonomy terms - var otherTerms: [String: [Term]] = [:] - for taxonomy in taxonomies { - let termIds = params.additionalFields?.termIdsForKey(key: taxonomy.restBase) ?? [] - if !termIds.isEmpty { - otherTerms[taxonomy.slug] = termIds.map { Term(id: Int($0), name: "") } - } - } - self.otherTerms = otherTerms + /// Use `defaults(from:)` to apply blog-derived defaults (default category, + /// default post format, current user as author). + init() { + excerpt = "" + slug = "" + status = .draft + metadata = PostMetadata(from: PostMetadataContainer()) } /// Creates PostSettings from an AbstractPost instance. @@ -227,6 +178,27 @@ struct PostSettings: Hashable { sharing = nil } + /// Settings for a brand-new post on the given blog, prefilled with the + /// blog's defaults (default category, default post format, current user + /// as author, site-level discussion defaults). + static func defaults(from blog: Blog) -> PostSettings { + var settings = PostSettings() + if let categoryID = blog.settings?.defaultCategoryID, + categoryID != PostCategory.uncategorized + { + settings.categoryIDs = [categoryID.intValue] + } + settings.postFormat = blog.settings?.defaultPostFormat + if let userID = blog.userID { + settings.author = Author(id: userID.intValue, displayName: "–", avatarURL: nil) + } + if let blogSettings = blog.settings { + settings.allowComments = blogSettings.commentsAllowed + settings.allowPings = blogSettings.pingbackInboundEnabled + } + return settings + } + // MARK: - Applying Changes /// Applies the settings to an AbstractPost instance. @@ -452,7 +424,7 @@ struct PostSettings: Hashable { } /// Creates `PostCreateParams` from the current settings for a new post. - func makeCreateParameters(from existing: PostCreateParams, taxonomies: [SiteTaxonomy] = []) -> PostCreateParams { + func makeCreateParameters(taxonomies: [SiteTaxonomy] = []) -> PostCreateParams { let tagIds = tags.filter { $0.id > 0 }.map { TermId(Int64($0.id)) } let categoryIds = categoryIDs.map { TermId(Int64($0)) } @@ -470,7 +442,7 @@ struct PostSettings: Hashable { ? nil : WpAdditionalFields.fromTermIdMap(map: customTerms) - var params = existing + var params = PostCreateParams(meta: nil) params.dateGmt = publishDate params.slug = slug.isEmpty ? nil : slug params.status = PostStatus(status)