Skip to content

Commit ead6a8e

Browse files
authored
Merge branch 'trunk' into fix-12673-trim-preview-newlines
2 parents 9f5831e + 468afe6 commit ead6a8e

593 files changed

Lines changed: 2930 additions & 6853 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.buildkite/pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ steps:
150150
- label: ":danger: Danger - PR Check"
151151
command: danger
152152
key: danger
153-
if: "build.pull_request.id != null"
153+
# No "build.pull_request.id" condition as we want this check to run also on merge queues where it is a no-op
154154
retry:
155155
manual:
156156
permit_on_passed: true

Gemfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ gem 'dotenv'
99
# See failures like https://buildkite.com/automattic/wordpress-ios/builds/24053#019234f2-80a5-40f6-b55e-2f420e6483a8/3840-3915
1010
# and https://github.com/fastlane/fastlane/pull/22256
1111
gem 'fastlane', '~> 2.232'
12-
gem 'fastlane-plugin-firebase_app_distribution', '~> 0.10'
12+
gem 'fastlane-plugin-firebase_app_distribution', '~> 1.0'
1313
gem 'fastlane-plugin-sentry'
1414
# This comment avoids typing to switch to a development version for testing.
1515
#
1616
# gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', ref: ''
17-
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.1'
17+
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.2'
1818
gem 'rake'
1919
gem 'rubocop', '~> 1.85'
2020
gem 'rubocop-rake', '~> 0.7'

