From 3ec17924e3476dbcc701ef8752d6f5d5242df446 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 21 Sep 2023 13:47:51 +0300 Subject: [PATCH 1/6] add html parser --- Podfile | 3 +- Podfile.lock | 10 ++- .../MarkdownParsingOperationFactory.swift | 86 +++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/Podfile b/Podfile index eeddf625ed..dcec44c231 100644 --- a/Podfile +++ b/Podfile @@ -25,7 +25,8 @@ abstract_target 'novawalletAll' do pod 'WalletConnectSwiftV2', :git => 'https://github.com/WalletConnect/WalletConnectSwiftV2.git', :tag => '1.5.14' pod 'EthereumSignTypedDataUtil', :git => 'https://github.com/ERussel/EthereumSignTypedDataUtil.git', :tag => '0.1.3' pod 'SwiftAlgorithms', '~> 1.0.0' - + pod 'ZMarkupParser' + target 'novawalletTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index aa52011eeb..c4ad16509c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -143,6 +143,9 @@ PODS: - Starscream (~> 4.0.4) - Web3Core (~> 3.0.6) - xxHash-Swift (1.0.13) + - ZMarkupParser (1.6.1): + - ZNSTextAttachment (~> 1.1.6) + - ZNSTextAttachment (1.1.6) DEPENDENCIES: - CDMarkdownKit (from `https://github.com/nova-wallet/CDMarkdownKit.git`, tag `2.5.2`) @@ -168,6 +171,7 @@ DEPENDENCIES: - SwiftyBeaver - WalletConnectSwiftV2 (from `https://github.com/WalletConnect/WalletConnectSwiftV2.git`, tag `1.5.14`) - web3swift (from `https://github.com/web3swift-team/web3swift.git`, tag `3.0.6`) + - ZMarkupParser SPEC REPOS: trunk: @@ -195,6 +199,8 @@ SPEC REPOS: - TweetNacl - Web3Core - xxHash-Swift + - ZMarkupParser + - ZNSTextAttachment EXTERNAL SOURCES: CDMarkdownKit: @@ -294,7 +300,9 @@ SPEC CHECKSUMS: Web3Core: 4a62a109cac056915d2d5023606438c89e229a1e web3swift: 944e76579b953a7b7e81dbb351c6dc0ed1defe63 xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 + ZMarkupParser: a92d31ba40695b790f1da5fec98c3d4505341aff + ZNSTextAttachment: 4a9b4e8ee1ed087fc893ae6657dfb678f1a00340 -PODFILE CHECKSUM: b9535fb877c890660e9ff1754c3d7881113d1030 +PODFILE CHECKSUM: 9da63967acbaf0ea90ab5cf6e0cef78c59d3668f COCOAPODS: 1.12.1 diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift index 70906207c5..58fe536560 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift @@ -105,3 +105,89 @@ final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProt createOperation(for: string, preferredWidth: preferredWidth, maxSize: maxSize) } } + +import ZMarkupParser + +protocol HtmlParsingOperationFactoryProtocol { + func createParseOperation(for string: NSAttributedString) -> BaseOperation +} + +final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { + private func createParser() -> ZHTMLParser { + ZHTMLParserBuilder.initWithDefault() + .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))) + .build() + } + + private func createOperation( + for string: NSAttributedString + ) -> BaseOperation { + ClosureOperation { + let parser: ZHTMLParser = self.createParser() + return parser.render(string) + } + } + + func createParseOperation(for string: NSAttributedString) -> BaseOperation { + createOperation(for: string) + } +} + +protocol MarkupParsingOperationFactoryProtocol { + func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper +} + +final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol { + let markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol + let htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol + let operationQueue: OperationQueue + private lazy var operationManager = OperationManager(operationQueue: operationQueue) + + init( + markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol, + htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol, + operationQueue: OperationQueue + ) { + self.operationQueue = operationQueue + self.markdownParsingOperationFactory = markdownParsingOperationFactory + self.htmlParsingOperationFactory = htmlParsingOperationFactory + } + + func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper { + let markdownOperation = markdownParsingOperationFactory.createParseOperation( + for: string, + preferredWidth: preferredWidth + ) + + let wrapper: CompoundOperationWrapper = OperationCombiningService.compoundWrapper( + operationManager: operationManager + ) { [weak self] in + let markdownResult = try markdownOperation.extractNoCancellableResultData() + + guard let self = self else { + return CompoundOperationWrapper.createWithResult(markdownResult) + } + + let htmlParseOperation = self.htmlParsingOperationFactory.createParseOperation(for: markdownResult.attributedString) + + let mergeOperation = ClosureOperation { + let htmlResult = try htmlParseOperation.extractNoCancellableResultData() + return MarkdownText( + originalString: markdownResult.originalString, + attributedString: htmlResult, + preferredSize: markdownResult.preferredSize, + isFull: markdownResult.isFull + ) + } + + mergeOperation.addDependency(htmlParseOperation) + + return CompoundOperationWrapper( + targetOperation: mergeOperation, + dependencies: htmlParseOperation.dependencies + markdownOperation.dependencies + ) + } + + return wrapper + } +} From 9197fd3ed0747dd7eeec84172e7e77e7667faefd Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 22 Sep 2023 11:27:53 +0300 Subject: [PATCH 2/6] tests --- novawallet.xcodeproj/project.pbxproj | 12 ++ .../View/HtmlParsingOperationFactory.swift | 99 +++++++++++++++ .../MarkdownParsingOperationFactory.swift | 86 ------------- .../View/MarkdownViewContainer.swift | 26 ++-- .../View/MarkupParsingOperationFactory.swift | 65 ++++++++++ .../MarkupParsingOperationFactoryTests.swift | 119 ++++++++++++++++++ 6 files changed, 313 insertions(+), 94 deletions(-) create mode 100644 novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift create mode 100644 novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift create mode 100644 novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 7b37e949cd..3970648569 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -674,6 +674,8 @@ 77799AEE2A7CFB6A00B7E564 /* DirectStakingTypeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77799AED2A7CFB6A00B7E564 /* DirectStakingTypeViewModel.swift */; }; 77799AF02A7CFB7C00B7E564 /* ValidatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77799AEF2A7CFB7C00B7E564 /* ValidatorViewModel.swift */; }; 77799AF22A7CFB8D00B7E564 /* PoolAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77799AF12A7CFB8D00B7E564 /* PoolAccountViewModel.swift */; }; + 777AE2AB2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777AE2AA2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift */; }; + 777AE2AD2ABCB4BD004989C0 /* MarkupParsingOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777AE2AC2ABCB4BD004989C0 /* MarkupParsingOperationFactory.swift */; }; 777BD86029F9730F004969A2 /* ReferendumsFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777BD85F29F9730F004969A2 /* ReferendumsFilterViewModel.swift */; }; 777BD86229F97322004969A2 /* ReferendumsFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777BD86129F97322004969A2 /* ReferendumsFilter.swift */; }; 777BD86429F979DA004969A2 /* SelectableFilterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777BD86329F979DA004969A2 /* SelectableFilterCell.swift */; }; @@ -720,6 +722,7 @@ 77AB55592AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB55582AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift */; }; 77AB555B2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB555A2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift */; }; 77AB555D2AA24BA90058814E /* OperationDetailsPoolRewardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB555C2AA24BA90058814E /* OperationDetailsPoolRewardView.swift */; }; + 77BAD3222ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */; }; 77CB33CE2A38780700B6709A /* structures_output.json in Resources */ = {isa = PBXBuildFile; fileRef = 77CB33CD2A38780700B6709A /* structures_output.json */; }; 77CB33D22A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CB33D12A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift */; }; 77CB33D72A3998FD00B6709A /* Array+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CB33D62A3998FC00B6709A /* Array+Sort.swift */; }; @@ -4620,6 +4623,8 @@ 77799AED2A7CFB6A00B7E564 /* DirectStakingTypeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectStakingTypeViewModel.swift; sourceTree = ""; }; 77799AEF2A7CFB7C00B7E564 /* ValidatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatorViewModel.swift; sourceTree = ""; }; 77799AF12A7CFB8D00B7E564 /* PoolAccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoolAccountViewModel.swift; sourceTree = ""; }; + 777AE2AA2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HtmlParsingOperationFactory.swift; sourceTree = ""; }; + 777AE2AC2ABCB4BD004989C0 /* MarkupParsingOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkupParsingOperationFactory.swift; sourceTree = ""; }; 777BD85F29F9730F004969A2 /* ReferendumsFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsFilterViewModel.swift; sourceTree = ""; }; 777BD86129F97322004969A2 /* ReferendumsFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsFilter.swift; sourceTree = ""; }; 777BD86329F979DA004969A2 /* SelectableFilterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableFilterCell.swift; sourceTree = ""; }; @@ -4666,6 +4671,7 @@ 77AB55582AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationPoolRewardOrSlashModel.swift; sourceTree = ""; }; 77AB555A2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationPoolRewardOrSlashViewModel.swift; sourceTree = ""; }; 77AB555C2AA24BA90058814E /* OperationDetailsPoolRewardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationDetailsPoolRewardView.swift; sourceTree = ""; }; + 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkupParsingOperationFactoryTests.swift; sourceTree = ""; }; 77CB33CD2A38780700B6709A /* structures_output.json */ = {isa = PBXFileReference; explicitFileType = text.json; fileEncoding = 4; path = structures_output.json; sourceTree = ""; usesTabs = 0; wrapsLines = 0; }; 77CB33D12A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifierWithCanonicalizationData.swift; sourceTree = ""; }; 77CB33D62A3998FC00B6709A /* Array+Sort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Sort.swift"; sourceTree = ""; }; @@ -10519,6 +10525,7 @@ children = ( 843461F9290E55D100379936 /* GovernanceUnlocksTestBuilding.swift */, 84741ADE290F116B00C98E17 /* Gov2UnlockScheduleTests.swift */, + 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */, ); path = Governance; sourceTree = ""; @@ -16212,6 +16219,8 @@ 849D3224291CC4A500D25839 /* MarkdownParsingOperationFactory.swift */, 8451720E298C495500489EF1 /* BorderedIconLabelView+TrackStyle.swift */, 777BD86729FA3376004969A2 /* ReferendumsSettingsCell.swift */, + 777AE2AA2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift */, + 777AE2AC2ABCB4BD004989C0 /* MarkupParsingOperationFactory.swift */, ); path = View; sourceTree = ""; @@ -20824,6 +20833,7 @@ 84CFF1EE26526FBC00DB7CF7 /* StakingBondMoreConfirmationProtocols.swift in Sources */, 8499FE7B27BE58A000712589 /* IdentityMapper.swift in Sources */, 845B89262959627A00EE25B0 /* SecurityLayerWireframe.swift in Sources */, + 777AE2AB2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift in Sources */, 8489A6D227FD5FB80040C066 /* StackActionCell.swift in Sources */, F429324F26280F6B00752C2C /* StakingRewardDetailsViewModel.swift in Sources */, F4A11B5A272FEB0B0030E85B /* CrowdloanYourContributionsCell.swift in Sources */, @@ -22093,6 +22103,7 @@ 0C9C64382A8D6949004DC078 /* NPoolsStakingSharedState.swift in Sources */, 84757E17299A2E1200616C6C /* Gov2SubscriptionFactory+Votes.swift in Sources */, 84F76ED629006A0900D7206C /* ReferendumConvictionView.swift in Sources */, + 777AE2AD2ABCB4BD004989C0 /* MarkupParsingOperationFactory.swift in Sources */, 9979A61C1677B7B1D44E58B4 /* ParitySignerTxQrProtocols.swift in Sources */, 0C13D3212A822B220054BB6F /* StartStakingDirectConfirmWireframe.swift in Sources */, 84CFE448292B9CB100CDDD7C /* OnChainTransferBaseInteractor.swift in Sources */, @@ -22749,6 +22760,7 @@ 842B2FCD2947239B002829B6 /* CoinGeckoUrlParserTests.swift in Sources */, 84B7C711289BFA79001A3566 /* OnboardingMainTests.swift in Sources */, 84625101263EDBD4001039B2 /* ExtrinsicServiceFactoryStub.swift in Sources */, + 77BAD3222ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift in Sources */, 846A2C54252A0FDF00731018 /* FilterTests.swift in Sources */, 84F13F0E26F1D9B1006725FF /* CrowdloadSettingsTests.swift in Sources */, 845C7F06263C45EC0024E797 /* AnyProviderAutoCleaner.swift in Sources */, diff --git a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift new file mode 100644 index 0000000000..f9985dc7bb --- /dev/null +++ b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift @@ -0,0 +1,99 @@ +import ZMarkupParser +import ZNSTextAttachment +import RobinHood + +protocol HtmlParsingOperationFactoryProtocol { + func createParseOperation(for string: NSAttributedString) -> BaseOperation +} + +final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { + let maxSize: Int? + + init(maxSize: Int? = nil) { + self.maxSize = maxSize + } + + private func style() -> MarkupStyle { + let textParagraphStyle = NSMutableParagraphStyle() + textParagraphStyle.paragraphSpacing = 8 + textParagraphStyle.paragraphSpacingBefore = 8 + + var style = MarkupStyle() + style.font = MarkupStyleFont(UIFont.systemFont(ofSize: 15)) + style.paragraphStyle = MarkupStyleParagraphStyle(textParagraphStyle) + style.foregroundColor = MarkupStyleColor(color: R.color.colorTextSecondary()!) + + return style + } + + private func createParser() -> ZHTMLParser { + var builder = ZHTMLParserBuilder() + var tags = Self.htmlTags + if maxSize == nil { + tags.append(Self.imageTag(handler: nil)) + } + for htmlTagName in tags { + builder = builder.add(htmlTagName) + } + for styleAttribute in ZHTMLParserBuilder.styleAttributes { + builder = builder.add(styleAttribute) + } + builder = builder.set(rootStyle: style()) + + return builder.build() + } + + private func createOperation( + for string: NSAttributedString + ) -> BaseOperation { + ClosureOperation { + let parser: ZHTMLParser = self.createParser() + return parser.render(string) + } + } + + func createParseOperation(for string: NSAttributedString) -> BaseOperation { + createOperation(for: string) + } +} + +extension HtmlParsingOperationFactory { + static var htmlTags: [HTMLTagName] { + [ + A_HTMLTagName(), + B_HTMLTagName(), + BR_HTMLTagName(), + DIV_HTMLTagName(), + HR_HTMLTagName(), + I_HTMLTagName(), + LI_HTMLTagName(), + OL_HTMLTagName(), + P_HTMLTagName(), + SPAN_HTMLTagName(), + STRONG_HTMLTagName(), + U_HTMLTagName(), + UL_HTMLTagName(), + DEL_HTMLTagName(), + TR_HTMLTagName(), + TD_HTMLTagName(), + TH_HTMLTagName(), + TABLE_HTMLTagName(), + FONT_HTMLTagName(), + H1_HTMLTagName(), + H2_HTMLTagName(), + H3_HTMLTagName(), + H4_HTMLTagName(), + H5_HTMLTagName(), + H6_HTMLTagName(), + S_HTMLTagName(), + PRE_HTMLTagName(), + CODE_HTMLTagName(), + EM_HTMLTagName(), + BLOCKQUOTE_HTMLTagName() + ] + } + + static func imageTag(handler: ZNSTextAttachmentHandler?) -> HTMLTagName { + IMG_HTMLTagName(handler: handler) + } +} diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift index 58fe536560..70906207c5 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift @@ -105,89 +105,3 @@ final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProt createOperation(for: string, preferredWidth: preferredWidth, maxSize: maxSize) } } - -import ZMarkupParser - -protocol HtmlParsingOperationFactoryProtocol { - func createParseOperation(for string: NSAttributedString) -> BaseOperation -} - -final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { - private func createParser() -> ZHTMLParser { - ZHTMLParserBuilder.initWithDefault() - .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))) - .build() - } - - private func createOperation( - for string: NSAttributedString - ) -> BaseOperation { - ClosureOperation { - let parser: ZHTMLParser = self.createParser() - return parser.render(string) - } - } - - func createParseOperation(for string: NSAttributedString) -> BaseOperation { - createOperation(for: string) - } -} - -protocol MarkupParsingOperationFactoryProtocol { - func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper -} - -final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol { - let markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol - let htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol - let operationQueue: OperationQueue - private lazy var operationManager = OperationManager(operationQueue: operationQueue) - - init( - markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol, - htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol, - operationQueue: OperationQueue - ) { - self.operationQueue = operationQueue - self.markdownParsingOperationFactory = markdownParsingOperationFactory - self.htmlParsingOperationFactory = htmlParsingOperationFactory - } - - func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper { - let markdownOperation = markdownParsingOperationFactory.createParseOperation( - for: string, - preferredWidth: preferredWidth - ) - - let wrapper: CompoundOperationWrapper = OperationCombiningService.compoundWrapper( - operationManager: operationManager - ) { [weak self] in - let markdownResult = try markdownOperation.extractNoCancellableResultData() - - guard let self = self else { - return CompoundOperationWrapper.createWithResult(markdownResult) - } - - let htmlParseOperation = self.htmlParsingOperationFactory.createParseOperation(for: markdownResult.attributedString) - - let mergeOperation = ClosureOperation { - let htmlResult = try htmlParseOperation.extractNoCancellableResultData() - return MarkdownText( - originalString: markdownResult.originalString, - attributedString: htmlResult, - preferredSize: markdownResult.preferredSize, - isFull: markdownResult.isFull - ) - } - - mergeOperation.addDependency(htmlParseOperation) - - return CompoundOperationWrapper( - targetOperation: mergeOperation, - dependencies: htmlParseOperation.dependencies + markdownOperation.dependencies - ) - } - - return wrapper - } -} diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift index 69805b7a95..f1a61bd9cb 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift @@ -17,7 +17,7 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { let operationQueue: OperationQueue - private let operationFactory: MarkdownParsingOperationFactoryProtocol + private let operationFactory: MarkupParsingOperationFactoryProtocol private var operation: CancellableCall? @@ -29,7 +29,15 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { operationQueue: OperationQueue = OperationQueue() ) { self.preferredWidth = preferredWidth - operationFactory = MarkdownParsingOperationFactory(maxSize: maxTextLength) + let markdownParsingOperationFactory = MarkdownParsingOperationFactory(maxSize: maxTextLength.map { $0 * 2 }) + let htmlParsingOperationFactory = HtmlParsingOperationFactory(maxSize: maxTextLength) + + operationFactory = MarkupParsingOperationFactory( + markdownParsingOperationFactory: markdownParsingOperationFactory, + htmlParsingOperationFactory: htmlParsingOperationFactory, + operationQueue: operationQueue + ) + self.operationQueue = operationQueue super.init(frame: .zero) @@ -105,17 +113,19 @@ extension MarkdownViewContainer { clear(cancellable: &operation) clearTextView() - let parsingOperation = operationFactory.createParseOperation(for: string, preferredWidth: preferredWidth) + let wrapper = operationFactory.createParseOperation(for: string, preferredWidth: preferredWidth) - parsingOperation.completionBlock = { [weak self] in + wrapper.targetOperation.completionBlock = { [weak self] in DispatchQueue.main.async { - guard self?.operation === parsingOperation else { + guard self?.operation === wrapper else { completion?(nil) return } do { - let model = try parsingOperation.extractNoCancellableResultData() + guard let model = try wrapper.targetOperation.extractNoCancellableResultData() else { + return + } self?.bind(model: model) completion?(model) } catch { @@ -124,9 +134,9 @@ extension MarkdownViewContainer { } } - operation = parsingOperation + operation = wrapper - operationQueue.addOperation(parsingOperation) + operationQueue.addOperations(wrapper.allOperations, waitUntilFinished: false) } } diff --git a/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift new file mode 100644 index 0000000000..41b93e51e3 --- /dev/null +++ b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift @@ -0,0 +1,65 @@ +import RobinHood + +protocol MarkupParsingOperationFactoryProtocol { + func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper +} + +final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol { + let markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol + let htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol + let operationQueue: OperationQueue + private lazy var operationManager = OperationManager(operationQueue: operationQueue) + + init( + markdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol, + htmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol, + operationQueue: OperationQueue + ) { + self.operationQueue = operationQueue + self.markdownParsingOperationFactory = markdownParsingOperationFactory + self.htmlParsingOperationFactory = htmlParsingOperationFactory + } + + func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper { + let markdownOperation = markdownParsingOperationFactory.createParseOperation( + for: string, + preferredWidth: preferredWidth + ) + + let wrapper: CompoundOperationWrapper = OperationCombiningService.compoundWrapper( + operationManager: operationManager + ) { [weak self] in + let markdownResult = try markdownOperation.extractNoCancellableResultData() + + guard let self = self else { + return CompoundOperationWrapper.createWithResult(markdownResult) + } + + let htmlParseOperation = self.htmlParsingOperationFactory.createParseOperation(for: markdownResult.attributedString) + + let mergeOperation = ClosureOperation { + let htmlResult = try htmlParseOperation.extractNoCancellableResultData() + return MarkdownText( + originalString: markdownResult.originalString, + attributedString: htmlResult, + preferredSize: markdownResult.preferredSize, + isFull: markdownResult.isFull + ) + } + + mergeOperation.addDependency(htmlParseOperation) + + return CompoundOperationWrapper( + targetOperation: mergeOperation, + dependencies: [htmlParseOperation] + ) + } + + wrapper.addDependency(operations: [markdownOperation]) + + return CompoundOperationWrapper( + targetOperation: wrapper.targetOperation, + dependencies: [markdownOperation] + wrapper.dependencies + ) + } +} diff --git a/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift b/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift new file mode 100644 index 0000000000..f70e724584 --- /dev/null +++ b/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift @@ -0,0 +1,119 @@ +import XCTest +@testable import novawallet + +class MarkupParsingOperationFactoryTests: XCTestCase { + + func testMarkupParse() throws { + let queue = OperationQueue() + let factory = MarkupParsingOperationFactory( + markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), + htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + operationQueue: queue) + let wrapper = factory.createParseOperation(for: TestData.markupTextInput, + preferredWidth: 350) + queue.addOperations(wrapper.allOperations, waitUntilFinished: true) + let result = try wrapper.targetOperation.extractNoCancellableResultData() + + XCTAssertEqual(result?.attributedString.string, TestData.markupTextOutput) + } + + func testMarkdownParse() throws { + let queue = OperationQueue() + let factory = MarkupParsingOperationFactory( + markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), + htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + operationQueue: queue) + let wrapper = factory.createParseOperation(for: TestData.markdownTextInput, + preferredWidth: 350) + queue.addOperations(wrapper.allOperations, waitUntilFinished: true) + let result = try wrapper.targetOperation.extractNoCancellableResultData() + + XCTAssertEqual(result?.attributedString.string, TestData.markdownTextOutput) + + } + + func testHtmlMarkdownParse() throws { + let queue = OperationQueue() + let factory = MarkupParsingOperationFactory( + markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), + htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + operationQueue: queue) + let wrapper = factory.createParseOperation(for: TestData.htmlWithMarkdownInput, + preferredWidth: 350) + queue.addOperations(wrapper.allOperations, waitUntilFinished: true) + let result = try wrapper.targetOperation.extractNoCancellableResultData() + + XCTAssertEqual(result?.attributedString.string, TestData.htmlWithMarkdownOutput) + + } + +} + + +enum TestData { + static var markupTextInput: String = """ +Этот текст содержит пример использования **Markdown** форматирования, а также фрагмент HTML кода. + + + + Пример HTML + + +

