Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
f27f258
Add PostAuthorPicker
kean Jun 25, 2025
5f2c102
Remvoe preview
kean Jun 26, 2025
93cf0b4
Add .postSettingsV2 FF
kean Jun 25, 2025
e5bf352
Add empty PostSettingsView
kean Jun 25, 2025
76dda6a
Add PostSettings
kean Jun 25, 2025
b4bf362
Remove localizable strings
kean Jun 25, 2025
79e7587
Make status type-safe
kean Jun 25, 2025
d21ee92
Remove publicize options for now
kean Jun 25, 2025
a34e288
Fix compilation
kean Jun 25, 2025
90b32e4
Fix cancel flow
kean Jun 25, 2025
0b54a06
Fix presenation from editor
kean Jun 25, 2025
41e4c0d
Show Done button when shown from the editor
kean Jun 25, 2025
f82adf4
Cleanup PostSettings
kean Jun 25, 2025
004b136
Cleanup
kean Jun 25, 2025
c2f845e
Fix compilation with AnyView
kean Jun 25, 2025
87b03f6
Add PostFormatPicker
kean Jun 24, 2025
bc4990a
Remove unused blogervice
kean Jun 26, 2025
093342a
Add the General section
kean Jun 26, 2025
80068bf
Finish author row changes
kean Jun 26, 2025
00311cf
Add pending review row
kean Jun 26, 2025
8c691ff
Move analytics to ViewModel
kean Jun 26, 2025
f5fb7b6
Display time zone at the bottom of date picker making it clear that i…
kean Jun 26, 2025
b7c365b
Implement Publish Row
kean Jun 26, 2025
54194cb
Add visibility picker
kean Jun 26, 2025
78435de
Add visibility section
kean Jun 26, 2025
2ef31f6
Cleanup
kean Jun 26, 2025
f9d6173
Add Featured Image section
kean Jun 26, 2025
274ee32
Add animations
kean Jun 26, 2025
6bcf73e
Add Taxonomy section
kean Jun 26, 2025
a4417b9
Update unit tests
kean Jun 26, 2025
10794a1
Add Excerpt
kean Jun 27, 2025
416437a
Add Slug field
kean Jun 27, 2025
14e222c
Move Pending Review also to More options
kean Jun 27, 2025
926bafb
Add post format field
kean Jun 27, 2025
df56330
Add Parent Page
kean Jun 27, 2025
2d10f21
Cleanup
kean Jun 27, 2025
3aca647
Post Settings: Other (#24640)
kean Jun 27, 2025
172d21e
Minor fixes
kean Jun 27, 2025
1667fba
Fix CMM-548: an issue with social sharing options in the prepublishin…
kean Jun 27, 2025
3a55ed1
Post Setting: Social Sharing (#24642)
kean Jun 27, 2025
738fecd
Remove Social Sharing
kean Jun 27, 2025
74ad7a6
Make enablePublicizeConnectionWithKeyringID non-objc
kean Jun 27, 2025
9740ad6
Remove isMultiAuthorBlog
kean Jun 27, 2025
b63ddc6
Remove PublishSettingsViewModel
kean Jun 27, 2025
86267ca
Remove titleForVisibility
kean Jun 27, 2025
a775015
Remove FeaturedImageDelegate
kean Jun 27, 2025
8324cc0
Remove ategoriesText
kean Jun 27, 2025
3213f35
Remove PostSettingsViewController
kean Jun 27, 2025
2c8323c
Remove PageSettingsViewController
kean Jun 27, 2025
f53157d
Rename NewPostSettingsVC
kean Jun 27, 2025
63ea280
Remove postSettingsV2 FF
kean Jun 27, 2025
41730d0
Fix build
kean Jun 27, 2025
97520e4
Post Settings: Core Removal (#24643)
kean Jun 27, 2025
15963d7
Add Sticky
kean Jun 27, 2025
a44155b
Post Settings: Sticky (#24644)
kean Jun 27, 2025
196d2f7
Move analytics to save
kean Jun 27, 2025
448693d
Refresh only what changed
kean Jun 27, 2025
9c7521f
Post Settings: Optimizations (#24645)
kean Jun 27, 2025
665322f
Add organization improvements
kean Jun 27, 2025
6e4789b
Add excerpt counter outside
kean Jun 27, 2025
80e1693
Improve excerpt editing experience
kean Jun 27, 2025
5be201d
Add Info section
kean Jun 27, 2025
88ce006
Cleanup the Info section
kean Jun 27, 2025
d69502d
Remove unused Slugfordisplay
kean Jun 27, 2025
a9ae056
Use switch for Post/Page
kean Jun 27, 2025
48f7b9c
Add new section headers with WordPress fonst
kean Jun 28, 2025
925ed9b
Nicer PostSettingsFeaturedImageRow
kean Jun 28, 2025
a18e42b
Add improved excerpt editor
kean Jun 28, 2025
2cdf88a
Increase featured image row
kean Jun 28, 2025
126fd13
Simplify excerpt header
kean Jun 28, 2025
c10a4a1
Add new design for categories and tags
kean Jun 28, 2025
a71302d
Add a better way to display categories and tags
kean Jun 29, 2025
6e82511
Revert drag-n-drop code
kean Jun 29, 2025
605ea0a
Cleanup
kean Jun 29, 2025
17395af
Move sections in code according to design
kean Jun 29, 2025
d823c08
Upadte last edited to show relative date
kean Jun 29, 2025
85eb88a
Move slug to the General section
kean Jun 29, 2025
f33b801
Update UI tests
kean Jun 30, 2025
40007dc
Cleanup
kean Jun 30, 2025
de8171d
Update UI tests
kean Jun 30, 2025
8cd6db0
Post Settings: Refinement (#24646)
kean Jun 30, 2025
aa03dc6
Cleanur Me menu
kean Jul 1, 2025
d588e5f
Merge branch 'trunk' into feature/post-settings
kean Jul 9, 2025
55b729a
Simplify ParentPagePicker
kean Jul 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ WordPress-iOS uses a modular architecture with the main app and separate Swift p
- Follow Swift API Design Guidelines
- Use strict access control modifiers where possible
- Use four spaces (not tabs)
- Lines should not have trailing whitespace
- Follow the standard formatting practices enforced by SwiftLint
- Don't create `body` for `View` that are too long
- Use semantics text sizes like `.headline`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@ import XCTest
public class EditorPostSettings: ScreenObject {

private let settingsTableGetter: (XCUIApplication) -> XCUIElement = {
$0.tables["SettingsTable"]
$0.collectionViews["post_settings_form"].firstMatch
}

private let categoriesSectionGetter: (XCUIApplication) -> XCUIElement = {
$0.cells["Categories"]
$0.buttons["post_settings_categories"].firstMatch
}

private let tagsSectionGetter: (XCUIApplication) -> XCUIElement = {
$0.cells["Tags"]
$0.buttons["post_settings_tags"].firstMatch
}

private let publishDateButtonGetter: (XCUIApplication) -> XCUIElement = {
$0.staticTexts["Publish Date"]
$0.staticTexts["Publish Date"].firstMatch
}

private let dateSelectorGetter: (XCUIApplication) -> XCUIElement = {
$0.staticTexts["Immediately"]
$0.staticTexts["Immediately"].firstMatch
}

private let nextMonthButtonGetter: (XCUIApplication) -> XCUIElement = {
$0.buttons["Next Month"]
$0.buttons["Next Month"].firstMatch
}

private let monthLabelGetter: (XCUIApplication) -> XCUIElement = {
$0.buttons["Month"]
$0.buttons["Month"].firstMatch
}

private let firstCalendarDayButtonGetter: (XCUIApplication) -> XCUIElement = {
$0.buttons.containing(.staticText, identifier: "1").element
}

private let closeButtonGetter: (XCUIApplication) -> XCUIElement = {
$0.navigationBars.buttons["close"]
private let saveButtonGetter: (XCUIApplication) -> XCUIElement = {
$0.navigationBars.buttons["post_settings_save_button"].firstMatch
}

private let backButtonGetter: (XCUIApplication) -> XCUIElement? = {
Expand All @@ -45,10 +45,9 @@ public class EditorPostSettings: ScreenObject {

var categoriesSection: XCUIElement { categoriesSectionGetter(app) }
var chooseFromMediaButton: XCUIElement { app.buttons["Choose from Media"].firstMatch }
var closeButton: XCUIElement { closeButtonGetter(app) }
var saveButton: XCUIElement { saveButtonGetter(app) }
var backButton: XCUIElement? { backButtonGetter(app) }
var featuredImageCell: XCUIElement { app.cells["post_settings_featured_image_cell"].firstMatch }
var selectedFeaturedImage: XCUIElement { app.otherElements["featured_image_current_image"].firstMatch }
var selectedFeaturedImage: XCUIElement { app.images["featured_image_current_image_menu"].firstMatch }
var firstCalendarDayButton: XCUIElement { firstCalendarDayButtonGetter(app) }
var monthLabel: XCUIElement { monthLabelGetter(app) }
var nextMonthButton: XCUIElement { nextMonthButtonGetter(app) }
Expand Down Expand Up @@ -88,13 +87,13 @@ public class EditorPostSettings: ScreenObject {
}

public func removeFeatureImage() throws -> EditorPostSettings {
featuredImageCell.tap()
app.buttons["post_settings_featured_image_cell"].firstMatch.tap()
app.buttons["featured_image_button_remove"].firstMatch.tap()
return try EditorPostSettings()
}

public func setFeaturedImage() throws -> EditorPostSettings {
featuredImageCell.tap()
app.buttons["post_settings_featured_image_cell"].firstMatch.tap()
chooseFromMediaButton.tap()
try MediaPickerAlbumScreen()
.selectImage(atIndex: 0) // Select latest uploaded image
Expand All @@ -118,10 +117,13 @@ public class EditorPostSettings: ScreenObject {
return try EditorPostSettings()
}

@discardableResult
public func closePostSettings() throws -> BlockEditorScreen {
closeButton.tap()
public func closePostSettings() {
app.buttons["post_settings_cancel_button"].firstMatch.tap()
}

@discardableResult
public func savePostSettings() throws -> BlockEditorScreen {
saveButton.tap()
return try BlockEditorScreen()
}

Expand Down
19 changes: 0 additions & 19 deletions Modules/Sources/WordPressUI/Extensions/SwiftUI+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,3 @@ import SwiftUI
public extension EdgeInsets {
static let zero = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
}

private struct PresentingViewControllerKey: EnvironmentKey {
static let defaultValue = WeakEnvironmentValueWrapper<UIViewController>()
}

extension EnvironmentValues {
public var presentingViewController: UIViewController? {
get {
self[PresentingViewControllerKey.self].value ?? UIViewController.topViewController
}
set {
self[PresentingViewControllerKey.self].value = newValue
}
}
}

private final class WeakEnvironmentValueWrapper<T: AnyObject> {
weak var value: T?
}
16 changes: 16 additions & 0 deletions Modules/Sources/WordPressUI/Views/SectionHeader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftUI
import DesignSystem

public struct SectionHeader: View {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use the default header style in Section("Header text") {}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes it a bit easier to read, which we want in a form, and it's more consistent with the WordPress design. I plan to keep using these for forms.

let title: String

public init(_ title: String) {
self.title = title
}

public var body: some View {
Text(title.uppercased())
.font(.caption2).fontWeight(.medium)
.foregroundStyle(Color.secondary)
}
}
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
-----
* [**] Add new “Subscribers” screen that shows both your email and Reader subscribers [#24513]
* [*] Improve search in TimeZone Picker for more accurate results [#24612]
* [*] Add new "Author Picker" for "Post Settings" with search and better design [#24621]
* [*] Fix an issue with “Stats / Subscribers” sometimes not showing the latest email subscribers [#24513]
* [*] Fix an issue with "Stats" / "Subscribers" / "Emails" showing html encoded characters [#24513]
* [*] Add search to “Jetpack Activity List” and display actors and dates [#24597]
Expand Down
2 changes: 0 additions & 2 deletions Sources/Keystone/WordPress.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ FOUNDATION_EXPORT const unsigned char WordPressVersionString[];

#import <WordPress/NSObject+Helpers.h>

#import <WordPress/PageSettingsViewController.h>
#import <WordPress/PostCategoryService.h>
#import <WordPress/PostSettingsViewController.h>
#import <WordPress/PostTagService.h>

#import <WordPress/ReaderPostService.h>
Expand Down
75 changes: 75 additions & 0 deletions Sources/WordPressData/Blog+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Foundation
import CoreData
import WordPressShared

extension Blog {

// MARK: - Post Formats

/// Returns an array of post format keys sorted with "standard" first, then alphabetically
@objc public var sortedPostFormats: [String] {
guard let postFormats = postFormats as? [String: String], !postFormats.isEmpty else {
return []
}

var sortedFormats: [String] = []

// Add standard format first if it exists
if postFormats[PostFormatStandard] != nil {
sortedFormats.append(PostFormatStandard)
}

// Add remaining formats sorted by their display names
let nonStandardFormats = postFormats
.filter { $0.key != PostFormatStandard }
.sorted { $0.value.localizedCaseInsensitiveCompare($1.value) == .orderedAscending }
.map { $0.key }

sortedFormats.append(contentsOf: nonStandardFormats)

return sortedFormats
}

/// Returns an array of post format display names sorted with "Standard" first, then alphabetically
@objc public var sortedPostFormatNames: [String] {
guard let postFormats = postFormats as? [String: String] else {
return []
}
return sortedPostFormats.compactMap { postFormats[$0] }
}

/// Returns the default post format display text
@objc public var defaultPostFormatText: String? {
postFormatText(fromSlug: settings?.defaultPostFormat)
}

// MARK: - Connections

/// Returns an array of PublicizeConnection objects sorted by service name, then by external name
@objc public var sortedConnections: [PublicizeConnection] {
guard let connections = Array(connections ?? []) as? [PublicizeConnection] else {
return []
}
return connections.sorted { lhs, rhs in
// First sort by service name (case insensitive, localized)
let serviceComparison = lhs.service.localizedCaseInsensitiveCompare(rhs.service)
if serviceComparison != .orderedSame {
return serviceComparison == .orderedAscending
}
// Then sort by external name (case insensitive)
return lhs.externalName.caseInsensitiveCompare(rhs.externalName) == .orderedAscending
}
}

// MARK: - Roles

/// Returns an array of roles sorted by order.
public var sortedRoles: [Role] {
guard let roles = Array(roles ?? []) as? [Role] else {
return []
}
return roles.sorted { lhs, rhs in
(lhs.order?.intValue ?? 0) < (rhs.order?.intValue ?? 0)
}
}
}
5 changes: 0 additions & 5 deletions Sources/WordPressData/Objective-C/AbstractPost.m
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,6 @@ - (BOOL)isPrivateAtWPCom
return self.blog.isPrivateAtWPCom;
}

- (BOOL)isMultiAuthorBlog
{
return self.blog.isMultiAuthor;
}

- (BOOL)isUploading
{
return self.remoteStatus == AbstractPostRemoteStatusPushing;
Expand Down
8 changes: 0 additions & 8 deletions Sources/WordPressData/Objective-C/BasePost.m
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,6 @@ - (NSDate *)dateForDisplay
return [self dateCreated];
}

- (NSString *)slugForDisplay
{
if (self.wp_slug.length > 0) {
return self.wp_slug;
}
return self.suggested_slug;
}

- (BOOL)hasContent
{
BOOL titleIsEmpty = self.postTitle ? self.postTitle.isEmpty : YES;
Expand Down
50 changes: 0 additions & 50 deletions Sources/WordPressData/Objective-C/Blog.m
Original file line number Diff line number Diff line change
Expand Up @@ -281,56 +281,6 @@ - (NSArray *)sortedCategories
return [[self.categories allObjects] sortedArrayUsingDescriptors:sortDescriptors];
}

- (NSArray *)sortedPostFormats
{
if ([self.postFormats count] == 0) {
return @[];
}

NSMutableArray *sortedFormats = [NSMutableArray arrayWithCapacity:[self.postFormats count]];

if (self.postFormats[PostFormatStandard]) {
[sortedFormats addObject:PostFormatStandard];
}

NSArray *sortedNonStandardFormats = [[self.postFormats keysSortedByValueUsingSelector:@selector(localizedCaseInsensitiveCompare:)] wp_filter:^BOOL(id obj) {
return ![obj isEqual:PostFormatStandard];
}];

[sortedFormats addObjectsFromArray:sortedNonStandardFormats];

return [NSArray arrayWithArray:sortedFormats];
}

- (NSArray *)sortedPostFormatNames
{
return [[self sortedPostFormats] wp_map:^id(NSString *key) {
return self.postFormats[key];
}];
}

- (NSArray *)sortedConnections
{
NSSortDescriptor *sortServiceDescriptor = [[NSSortDescriptor alloc] initWithKey:@"service"
ascending:YES
selector:@selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *sortExternalNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"externalName"
ascending:YES
selector:@selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = @[sortServiceDescriptor, sortExternalNameDescriptor];
return [[self.connections allObjects] sortedArrayUsingDescriptors:sortDescriptors];
}

- (NSArray<Role *> *)sortedRoles
{
return [self.roles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]]];
}

- (NSString *)defaultPostFormatText
{
return [self postFormatTextFromSlug:self.settings.defaultPostFormat];
}

- (BOOL)hasMappedDomain {
if (![self isHostedAtWPcom]) {
return NO;
Expand Down
1 change: 0 additions & 1 deletion Sources/WordPressData/Objective-C/include/AbstractPost.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) {
- (NSString *)authorNameForDisplay;
- (NSString *)blavatarForDisplay;
- (NSString *)dateStringForDisplay;
- (BOOL)isMultiAuthorBlog;
- (BOOL)isPrivateAtWPCom;


Expand Down
10 changes: 4 additions & 6 deletions Sources/WordPressData/Objective-C/include/Blog.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN
@class PageTemplateCategory;
@class PublicizeInfo;
@class BlobEntity;
@class PostCategory;
@class PublicizeConnection;

extern NSString * const BlogEntityName;
extern NSString * const PostFormatStandard;
Expand Down Expand Up @@ -132,10 +134,10 @@ typedef NS_ENUM(NSInteger, SiteVisibility) {
@property (nonatomic, strong, readwrite, nullable) NSNumber *hasOlderPosts;
@property (nonatomic, strong, readwrite, nullable) NSNumber *hasOlderPages;
@property (nonatomic, strong, readwrite, nullable) NSSet<AbstractPost *> *posts;
@property (nonatomic, strong, readwrite, nullable) NSSet *categories;
@property (nonatomic, strong, readwrite, nullable) NSSet<PostCategory *> *categories;
@property (nonatomic, strong, readwrite, nullable) NSSet *tags;
@property (nonatomic, strong, readwrite, nullable) NSSet *comments;
@property (nonatomic, strong, readwrite, nullable) NSSet *connections;
@property (nonatomic, strong, readwrite, nullable) NSSet<PublicizeConnection *> *connections;
@property (nonatomic, strong, readwrite, nullable) NSSet *inviteLinks;
@property (nonatomic, strong, readwrite, nullable) NSSet *domains;
@property (nonatomic, strong, readwrite, nullable) NSSet *themes;
Expand Down Expand Up @@ -202,10 +204,6 @@ typedef NS_ENUM(NSInteger, SiteVisibility) {


// Readonly Properties
@property (nonatomic, weak, readonly, nullable) NSArray *sortedPostFormatNames;
@property (nonatomic, weak, readonly, nullable) NSArray *sortedPostFormats;
@property (nonatomic, weak, readonly, nullable) NSArray *sortedConnections;
@property (nonatomic, readonly, nullable) NSArray<Role *> *sortedRoles;
@property (nonatomic, strong, readonly, nullable) WordPressOrgXMLRPCApi *xmlrpcApi;
@property (nonatomic, strong, readonly, nullable) WordPressOrgRestApi *selfHostedSiteRestApi;
@property (nonatomic, weak, readonly, nullable) NSString *version;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,5 @@
- (NSString *)blogNameForDisplay;
- (NSURL *)featuredImageURLForDisplay;
- (NSURL *)authorURL;
- (NSString *)slugForDisplay;
- (NSArray <NSString *> *)tagsForDisplay;
@end
16 changes: 9 additions & 7 deletions Sources/WordPressData/Swift/AbstractPost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ public extension AbstractPost {

private static let deprecatedStatuses: Set<AbstractPostRemoteStatus> = [.pushing, .failed, .local, .sync, .pushingMedia, .autoSaved]

/// Creates an array of tags by parsing a comma-separate list of tags.
static func makeTags(from tags: String) -> [String] {
tags
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
}

// MARK: - Status

/// Returns `true` is the post has one of the given statuses.
Expand Down Expand Up @@ -92,13 +101,6 @@ public extension AbstractPost {
}
}

// MARK: - Misc

/// A title describing the status. Ie.: "Public" or "Private" or "Password protected"
@objc var titleForVisibility: String {
PostVisibility(post: self).localizedTitle
}

/// Represent the supported properties used to sort posts.
///
enum SortField {
Expand Down
Loading