Gemfile.lock

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,26 @@ GEM
33
specs:
44
CFPropertyList (3.0.8)
55
abbrev (0.1.2)
6-
activesupport (8.1.2)
6+
activesupport (7.2.3)
77
base64
8+
benchmark (>= 0.3)
89
bigdecimal
910
concurrent-ruby (~> 1.0, >= 1.3.1)
1011
connection_pool (>= 2.2.5)
1112
drb
1213
i18n (>= 1.6, < 2)
13-
json
1414
logger (>= 1.4.2)
1515
minitest (>= 5.1)
1616
securerandom (>= 0.3)
1717
tzinfo (~> 2.0, >= 2.0.5)
18-
uri (>= 0.13.1)
1918
addressable (2.8.9)
2019
public_suffix (>= 2.0.2, < 8.0)
2120
artifactory (3.0.17)
2221
ast (2.4.3)
2322
atomos (0.1.3)
2423
aws-eventstream (1.4.0)
25-
aws-partitions (1.1220.0)
26-
aws-sdk-core (3.242.0)
24+
aws-partitions (1.1223.0)
25+
aws-sdk-core (3.243.0)
2726
aws-eventstream (~> 1, >= 1.3.0)
2827
aws-partitions (~> 1, >= 1.992.0)
2928
aws-sigv4 (~> 1.9)
@@ -34,8 +33,8 @@ GEM
3433
aws-sdk-kms (1.122.0)
3534
aws-sdk-core (~> 3, >= 3.241.4)
3635
aws-sigv4 (~> 1.5)
37-
aws-sdk-s3 (1.213.0)
38-
aws-sdk-core (~> 3, >= 3.241.4)
36+
aws-sdk-s3 (1.215.0)
37+
aws-sdk-core (~> 3, >= 3.243.0)
3938
aws-sdk-kms (~> 1)
4039
aws-sigv4 (~> 1.5)
4140
aws-sigv4 (1.12.1)
@@ -176,13 +175,14 @@ GEM
176175
xcodeproj (>= 1.13.0, < 2.0.0)
177176
xcpretty (~> 0.4.1)
178177
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
179-
fastlane-plugin-firebase_app_distribution (0.10.1)
180-
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
181-
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
182-
fastlane-plugin-sentry (2.1.1)
178+
fastlane-plugin-firebase_app_distribution (1.0.0)
179+
fastlane (>= 2.232.0)
180+
google-apis-firebaseappdistribution_v1 (>= 0.9.0)
181+
google-apis-firebaseappdistribution_v1alpha (>= 0.12.0)
182+
fastlane-plugin-sentry (2.2.0)
183183
os (~> 1.1, >= 1.1.4)
184-
fastlane-plugin-wpmreleasetoolkit (14.1.0)
185-
activesupport (>= 6.1.7.1)
184+
fastlane-plugin-wpmreleasetoolkit (14.2.0)
185+
activesupport (>= 6.1.7.1, < 8)
186186
buildkit (~> 1.5)
187187
chroma (= 0.2.0)
188188
diffy (~> 3.3)
@@ -223,10 +223,10 @@ GEM
223223
mutex_m
224224
representable (~> 3.0)
225225
retriable (>= 2.0, < 4.a)
226-
google-apis-firebaseappdistribution_v1 (0.3.0)
227-
google-apis-core (>= 0.11.0, < 2.a)
228-
google-apis-firebaseappdistribution_v1alpha (0.2.0)
229-
google-apis-core (>= 0.11.0, < 2.a)
226+
google-apis-firebaseappdistribution_v1 (0.17.0)
227+
google-apis-core (>= 0.15.0, < 2.a)
228+
google-apis-firebaseappdistribution_v1alpha (0.26.0)
229+
google-apis-core (>= 0.15.0, < 2.a)
230230
google-apis-iamcredentials_v1 (0.26.0)
231231
google-apis-core (>= 0.15.0, < 2.a)
232232
google-apis-playcustomapp_v1 (0.17.0)
@@ -264,8 +264,8 @@ GEM
264264
concurrent-ruby (~> 1.0)
265265
java-properties (0.3.0)
266266
jmespath (1.6.2)
267-
json (2.18.1)
268-
json-schema (6.1.0)
267+
json (2.19.1)
268+
json-schema (6.2.0)
269269
addressable (~> 2.8)
270270
bigdecimal (>= 3.1, < 5)
271271
jwt (2.10.2)
@@ -279,7 +279,7 @@ GEM
279279
locale (2.1.5)
280280
fiddle
281281
logger (1.7.0)
282-
mcp (0.7.1)
282+
mcp (0.8.0)
283283
json-schema (>= 4.1)
284284
mini_magick (4.13.2)
285285
mini_mime (1.1.5)
@@ -321,7 +321,7 @@ GEM
321321
highline (>= 1.6)
322322
options (~> 2.3.0)
323323
pstore (0.2.0)
324-
public_suffix (7.0.2)
324+
public_suffix (7.0.5)
325325
racc (1.8.1)
326326
rainbow (3.1.1)
327327
rake (13.3.1)
@@ -333,13 +333,13 @@ GEM
333333
declarative (< 0.1.0)
334334
trailblazer-option (>= 0.1.1, < 0.2.0)
335335
uber (< 0.2.0)
336-
retriable (3.2.1)
336+
retriable (3.4.0)
337337
rexml (3.4.4)
338338
rmagick (6.2.0)
339339
observer (~> 0.1)
340340
pkg-config (~> 1.4)
341341
rouge (3.28.0)
342-
rubocop (1.85.0)
342+
rubocop (1.85.1)
343343
json (~> 2.3)
344344
language_server-protocol (~> 3.17.0.2)
345345
lint_roller (~> 1.1.0)
@@ -388,7 +388,6 @@ GEM
388388
concurrent-ruby (~> 1.0)
389389
uber (0.1.0)
390390
unicode-display_width (2.6.0)
391-
uri (1.1.1)
392391
word_wrap (1.0.0)
393392
xcodeproj (1.27.0)
394393
CFPropertyList (>= 2.3.3, < 4.0)
@@ -409,9 +408,9 @@ DEPENDENCIES
409408
danger-dangermattic (~> 1.2)
410409
dotenv
411410
fastlane (~> 2.232)
412-
fastlane-plugin-firebase_app_distribution (~> 0.10)
411+
fastlane-plugin-firebase_app_distribution (~> 1.0)
413412
fastlane-plugin-sentry
414-
fastlane-plugin-wpmreleasetoolkit (~> 14.1)
413+
fastlane-plugin-wpmreleasetoolkit (~> 14.2)
415414
openssl (~> 4.0)
416415
rake
417416
rmagick (~> 6.2.0)

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: help dependencies
2+
3+
help: ## Show available targets
4+
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk -F ':.*## ' '{printf " %-20s %s\n", $$1, $$2}'
5+
6+
dependencies: ## Download and cache Gutenberg XCFrameworks
7+
./Scripts/download-gutenberg-xcframeworks.sh