Это заголовок в HTML

+

Это абзац текста в HTML.

+ + +""" + static var markupTextOutput: String = """ +Этот текст содержит пример использования Markdown форматирования, а также фрагмент HTML кода. +Пример HTML +Это заголовок в HTML + +Это абзац текста в HTML. + +""" + + static var markdownTextInput = """ +**Header 1** +This is a paragraph of regular text. You can emphasize text with *italics* or **bold**, and also create [links](https://www.example.com). +- List item 1 +- List item 2 +- List item 3 +1. Numbered list item 1 +2. Numbered list item 2 +3. Numbered list item 3 +""" + + static var markdownTextOutput = """ +Header 1 +This is a paragraph of regular text. You can emphasize text with italics or bold, and also create links. + • List item 1 + • List item 2 + • List item 3 +1. Numbered list item 1 +2. Numbered list item 2 +3. Numbered list item 3 +""" + + static var htmlWithMarkdownInput = """ +**HTML and Markdown Example** +In this example, we will combine both HTML and Markdown to format text. +Here's some **bold** and *italic* text in Markdown, within an HTML `div` element: +
+ This is a Markdown paragraph inside an HTML div. + - You can create lists in Markdown. + - Like this one. + - And you can use HTML elements as well. +
+""" + static var htmlWithMarkdownOutput = """ +HTML and Markdown Example +In this example, we will combine both HTML and Markdown to format text. +Here's some bold and italic text in Markdown, within an HTML div element: +This is a Markdown paragraph inside an HTML div. + • You can create lists in Markdown. + • Like this one. + m• And you can use HTML elements as well. + +""" +} From 140177c377d72067d93fb60c94a441fc76c8d68a Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 22 Sep 2023 12:55:37 +0300 Subject: [PATCH 3/6] cleanup --- .../View/HtmlParsingOperationFactory.swift | 8 ++--- .../View/MarkdownViewContainer.swift | 4 +-- .../View/MarkupParsingOperationFactory.swift | 17 +++++----- .../MarkupParsingOperationFactoryTests.swift | 32 +++++++++---------- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift index f9985dc7bb..67abf228b1 100644 --- a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift @@ -7,10 +7,10 @@ protocol HtmlParsingOperationFactoryProtocol { } final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { - let maxSize: Int? + let includeImages: Bool - init(maxSize: Int? = nil) { - self.maxSize = maxSize + init(includeImages: Bool = true) { + self.includeImages = includeImages } private func style() -> MarkupStyle { @@ -29,7 +29,7 @@ final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { private func createParser() -> ZHTMLParser { var builder = ZHTMLParserBuilder() var tags = Self.htmlTags - if maxSize == nil { + if includeImages { tags.append(Self.imageTag(handler: nil)) } for htmlTagName in tags { diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift index f1a61bd9cb..51e20409c2 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift @@ -29,8 +29,8 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { operationQueue: OperationQueue = OperationQueue() ) { self.preferredWidth = preferredWidth - let markdownParsingOperationFactory = MarkdownParsingOperationFactory(maxSize: maxTextLength.map { $0 * 2 }) - let htmlParsingOperationFactory = HtmlParsingOperationFactory(maxSize: maxTextLength) + let markdownParsingOperationFactory = MarkdownParsingOperationFactory(maxSize: maxTextLength) + let htmlParsingOperationFactory = HtmlParsingOperationFactory(includeImages: maxTextLength == nil) operationFactory = MarkupParsingOperationFactory( markdownParsingOperationFactory: markdownParsingOperationFactory, diff --git a/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift index 41b93e51e3..98413b7e6f 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift @@ -29,21 +29,22 @@ final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol let wrapper: CompoundOperationWrapper = OperationCombiningService.compoundWrapper( operationManager: operationManager ) { [weak self] in - let markdownResult = try markdownOperation.extractNoCancellableResultData() + let markdownText = try markdownOperation.extractNoCancellableResultData() guard let self = self else { - return CompoundOperationWrapper.createWithResult(markdownResult) + return CompoundOperationWrapper.createWithResult(markdownText) } - let htmlParseOperation = self.htmlParsingOperationFactory.createParseOperation(for: markdownResult.attributedString) + let htmlParseOperation = self.htmlParsingOperationFactory + .createParseOperation(for: markdownText.attributedString) let mergeOperation = ClosureOperation { - let htmlResult = try htmlParseOperation.extractNoCancellableResultData() + let htmlText = try htmlParseOperation.extractNoCancellableResultData() return MarkdownText( - originalString: markdownResult.originalString, - attributedString: htmlResult, - preferredSize: markdownResult.preferredSize, - isFull: markdownResult.isFull + originalString: markdownText.originalString, + attributedString: htmlText, + preferredSize: markdownText.preferredSize, + isFull: markdownText.isFull ) } diff --git a/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift b/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift index f70e724584..3e314cfa9d 100644 --- a/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift +++ b/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift @@ -1,13 +1,13 @@ import XCTest @testable import novawallet -class MarkupParsingOperationFactoryTests: XCTestCase { +final class MarkupParsingOperationFactoryTests: XCTestCase { - func testMarkupParse() throws { + func testMarkupParsing() throws { let queue = OperationQueue() let factory = MarkupParsingOperationFactory( markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + htmlParsingOperationFactory: HtmlParsingOperationFactory(), operationQueue: queue) let wrapper = factory.createParseOperation(for: TestData.markupTextInput, preferredWidth: 350) @@ -17,11 +17,11 @@ class MarkupParsingOperationFactoryTests: XCTestCase { XCTAssertEqual(result?.attributedString.string, TestData.markupTextOutput) } - func testMarkdownParse() throws { + func testMarkdownParsing() throws { let queue = OperationQueue() let factory = MarkupParsingOperationFactory( markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + htmlParsingOperationFactory: HtmlParsingOperationFactory(), operationQueue: queue) let wrapper = factory.createParseOperation(for: TestData.markdownTextInput, preferredWidth: 350) @@ -32,11 +32,11 @@ class MarkupParsingOperationFactoryTests: XCTestCase { } - func testHtmlMarkdownParse() throws { + func testHtmlMarkdownParsing() throws { let queue = OperationQueue() let factory = MarkupParsingOperationFactory( markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(imageDetectionEnabled: true), + htmlParsingOperationFactory: HtmlParsingOperationFactory(), operationQueue: queue) let wrapper = factory.createParseOperation(for: TestData.htmlWithMarkdownInput, preferredWidth: 350) @@ -52,24 +52,24 @@ class MarkupParsingOperationFactoryTests: XCTestCase { enum TestData { static var markupTextInput: String = """ -Этот текст содержит пример использования **Markdown** форматирования, а также фрагмент HTML кода. +This text contains an example of **Markdown** formatting usage, as well as a fragment of HTML code. - Пример HTML + Sample HTML -

Это заголовок в HTML

-

Это абзац текста в HTML.

+

This is a HTML heading

+

This is a paragraph of text in HTML.

""" static var markupTextOutput: String = """ -Этот текст содержит пример использования Markdown форматирования, а также фрагмент HTML кода. -Пример HTML -Это заголовок в HTML +This text contains an example of Markdown formatting usage, as well as a fragment of HTML code. +Sample HTML +This is a HTML heading -Это абзац текста в HTML. +This is a paragraph of text in HTML. """ @@ -113,7 +113,7 @@ Here's some bold and italic text in Markdown, within an HTML div element: This is a Markdown paragraph inside an HTML div. • You can create lists in Markdown. • Like this one. - m• And you can use HTML elements as well. + • And you can use HTML elements as well. """ } From 9abf142fe1580221d17d76a4bd6fcafa00210277 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 25 Sep 2023 10:05:57 +0500 Subject: [PATCH 4/6] apply either markdown or html markup --- novawallet.xcodeproj/project.pbxproj | 12 ++- .../Extension/Foundation/String+Html.swift | 14 +++ ...nText.swift => MarkupAttributedText.swift} | 2 +- .../GovernanceDelegateInfoViewLayout.swift | 2 +- .../View/ReferendumDetailsTitleView.swift | 4 +- .../View/HtmlParsingOperationFactory.swift | 101 +++++++++++------- .../MarkdownParsingOperationFactory.swift | 32 +++--- .../View/MarkdownViewContainer.swift | 8 +- .../View/MarkupParsingOperationFactory.swift | 67 ++++++------ 9 files changed, 148 insertions(+), 94 deletions(-) create mode 100644 novawallet/Common/Extension/Foundation/String+Html.swift rename novawallet/Common/Model/{MarkdownText.swift => MarkupAttributedText.swift} (85%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 3970648569..0360fbe7ee 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -173,6 +173,7 @@ 0C7C886A2A95591900DD96A1 /* StakingTotalRewardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C88692A95591900DD96A1 /* StakingTotalRewardView.swift */; }; 0C7C886C2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C886B2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift */; }; 0C7C886E2A962B0D00DD96A1 /* StackAddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C886D2A962B0D00DD96A1 /* StackAddressCell.swift */; }; + 0C7C9B992ABFF355009A0362 /* String+Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C9B982ABFF355009A0362 /* String+Html.swift */; }; 0C7E7FAB2A9F27FB00596628 /* NominationPoolsRedeemCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E7FAA2A9F27FB00596628 /* NominationPoolsRedeemCall.swift */; }; 0C83775D2A4EEB380072102D /* AssetListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C83775C2A4EEB380072102D /* AssetListState.swift */; }; 0C893E6A2A65591C00781503 /* PoolsMultistakingUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C893E692A65591C00781503 /* PoolsMultistakingUpdateService.swift */; }; @@ -2198,7 +2199,7 @@ 849D14CA2994D9BC0048E947 /* StackIconTitleValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D14C92994D9BC0048E947 /* StackIconTitleValueCell.swift */; }; 849D14CC2994EEA50048E947 /* GovernanceDelegateFlowDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D14CB2994EEA50048E947 /* GovernanceDelegateFlowDisplayInfo.swift */; }; 849D3220291C26BA00D25839 /* GovernanceDAppList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D321F291C26BA00D25839 /* GovernanceDAppList.swift */; }; - 849D3223291CC43D00D25839 /* MarkdownText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D3222291CC43D00D25839 /* MarkdownText.swift */; }; + 849D3223291CC43D00D25839 /* MarkupAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D3222291CC43D00D25839 /* MarkupAttributedText.swift */; }; 849D3225291CC4A500D25839 /* MarkdownParsingOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D3224291CC4A500D25839 /* MarkdownParsingOperationFactory.swift */; }; 849D3227291CE25E00D25839 /* MarkdownViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D3226291CE25E00D25839 /* MarkdownViewContainer.swift */; }; 849D755B2756910A007726C3 /* RoundedView+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D755A2756910A007726C3 /* RoundedView+Styles.swift */; }; @@ -4123,6 +4124,7 @@ 0C7C88692A95591900DD96A1 /* StakingTotalRewardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingTotalRewardView.swift; sourceTree = ""; }; 0C7C886B2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingSelectedEntityViewModel.swift; sourceTree = ""; }; 0C7C886D2A962B0D00DD96A1 /* StackAddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackAddressCell.swift; sourceTree = ""; }; + 0C7C9B982ABFF355009A0362 /* String+Html.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Html.swift"; sourceTree = ""; }; 0C7E7FAA2A9F27FB00596628 /* NominationPoolsRedeemCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NominationPoolsRedeemCall.swift; sourceTree = ""; }; 0C83775C2A4EEB380072102D /* AssetListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListState.swift; sourceTree = ""; }; 0C893E692A65591C00781503 /* PoolsMultistakingUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoolsMultistakingUpdateService.swift; sourceTree = ""; }; @@ -6175,7 +6177,7 @@ 849D14C92994D9BC0048E947 /* StackIconTitleValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackIconTitleValueCell.swift; sourceTree = ""; }; 849D14CB2994EEA50048E947 /* GovernanceDelegateFlowDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateFlowDisplayInfo.swift; sourceTree = ""; }; 849D321F291C26BA00D25839 /* GovernanceDAppList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceDAppList.swift; sourceTree = ""; }; - 849D3222291CC43D00D25839 /* MarkdownText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownText.swift; sourceTree = ""; }; + 849D3222291CC43D00D25839 /* MarkupAttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkupAttributedText.swift; sourceTree = ""; }; 849D3224291CC4A500D25839 /* MarkdownParsingOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownParsingOperationFactory.swift; sourceTree = ""; }; 849D3226291CE25E00D25839 /* MarkdownViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownViewContainer.swift; sourceTree = ""; }; 849D755A2756910A007726C3 /* RoundedView+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RoundedView+Styles.swift"; sourceTree = ""; }; @@ -12816,7 +12818,7 @@ 887AFC8628BC95F0002A0422 /* MetaAccountChainResponse.swift */, 8831F0FF28C65B95009F7682 /* AssetLock.swift */, 2AC7BC832731A214001D99B0 /* AssetLocks+Sort.swift */, - 849D3222291CC43D00D25839 /* MarkdownText.swift */, + 849D3222291CC43D00D25839 /* MarkupAttributedText.swift */, 84BAD21F293B574900C55C49 /* MultichainToken.swift */, 77E255682A16148000B644C3 /* StakingRewardsFilter.swift */, 7756927C2A20B88200220756 /* TokenOperation.swift */, @@ -13113,6 +13115,7 @@ 0C1BE1A12A46F93B0010933C /* BigUInt+Scientific.swift */, 0CE550B22A49658700F0A7AC /* StakingDuration+Localizable.swift */, 77E0DC9D2A6940C400D03724 /* Calendar+Helpers.swift */, + 0C7C9B982ABFF355009A0362 /* String+Html.swift */, ); path = Foundation; sourceTree = ""; @@ -19325,6 +19328,7 @@ 88F33F1529CC1F92006125D5 /* Slip44CoinList.swift in Sources */, 84B018B026E0450F00C75E28 /* ValidatorStateView.swift in Sources */, AEE0C43A272A8B1F009F9AD5 /* AddChainAccount+AccountCreateWireframe.swift in Sources */, + 0C7C9B992ABFF355009A0362 /* String+Html.swift in Sources */, 0C7E7FAB2A9F27FB00596628 /* NominationPoolsRedeemCall.swift in Sources */, 84038FF226FFBE1900C73F3F /* JsonLocalSubscriptionHandler.swift in Sources */, 842898D1265A955A002D5D65 /* ImageViewModel.swift in Sources */, @@ -19736,7 +19740,7 @@ 8444D1BE2671465A00AF6D8C /* CrowdloanBonusServiceError.swift in Sources */, 8448F7A22882ABF50080CEA9 /* CustomSearchView.swift in Sources */, 84D2F1AF2774CEF00040C680 /* DAppURLBarView.swift in Sources */, - 849D3223291CC43D00D25839 /* MarkdownText.swift in Sources */, + 849D3223291CC43D00D25839 /* MarkupAttributedText.swift in Sources */, 0C77B5632A83747200B5AE08 /* StaticValidatorListViewLayout.swift in Sources */, 84FFE45B2862076F002432BB /* XcmUnweightedTransferRequest.swift in Sources */, 84D1110C26B922D50016D962 /* ConnectionPool.swift in Sources */, diff --git a/novawallet/Common/Extension/Foundation/String+Html.swift b/novawallet/Common/Extension/Foundation/String+Html.swift new file mode 100644 index 0000000000..7e4fd36eba --- /dev/null +++ b/novawallet/Common/Extension/Foundation/String+Html.swift @@ -0,0 +1,14 @@ +import Foundation + +extension String { + private static let htmlSpecificTags: Set = [ + "

", "", "", "
", "", "", "", "", "", "", + "", "" + ] + + func isHtml() -> Bool { + Self.htmlSpecificTags.contains { tag in + range(of: tag) != nil + } + } +} diff --git a/novawallet/Common/Model/MarkdownText.swift b/novawallet/Common/Model/MarkupAttributedText.swift similarity index 85% rename from novawallet/Common/Model/MarkdownText.swift rename to novawallet/Common/Model/MarkupAttributedText.swift index f4632aa5db..005c47ce83 100644 --- a/novawallet/Common/Model/MarkdownText.swift +++ b/novawallet/Common/Model/MarkupAttributedText.swift @@ -1,6 +1,6 @@ import UIKit -struct MarkdownText { +struct MarkupAttributedText { static let readMoreThreshold = 180 let originalString: String diff --git a/novawallet/Modules/Vote/Governance/Delegations/DelegateInfo/GovernanceDelegateInfoViewLayout.swift b/novawallet/Modules/Vote/Governance/Delegations/DelegateInfo/GovernanceDelegateInfoViewLayout.swift index 6747f35759..24e1ff74f3 100644 --- a/novawallet/Modules/Vote/Governance/Delegations/DelegateInfo/GovernanceDelegateInfoViewLayout.swift +++ b/novawallet/Modules/Vote/Governance/Delegations/DelegateInfo/GovernanceDelegateInfoViewLayout.swift @@ -127,7 +127,7 @@ final class GovernanceDelegateInfoViewLayout: UIView { if let details = viewModel.details { optDescriptionView = MarkdownViewContainer( preferredWidth: UIScreen.main.bounds.width - 2 * UIConstants.horizontalInset, - maxTextLength: MarkdownText.readMoreThreshold + maxTextLength: MarkupAttributedText.readMoreThreshold ) optDescriptionView?.load(from: details, completion: nil) diff --git a/novawallet/Modules/Vote/Governance/ReferendumDetails/View/ReferendumDetailsTitleView.swift b/novawallet/Modules/Vote/Governance/ReferendumDetails/View/ReferendumDetailsTitleView.swift index 0327b13145..f8c82b3ecd 100644 --- a/novawallet/Modules/Vote/Governance/ReferendumDetails/View/ReferendumDetailsTitleView.swift +++ b/novawallet/Modules/Vote/Governance/ReferendumDetails/View/ReferendumDetailsTitleView.swift @@ -40,7 +40,7 @@ final class ReferendumDetailsTitleView: UIView { let descriptionView = MarkdownViewContainer( preferredWidth: UIScreen.main.bounds.width - 2 * UIConstants.horizontalInset, - maxTextLength: MarkdownText.readMoreThreshold + maxTextLength: MarkupAttributedText.readMoreThreshold ) let moreButton: RoundedButton = .create { button in @@ -132,7 +132,7 @@ extension ReferendumDetailsTitleView { moreButton.isHidden = true descriptionView.isHidden = false - descriptionView.load(from: details.description) { [weak self] (model: MarkdownText?) in + descriptionView.load(from: details.description) { [weak self] (model: MarkupAttributedText?) in if let shouldReadMore = model?.isFull { self?.moreButton.isHidden = shouldReadMore } diff --git a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift index 67abf228b1..f197d7ef39 100644 --- a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift @@ -3,57 +3,84 @@ import ZNSTextAttachment import RobinHood protocol HtmlParsingOperationFactoryProtocol { - func createParseOperation(for string: NSAttributedString) -> BaseOperation + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> BaseOperation } final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { - let includeImages: Bool + let maxSize: Int? - init(includeImages: Bool = true) { - self.includeImages = includeImages + init(maxSize: Int?) { + self.maxSize = maxSize } - private func style() -> MarkupStyle { - let textParagraphStyle = NSMutableParagraphStyle() - textParagraphStyle.paragraphSpacing = 8 - textParagraphStyle.paragraphSpacingBefore = 8 + func createParseOperation( + for string: String, + preferredWidth: CGFloat, + maxSize: Int? + ) -> BaseOperation { + ClosureOperation { + var builder = ZHTMLParserBuilder() + var tags = Self.htmlTags - var style = MarkupStyle() - style.font = MarkupStyleFont(UIFont.systemFont(ofSize: 15)) - style.paragraphStyle = MarkupStyleParagraphStyle(textParagraphStyle) - style.foregroundColor = MarkupStyleColor(color: R.color.colorTextSecondary()!) + if maxSize == nil { + tags.append(Self.imageTag(handler: nil)) + } + for htmlTagName in tags { + builder = builder.add(htmlTagName) + } + for styleAttribute in ZHTMLParserBuilder.styleAttributes { + builder = builder.add(styleAttribute) + } - return style - } + var style = MarkupStyle() + style.font = MarkupStyleFont(UIFont.systemFont(ofSize: 15)) + style.foregroundColor = MarkupStyleColor(color: R.color.colorTextSecondary()!) - private func createParser() -> ZHTMLParser { - var builder = ZHTMLParserBuilder() - var tags = Self.htmlTags - if includeImages { - tags.append(Self.imageTag(handler: nil)) - } - for htmlTagName in tags { - builder = builder.add(htmlTagName) - } - for styleAttribute in ZHTMLParserBuilder.styleAttributes { - builder = builder.add(styleAttribute) - } - builder = builder.set(rootStyle: style()) + builder = builder.set(rootStyle: style) - return builder.build() - } + let attributedString: NSAttributedString + let isFull: Bool + + let parser = builder.build() + + if let maxSize = maxSize { + isFull = string.count <= maxSize + let preprocessed = string.convertToReadMore(after: 4 * maxSize) + let resultString = parser.render(preprocessed) + let maxLength = maxSize > resultString.length ? resultString.length : maxSize + attributedString = resultString.attributedSubstring( + from: NSRange(location: 0, length: maxLength) + ) + } else { + isFull = true + attributedString = parser.render(string) + } + + let preferredHeight = attributedString.boundingRect( + with: CGSize(width: preferredWidth, height: 0), + options: .usesLineFragmentOrigin, + context: nil + ).height + + let preferredSize = CGSize(width: preferredWidth, height: preferredHeight) - private func createOperation( - for string: NSAttributedString - ) -> BaseOperation { - ClosureOperation { - let parser: ZHTMLParser = self.createParser() - return parser.render(string) + return .init( + originalString: string, + attributedString: attributedString, + preferredSize: preferredSize, + isFull: isFull + ) } } - func createParseOperation(for string: NSAttributedString) -> BaseOperation { - createOperation(for: string) + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> BaseOperation { + createParseOperation(for: string, preferredWidth: preferredWidth, maxSize: maxSize) } } diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift index 70906207c5..9eeafbe9f8 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift @@ -4,7 +4,10 @@ import CDMarkdownKit import UIKit protocol MarkdownParsingOperationFactoryProtocol { - func createParseOperation(for string: String, preferredWidth: CGFloat) -> BaseOperation + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> BaseOperation } final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProtocol { @@ -65,25 +68,25 @@ final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProt for string: String, preferredWidth: CGFloat, maxSize: Int? - ) -> BaseOperation { - ClosureOperation { - let preprocessed: String + ) -> BaseOperation { + ClosureOperation { + let attributedString: NSAttributedString let isFull: Bool - let parser: CDMarkdownParser - if let maxSize = maxSize { isFull = string.count <= maxSize - preprocessed = string.convertToReadMore(after: maxSize) - parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: false) + let preprocessed = string.convertToReadMore(after: 4 * maxSize) + let parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: false) + let resultString = parser.parse(preprocessed) + + let maxLenth = maxSize > resultString.length ? resultString.length : maxSize + attributedString = resultString.attributedSubstring(from: NSRange(location: 0, length: maxLenth)) } else { isFull = true - preprocessed = string - parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: true) + let parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: true) + attributedString = parser.parse(string) } - let attributedString = parser.parse(preprocessed) - let preferredHeight = attributedString.boundingRect( with: CGSize(width: preferredWidth, height: 0), options: .usesLineFragmentOrigin, @@ -101,7 +104,10 @@ final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProt } } - func createParseOperation(for string: String, preferredWidth: CGFloat) -> BaseOperation { + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> BaseOperation { createOperation(for: string, preferredWidth: preferredWidth, maxSize: maxSize) } } diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift index 51e20409c2..3fd07f32fe 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownViewContainer.swift @@ -8,7 +8,7 @@ protocol MarkdownViewContainerDelegate: AnyObject { final class MarkdownViewContainer: UIView, AnyCancellableCleaning { private var textView: UITextView? - private var model: MarkdownText? + private var model: MarkupAttributedText? let preferredWidth: CGFloat override var intrinsicContentSize: CGSize { @@ -30,7 +30,7 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { ) { self.preferredWidth = preferredWidth let markdownParsingOperationFactory = MarkdownParsingOperationFactory(maxSize: maxTextLength) - let htmlParsingOperationFactory = HtmlParsingOperationFactory(includeImages: maxTextLength == nil) + let htmlParsingOperationFactory = HtmlParsingOperationFactory(maxSize: maxTextLength) operationFactory = MarkupParsingOperationFactory( markdownParsingOperationFactory: markdownParsingOperationFactory, @@ -92,7 +92,7 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { self.textView = textView } - private func bind(model: MarkdownText) { + private func bind(model: MarkupAttributedText) { self.model = model setupTextView(for: model.preferredSize, text: model.attributedString) @@ -102,7 +102,7 @@ final class MarkdownViewContainer: UIView, AnyCancellableCleaning { } extension MarkdownViewContainer { - func load(from string: String, completion: ((MarkdownText?) -> Void)?) { + func load(from string: String, completion: ((MarkupAttributedText?) -> Void)?) { guard model?.originalString != string else { completion?(model) return diff --git a/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift index 98413b7e6f..7aa2c09231 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkupParsingOperationFactory.swift @@ -1,7 +1,10 @@ import RobinHood protocol MarkupParsingOperationFactoryProtocol { - func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> CompoundOperationWrapper } final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol { @@ -20,47 +23,47 @@ final class MarkupParsingOperationFactory: MarkupParsingOperationFactoryProtocol self.htmlParsingOperationFactory = htmlParsingOperationFactory } - func createParseOperation(for string: String, preferredWidth: CGFloat) -> CompoundOperationWrapper { - let markdownOperation = markdownParsingOperationFactory.createParseOperation( - for: string, - preferredWidth: preferredWidth - ) - - let wrapper: CompoundOperationWrapper = OperationCombiningService.compoundWrapper( - operationManager: operationManager - ) { [weak self] in - let markdownText = try markdownOperation.extractNoCancellableResultData() + func createParseOperation( + for string: String, + preferredWidth: CGFloat + ) -> CompoundOperationWrapper { + let detectionOperation = ClosureOperation { + string.isHtml() + } - guard let self = self else { - return CompoundOperationWrapper.createWithResult(markdownText) - } + let markupOperation = OperationCombiningService( + operationManager: OperationManager(operationQueue: operationQueue) + ) { + let isHtml = try detectionOperation.extractNoCancellableResultData() - let htmlParseOperation = self.htmlParsingOperationFactory - .createParseOperation(for: markdownText.attributedString) + let operation: BaseOperation - let mergeOperation = ClosureOperation { - let htmlText = try htmlParseOperation.extractNoCancellableResultData() - return MarkdownText( - originalString: markdownText.originalString, - attributedString: htmlText, - preferredSize: markdownText.preferredSize, - isFull: markdownText.isFull + if isHtml { + operation = self.htmlParsingOperationFactory.createParseOperation( + for: string, + preferredWidth: preferredWidth + ) + } else { + operation = self.markdownParsingOperationFactory.createParseOperation( + for: string, + preferredWidth: preferredWidth ) } - mergeOperation.addDependency(htmlParseOperation) + return [CompoundOperationWrapper(targetOperation: operation)] + }.longrunOperation() + + markupOperation.addDependency(detectionOperation) - return CompoundOperationWrapper( - targetOperation: mergeOperation, - dependencies: [htmlParseOperation] - ) + let mergeOperation = ClosureOperation { + try markupOperation.extractNoCancellableResultData().first } - wrapper.addDependency(operations: [markdownOperation]) + mergeOperation.addDependency(markupOperation) - return CompoundOperationWrapper( - targetOperation: wrapper.targetOperation, - dependencies: [markdownOperation] + wrapper.dependencies + return CompoundOperationWrapper( + targetOperation: mergeOperation, + dependencies: [detectionOperation, markupOperation] ) } } From bef13d174219889396a6aea2718c6d0c256b8789 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 25 Sep 2023 12:54:36 +0500 Subject: [PATCH 5/6] add support truncation for attributed string --- novawallet.xcodeproj/project.pbxproj | 4 ++++ .../NSAttributedString+Helpers.swift | 19 +++++++++++++++++++ .../View/HtmlParsingOperationFactory.swift | 10 ++++------ .../MarkdownParsingOperationFactory.swift | 7 +++---- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 novawallet/Common/Extension/Foundation/NSAttributedString+Helpers.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 0360fbe7ee..0b79bbb4ed 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -174,6 +174,7 @@ 0C7C886C2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C886B2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift */; }; 0C7C886E2A962B0D00DD96A1 /* StackAddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C886D2A962B0D00DD96A1 /* StackAddressCell.swift */; }; 0C7C9B992ABFF355009A0362 /* String+Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C9B982ABFF355009A0362 /* String+Html.swift */; }; + 0C7C9B9B2AC16D7B009A0362 /* NSAttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C9B9A2AC16D7B009A0362 /* NSAttributedString+Helpers.swift */; }; 0C7E7FAB2A9F27FB00596628 /* NominationPoolsRedeemCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E7FAA2A9F27FB00596628 /* NominationPoolsRedeemCall.swift */; }; 0C83775D2A4EEB380072102D /* AssetListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C83775C2A4EEB380072102D /* AssetListState.swift */; }; 0C893E6A2A65591C00781503 /* PoolsMultistakingUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C893E692A65591C00781503 /* PoolsMultistakingUpdateService.swift */; }; @@ -4125,6 +4126,7 @@ 0C7C886B2A9622F800DD96A1 /* StakingSelectedEntityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingSelectedEntityViewModel.swift; sourceTree = ""; }; 0C7C886D2A962B0D00DD96A1 /* StackAddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackAddressCell.swift; sourceTree = ""; }; 0C7C9B982ABFF355009A0362 /* String+Html.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Html.swift"; sourceTree = ""; }; + 0C7C9B9A2AC16D7B009A0362 /* NSAttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Helpers.swift"; sourceTree = ""; }; 0C7E7FAA2A9F27FB00596628 /* NominationPoolsRedeemCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NominationPoolsRedeemCall.swift; sourceTree = ""; }; 0C83775C2A4EEB380072102D /* AssetListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListState.swift; sourceTree = ""; }; 0C893E692A65591C00781503 /* PoolsMultistakingUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoolsMultistakingUpdateService.swift; sourceTree = ""; }; @@ -13116,6 +13118,7 @@ 0CE550B22A49658700F0A7AC /* StakingDuration+Localizable.swift */, 77E0DC9D2A6940C400D03724 /* Calendar+Helpers.swift */, 0C7C9B982ABFF355009A0362 /* String+Html.swift */, + 0C7C9B9A2AC16D7B009A0362 /* NSAttributedString+Helpers.swift */, ); path = Foundation; sourceTree = ""; @@ -19728,6 +19731,7 @@ 84754C9C2513B26000854599 /* AccountPickerTableViewCell.swift in Sources */, 8472C5B1265CF9C500E2481B /* StakingRewardDestConfirmViewFactory.swift in Sources */, 84532D6128E4234700EF4ADC /* ConvictionVotingForKey.swift in Sources */, + 0C7C9B9B2AC16D7B009A0362 /* NSAttributedString+Helpers.swift in Sources */, F4488CF226143E0000AEE6DB /* EraRewardPoints.swift in Sources */, 849DF02F26C53DB900B702F4 /* RuntimeSyncEvents.swift in Sources */, 844DD65525EAF32D00B1DA97 /* SelectValidatorsStartViewModel.swift in Sources */, diff --git a/novawallet/Common/Extension/Foundation/NSAttributedString+Helpers.swift b/novawallet/Common/Extension/Foundation/NSAttributedString+Helpers.swift new file mode 100644 index 0000000000..279f1480a7 --- /dev/null +++ b/novawallet/Common/Extension/Foundation/NSAttributedString+Helpers.swift @@ -0,0 +1,19 @@ +import Foundation + +extension NSAttributedString { + func truncate(maxLength: Int) -> NSAttributedString { + if length > maxLength, maxLength > 0 { + let range = NSRange(location: 0, length: maxLength) + let mutableString = NSMutableAttributedString(attributedString: attributedSubstring(from: range)) + + let attributes = mutableString.attributes(at: maxLength - 1, effectiveRange: nil) + let truncation = NSAttributedString(string: String.readMore, attributes: attributes) + + mutableString.append(truncation) + + return mutableString + } else { + return self + } + } +} diff --git a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift index f197d7ef39..0b6d7a44ce 100644 --- a/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/HtmlParsingOperationFactory.swift @@ -47,13 +47,11 @@ final class HtmlParsingOperationFactory: HtmlParsingOperationFactoryProtocol { let parser = builder.build() if let maxSize = maxSize { - isFull = string.count <= maxSize - let preprocessed = string.convertToReadMore(after: 4 * maxSize) + let preprocessed = String(string.prefix(4 * maxSize)) let resultString = parser.render(preprocessed) - let maxLength = maxSize > resultString.length ? resultString.length : maxSize - attributedString = resultString.attributedSubstring( - from: NSRange(location: 0, length: maxLength) - ) + + attributedString = resultString.truncate(maxLength: maxSize) + isFull = resultString.length <= maxSize } else { isFull = true attributedString = parser.render(string) diff --git a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift index 9eeafbe9f8..212bbc552e 100644 --- a/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/View/MarkdownParsingOperationFactory.swift @@ -74,13 +74,12 @@ final class MarkdownParsingOperationFactory: MarkdownParsingOperationFactoryProt let isFull: Bool if let maxSize = maxSize { - isFull = string.count <= maxSize - let preprocessed = string.convertToReadMore(after: 4 * maxSize) + let preprocessed = String(string.prefix(4 * maxSize)) let parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: false) let resultString = parser.parse(preprocessed) - let maxLenth = maxSize > resultString.length ? resultString.length : maxSize - attributedString = resultString.attributedSubstring(from: NSRange(location: 0, length: maxLenth)) + attributedString = resultString.truncate(maxLength: maxSize) + isFull = resultString.length <= maxSize } else { isFull = true let parser = self.createMarkdownParser(for: preferredWidth, imageDetectionEnabled: true) From f08586879221cb10cb33ba540a842a18914ca0fc Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 25 Sep 2023 22:33:10 +0500 Subject: [PATCH 6/6] fix text --- novawallet.xcodeproj/project.pbxproj | 4 - .../MarkupParsingOperationFactoryTests.swift | 119 ------------------ 2 files changed, 123 deletions(-) delete mode 100644 novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index d3c288c65f..2ac49fc6f5 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -731,7 +731,6 @@ 77AB55592AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB55582AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift */; }; 77AB555B2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB555A2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift */; }; 77AB555D2AA24BA90058814E /* OperationDetailsPoolRewardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB555C2AA24BA90058814E /* OperationDetailsPoolRewardView.swift */; }; - 77BAD3222ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */; }; 77CB33CE2A38780700B6709A /* structures_output.json in Resources */ = {isa = PBXBuildFile; fileRef = 77CB33CD2A38780700B6709A /* structures_output.json */; }; 77CB33D22A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CB33D12A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift */; }; 77CB33D72A3998FD00B6709A /* Array+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CB33D62A3998FC00B6709A /* Array+Sort.swift */; }; @@ -4692,7 +4691,6 @@ 77AB55582AA244BB0058814E /* OperationPoolRewardOrSlashModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationPoolRewardOrSlashModel.swift; sourceTree = ""; }; 77AB555A2AA246CA0058814E /* OperationPoolRewardOrSlashViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationPoolRewardOrSlashViewModel.swift; sourceTree = ""; }; 77AB555C2AA24BA90058814E /* OperationDetailsPoolRewardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationDetailsPoolRewardView.swift; sourceTree = ""; }; - 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkupParsingOperationFactoryTests.swift; sourceTree = ""; }; 77CB33CD2A38780700B6709A /* structures_output.json */ = {isa = PBXFileReference; explicitFileType = text.json; fileEncoding = 4; path = structures_output.json; sourceTree = ""; usesTabs = 0; wrapsLines = 0; }; 77CB33D12A38893900B6709A /* Web3NameIntegrityVerifierWithCanonicalizationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifierWithCanonicalizationData.swift; sourceTree = ""; }; 77CB33D62A3998FC00B6709A /* Array+Sort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Sort.swift"; sourceTree = ""; }; @@ -10571,7 +10569,6 @@ children = ( 843461F9290E55D100379936 /* GovernanceUnlocksTestBuilding.swift */, 84741ADE290F116B00C98E17 /* Gov2UnlockScheduleTests.swift */, - 77BAD3212ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift */, ); path = Governance; sourceTree = ""; @@ -22824,7 +22821,6 @@ 842B2FCD2947239B002829B6 /* CoinGeckoUrlParserTests.swift in Sources */, 84B7C711289BFA79001A3566 /* OnboardingMainTests.swift in Sources */, 84625101263EDBD4001039B2 /* ExtrinsicServiceFactoryStub.swift in Sources */, - 77BAD3222ABC5CED005B9797 /* MarkupParsingOperationFactoryTests.swift in Sources */, 846A2C54252A0FDF00731018 /* FilterTests.swift in Sources */, 84F13F0E26F1D9B1006725FF /* CrowdloadSettingsTests.swift in Sources */, 845C7F06263C45EC0024E797 /* AnyProviderAutoCleaner.swift in Sources */, diff --git a/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift b/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift deleted file mode 100644 index 3e314cfa9d..0000000000 --- a/novawalletTests/Modules/Governance/MarkupParsingOperationFactoryTests.swift +++ /dev/null @@ -1,119 +0,0 @@ -import XCTest -@testable import novawallet - -final class MarkupParsingOperationFactoryTests: XCTestCase { - - func testMarkupParsing() throws { - let queue = OperationQueue() - let factory = MarkupParsingOperationFactory( - markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(), - operationQueue: queue) - let wrapper = factory.createParseOperation(for: TestData.markupTextInput, - preferredWidth: 350) - queue.addOperations(wrapper.allOperations, waitUntilFinished: true) - let result = try wrapper.targetOperation.extractNoCancellableResultData() - - XCTAssertEqual(result?.attributedString.string, TestData.markupTextOutput) - } - - func testMarkdownParsing() throws { - let queue = OperationQueue() - let factory = MarkupParsingOperationFactory( - markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(), - operationQueue: queue) - let wrapper = factory.createParseOperation(for: TestData.markdownTextInput, - preferredWidth: 350) - queue.addOperations(wrapper.allOperations, waitUntilFinished: true) - let result = try wrapper.targetOperation.extractNoCancellableResultData() - - XCTAssertEqual(result?.attributedString.string, TestData.markdownTextOutput) - - } - - func testHtmlMarkdownParsing() throws { - let queue = OperationQueue() - let factory = MarkupParsingOperationFactory( - markdownParsingOperationFactory: MarkdownParsingOperationFactory(maxSize: nil), - htmlParsingOperationFactory: HtmlParsingOperationFactory(), - operationQueue: queue) - let wrapper = factory.createParseOperation(for: TestData.htmlWithMarkdownInput, - preferredWidth: 350) - queue.addOperations(wrapper.allOperations, waitUntilFinished: true) - let result = try wrapper.targetOperation.extractNoCancellableResultData() - - XCTAssertEqual(result?.attributedString.string, TestData.htmlWithMarkdownOutput) - - } - -} - - -enum TestData { - static var markupTextInput: String = """ -This text contains an example of **Markdown** formatting usage, as well as a fragment of HTML code. - - - - Sample HTML - - -

This is a HTML heading

-

This is a paragraph of text in HTML.

- - -""" - static var markupTextOutput: String = """ -This text contains an example of Markdown formatting usage, as well as a fragment of HTML code. -Sample HTML -This is a HTML heading - -This is a paragraph of text in HTML. - -""" - - static var markdownTextInput = """ -**Header 1** -This is a paragraph of regular text. You can emphasize text with *italics* or **bold**, and also create [links](https://www.example.com). -- List item 1 -- List item 2 -- List item 3 -1. Numbered list item 1 -2. Numbered list item 2 -3. Numbered list item 3 -""" - - static var markdownTextOutput = """ -Header 1 -This is a paragraph of regular text. You can emphasize text with italics or bold, and also create links. - • List item 1 - • List item 2 - • List item 3 -1. Numbered list item 1 -2. Numbered list item 2 -3. Numbered list item 3 -""" - - static var htmlWithMarkdownInput = """ -**HTML and Markdown Example** -In this example, we will combine both HTML and Markdown to format text. -Here's some **bold** and *italic* text in Markdown, within an HTML `div` element: -
- This is a Markdown paragraph inside an HTML div. - - You can create lists in Markdown. - - Like this one. - - And you can use HTML elements as well. -
-""" - static var htmlWithMarkdownOutput = """ -HTML and Markdown Example -In this example, we will combine both HTML and Markdown to format text. -Here's some bold and italic text in Markdown, within an HTML div element: -This is a Markdown paragraph inside an HTML div. - • You can create lists in Markdown. - • Like this one. - • And you can use HTML elements as well. - -""" -}