From 125452f92c69e95a23baff9cd9b48910ab6edd4d Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Tue, 21 Mar 2023 21:05:56 +0100 Subject: [PATCH 1/6] Add VideoPress upload processor --- .../GutenbergVideoPressUploadProcessor.swift | 174 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 6 + 2 files changed, 180 insertions(+) create mode 100644 WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift new file mode 100644 index 000000000000..c4d0fdbe2ccd --- /dev/null +++ b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift @@ -0,0 +1,174 @@ +import Foundation +import Aztec + +class GutenbergVideoPressUploadProcessor: Processor { + + let mediaUploadID: Int32 + let serverMediaID: Int + let videoPressGUID: String + var videoPressURL: String = "" + + private struct VideoPressBlockKeys { + static var name = "wp:videopress/video" + static var id = "id" + static var guid = "guid" + static var resizeToParent = "resizeToParent" + static var cover = "cover" + static var autoplay = "autoplay" + static var controls = "controls" + static var loop = "loop" + static var muted = "muted" + static var playsinline = "playsinline" + static var poster = "poster" + static var preload = "preload" + static var seekbarColor = "seekbarColor" + static var seekbarPlayedColor = "seekbarPlayedColor" + static var seekbarLoadingColor = "seekbarLoadingColor" + static var useAverageColor = "useAverageColor" + } + + private struct VideoPressURLQueryParams { + static var resizeToParent = "resizeToParent" + static var cover = "cover" + static var autoPlay = "autoPlay" + static var controls = "controls" + static var loop = "loop" + static var muted = "muted" + static var persistVolume = "persistVolume" + static var playsinline = "playsinline" + static var posterUrl = "posterUrl" + static var preloadContent = "preloadContent" + static var sbc = "sbc" + static var sbpc = "sbpc" + static var sblc = "sblc" + static var useAverageColor = "useAverageColor" + } + + init(mediaUploadID: Int32, serverMediaID: Int, videoPressGUID: String) { + self.mediaUploadID = mediaUploadID + self.serverMediaID = serverMediaID + self.videoPressGUID = videoPressGUID + } + + lazy var videoPressHtmlProcessor = HTMLProcessor(for: "figure", replacer: { (figure) in + var attributes = figure.attributes + var html = "
" + html += "\n\(self.videoPressURL.escapeHtmlNamedEntities())\n" + html += "
" + return html + }) + + lazy var videoPressBlockProcessor = GutenbergBlockProcessor(for: VideoPressBlockKeys.name, replacer: { videoPressBlock in + guard let mediaID = videoPressBlock.attributes[VideoPressBlockKeys.id] as? Int, mediaID == self.mediaUploadID else { + return nil + } + var block = "" + + self.videoPressURL = self.getVideoPressURL(attributes) + block += self.videoPressHtmlProcessor.process(videoPressBlock.content) + + block += "" + return block + }) + + /// The VideoPress URL is built using the same logic we have in Jetpack: + /// https://github.com/Automattic/jetpack/blob/b1b826ab38690c5fad18789301ac81297a458878/projects/packages/videopress/src/client/lib/url/index.ts#L19-L67 + /// + /// In order to have a cleaner URL, we only set the options differing from the default VideoPress player settings: + /// - Autoplay: Turned OFF by default. + /// - Controls: Turned ON by default. + /// - Loop: Turned OFF by default. + /// - Muted: Turned OFF by default. + /// - Plays Inline: Turned OFF by default. + /// - Poster: No image by default. + /// - Preload: Metadata by default. + /// - SeekbarColor: No color by default. + /// - SeekbarPlayerColor: No color by default. + /// - SeekbarLoadingColor: No color by default. + /// - UseAverageColor: Turned ON by default. + func getVideoPressURL(_ attributes: [String: Any]) -> String { + // Setting default values + var options: [URLQueryItem] = [ + URLQueryItem(name: VideoPressURLQueryParams.resizeToParent, value: true.stringLiteral), + URLQueryItem(name: VideoPressURLQueryParams.cover, value: true.stringLiteral), + ] + + if let autoplay = attributes[VideoPressBlockKeys.autoplay] as? Bool, autoplay == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.autoPlay, value: autoplay.stringLiteral)) + } + if let controls = attributes[VideoPressBlockKeys.controls] as? Bool, controls == false { + options.append(URLQueryItem(name: VideoPressURLQueryParams.controls, value: controls.stringLiteral)) + } + if let loop = attributes[VideoPressBlockKeys.loop] as? Bool, loop == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.loop, value: loop.stringLiteral)) + } + if let muted = attributes[VideoPressBlockKeys.muted] as? Bool, muted == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.muted, value: muted.stringLiteral)) + options.append(URLQueryItem(name: VideoPressURLQueryParams.persistVolume, value: false.stringLiteral)) + } + if let playinline = attributes[VideoPressBlockKeys.playsinline] as? Bool, playinline == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.playsinline, value: playinline.stringLiteral)) + } + if let poster = attributes[VideoPressBlockKeys.poster] as? String, !poster.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.posterUrl, value: poster)) + } + if let preload = attributes[VideoPressBlockKeys.preload] as? String, !preload.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent, value: preload)) + } + else { + options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent, value: "metadata")) + } + if let seekbarColor = attributes[VideoPressBlockKeys.seekbarColor] as? String, !seekbarColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sbc, value: seekbarColor)) + } + if let seekbarPlayedColor = attributes[VideoPressBlockKeys.seekbarPlayedColor] as? String, !seekbarPlayedColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sbpc, value: seekbarPlayedColor)) + } + if let seekbarLoadingColor = attributes[VideoPressBlockKeys.seekbarLoadingColor] as? String, !seekbarLoadingColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sblc, value: seekbarLoadingColor)) + } + if let useAverageColor = attributes[VideoPressBlockKeys.useAverageColor] as? Bool { + if useAverageColor == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor, value: useAverageColor.stringLiteral)) + } + } + // Adding `useAverageColor` param as its default value is true + else { + options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor, value: true.stringLiteral)) + } + + guard let url = URL(string: "https://videopress.com/v/\(self.videoPressGUID)"), var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return "" + } + urlComponents.queryItems = options + guard + let query = urlComponents.query, + /// In web, the query parameters are encoded using `encodeURIComponent` that percent encodes reserved characters (like `/` and `:`) that are not strictly necessary to be encoded based on RFC 3986. + /// In the spirit of generating an URL with the same encoding, we encode using `urlHostAllowed` which percent encodes all reserved characters. + /// + /// References: + /// - https://en.wikipedia.org/wiki/URL_encoding#Reserved_characters + /// - https://www.ietf.org/rfc/rfc3986.txt + /// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + /// - https://github.com/WordPress/gutenberg/blob/1dfec0ab5f0977dcce2722bdfbe823926903e2a6/packages/url/src/build-query-string.js#L53 + let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { + return url.absoluteString + } + return "\(url.absoluteString)?\(encodedQuery)" + } + + func process(_ text: String) -> String { + return videoPressBlockProcessor.process(text) + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e17fa9f49fbb..e887d33639b9 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -532,6 +532,8 @@ 17FCA6811FD84B4600DBA9C8 /* NoticeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FCA6801FD84B4600DBA9C8 /* NoticeStore.swift */; }; 1A433B1D2254CBEE00AE7910 /* WordPressComRestApi+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A433B1C2254CBEE00AE7910 /* WordPressComRestApi+Defaults.swift */; }; 1ABA150822AE5F870039311A /* WordPressUIBundleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABA150722AE5F870039311A /* WordPressUIBundleTests.swift */; }; + 1D19C56329C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */; }; + 1D19C56429C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1E0462162566938300EB98EF /* GutenbergFileUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0462152566938300EB98EF /* GutenbergFileUploadProcessor.swift */; }; 1E0FF01E242BC572008DA898 /* GutenbergWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0FF01D242BC572008DA898 /* GutenbergWebViewController.swift */; }; @@ -6091,6 +6093,7 @@ 1A433B1C2254CBEE00AE7910 /* WordPressComRestApi+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComRestApi+Defaults.swift"; sourceTree = ""; }; 1ABA150722AE5F870039311A /* WordPressUIBundleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressUIBundleTests.swift; sourceTree = ""; }; 1BC96E982E9B1A6DD86AF491 /* Pods-WordPressShareExtension.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressShareExtension.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressShareExtension/Pods-WordPressShareExtension.release-alpha.xcconfig"; sourceTree = ""; }; + 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GutenbergVideoPressUploadProcessor.swift; sourceTree = ""; }; 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D6058910D05DD3D006BFB54 /* WordPress.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WordPress.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1E0462152566938300EB98EF /* GutenbergFileUploadProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFileUploadProcessor.swift; sourceTree = ""; }; @@ -17750,6 +17753,7 @@ 91138454228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift */, 4629E4202440C5B20002E15C /* GutenbergCoverUploadProcessor.swift */, F5E1577E25DE04E200EEEDFB /* GutenbergMediaFilesUploadProcessor.swift */, + 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */, 46638DF5244904A3006E8439 /* GutenbergBlockProcessor.swift */, ); path = Processors; @@ -21124,6 +21128,7 @@ 40EE947F2213213F00CD264F /* PublicizeConnectionStatsRecordValue+CoreDataClass.swift in Sources */, 1702BBE01CF3034E00766A33 /* DomainsService.swift in Sources */, 8B1CF00F2433902700578582 /* PasswordAlertController.swift in Sources */, + 1D19C56329C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */, 1770BD0D267A368100D5F8C0 /* BloggingRemindersPushPromptViewController.swift in Sources */, FA7F92B825E61C7E00502D2A /* ReaderTagsFooter.swift in Sources */, 80535DB82946C79700873161 /* JetpackBrandingMenuCardCell.swift in Sources */, @@ -24740,6 +24745,7 @@ FABB25352602FC2C00C8785C /* ReaderListTopic.swift in Sources */, FABB25362602FC2C00C8785C /* PublicizeService.swift in Sources */, FABB25372602FC2C00C8785C /* ActivityContentFactory.swift in Sources */, + 1D19C56429C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */, FABB25382602FC2C00C8785C /* TenorDataLoader.swift in Sources */, FABB25392602FC2C00C8785C /* OverviewCell.swift in Sources */, F4D829662931046F00038726 /* UIButton+Dismiss.swift in Sources */, From 056378fca97db25af29ec885421dda51c85ce57b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Tue, 21 Mar 2023 21:06:19 +0100 Subject: [PATCH 2/6] Add tests for VideoPress upload processor --- WordPress/WordPress.xcodeproj/project.pbxproj | 4 ++ ...enbergVideoPressUploadProcessorTests.swift | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 WordPress/WordPressTest/GutenbergVideoPressUploadProcessorTests.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e887d33639b9..5cb16dc37ad4 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -534,6 +534,7 @@ 1ABA150822AE5F870039311A /* WordPressUIBundleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABA150722AE5F870039311A /* WordPressUIBundleTests.swift */; }; 1D19C56329C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */; }; 1D19C56429C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */; }; + 1D19C56629C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D19C56529C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1E0462162566938300EB98EF /* GutenbergFileUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0462152566938300EB98EF /* GutenbergFileUploadProcessor.swift */; }; 1E0FF01E242BC572008DA898 /* GutenbergWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0FF01D242BC572008DA898 /* GutenbergWebViewController.swift */; }; @@ -6094,6 +6095,7 @@ 1ABA150722AE5F870039311A /* WordPressUIBundleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressUIBundleTests.swift; sourceTree = ""; }; 1BC96E982E9B1A6DD86AF491 /* Pods-WordPressShareExtension.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressShareExtension.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressShareExtension/Pods-WordPressShareExtension.release-alpha.xcconfig"; sourceTree = ""; }; 1D19C56229C9D9A700FB0087 /* GutenbergVideoPressUploadProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GutenbergVideoPressUploadProcessor.swift; sourceTree = ""; }; + 1D19C56529C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GutenbergVideoPressUploadProcessorTests.swift; sourceTree = ""; }; 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D6058910D05DD3D006BFB54 /* WordPress.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WordPress.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1E0462152566938300EB98EF /* GutenbergFileUploadProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFileUploadProcessor.swift; sourceTree = ""; }; @@ -17795,6 +17797,7 @@ FF2EC3C12209AC19006176E1 /* GutenbergImgUploadProcessorTests.swift */, FF1B11E6238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift */, 9123471A221449E200BD9F97 /* GutenbergInformativeDialogTests.swift */, + 1D19C56529C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift */, FF0B2566237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift */, 4629E4222440C8160002E15C /* GutenbergCoverUploadProcessorTests.swift */, AEE082892681C23C00DCF54B /* GutenbergRefactoredGalleryUploadProcessorTests.swift */, @@ -23148,6 +23151,7 @@ 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */, 400199AB222590E100EB0906 /* AllTimeStatsRecordValueTests.swift in Sources */, C8567498243F41CA001A995E /* MockTenorService.swift in Sources */, + 1D19C56629C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift in Sources */, 4A9314DC297790C300360232 /* PeopleServiceTests.swift in Sources */, 4054F4642214F94D00D261AB /* StreakStatsRecordValueTests.swift in Sources */, 85B125411B028E34008A3D95 /* PushAuthenticationManagerTests.swift in Sources */, diff --git a/WordPress/WordPressTest/GutenbergVideoPressUploadProcessorTests.swift b/WordPress/WordPressTest/GutenbergVideoPressUploadProcessorTests.swift new file mode 100644 index 000000000000..09bb804e28d9 --- /dev/null +++ b/WordPress/WordPressTest/GutenbergVideoPressUploadProcessorTests.swift @@ -0,0 +1,56 @@ +import Foundation +import XCTest +@testable import WordPress + +class GutenbergVideoPressUploadProcessorTests: XCTestCase { + + let blockWithDefaultAttrsContent = """ + +
+ + """ + + let blockWithDefaultAttrsResultContent = """ + +
+ https://videopress.com/v/AbCdE?resizeToParent=true&cover=true&preloadContent=metadata&useAverageColor=true +
+ + """ + + func testVideoPressBlockWithDefaultAttrsProcessor() { + let gutenbergMediaUploadID = Int32(-181231834) + let mediaID = 100 + let videoPressGUID = "AbCdE" + + let gutenbergVideoPressUploadProcessor = GutenbergVideoPressUploadProcessor(mediaUploadID: gutenbergMediaUploadID, serverMediaID: mediaID, videoPressGUID: videoPressGUID) + let resultContent = gutenbergVideoPressUploadProcessor.process(blockWithDefaultAttrsContent) + + XCTAssertEqual(resultContent, blockWithDefaultAttrsResultContent, "Post content should be updated correctly") + } + + let blockWithAttrsContent = """ + +
+ + """ + + let blockWithAttrsResultContent = """ + +
+ https://videopress.com/v/AbCdE?resizeToParent=true&cover=true&autoPlay=true&controls=false&loop=true&muted=true&persistVolume=false&playsinline=true&posterUrl=https%3A%2F%2Ftest.files.wordpress.com%2F2022%2F02%2F265-5000x5000-1.jpeg&preloadContent=none&sbc=%23abb8c3&sbpc=%239b51e0&sblc=%23cf2e2e +
+ + """ + + func testVideoPressBlockWithAttrsProcessor() { + let gutenbergMediaUploadID = Int32(-181231834) + let mediaID = 100 + let videoPressGUID = "AbCdE" + + let gutenbergVideoPressUploadProcessor = GutenbergVideoPressUploadProcessor(mediaUploadID: gutenbergMediaUploadID, serverMediaID: mediaID, videoPressGUID: videoPressGUID) + let resultContent = gutenbergVideoPressUploadProcessor.process(blockWithAttrsContent) + + XCTAssertEqual(resultContent, blockWithAttrsResultContent, "Post content should be updated correctly") + } +} From 7cc5d89cc30d16a3dc50d88c8a797749f805ed38 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Tue, 21 Mar 2023 21:06:47 +0100 Subject: [PATCH 3/6] Add VideoPress upload processor in `PostCoordinator` --- WordPress/Classes/Services/PostCoordinator.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WordPress/Classes/Services/PostCoordinator.swift b/WordPress/Classes/Services/PostCoordinator.swift index 98996cdd8f83..a26a3ba81441 100644 --- a/WordPress/Classes/Services/PostCoordinator.swift +++ b/WordPress/Classes/Services/PostCoordinator.swift @@ -374,6 +374,11 @@ class PostCoordinator: NSObject { let gutenbergMediaFilesUploadProcessor = GutenbergMediaFilesUploadProcessor(mediaUploadID: gutenbergMediaUploadID, serverMediaID: mediaID, remoteURLString: remoteURLStr) gutenbergProcessors.append(gutenbergMediaFilesUploadProcessor) + if let videoPressGUID = media.videopressGUID { + let gutenbergVideoPressUploadProcessor = GutenbergVideoPressUploadProcessor(mediaUploadID: gutenbergMediaUploadID, serverMediaID: mediaID, videoPressGUID: videoPressGUID) + gutenbergProcessors.append(gutenbergVideoPressUploadProcessor) + } + } else if media.mediaType == .audio { let gutenbergAudioProcessor = GutenbergAudioUploadProcessor(mediaUploadID: gutenbergMediaUploadID, serverMediaID: mediaID, remoteURLString: remoteURLStr) gutenbergProcessors.append(gutenbergAudioProcessor) From dacbf5310066fa9c8b6717b2ea48d1586c31e151 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 23 Mar 2023 18:15:29 +0100 Subject: [PATCH 4/6] Use enum for VideoPress block and query param keys --- .../GutenbergVideoPressUploadProcessor.swift | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift index c4d0fdbe2ccd..d09885ff26b3 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift @@ -8,40 +8,40 @@ class GutenbergVideoPressUploadProcessor: Processor { let videoPressGUID: String var videoPressURL: String = "" - private struct VideoPressBlockKeys { - static var name = "wp:videopress/video" - static var id = "id" - static var guid = "guid" - static var resizeToParent = "resizeToParent" - static var cover = "cover" - static var autoplay = "autoplay" - static var controls = "controls" - static var loop = "loop" - static var muted = "muted" - static var playsinline = "playsinline" - static var poster = "poster" - static var preload = "preload" - static var seekbarColor = "seekbarColor" - static var seekbarPlayedColor = "seekbarPlayedColor" - static var seekbarLoadingColor = "seekbarLoadingColor" - static var useAverageColor = "useAverageColor" + private enum VideoPressBlockKeys: String { + case name = "wp:videopress/video" + case id + case guid + case resizeToParent + case cover + case autoplay + case controls + case loop + case muted + case playsinline + case poster + case preload + case seekbarColor + case seekbarPlayedColor + case seekbarLoadingColor + case useAverageColor } - private struct VideoPressURLQueryParams { - static var resizeToParent = "resizeToParent" - static var cover = "cover" - static var autoPlay = "autoPlay" - static var controls = "controls" - static var loop = "loop" - static var muted = "muted" - static var persistVolume = "persistVolume" - static var playsinline = "playsinline" - static var posterUrl = "posterUrl" - static var preloadContent = "preloadContent" - static var sbc = "sbc" - static var sbpc = "sbpc" - static var sblc = "sblc" - static var useAverageColor = "useAverageColor" + private enum VideoPressURLQueryParams: String { + case resizeToParent + case cover + case autoPlay + case controls + case loop + case muted + case persistVolume + case playsinline + case posterUrl + case preloadContent + case sbc + case sbpc + case sblc + case useAverageColor } init(mediaUploadID: Int32, serverMediaID: Int, videoPressGUID: String) { @@ -61,14 +61,14 @@ class GutenbergVideoPressUploadProcessor: Processor { return html }) - lazy var videoPressBlockProcessor = GutenbergBlockProcessor(for: VideoPressBlockKeys.name, replacer: { videoPressBlock in - guard let mediaID = videoPressBlock.attributes[VideoPressBlockKeys.id] as? Int, mediaID == self.mediaUploadID else { + lazy var videoPressBlockProcessor = GutenbergBlockProcessor(for: VideoPressBlockKeys.name.rawValue, replacer: { videoPressBlock in + guard let mediaID = videoPressBlock.attributes[VideoPressBlockKeys.id.rawValue] as? Int, mediaID == self.mediaUploadID else { return nil } var block = "" - self.videoPressURL = self.getVideoPressURL(attributes) + self.videoPressURL = VideoPressURL(attributes: attributes, guid: self.videoPressGUID) block += self.videoPressHtmlProcessor.process(videoPressBlock.content) block += "" @@ -84,88 +85,156 @@ class GutenbergVideoPressUploadProcessor: Processor { /// The VideoPress URL is built using the same logic we have in Jetpack: /// https://github.com/Automattic/jetpack/blob/b1b826ab38690c5fad18789301ac81297a458878/projects/packages/videopress/src/client/lib/url/index.ts#L19-L67 - /// - /// In order to have a cleaner URL, we only set the options differing from the default VideoPress player settings: - /// - Autoplay: Turned OFF by default. - /// - Controls: Turned ON by default. - /// - Loop: Turned OFF by default. - /// - Muted: Turned OFF by default. - /// - Plays Inline: Turned OFF by default. - /// - Poster: No image by default. - /// - Preload: Metadata by default. - /// - SeekbarColor: No color by default. - /// - SeekbarPlayerColor: No color by default. - /// - SeekbarLoadingColor: No color by default. - /// - UseAverageColor: Turned ON by default. - func getVideoPressURL(_ attributes: [String: Any]) -> String { - // Setting default values - var options: [URLQueryItem] = [ - URLQueryItem(name: VideoPressURLQueryParams.resizeToParent.rawValue, value: true.stringLiteral), - URLQueryItem(name: VideoPressURLQueryParams.cover.rawValue, value: true.stringLiteral), - ] - - if let autoplay = attributes[VideoPressBlockKeys.autoplay.rawValue] as? Bool, autoplay == true { - options.append(URLQueryItem(name: VideoPressURLQueryParams.autoPlay.rawValue, value: autoplay.stringLiteral)) - } - if let controls = attributes[VideoPressBlockKeys.controls.rawValue] as? Bool, controls == false { - options.append(URLQueryItem(name: VideoPressURLQueryParams.controls.rawValue, value: controls.stringLiteral)) - } - if let loop = attributes[VideoPressBlockKeys.loop.rawValue] as? Bool, loop == true { - options.append(URLQueryItem(name: VideoPressURLQueryParams.loop.rawValue, value: loop.stringLiteral)) - } - if let muted = attributes[VideoPressBlockKeys.muted.rawValue] as? Bool, muted == true { - options.append(URLQueryItem(name: VideoPressURLQueryParams.muted.rawValue, value: muted.stringLiteral)) - options.append(URLQueryItem(name: VideoPressURLQueryParams.persistVolume.rawValue, value: false.stringLiteral)) + private struct VideoPressURL { + let attributes: [String: Any] + let guid: String + private var options: [URLQueryItem] + + init(attributes: [String: Any], guid: String) { + self.attributes = attributes + self.guid = guid + self.options = [URLQueryItem]() + + // Populate options + addDefaultOptions() + addAutoPlayOption() + addControlsOption() + addLoopOption() + addVolumeOptions() + addPlaysInlineOption() + addPosterOption() + addPreloadOption() + addSeekbarOptions() + addUseAverageColorOption() } - if let playinline = attributes[VideoPressBlockKeys.playsinline.rawValue] as? Bool, playinline == true { - options.append(URLQueryItem(name: VideoPressURLQueryParams.playsinline.rawValue, value: playinline.stringLiteral)) + + /// Returns the string of the VideoPress URL. + func getURLString() -> String { + guard let url = URL(string: "https://videopress.com/v/\(guid)"), var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return "" + } + urlComponents.queryItems = options + guard + let query = urlComponents.query, + /// In web, the query parameters are encoded using `encodeURIComponent` that percent encodes reserved characters (like `/` and `:`) that are not strictly necessary to be encoded based on RFC 3986. + /// In the spirit of generating an URL with the same encoding, we encode using `urlHostAllowed` which percent encodes all reserved characters. + /// + /// References: + /// - https://en.wikipedia.org/wiki/URL_encoding#Reserved_characters + /// - https://www.ietf.org/rfc/rfc3986.txt + /// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + /// - https://github.com/WordPress/gutenberg/blob/1dfec0ab5f0977dcce2722bdfbe823926903e2a6/packages/url/src/build-query-string.js#L53 + let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { + return url.absoluteString + } + return "\(url.absoluteString)?\(encodedQuery)" } - if let poster = attributes[VideoPressBlockKeys.poster.rawValue] as? String, !poster.isEmpty { - options.append(URLQueryItem(name: VideoPressURLQueryParams.posterUrl.rawValue, value: poster)) + + /// Adds default options. + private mutating func addDefaultOptions() { + options += [ + URLQueryItem(name: VideoPressURLQueryParams.resizeToParent.rawValue, value: true.stringLiteral), + URLQueryItem(name: VideoPressURLQueryParams.cover.rawValue, value: true.stringLiteral), + ] } - if let preload = attributes[VideoPressBlockKeys.preload.rawValue] as? String, !preload.isEmpty { - options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent.rawValue, value: preload)) + + /// Adds AutoPlay option. + /// + /// Turned OFF by default. + private mutating func addAutoPlayOption() { + if let autoplay = attributes[VideoPressBlockKeys.autoplay.rawValue] as? Bool, autoplay == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.autoPlay.rawValue, value: autoplay.stringLiteral)) + } } - else { - options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent.rawValue, value: "metadata")) + + /// Adds Controls option. + /// + /// Turned ON by default. + private mutating func addControlsOption() { + if let controls = attributes[VideoPressBlockKeys.controls.rawValue] as? Bool, controls == false { + options.append(URLQueryItem(name: VideoPressURLQueryParams.controls.rawValue, value: controls.stringLiteral)) + } } - if let seekbarColor = attributes[VideoPressBlockKeys.seekbarColor.rawValue] as? String, !seekbarColor.isEmpty { - options.append(URLQueryItem(name: VideoPressURLQueryParams.sbc.rawValue, value: seekbarColor)) + + /// Adds Loop option. + /// + /// Turned OFF by default. + private mutating func addLoopOption() { + if let loop = attributes[VideoPressBlockKeys.loop.rawValue] as? Bool, loop == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.loop.rawValue, value: loop.stringLiteral)) + } } - if let seekbarPlayedColor = attributes[VideoPressBlockKeys.seekbarPlayedColor.rawValue] as? String, !seekbarPlayedColor.isEmpty { - options.append(URLQueryItem(name: VideoPressURLQueryParams.sbpc.rawValue, value: seekbarPlayedColor)) + + /// Adds Volume-related options. + /// + /// - Muted: Turned OFF by default. + private mutating func addVolumeOptions() { + if let muted = attributes[VideoPressBlockKeys.muted.rawValue] as? Bool, muted == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.muted.rawValue, value: muted.stringLiteral)) + options.append(URLQueryItem(name: VideoPressURLQueryParams.persistVolume.rawValue, value: false.stringLiteral)) + } } - if let seekbarLoadingColor = attributes[VideoPressBlockKeys.seekbarLoadingColor.rawValue] as? String, !seekbarLoadingColor.isEmpty { - options.append(URLQueryItem(name: VideoPressURLQueryParams.sblc.rawValue, value: seekbarLoadingColor)) + + /// Adds PlaysInline option. + /// + /// Turned OFF by default. + private mutating func addPlaysInlineOption() { + if let playinline = attributes[VideoPressBlockKeys.playsinline.rawValue] as? Bool, playinline == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.playsinline.rawValue, value: playinline.stringLiteral)) + } } - if let useAverageColor = attributes[VideoPressBlockKeys.useAverageColor.rawValue] as? Bool { - if useAverageColor == true { - options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor.rawValue, value: useAverageColor.stringLiteral)) + + /// Adds Poster option. + /// + /// No image by default. + private mutating func addPosterOption() { + if let poster = attributes[VideoPressBlockKeys.poster.rawValue] as? String, !poster.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.posterUrl.rawValue, value: poster)) } } - // Adding `useAverageColor` param as its default value is true - else { - options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor.rawValue, value: true.stringLiteral)) + + /// Adds Preload option. + /// + /// Metadata by default. + private mutating func addPreloadOption() { + if let preload = attributes[VideoPressBlockKeys.preload.rawValue] as? String, !preload.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent.rawValue, value: preload)) + } + else { + options.append(URLQueryItem(name: VideoPressURLQueryParams.preloadContent.rawValue, value: "metadata")) + } } - guard let url = URL(string: "https://videopress.com/v/\(self.videoPressGUID)"), var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return "" + /// Adds Seekbar options. + /// + /// - SeekbarColor: No color by default. + /// - SeekbarPlayerColor: No color by default. + /// - SeekbarLoadingColor: No color by default. + private mutating func addSeekbarOptions() { + if let seekbarColor = attributes[VideoPressBlockKeys.seekbarColor.rawValue] as? String, !seekbarColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sbc.rawValue, value: seekbarColor)) + } + if let seekbarPlayedColor = attributes[VideoPressBlockKeys.seekbarPlayedColor.rawValue] as? String, !seekbarPlayedColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sbpc.rawValue, value: seekbarPlayedColor)) + } + if let seekbarLoadingColor = attributes[VideoPressBlockKeys.seekbarLoadingColor.rawValue] as? String, !seekbarLoadingColor.isEmpty { + options.append(URLQueryItem(name: VideoPressURLQueryParams.sblc.rawValue, value: seekbarLoadingColor)) + } } - urlComponents.queryItems = options - guard - let query = urlComponents.query, - /// In web, the query parameters are encoded using `encodeURIComponent` that percent encodes reserved characters (like `/` and `:`) that are not strictly necessary to be encoded based on RFC 3986. - /// In the spirit of generating an URL with the same encoding, we encode using `urlHostAllowed` which percent encodes all reserved characters. - /// - /// References: - /// - https://en.wikipedia.org/wiki/URL_encoding#Reserved_characters - /// - https://www.ietf.org/rfc/rfc3986.txt - /// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent - /// - https://github.com/WordPress/gutenberg/blob/1dfec0ab5f0977dcce2722bdfbe823926903e2a6/packages/url/src/build-query-string.js#L53 - let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { - return url.absoluteString + + /// Adds UseAverageColor option. + /// + /// Turned ON by default. + private mutating func addUseAverageColorOption() { + if let useAverageColor = attributes[VideoPressBlockKeys.useAverageColor.rawValue] as? Bool { + if useAverageColor == true { + options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor.rawValue, value: useAverageColor.stringLiteral)) + } + } + else { + options.append(URLQueryItem(name: VideoPressURLQueryParams.useAverageColor.rawValue, value: true.stringLiteral)) + } } - return "\(url.absoluteString)?\(encodedQuery)" } func process(_ text: String) -> String { From 2ddbacb28a54ef608e1250256c4b05381d3662d4 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 24 Mar 2023 10:10:33 +0100 Subject: [PATCH 6/6] Fix VideoPress block processor --- .../Processors/GutenbergVideoPressUploadProcessor.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift index 340f9c84db9e..1f5b26fa51c4 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergVideoPressUploadProcessor.swift @@ -66,7 +66,7 @@ class GutenbergVideoPressUploadProcessor: Processor { guard let mediaID = videoPressBlock.attributes[VideoPressBlockKeys.id.rawValue] as? Int, mediaID == self.mediaUploadID else { return nil } - var block = "" + block += "" return block })