Modules/Package.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,12 @@ let package = Package(
235235
),
236236
.target(
237237
name: "WordPressReader",
238-
dependencies: ["AsyncImageKit", "WordPressUI", "WordPressShared"],
238+
dependencies: [
239+
"AsyncImageKit",
240+
"WordPressUI",
241+
"WordPressShared",
242+
.product(name: "SwiftSoup", package: "SwiftSoup"),
243+
],
239244
resources: [.process("Resources")]
240245
),
241246
.testTarget(name: "JetpackStatsTests", dependencies: ["JetpackStats"]),
@@ -256,7 +261,8 @@ let package = Package(
256261
.testTarget(name: "WordPressSharedObjCTests", dependencies: [.target(name: "WordPressShared"), .target(name: "WordPressTesting")], swiftSettings: [.swiftLanguageMode(.v5)]),
257262
.testTarget(name: "WordPressUIUnitTests", dependencies: [.target(name: "WordPressUI")], swiftSettings: [.swiftLanguageMode(.v5)]),
258263
.testTarget(name: "WordPressCoreTests", dependencies: [.target(name: "WordPressCore")]),
259-
.testTarget(name: "WordPressIntelligenceTests", dependencies: [.target(name: "WordPressIntelligence")])
264+
.testTarget(name: "WordPressIntelligenceTests", dependencies: [.target(name: "WordPressIntelligence")]),
265+
.testTarget(name: "WordPressReaderTests", dependencies: [.target(name: "WordPressReader")])
260266
]
261267
)
262268

Modules/Sources/JetpackStats/Cards/TodayCard.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct TodayCard: View {
1515
}
1616

