Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion Modules/Sources/WordPressCore/WordPressClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,28 @@ public actor WordPressClient {
}
}

/// Returns whether the current user has the specified capability.
///
/// Uses the cached current user data, so this typically does not trigger a network request.
///
/// - Parameter capability: The capability to check.
/// - Returns: `true` if the user has the capability, `false` otherwise.
public func currentUserCan(_ capability: UserCapability) async throws -> Bool {
let user = try await fetchCurrentUser()
return user.capabilities.hasCap(capability: capability)
}

/// Fetches the site settings, using the cached value if available.
///
/// If the cached task has failed, creates a new task and retries the fetch.
public func fetchSiteSettings() async throws -> SiteSettingsWithEditContext {
/// Pass `forceRefresh: true` to bypass the cache and refetch from the server —
/// callers should do this when they know the server-side settings may have changed
/// outside this client (e.g. on pull-to-refresh).
public func fetchSiteSettings(forceRefresh: Bool = false) async throws -> SiteSettingsWithEditContext {
if forceRefresh {
self.loadSiteSettingsTask = newSiteSettingsTask()
return try await self.loadSiteSettingsTask.value
}
switch await self.loadSiteSettingsTask.result {
case .success(let settings): return settings
case .failure:
Expand Down
127 changes: 112 additions & 15 deletions WordPress/Classes/ViewRelated/CustomPostTypes/CustomPostListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ struct CustomPostListView<Header: View>: View {
)
.overlay {
if viewModel.shouldDisplayEmptyView {
let emptyText = details.labels.notFound.isEmpty
let emptyText =
details.labels.notFound.isEmpty
? String.localizedStringWithFormat(Strings.emptyStateMessage, details.name)
: details.labels.notFound
EmptyStateView(emptyText, systemImage: "doc.text")
Expand Down Expand Up @@ -307,7 +308,8 @@ private struct PaginatedList<Header: View>: View {
Text(verbatim: SharedStrings.Button.retry)
}
.buttonStyle(.borderedProminent)
}.frame(maxWidth: .infinity, alignment: .center)
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
}
Expand Down Expand Up @@ -354,7 +356,12 @@ private struct ForEachContent: View {
} else if showsPostActions {
button
.contextMenu {
PostActionMenuContent(post: fullPost, viewModel: viewModel, onDuplicate: onDuplicate)
PostActionMenuContent(
post: fullPost,
pageRole: item.pageRole,
viewModel: viewModel,
onDuplicate: onDuplicate
)
}
.swipeActions(edge: .leading) {
if fullPost.status == .publish {
Expand Down Expand Up @@ -392,7 +399,12 @@ private struct ForEachContent: View {
}
}
.overlay(alignment: .topTrailing) {
PostActionMenu(post: fullPost, viewModel: viewModel, onDuplicate: onDuplicate)
PostActionMenu(
post: fullPost,
pageRole: item.pageRole,
viewModel: viewModel,
onDuplicate: onDuplicate
)
.offset(y: -6)
}
} else {
Expand Down Expand Up @@ -459,12 +471,13 @@ private struct ForEachContentWithIndentation: View {

private struct PostActionMenu: View {
let post: AnyPostWithEditContext
let pageRole: PageRole?
let viewModel: CustomPostListViewModel
let onDuplicate: (AnyPostWithEditContext) -> Void

var body: some View {
Menu {
PostActionMenuContent(post: post, viewModel: viewModel, onDuplicate: onDuplicate)
PostActionMenuContent(post: post, pageRole: pageRole, viewModel: viewModel, onDuplicate: onDuplicate)
} label: {
Image(systemName: "ellipsis")
.font(.body)
Expand All @@ -477,15 +490,29 @@ private struct PostActionMenu: View {

private struct PostActionMenuContent: View {
let post: AnyPostWithEditContext
let pageRole: PageRole?
let viewModel: CustomPostListViewModel
let onDuplicate: (AnyPostWithEditContext) -> Void

var body: some View {
primarySection
pageAttributesSection
navigationSection
trashSection
}

@ViewBuilder
private var pageAttributesSection: some View {
if viewModel.canChangePageAttributes, post.status == .publish {
PageAttributeMenuSection(
pageRole: pageRole,
onSetHomepage: { Task { await viewModel.setAsHomepage(post) } },
onSetPostsPage: { Task { await viewModel.setAsPostsPage(post) } },
onSetRegularPage: { Task { await viewModel.setAsRegularPage(post) } }
)
}
}

@ViewBuilder
private var primarySection: some View {
Section {
Expand Down Expand Up @@ -538,13 +565,16 @@ private struct PostActionMenuContent: View {
private var trashSection: some View {
Section {
if post.status != .trash {
Button(role: .destructive, action: {
if post.status == .publish {
viewModel.confirmTrash(post)
} else {
Task { await viewModel.trashPost(post) }
Button(
role: .destructive,
action: {
if post.status == .publish {
viewModel.confirmTrash(post)
} else {
Task { await viewModel.trashPost(post) }
}
}
}) {
) {
Label(Strings.moveToTrash, systemImage: "trash")
}
} else {
Expand All @@ -556,6 +586,37 @@ private struct PostActionMenuContent: View {
}
}

private struct PageAttributeMenuSection: View {
let pageRole: PageRole?
let onSetHomepage: () -> Void
let onSetPostsPage: () -> Void
let onSetRegularPage: () -> Void

var body: some View {
Section {
Menu {
if pageRole != .homepage {
Button(action: onSetHomepage) {
Label(Strings.setHomepage, systemImage: "house")
}
}
if pageRole != .postsPage {
Button(action: onSetPostsPage) {
Label(Strings.setPostsPage, systemImage: "text.word.spacing")
}
}
if pageRole == .postsPage {
Button(action: onSetRegularPage) {
Label(Strings.setRegularPage, systemImage: "arrow.uturn.backward")
}
}
} label: {
Label(Strings.pageAttributes, systemImage: "doc")
}
}
}
}

private struct PostContent: View {
let post: CustomPostCollectionDisplayPost
let client: WordPressClient?
Expand All @@ -566,7 +627,7 @@ private struct PostContent: View {
header
content
footer
homepageBadge
pageRoleBadge
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
Expand Down Expand Up @@ -614,14 +675,24 @@ private struct PostContent: View {
}

@ViewBuilder
private var homepageBadge: some View {
if post.isHomepage {
private var pageRoleBadge: some View {
switch post.pageRole {
case .homepage:
HStack(spacing: 2) {
Image(systemName: "house.fill")
Text(verbatim: Strings.homepageBadge)
}
.font(.footnote)
.foregroundStyle(.secondary)
case .postsPage:
HStack(spacing: 2) {
Image(systemName: "paragraphsign")
Text(verbatim: Strings.postsPageBadge)
}
.font(.footnote)
.foregroundStyle(.secondary)
case nil:
EmptyView()
}
}
}
Expand All @@ -646,7 +717,8 @@ private enum Strings {
static let emptyStateMessage = NSLocalizedString(
"customPostList.emptyState.message",
value: "No %1$@",
comment: "Empty state message when no custom posts exist. %1$@ is the post type name (e.g., 'Podcasts', 'Products')."
comment:
"Empty state message when no custom posts exist. %1$@ is the post type name (e.g., 'Podcasts', 'Products')."
)
static let homepageBadge = NSLocalizedString(
"customPostList.badge.homepage",
Expand Down Expand Up @@ -708,6 +780,31 @@ private enum Strings {
value: "Delete",
comment: "Short label for the swipe action to permanently delete a trashed post. Keep this translation short."
)
static let setHomepage = NSLocalizedString(
"customPostList.action.setHomepage",
value: "Set as Homepage",
comment: "Menu action to set a page as the site homepage"
)
static let setPostsPage = NSLocalizedString(
"customPostList.action.setPostsPage",
value: "Set as Posts Page",
comment: "Menu action to set a page as the posts page"
)
static let setRegularPage = NSLocalizedString(
"customPostList.action.setRegularPage",
value: "Set as Regular Page",
comment: "Menu action to remove the posts page designation from a page"
)
static let pageAttributes = NSLocalizedString(
"customPostList.action.pageAttributes",
value: "Page Attributes",
comment: "Label for the page attributes submenu in the context menu"
)
static let postsPageBadge = NSLocalizedString(
"customPostList.badge.postsPage",
value: "Posts page",
comment: "Badge label shown on the posts page row in the custom post list for pages"
)
}

// MARK: - Previews
Expand Down
Loading