Skip to content

Commit 9df3cf8

Browse files
authored
Show taxonomies and manage terms (#24955)
* Decouple `TagsService.getTags` from WordPressKit API * Decouple from WordPressKit.RemotePostTag * Add managing terms and taxonomies * Update release notes * Extract duplicated code * Rephrase a user-facing message
1 parent 072414c commit 9df3cf8

File tree

10 files changed

+526
-162
lines changed

10 files changed

+526
-162
lines changed

RELEASE-NOTES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* [*] Add "File Size" to Site Media Details [#24947]
99
* [*] Add "Email to Subscribers" row to "Publishing" sheet [#24946]
1010
* [*] Add permalink preview in the slug editor and make other improvements [#24949]
11+
* [*] Add "Taxonomies" to Site Settings [#24955]
1112
* [*] Update "Categories" picker to indicate multiple selection [#24952]
1213

1314
26.4
@@ -237,7 +238,7 @@
237238
* [*] [internal] Incorporate a parser to handle Gutenberg blocks more efficiently for improved performance [#22886]
238239
* [**] Improve performance of Image and Gallery block processors to avoid long delay when saving a post [#22896]
239240
* [*] Improve performance of File block processor [#22897]
240-
* [*] [internal] Prompt users to keep the app up to date on their devices, so they can try new features, as well as benefit from performance improvements and bug fixes [#23195, #23216, #23218, #23211]
241+
* [*] [internal] Prompt users to keep the app up to date on their devices, so they can try new features, as well as benefit from performance improvements and bug fixes [#23195, #23216, #23218, #23211]
241242
* [**] [Jetpack-only] Reader: Add a new feed dedicated to tags [#23242]
242243

243244
24.9.1

WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import SwiftUI
33
import WordPressData
44
import WordPressFlux
55
import WordPressShared
6+
import WordPressAPI
7+
import WordPressAPIInternal
8+
import WordPressCore
69

710
extension SiteSettingsViewController {
811
// MARK: - General
@@ -52,6 +55,26 @@ extension SiteSettingsViewController {
5255
navigationController?.pushViewController(tagsVC, animated: true)
5356
}
5457

58+
@objc public func showCustomTaxonomies() {
59+
let viewController: UIViewController
60+
if let client = try? WordPressClient(site: .init(blog: blog)) {
61+
let rootView = SiteCustomTaxonomiesView(blog: self.blog, api: client.api)
62+
viewController = UIHostingController(rootView: rootView)
63+
} else {
64+
let feature = NSLocalizedString(
65+
"applicationPasswordRequired.feature.customTaxonomies",
66+
value: "Taxonomies Management",
67+
comment: "Feature name for managing terms and taxonomies in the app"
68+
)
69+
let rootView = ApplicationPasswordRequiredView(blog: self.blog, localizedFeatureName: feature, presentingViewController: self) { client in
70+
SiteCustomTaxonomiesView(blog: self.blog, api: client.api)
71+
}
72+
viewController = UIHostingController(rootView: rootView)
73+
}
74+
75+
self.navigationController?.pushViewController(viewController, animated: true)
76+
}
77+
5578
// MARK: - Timezone
5679

5780
func formattedTimezoneValue() -> String? {

WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
NS_ENUM(NSInteger, SiteSettingsWriting) {
3434
SiteSettingsWritingDefaultCategory = 0,
3535
SiteSettingsWritingTags,
36+
SiteSettingsWritingCustomTaxonomies,
3637
SiteSettingsWritingDefaultPostFormat,
3738
SiteSettingsWritingRelatedPosts,
3839
SiteSettingsWritingDateAndTimeFormat,
@@ -62,6 +63,7 @@ @interface SiteSettingsViewController () <UITableViewDelegate, UITextFieldDelega
6263
@property (nonatomic, strong) SwitchTableViewCell *editorSelectorCell;
6364
@property (nonatomic, strong) SettingTableViewCell *defaultCategoryCell;
6465
@property (nonatomic, strong) SettingTableViewCell *tagsCell;
66+
@property (nonatomic, strong) SettingTableViewCell *customTaxonomiesCell;
6567
@property (nonatomic, strong) SettingTableViewCell *defaultPostFormatCell;
6668
@property (nonatomic, strong) SettingTableViewCell *relatedPostsCell;
6769
@property (nonatomic, strong) SettingTableViewCell *dateAndTimeFormatCell;
@@ -204,6 +206,7 @@ - (NSArray *)writingSectionRows
204206
NSMutableArray *rows = [NSMutableArray arrayWithObjects:
205207
@(SiteSettingsWritingDefaultCategory),
206208
@(SiteSettingsWritingTags),
209+
@(SiteSettingsWritingCustomTaxonomies),
207210
@(SiteSettingsWritingDefaultPostFormat), nil];
208211

209212
BOOL jetpackFeaturesEnabled = [JetpackFeaturesRemovalCoordinator jetpackFeaturesEnabled];
@@ -366,6 +369,17 @@ - (SettingTableViewCell *)tagsCell
366369
return _tagsCell;
367370
}
368371

372+
- (SettingTableViewCell *)customTaxonomiesCell
373+
{
374+
if (_customTaxonomiesCell){
375+
return _customTaxonomiesCell;
376+
}
377+
_customTaxonomiesCell = [[SettingTableViewCell alloc] initWithLabel:NSLocalizedString(@"Taxonomies", @"Label for viewing the custom taxonomies")
378+
editable:self.blog.isAdmin
379+
reuseIdentifier:nil];
380+
return _customTaxonomiesCell;
381+
}
382+
369383
- (SettingTableViewCell *)defaultPostFormatCell
370384
{
371385
if (_defaultPostFormatCell){
@@ -533,6 +547,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForWritingSettingsAt
533547
case (SiteSettingsWritingTags):
534548
return self.tagsCell;
535549

550+
case (SiteSettingsWritingCustomTaxonomies):
551+
return self.customTaxonomiesCell;
552+
536553
case (SiteSettingsWritingDefaultPostFormat):
537554
[self configureDefaultPostFormatCell];
538555
return self.defaultPostFormatCell;
@@ -858,6 +875,10 @@ - (void)tableView:(UITableView *)tableView didSelectInWritingSectionRow:(NSInteg
858875
[self showTagList];
859876
break;
860877

878+
case SiteSettingsWritingCustomTaxonomies:
879+
[self showCustomTaxonomies];
880+
break;
881+
861882
case SiteSettingsWritingDefaultPostFormat:
862883
[self showPostFormatSelector];
863884
break;

WordPress/Classes/ViewRelated/Tags/EditTagView.swift

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ import SwiftUI
22
import WordPressUI
33
import WordPressKit
44
import WordPressData
5+
import WordPressAPI
56
import SVProgressHUD
67

78
struct EditTagView: View {
89
@Environment(\.dismiss) private var dismiss
910
@StateObject private var viewModel: EditTagViewModel
1011

11-
init(tag: RemotePostTag?, tagsService: TagsService) {
12-
self._viewModel = StateObject(wrappedValue: EditTagViewModel(tag: tag, tagsService: tagsService))
12+
init(term: AnyTermWithViewContext?, taxonomy: SiteTaxonomy?, tagsService: TaxonomyServiceProtocol) {
13+
self._viewModel = StateObject(wrappedValue: EditTagViewModel(term: term, taxonomy: taxonomy, tagsService: tagsService))
1314
}
1415

1516
var body: some View {
1617
Form {
17-
Section(Strings.tagSectionHeader) {
18+
Section {
1819
HStack {
19-
TextField(Strings.tagNamePlaceholder, text: $viewModel.tagName)
20+
TextField(viewModel.localizedLabels.newPlaceholder, text: $viewModel.tagName)
2021
.textFieldStyle(.plain)
2122
.autocorrectionDisabled()
2223
.textInputAutocapitalization(.never)
@@ -31,12 +32,24 @@ struct EditTagView: View {
3132
}
3233
}
3334
}
35+
} header: {
36+
Text(Strings.tagSectionHeader)
37+
} footer: {
38+
if let text = viewModel.localizedLabels.nameFieldDescription {
39+
Text(verbatim: text)
40+
}
3441
}
3542

36-
Section(Strings.descriptionSectionHeader) {
43+
Section {
3744
TextField(Strings.descriptionPlaceholder, text: $viewModel.tagDescription, axis: .vertical)
3845
.textFieldStyle(.plain)
3946
.lineLimit(5...15)
47+
} header: {
48+
Text(Strings.descriptionSectionHeader)
49+
} footer: {
50+
if let text = viewModel.localizedLabels.descriptionFieldDescription {
51+
Text(verbatim: text)
52+
}
4053
}
4154

4255
if viewModel.isExistingTag {
@@ -98,38 +111,39 @@ class EditTagViewModel: ObservableObject {
98111
@Published var showError = false
99112
@Published var errorMessage = ""
100113

101-
private let originalTag: RemotePostTag?
102-
private let tagsService: TagsService
114+
private let originalTerm: AnyTermWithViewContext?
115+
private let tagsService: TaxonomyServiceProtocol
116+
fileprivate let localizedLabels: LocalizedLabels
103117

104118
var isExistingTag: Bool {
105-
originalTag != nil
119+
originalTerm != nil
106120
}
107121

108122
var navigationTitle: String {
109-
originalTag?.name ?? Strings.newTagTitle
123+
originalTerm?.name ?? localizedLabels.newItemTitle
110124
}
111125

112-
init(tag: RemotePostTag?, tagsService: TagsService) {
113-
self.originalTag = tag
126+
init(term: AnyTermWithViewContext?, taxonomy: SiteTaxonomy?, tagsService: TaxonomyServiceProtocol) {
127+
self.originalTerm = term
128+
self.localizedLabels = taxonomy.flatMap(LocalizedLabels.from) ?? .tag
114129
self.tagsService = tagsService
115-
self.tagName = tag?.name ?? ""
116-
self.tagDescription = tag?.tagDescription ?? ""
130+
self.tagName = term?.name ?? ""
131+
self.tagDescription = term?.description ?? ""
117132
}
118133

119134
func deleteTag() async -> Bool {
120-
guard let tag = originalTag else { return false }
135+
guard let term = originalTerm else { return false }
121136

122137
SVProgressHUD.show()
123138
defer { SVProgressHUD.dismiss() }
124139

125140
do {
126-
try await tagsService.deleteTag(tag)
141+
try await tagsService.deleteTag(term)
127142

128-
// Post notification to update the UI
129143
NotificationCenter.default.post(
130144
name: .tagDeleted,
131145
object: nil,
132-
userInfo: [TagNotificationUserInfoKeys.tagID: tag.tagID ?? 0]
146+
userInfo: [TagNotificationUserInfoKeys.tagID: NSNumber(value: term.id)]
133147
)
134148
return true
135149
} catch {
@@ -143,23 +157,21 @@ class EditTagViewModel: ObservableObject {
143157
SVProgressHUD.show()
144158
defer { SVProgressHUD.dismiss() }
145159

146-
let tagToSave: RemotePostTag
147-
if let existingTag = originalTag {
148-
tagToSave = existingTag
149-
} else {
150-
tagToSave = RemotePostTag()
151-
}
152-
153-
tagToSave.name = tagName.trimmingCharacters(in: .whitespacesAndNewlines)
154-
tagToSave.tagDescription = tagDescription.trimmingCharacters(in: .whitespacesAndNewlines)
155-
156160
do {
157-
let savedTag = try await tagsService.saveTag(tagToSave)
161+
let savedTerm: AnyTermWithViewContext
162+
163+
let tagName = tagName.trimmingCharacters(in: .whitespacesAndNewlines)
164+
let tagDescription = tagDescription.trimmingCharacters(in: .whitespacesAndNewlines)
165+
if let existingTerm = originalTerm {
166+
savedTerm = try await tagsService.updateTag(existingTerm, name: tagName, description: tagDescription)
167+
} else {
168+
savedTerm = try await tagsService.createTag(name: tagName, description: tagDescription)
169+
}
158170

159171
NotificationCenter.default.post(
160-
name: originalTag == nil ? .tagCreated : .tagUpdated,
172+
name: originalTerm == nil ? .tagCreated : .tagUpdated,
161173
object: nil,
162-
userInfo: [TagNotificationUserInfoKeys.tag: savedTag]
174+
userInfo: [TagNotificationUserInfoKeys.tag: savedTerm]
163175
)
164176
return true
165177
} catch {
@@ -170,10 +182,43 @@ class EditTagViewModel: ObservableObject {
170182
}
171183
}
172184

185+
private struct LocalizedLabels {
186+
var newPlaceholder: String
187+
var newItemTitle: String
188+
var nameFieldDescription: String?
189+
var descriptionFieldDescription: String?
190+
191+
static func from(taxonomy: SiteTaxonomy) -> Self {
192+
Self(
193+
newPlaceholder: (taxonomy.details.labels[.newItemName] ?? nil) ?? "",
194+
newItemTitle: (taxonomy.details.labels[.addNewItem] ?? nil) ?? "",
195+
nameFieldDescription: taxonomy.details.labels[.nameFieldDescription] ?? nil,
196+
descriptionFieldDescription: taxonomy.details.labels[.descFieldDescription] ?? nil
197+
)
198+
}
199+
200+
static var tag: Self {
201+
Self(
202+
newPlaceholder: NSLocalizedString(
203+
"edit.tag.name.placeholder",
204+
value: "Tag name",
205+
comment: "Placeholder text for tag name field"
206+
),
207+
newItemTitle: NSLocalizedString(
208+
"edit.tag.new.title",
209+
value: "New Tag",
210+
comment: "Navigation title for new tag creation"
211+
),
212+
nameFieldDescription: nil,
213+
descriptionFieldDescription: nil
214+
)
215+
}
216+
}
217+
173218
private enum Strings {
174219
static let tagSectionHeader = NSLocalizedString(
175220
"edit.tag.section.tag",
176-
value: "Tag",
221+
value: "Name",
177222
comment: "Section header for tag name in edit tag view"
178223
)
179224

@@ -183,33 +228,21 @@ private enum Strings {
183228
comment: "Section header for tag description in edit tag view"
184229
)
185230

186-
static let tagNamePlaceholder = NSLocalizedString(
187-
"edit.tag.name.placeholder",
188-
value: "Tag name",
189-
comment: "Placeholder text for tag name field"
190-
)
191-
192231
static let descriptionPlaceholder = NSLocalizedString(
193232
"edit.tag.description.placeholder",
194233
value: "Add a description...",
195234
comment: "Placeholder text for tag description field"
196235
)
197236

198-
static let newTagTitle = NSLocalizedString(
199-
"edit.tag.new.title",
200-
value: "New Tag",
201-
comment: "Navigation title for new tag creation"
202-
)
203-
204237
static let deleteConfirmationTitle = NSLocalizedString(
205238
"edit.tag.delete.confirmation.title",
206-
value: "Delete Tag",
207-
comment: "Title for delete tag confirmation dialog"
239+
value: "Delete",
240+
comment: "Title for delete a term confirmation dialog"
208241
)
209242

210243
static let deleteConfirmationMessage = NSLocalizedString(
211244
"edit.tag.delete.confirmation.message",
212-
value: "Are you sure you want to delete this tag?",
245+
value: "Are you sure you want to delete this?",
213246
comment: "Message for delete tag confirmation dialog"
214247
)
215248
}

WordPress/Classes/ViewRelated/Tags/RemotePostTag+Identifiable.swift

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)