1717
var body: some View {
18-
VStack(alignment: .leading, spacing: 0) {
18+
VStack(alignment: .leading, spacing: 6) {
1919
HStack(spacing: 32) {
2020
VStack(alignment: .leading, spacing: 0) {
2121
headerView
@@ -71,7 +71,7 @@ struct TodayCard: View {
7171
.font(.caption.weight(.medium))
7272
}
7373
.foregroundStyle(Color.secondary)
74-
.offset(y: 3) // Get it close to the value
74+
.offset(y: 9) // Get it close to the value
7575
.dynamicTypeSize(...DynamicTypeSize.large)
7676
}
7777

@@ -151,7 +151,7 @@ struct TodayCard: View {
151151
)
152152
.frame(maxWidth: .infinity)
153153
.padding(.trailing, 32)
154-
.padding(.vertical, 2)
154+
.padding(.vertical, 6)
155155
.transition(.opacity.combined(with: .scale(scale: 0.97)))
156156
}
157157

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import Foundation
2+
import SwiftSoup
3+
4+
public enum ReaderPostParser {
5+
public enum InteractiveElement: Sendable {
6+
case gallery(Gallery)
7+
}
8+
9+
public struct Gallery: Sendable {
10+
public let images: [GalleryImage]
11+
}
12+
13+
public struct GalleryImage: Sendable {
14+
/// URL from the `src` attribute (displayed, possibly resized).
15+
public let src: URL
16+
/// Full-resolution URL from `data-orig-file`.
17+
public let originalFileURL: URL?
18+
/// Original dimensions from `data-orig-size` (e.g. "4032,3024").
19+
public let originalSize: CGSize?
20+
/// All srcset variants with their width descriptors.
21+
public let srcset: [SrcsetEntry]
22+
/// From `data-image-description`.
23+
public let description: String?
24+
/// From `data-image-caption`.
25+
public let caption: String?
26+
}
27+
28+
public struct SrcsetEntry: Sendable {
29+
public let url: URL
30+
public let width: Int
31+
}
32+
33+
/// Parses post HTML and returns interactive elements (galleries).
34+
public static func parse(_ html: String) -> [InteractiveElement] {
35+
guard let document = try? SwiftSoup.parse(html) else {
36+
return []
37+
}
38+
39+
var elements: [InteractiveElement] = []
40+
41+
// Supported gallery selectors (order matters for specificity)
42+
let selectors = [
43+
"figure.wp-block-gallery",
44+
"div.wp-block-gallery",
45+
"figure.wp-block-jetpack-tiled-gallery",
46+
"div.wp-block-jetpack-tiled-gallery",
47+
"div.tiled-gallery",
48+
"div.gallery"
49+
]
50+
51+
for selector in selectors {
52+
guard let containers = try? document.select(selector) else { continue }
53+
for container in containers {
54+
let images = parseImages(from: container)
55+
if !images.isEmpty {
56+
elements.append(.gallery(Gallery(images: images)))
57+
}
58+
// Remove the container so nested galleries aren't matched again
59+
try? container.remove()
60+
}
61+
}
62+
63+
return elements
64+
}
65+
66+
private static func parseImages(from container: Element) -> [GalleryImage] {
67+
guard let imgElements = try? container.select("img") else {
68+
return []
69+
}
70+
return imgElements.compactMap { parseImage(from: $0) }
71+
}
72+
73+
private static func parseImage(from img: Element) -> GalleryImage? {
74+
guard let srcString = try? img.attr("src"),
75+
!srcString.isEmpty,
76+
let src = URL(string: srcString) else {
77+
return nil
78+
}
79+
80+
let originalFileURL: URL? = {
81+
guard let value = try? img.attr("data-orig-file"), !value.isEmpty else { return nil }
82+
return URL(string: value)
83+
}()
84+
85+
let originalSize: CGSize? = {
86+
guard let value = try? img.attr("data-orig-size"), !value.isEmpty else { return nil }
87+
return parseSize(value)
88+
}()
89+
90+
let srcset: [SrcsetEntry] = {
91+
guard let value = try? img.attr("srcset"), !value.isEmpty else { return [] }
92+
return parseSrcset(value)
93+
}()
94+
95+
let description: String? = {
96+
guard let value = try? img.attr("data-image-description"), !value.isEmpty else { return nil }
97+
// Strip HTML tags from description
98+
return try? SwiftSoup.clean(value, Whitelist.none())
99+
}()
100+
101+
let caption: String? = {
102+
guard let value = try? img.attr("data-image-caption"), !value.isEmpty else { return nil }
103+
// Strip HTML tags from caption
104+
return try? SwiftSoup.clean(value, Whitelist.none())
105+
}()
106+
107+
return GalleryImage(
108+
src: src,
109+
originalFileURL: originalFileURL,
110+
originalSize: originalSize,
111+
srcset: srcset,
112+
description: description,
113+
caption: caption
114+
)
115+
}
116+
117+
/// Parses "W,H" format (e.g. "4032,3024") into CGSize.
118+
private static func parseSize(_ value: String) -> CGSize? {
119+
let parts = value.split(separator: ",")
120+
guard parts.count == 2,
121+
let width = Double(parts[0].trimmingCharacters(in: .whitespaces)),
122+
let height = Double(parts[1].trimmingCharacters(in: .whitespaces)) else {
123+
return nil
124+
}
125+
return CGSize(width: width, height: height)
126+
}
127+
128+
/// Parses srcset string (e.g. "url1 300w, url2 600w") into entries.
129+
private static func parseSrcset(_ value: String) -> [SrcsetEntry] {
130+
value.split(separator: ",").compactMap { entry in
131+
let parts = entry.trimmingCharacters(in: .whitespaces).split(separator: " ")
132+
guard parts.count == 2,
133+
let url = URL(string: String(parts[0])),
134+
let widthStr = parts[1].dropLast().description.nilIfEmpty,
135+
let width = Int(widthStr) else {
136+
return nil
137+
}
138+
return SrcsetEntry(url: url, width: width)
139+
}
140+
}
141+
}
142+
143+
private extension String {
144+
var nilIfEmpty: String? {
145+
isEmpty ? nil : self
146+
}
147+
}

Modules/Sources/WordPressUI/Views/AlertView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public struct AlertDismissButton: View {
102102
.frame(maxWidth: .infinity)
103103
}
104104
.buttonStyle(.borderedProminent)
105+
.buttonBorderShape(.roundedRectangle(radius: 8))
105106
.controlSize(.extraLarge)
106107
}
107108
}

0 commit comments

Comments
 (0)