├── .ruby-version ├── .xcode-version ├── Sources ├── Aztec │ ├── Classes │ │ ├── .gitkeep │ │ ├── Processor │ │ │ ├── Processor.swift │ │ │ ├── PipelineProcessor.swift │ │ │ ├── HTMLTreeProcessor.swift │ │ │ ├── RegexProcessor.swift │ │ │ └── ShortcodeAttribute.swift │ │ ├── Extensions │ │ │ ├── UILayoutPriority+Swift4.swift │ │ │ ├── NSAttributedString+CharacterName.swift │ │ │ ├── UIFont+Emoji.swift │ │ │ ├── DocumentReadingOptionKey+Swift4.swift │ │ │ ├── Array+Helpers.swift │ │ │ ├── DocumentType+Swift4.swift │ │ │ ├── UIStackView+Helpers.swift │ │ │ ├── NSBundle+AztecBundle.swift │ │ │ ├── NSAttributedString+Archive.swift │ │ │ ├── NSAttributedString+ReplaceOcurrences.swift │ │ │ ├── UITextView+Delegate.swift │ │ │ ├── StringUTF16+RangeConversion.swift │ │ │ ├── NSLayoutManager+Attachments.swift │ │ │ ├── NSMutableAttributedString+ParagraphProperty.swift │ │ │ ├── Array+ShortcodeAttribute.swift │ │ │ ├── UIColor+Parsers.swift │ │ │ ├── NSTextingResult+Helpers.swift │ │ │ ├── NSMutableAttributedString+ReplaceAttributes.swift │ │ │ ├── Array+Attribute.swift │ │ │ ├── NSAttributedStringKey+Conversion.swift │ │ │ ├── NSAttributedString+Analyzers.swift │ │ │ ├── UIFont+Traits.swift │ │ │ └── UITextView+Undoable.swift │ │ ├── Formatters │ │ │ ├── Implementations │ │ │ │ ├── BoldFormatter.swift │ │ │ │ ├── ItalicFormatter.swift │ │ │ │ ├── ColorFormatter.swift │ │ │ │ ├── StrikethroughFormatter.swift │ │ │ │ ├── CiteFormatter.swift │ │ │ │ ├── UnderlineFormatter.swift │ │ │ │ ├── SubscriptFormatter.swift │ │ │ │ ├── SuperscriptFormatter.swift │ │ │ │ └── FigureFormatter.swift │ │ │ └── Base │ │ │ │ └── FontFormatter.swift │ │ ├── NSAttributedString │ │ │ ├── Conversions │ │ │ │ ├── ParagraphPropertyConverters │ │ │ │ │ └── Base │ │ │ │ │ │ └── ParagraphPropertyConverter.swift │ │ │ │ └── AttachmentToElementConverter │ │ │ │ │ ├── CommentAttachmentToElementConverter.swift │ │ │ │ │ ├── HTMLAttachmentToElementConverter.swift │ │ │ │ │ ├── LineAttachmentToElementConverter.swift │ │ │ │ │ └── Base │ │ │ │ │ └── AttachmentToElementConverter.swift │ │ │ └── Attributes │ │ │ │ └── UnsupportedHTML.swift │ │ ├── Converters │ │ │ ├── StringAttributesToAttributes │ │ │ │ ├── Base │ │ │ │ │ └── StringAttributeConverter.swift │ │ │ │ ├── ConditionalConverters │ │ │ │ │ └── ConditionalItalicStringAttributeConverter.swift │ │ │ │ └── Implementations │ │ │ │ │ ├── UnderlineStringAttributeConverter.swift │ │ │ │ │ ├── SubscriptStringAttributeConverter.swift │ │ │ │ │ ├── SuperscriptStringAttributeConverter.swift │ │ │ │ │ └── ItalicStringAttributeConverter.swift │ │ │ ├── AttributesToStringAttributes │ │ │ │ ├── Base │ │ │ │ │ ├── MainAttributesConverter.swift │ │ │ │ │ └── ElementAttributeConverter.swift │ │ │ │ └── Implementations │ │ │ │ │ ├── UnderlineElementAttributeConverter.swift │ │ │ │ │ ├── ForegroundColorElementAttributeConverter.swift │ │ │ │ │ ├── BoldElementAttributeConverter.swift │ │ │ │ │ └── ItalicElementAttributeConverter.swift │ │ │ └── ElementsToAttributedString │ │ │ │ ├── Implementations │ │ │ │ ├── BRElementConverter.swift │ │ │ │ ├── CiteElementConverter.swift │ │ │ │ ├── FigcaptionElementConverter.swift │ │ │ │ ├── FigureElementConverter.swift │ │ │ │ └── HRElementConverter.swift │ │ │ │ └── Base │ │ │ │ ├── AttachmentElementConverter.swift │ │ │ │ ├── ElementConverter.swift │ │ │ │ └── FormatterElementConverter.swift │ │ ├── TextKit │ │ │ ├── Configuration.swift │ │ │ ├── ParagraphProperty │ │ │ │ ├── Figure.swift │ │ │ │ ├── HTMLPre.swift │ │ │ │ ├── HTMLDiv.swift │ │ │ │ ├── HTMLParagraph.swift │ │ │ │ ├── HTMLLi.swift │ │ │ │ ├── Blockquote.swift │ │ │ │ ├── Figcaption.swift │ │ │ │ └── ParagraphProperty.swift │ │ │ ├── FontProvider.swift │ │ │ ├── RenderableAttachment.swift │ │ │ └── LineAttachment.swift │ │ ├── GUI │ │ │ ├── Assets.swift │ │ │ └── FormatBar │ │ │ │ ├── FormatBarDelegate.swift │ │ │ │ └── FormattingIdentifier.swift │ │ ├── Constants │ │ │ └── Metrics.swift │ │ └── Plugin │ │ │ ├── PluginInputCustomizer.swift │ │ │ ├── Plugin.swift │ │ │ └── PluginOutputCustomizer.swift │ ├── Aztec.swift │ └── Assets │ │ └── Media.xcassets │ │ ├── Contents.json │ │ ├── play.imageset │ │ ├── gridicons-play.pdf │ │ └── Contents.json │ │ └── image.imageset │ │ ├── gridicons-image.pdf │ │ ├── gridicons-image-dark.pdf │ │ └── Contents.json ├── WordPressEditor │ ├── Assets │ │ └── aztec.png │ └── Classes │ │ ├── Plugins │ │ └── WordPressPlugin │ │ │ ├── Calypso │ │ │ ├── GalleryShortcode │ │ │ │ ├── GallerySupportedAttribute.swift │ │ │ │ ├── GalleryAttachment.swift │ │ │ │ ├── GalleryShortcodeInputProcessor.swift │ │ │ │ └── GalleryElementToTagConverter.swift │ │ │ └── Embeds │ │ │ │ └── WordPressPasteboardDelegate.swift │ │ │ ├── Gutenberg │ │ │ ├── GutenbergAttributeNames.swift │ │ │ ├── Gutenblock.swift │ │ │ ├── GutenpackAttachmentToElementConverter.swift │ │ │ ├── GutenbergAttributeDecoder.swift │ │ │ ├── GutenblockConverter.swift │ │ │ ├── GutenpackConverter.swift │ │ │ └── GutenbergAttributeEncoder.swift │ │ │ └── WordPressPlugin.swift │ │ └── Extensions │ │ ├── MediaAttachment+WordPress.swift │ │ └── VideoAttachment+WordPress.swift └── HTMLParser │ ├── DOM │ ├── Logic │ │ └── CSS │ │ │ ├── CSSAttributeMatcher.swift │ │ │ ├── ForegroundColorCSSAttributeMatcher.swift │ │ │ ├── ItalicCSSAttributeMatcher.swift │ │ │ ├── UnderlineCSSAttributeMatcher.swift │ │ │ └── BoldCSSAttributeMatcher.swift │ └── Data │ │ ├── CommentNode.swift │ │ ├── AttributeType.swift │ │ └── CSSAttributeType.swift │ ├── Converters │ └── In │ │ ├── InAttributesConverter.swift │ │ ├── InNodesConverter.swift │ │ └── InAttributeConverter.swift │ ├── Extensions │ ├── String+CharacterName.swift │ ├── Character+Name.swift │ └── String+HTML.swift │ ├── ElementsToHTML │ ├── Base │ │ └── ElementToStringConverter.swift │ └── Implementations │ │ └── GenericElementToTagConverter.swift │ └── HTMLToElements │ └── Converter.swift ├── docs └── resources │ ├── application.css │ ├── application.js │ ├── file_cpp.png │ ├── file_objc.png │ ├── xcov_logo.png │ ├── file_swift.png │ ├── main.js │ └── opensans.css ├── .bundle └── config ├── RepoAssets └── aztec.png ├── Example ├── Example │ ├── Images.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── ItunesArtwork@2x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ ├── SampleContent │ │ ├── failedMedia.html │ │ ├── videoShortcodes.html │ │ ├── gallery.html │ │ ├── underline.html │ │ ├── video.html │ │ ├── imagesOverlays.html │ │ ├── bigLists.html │ │ └── captions.html │ ├── UIImage+SaveTo.swift │ └── Info.plist ├── AztecExample.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Example.xcworkspace │ └── contents.xcworkspacedata └── AztecUITests │ ├── Info.plist │ ├── Pages │ ├── BlogsPage.swift │ ├── BasePage.swift │ └── EditLinkPage.swift │ └── Logger.swift ├── Tests ├── AztecTests │ ├── Resources │ │ ├── aztec.png │ │ ├── HTMLAttachmentRender_2x.png.dat │ │ ├── HTMLAttachmentRender_3x.png.dat │ │ ├── UIImageResizeImage1_2x.png.dat │ │ ├── UIImageResizeImage1_3x.png.dat │ │ ├── UIImageResizeImage2_2x.png.dat │ │ ├── UIImageResizeImage2_3x.png.dat │ │ ├── CommentAttachmentRender_2x.png.dat │ │ ├── CommentAttachmentRender_3x.png.dat │ │ └── README.md │ ├── TestingSupport │ │ ├── UIKit+Extensions.swift │ │ ├── NSBundle+AztecTestsBundle.swift │ │ ├── TextViewStubDelegate.swift │ │ ├── TextViewStub.swift │ │ └── TextViewStubAttachmentDelegate.swift │ ├── Processor │ │ ├── ShortcodeAttributeSerializerTests.swift │ │ ├── HTMLProcessorTests.swift │ │ └── HTMLTreeProcessorTests.swift │ ├── Aztec.xctestplan │ ├── Extensions │ │ ├── UIStackViewHelpersTests.swift │ │ ├── ArrayHelperTests.swift │ │ ├── NSMutableAttributedStringParagraphProperty.swift │ │ ├── UIColorHexParserTests.swift │ │ └── NSAttributedStringKeyHelperTests.swift │ ├── TextKit │ │ ├── ParagraphStyleTests.swift │ │ └── UnsupportedHTMLTests.swift │ ├── Importer │ │ └── InAttributeConverterTests.swift │ └── Converters │ │ ├── ElementToAttributedString │ │ └── GenericElementConverterTests.swift │ │ └── AttributesToStringAttributes │ │ ├── ItalicElementAttributeConverterTests.swift │ │ └── UnderlineElementAttributeConverterTests.swift ├── WordPressEditorTests │ ├── Resources │ │ ├── GutenpackAttachmentRender_2x.png │ │ └── GutenpackAttachmentRender_3x.png │ ├── Extensions │ │ ├── StringRegExTests.swift │ │ ├── VideoAttachmentWordPressTests.swift │ │ └── MediaAttachmentWordPressTests.swift │ └── WordPressPlugin │ │ ├── Calypso │ │ ├── GalleryShortcode │ │ │ ├── GalleryElementToTagConverterTests.swift │ │ │ └── GalleryShortcodeInputProcessorTests.swift │ │ └── AutopRemovep │ │ │ └── RemovePProcessorTests.swift │ │ └── Gutenberg │ │ └── GutenblockTests.swift └── HTMLParserTests │ └── HTML │ └── Nodes │ └── TextNodeTests.swift ├── Gemfile ├── Documentation └── resources │ ├── html_to_nsattributedstring.png │ └── nsattributedstring_to_html.png ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Scripts └── build.sh ├── .rubocop.yml ├── .editorconfig ├── .buildkite ├── shared-pipeline-vars ├── publish-aztec-pod.sh └── publish-editor-pod.sh ├── WordPress-Editor-iOS.podspec ├── Package.swift ├── .gitignore ├── WordPress-Aztec-iOS.podspec └── fastlane └── Fastfile /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /.xcode-version: -------------------------------------------------------------------------------- 1 | 16.4 2 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/resources/application.css: -------------------------------------------------------------------------------- 1 | //= require_self 2 | -------------------------------------------------------------------------------- /docs/resources/application.js: -------------------------------------------------------------------------------- 1 | //= require_self 2 | -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /Sources/Aztec/Aztec.swift: -------------------------------------------------------------------------------- 1 | @_exported import HTMLParser 2 | -------------------------------------------------------------------------------- /RepoAssets/aztec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/RepoAssets/aztec.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/resources/file_cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/docs/resources/file_cpp.png -------------------------------------------------------------------------------- /docs/resources/file_objc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/docs/resources/file_objc.png -------------------------------------------------------------------------------- /docs/resources/xcov_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/docs/resources/xcov_logo.png -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/resources/file_swift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/docs/resources/file_swift.png -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/aztec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/aztec.png -------------------------------------------------------------------------------- /Sources/WordPressEditor/Assets/aztec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Sources/WordPressEditor/Assets/aztec.png -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Processor/Processor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol Processor { 4 | func process(_ text: String) -> String 5 | } 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'cocoapods', '~> 1.12' 6 | gem 'fastlane', '~> 2.226' 7 | gem 'rubocop', '~> 1.18' 8 | -------------------------------------------------------------------------------- /Documentation/resources/html_to_nsattributedstring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Documentation/resources/html_to_nsattributedstring.png -------------------------------------------------------------------------------- /Documentation/resources/nsattributedstring_to_html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Documentation/resources/nsattributedstring_to_html.png -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/HTMLAttachmentRender_2x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/HTMLAttachmentRender_2x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/HTMLAttachmentRender_3x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/HTMLAttachmentRender_3x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/UIImageResizeImage1_2x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/UIImageResizeImage1_2x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/UIImageResizeImage1_3x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/UIImageResizeImage1_3x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/UIImageResizeImage2_2x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/UIImageResizeImage2_2x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/UIImageResizeImage2_3x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/UIImageResizeImage2_3x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/CommentAttachmentRender_2x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/CommentAttachmentRender_2x.png.dat -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/CommentAttachmentRender_3x.png.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/AztecTests/Resources/CommentAttachmentRender_3x.png.dat -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/play.imageset/gridicons-play.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Sources/Aztec/Assets/Media.xcassets/play.imageset/gridicons-play.pdf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/image.imageset/gridicons-image.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Sources/Aztec/Assets/Media.xcassets/image.imageset/gridicons-image.pdf -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Tests/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | 4 | ### Actual behavior 5 | 6 | 7 | ### Steps to reproduce the behavior 8 | 9 | 10 | ##### Tested on [device], iOS [version], Aztec [version] 11 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Example/Example/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/image.imageset/gridicons-image-dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/AztecEditor-iOS/develop/Sources/Aztec/Assets/Media.xcassets/image.imageset/gridicons-image-dark.pdf -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | To test: 4 | 5 | --- 6 | 7 | - [ ] I have considered if this change warrants release notes and have added them to the appropriate section in the `CHANGELOG.md` if necessary. 8 | -------------------------------------------------------------------------------- /Example/AztecExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/UILayoutPriority+Swift4.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | #if swift(>=4.0) 4 | #else 5 | extension UILayoutPriority { 6 | static let defaultLow = UILayoutPriorityDefaultLow 7 | } 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/Example/SampleContent/failedMedia.html: -------------------------------------------------------------------------------- 1 |

Video

2 | 3 | 4 |

Image

5 | Coyote 6 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/BoldFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class BoldFormatter: FontFormatter { 4 | 5 | init() { 6 | super.init(traits: .traitBold, htmlRepresentationKey: .boldHtmlRepresentation) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/ItalicFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ItalicFormatter: FontFormatter { 4 | 5 | init() { 6 | super.init(traits: .traitItalic, htmlRepresentationKey: .italicHtmlRepresentation) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/NSAttributedString/Conversions/ParagraphPropertyConverters/Base/ParagraphPropertyConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol ParagraphPropertyConverter { 4 | func convert(_ property: ParagraphProperty) -> ElementNode? 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gridicons-play.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/AztecTests/TestingSupport/UIKit+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIPasteboard { 4 | static let forTesting = UIPasteboard.withUniqueName() 5 | 6 | /// Remove all items from the pasteboard 7 | /// 8 | func reset() { 9 | self.items = [[:]] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/AztecTests/TestingSupport/NSBundle+AztecTestsBundle.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | @objc public class var aztecTestsBundle: Bundle { 5 | #if SWIFT_PACKAGE 6 | return .module 7 | #else 8 | return Bundle(for: TextViewStub.self) 9 | #endif 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSAttributedString+CharacterName.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSAttributedString { 4 | convenience init(_ characterName: Character.Name, attributes: [NSAttributedString.Key: Any]?) { 5 | self.init(string: String(characterName), attributes: attributes) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Example/AztecExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/resources/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $(".target-details").click(function() { 3 | var file_id = "#" + $(this).attr("target-id"); 4 | $(file_id).slideToggle(); 5 | }); 6 | 7 | $(".file-row").click(function() { 8 | var file_id = "#" + $(this).attr("file-id"); 9 | $(file_id).slideToggle(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/ColorFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ColorFormatter: StandardAttributeFormatter { 4 | 5 | init(color: UIColor = .black) { 6 | super.init(attributeKey: .foregroundColor, 7 | attributeValue: color, 8 | htmlRepresentationKey: .colorHtmlRepresentation) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Logic/CSS/CSSAttributeMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol CSSAttributeMatcher { 4 | func check(_ cssAttribute: CSSAttribute) -> Bool 5 | } 6 | 7 | open class NeverCSSAttributeMatcher: CSSAttributeMatcher { 8 | 9 | public init() {} 10 | 11 | public func check(_ cssAttribute: CSSAttribute) -> Bool { 12 | return false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/StrikethroughFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class StrikethroughFormatter: StandardAttributeFormatter { 4 | 5 | init() { 6 | super.init(attributeKey: .strikethroughStyle, 7 | attributeValue: NSUnderlineStyle.single.rawValue, 8 | htmlRepresentationKey: .strikethroughHtmlRepresentation) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Scripts/build.sh: -------------------------------------------------------------------------------- 1 | XCODE_WORKSPACE=Aztec.xcworkspace 2 | XCODE_PROJECT=Aztec.xcodeproj 3 | XCODE_SCHEME=AztecExample 4 | XCODE_SDK=iphonesimulator 5 | 6 | xcodebuild build test \ 7 | -workspace "$XCODE_WORKSPACE" \ 8 | -scheme "$XCODE_SCHEME" \ 9 | -sdk "$XCODE_SDK" \ 10 | -destination "name=iPhone SE" \ 11 | -configuration Debug | xcpretty -c && exit ${PIPESTATUS[0]} 12 | 13 | exit ${PIPESTATUS[0]} 14 | -------------------------------------------------------------------------------- /Sources/HTMLParser/Converters/In/InAttributesConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import libxml2 3 | 4 | 5 | /// Converts a C linked list of xmlAttr to [HTML.Attribute]. 6 | /// 7 | class InAttributesConverter: SafeCLinkedListToArrayConverter { 8 | 9 | required init() { 10 | super.init(elementConverter: InAttributeConverter(), next: { return $0.next }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/UIFont+Emoji.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | 5 | // MARK: - UIFont Emoji Helpers 6 | // 7 | extension UIFont { 8 | 9 | /// Indicates if the current font instance matches with iOS's Internal Emoji Font, or not. 10 | /// 11 | var isAppleEmojiFont: Bool { 12 | return fontName == ".AppleColorEmojiUI" || fontName == "AppleColorEmoji" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/HTMLParser/Extensions/String+CharacterName.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension String { 4 | 5 | /// Initializes this instance with the specified character. 6 | /// 7 | /// - Parameters: 8 | /// - characterName: the name of the character to initialize this String with. 9 | /// 10 | init(_ characterName: Character.Name) { 11 | self.init(Character(characterName)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Opt in to new cops by default 2 | AllCops: 3 | NewCops: enable 4 | 5 | Metrics/BlockLength: 6 | Exclude: 7 | - 'WordPress-Aztec-iOS.podspec' 8 | - 'WordPress-Editor-iOS.podspec' 9 | 10 | # Allow the Podspec filename to match the project 11 | Naming/FileName: 12 | Exclude: 13 | - 'WordPress-Aztec-iOS.podspec' 14 | - 'WordPress-Editor-iOS.podspec' 15 | - 'Example/Carthage/Checkouts/Gridicons-iOS/Gridicons.podspec' 16 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/NSAttributedString/Conversions/AttachmentToElementConverter/CommentAttachmentToElementConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class CommentAttachmentToElementConverter: AttachmentToElementConverter { 5 | func convert(_ attachment: CommentAttachment, attributes: [NSAttributedString.Key : Any]) -> [Node] { 6 | let node = CommentNode(text: attachment.text) 7 | 8 | return [node] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Apply to all files 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.swift] 16 | indent_size = 4 17 | 18 | [{*.h,*.m}] 19 | indent_size = 4 20 | 21 | # Ruby specific rules 22 | [{*.rb,Fastfile,Gemfile}] 23 | indent_style = space 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Calypso/GalleryShortcode/GallerySupportedAttribute.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum GallerySupportedAttribute: String { 4 | case columns = "columns" 5 | case ids = "ids" 6 | case order = "order" 7 | case orderBy = "orderBy" 8 | 9 | static func isSupported(_ attributeName: String) -> Bool { 10 | return GallerySupportedAttribute(rawValue: attributeName) != nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/DocumentReadingOptionKey+Swift4.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | #if swift(>=4.0) 4 | public typealias DocumentReadingOptionKey = NSAttributedString.DocumentReadingOptionKey 5 | #else 6 | public typealias DocumentReadingOptionKey = String 7 | #endif 8 | 9 | extension DocumentReadingOptionKey { 10 | #if swift(>=4.0) 11 | #else 12 | public static let documentType = NSDocumentTypeDocumentAttribute 13 | #endif 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Logic/CSS/ForegroundColorCSSAttributeMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class ForegroundColorCSSAttributeMatcher: CSSAttributeMatcher { 4 | 5 | public init() {} 6 | 7 | public func check(_ cssAttribute: CSSAttribute) -> Bool { 8 | guard let value = cssAttribute.value else { 9 | return false 10 | } 11 | 12 | return cssAttribute.type == .foregroundColor && !value.isEmpty 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Gutenberg/GutenbergAttributeNames.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import Foundation 3 | 4 | enum GutenbergAttribute: String { 5 | case selfCloser = "selfcloser" 6 | case blockOpener = "opener" 7 | case blockCloser = "closer" 8 | } 9 | 10 | extension Attribute { 11 | convenience init(name: GutenbergAttribute, value: Attribute.Value) { 12 | self.init(name: name.rawValue, value: value) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Logic/CSS/ItalicCSSAttributeMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class ItalicCSSAttributeMatcher: CSSAttributeMatcher { 4 | 5 | public init() {} 6 | 7 | public func check(_ cssAttribute: CSSAttribute) -> Bool { 8 | guard let value = cssAttribute.value else { 9 | return false 10 | } 11 | 12 | return cssAttribute.type == .fontStyle && value.contains(FontStyle.italic.rawValue) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/AztecTests/Resources/README.md: -------------------------------------------------------------------------------- 1 | The reason why some PNG files in the folder have a .dat extension, is because it prevents 2 | Xcode from compressing those images further when building the test target. 3 | 4 | This allows us to compare the data in these files with the images generated by the tests. 5 | 6 | To update any of these images simply execute in the console (with a well placed breakpoint): 7 | 8 | UIImagePNGRepresentation(image).write(to: URL(fileURLWithPath: "some/path")) 9 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/StringAttributesToAttributes/Base/StringAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | /// This protocol is implemented by all of our classes that convert NSAttributedString attributes 5 | /// into either ElementNodes or Attributes. 6 | /// 7 | public protocol StringAttributeConverter { 8 | 9 | func convert( 10 | attributes: [NSAttributedString.Key: Any], 11 | andAggregateWith elementNodes: [ElementNode]) -> [ElementNode] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Logic/CSS/UnderlineCSSAttributeMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class UnderlineCSSAttributeMatcher: CSSAttributeMatcher { 4 | 5 | public init() {} 6 | 7 | public func check(_ cssAttribute: CSSAttribute) -> Bool { 8 | guard let value = cssAttribute.value else { 9 | return false 10 | } 11 | 12 | return cssAttribute.type == .textDecoration && value.contains(TextDecoration.underline.rawValue) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Processor/PipelineProcessor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class PipelineProcessor: Processor { 4 | private let processors: [Processor] 5 | 6 | public init(_ processors: [Processor]) { 7 | self.processors = processors 8 | } 9 | 10 | open func process(_ text: String) -> String { 11 | return processors.reduce(text, { (previousText, processor) -> String in 12 | return processor.process(previousText) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/Array+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | // MARK: - Array Helpers 5 | // 6 | extension Array { 7 | 8 | /// Returns the last Element Index within the current collection, that satisfies the specified closure. 9 | /// 10 | func lastIndex(where block: ((Element) -> Bool)) -> Int? { 11 | for (index, element) in enumerated().reversed() where block(element) { 12 | return index 13 | } 14 | 15 | return nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/NSAttributedString/Conversions/AttachmentToElementConverter/HTMLAttachmentToElementConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class HTMLAttachmentToElementConverter: AttachmentToElementConverter { 5 | func convert(_ attachment: HTMLAttachment, attributes: [NSAttributedString.Key : Any]) -> [Node] { 6 | let htmlParser = HTMLParser() 7 | let rootNode = htmlParser.parse(attachment.rawHTML) 8 | 9 | return rootNode.children 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Gutenberg/Gutenblock.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import Foundation 3 | import UIKit 4 | 5 | class Gutenblock: ParagraphProperty { 6 | public init(storing representation: HTMLRepresentation? = nil) { 7 | super.init(with: representation) 8 | } 9 | 10 | required public init?(coder aDecoder: NSCoder){ 11 | super.init(coder: aDecoder) 12 | } 13 | 14 | override open class var supportsSecureCoding: Bool { true } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Aztec/Assets/Media.xcassets/image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gridicons-image.pdf" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "gridicons-image-dark.pdf", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ] 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/DocumentType+Swift4.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | #if swift(>=4.0) 4 | public typealias DocumentType = NSAttributedString.DocumentType 5 | #else 6 | public typealias DocumentType = String 7 | #endif 8 | 9 | extension DocumentType { 10 | #if swift(>=4.0) 11 | #else 12 | 13 | public static let plain = NSPlainTextDocumentType 14 | public static let rtf = NSRTFTextDocumentType 15 | public static let rtfd = NSRTFDTextDocumentType 16 | #endif 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/Configuration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Configuration { 4 | 5 | public static var headersWithBoldTrait = false 6 | public static var useDefaultFont = false 7 | 8 | static var defaultBoldFormatter: AttributeFormatter { 9 | get { 10 | if headersWithBoldTrait { 11 | return BoldWithShadowForHeadingFormatter() 12 | } else { 13 | return BoldFormatter() 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.buildkite/shared-pipeline-vars: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used 4 | # to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. 5 | 6 | # The ~> modifier is not currently used, but we check for it just in case 7 | XCODE_VERSION=$(sed -E 's/^~> ?//' .xcode-version) 8 | CI_TOOLKIT_PLUGIN_VERSION="5.3.1" 9 | 10 | export IMAGE_ID="xcode-$XCODE_VERSION" 11 | export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#$CI_TOOLKIT_PLUGIN_VERSION" 12 | -------------------------------------------------------------------------------- /Example/Example/SampleContent/videoShortcodes.html: -------------------------------------------------------------------------------- 1 |

WordPress Video Shortcode

2 | [video src="https://videos.files.wordpress.com/kUJmAcSf/bbb_sunflower_1080p_30fps_normal.mp4" poster="https://videos.files.wordpress.com/kUJmAcSf/bbb_sunflower_1080p_30fps_normal_scruberthumbnail_2.jpg" alt="Another video with bunnies"/] 3 | 4 |

Standard Video with source element

5 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/GUI/Assets.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class Assets { 5 | 6 | public static var playIcon: UIImage { 7 | let bundle = Bundle.aztecBundle 8 | let playImage = UIImage(named: "play", in: bundle, compatibleWith: nil)! 9 | return playImage 10 | } 11 | 12 | public static var imageIcon: UIImage { 13 | let bundle = Bundle.aztecBundle 14 | let playImage = UIImage(named: "image", in: bundle, compatibleWith: nil)! 15 | return playImage 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/HTMLParser/Converters/In/InNodesConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import libxml2 3 | 4 | 5 | /// Converts a C linked list of xmlNode to [HTML.Node]. 6 | /// 7 | class InNodesConverter: SafeCLinkedListToArrayConverter { 8 | 9 | let shouldCollapseSpaces: Bool 10 | 11 | required init(shouldCollapseSpaces: Bool = true) { 12 | self.shouldCollapseSpaces = shouldCollapseSpaces 13 | super.init(elementConverter: InNodeConverter(shouldCollapseSpaces: shouldCollapseSpaces), next: { return $0.next }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/Figure.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class Figure: ParagraphProperty { 4 | 5 | public override func encode(with aCoder: NSCoder) { 6 | super.encode(with: aCoder) 7 | } 8 | 9 | override open class var supportsSecureCoding: Bool { true } 10 | 11 | override public init(with representation: HTMLRepresentation? = nil) { 12 | super.init(with: representation) 13 | } 14 | 15 | required public init?(coder aDecoder: NSCoder){ 16 | super.init(coder: aDecoder) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/AztecTests/TestingSupport/TextViewStubDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Aztec 3 | import UIKit 4 | 5 | 6 | // MARK: - Wraps UITextView Delegate methods into callbacks, for Unit Testing purposes. 7 | // 8 | class TextViewStubDelegate: NSObject { 9 | 10 | /// Closure to be executed whenever `textViewDidChange` is executed. 11 | /// 12 | var onDidChange: (() -> Void)? 13 | 14 | } 15 | 16 | 17 | extension TextViewStubDelegate: UITextViewDelegate { 18 | 19 | func textViewDidChange(_ textView: UITextView) { 20 | onDidChange?() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Processor/HTMLTreeProcessor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol HTMLTreeProcessor { 4 | func process(_ rootNode: RootNode) 5 | } 6 | 7 | public class HTMLTreeProcessorPipeline: HTMLTreeProcessor { 8 | private let processors: [HTMLTreeProcessor] 9 | 10 | public required init(processors: [HTMLTreeProcessor]) { 11 | self.processors = processors 12 | } 13 | 14 | public func process(_ rootNode: RootNode) { 15 | for processor in processors { 16 | processor.process(rootNode) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Calypso/GalleryShortcode/GalleryAttachment.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | 3 | class GalleryAttachment: MediaAttachment { 4 | enum Order: String { 5 | case asc = "ASC" 6 | case desc = "DESC" 7 | } 8 | 9 | enum OrderBy: String { 10 | case menu = "menu_order" 11 | case title = "title" 12 | case postDate = "post_date" 13 | case rand = "rand" 14 | case ID = "ID" 15 | } 16 | 17 | var columns: Int? 18 | var ids: [Int]? 19 | var order: Order? 20 | var orderBy: OrderBy? 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/UIStackView+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | 5 | // MARK: - StackView Helpers 6 | // 7 | extension UIStackView { 8 | 9 | /// Adds a collection of arranged Subviews 10 | /// 11 | func addArrangedSubviews(_ subviews: [UIView]) { 12 | for subview in subviews { 13 | addArrangedSubview(subview) 14 | } 15 | } 16 | 17 | /// Removes a collection fo arranged subviews 18 | /// 19 | func removeArrangedSubviews(_ subviews: [UIView]) { 20 | for subview in subviews { 21 | removeArrangedSubview(subview) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Gutenberg/GutenpackAttachmentToElementConverter.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import Foundation 3 | 4 | class GutenpackAttachmentToElementConverter: AttachmentToElementConverter { 5 | 6 | let encoder = GutenbergAttributeEncoder() 7 | 8 | func convert(_ attachment: GutenpackAttachment, attributes: [NSAttributedString.Key : Any]) -> [Node] { 9 | let text = attachment.blockContent + "/" 10 | let attributes = [encoder.selfClosingAttribute(text)] 11 | let gutenpack = ElementNode(type: .gutenpack, attributes: attributes, children: []) 12 | 13 | return [gutenpack] 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Constants/Metrics.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | /// A collection of constants and metrics shared between the Aztec importer 5 | /// and the editor. 6 | /// 7 | public enum Metrics { 8 | 9 | public static var defaultIndentation = CGFloat(12) 10 | public static var maxIndentation = CGFloat(200) 11 | public static var listTextIndentation = CGFloat(12) 12 | public static var listTextCharIndentation = CGFloat(8) 13 | public static var listMinimumIndentChars = 3 14 | public static var tabStepInterval = 4 15 | public static var tabStepCount = 12 16 | public static var paragraphSpacing = CGFloat(6) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/HTMLPre.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class HTMLPre: ParagraphProperty { 4 | 5 | override public func encode(with aCoder: NSCoder) { 6 | super.encode(with: aCoder) 7 | } 8 | 9 | override open class var supportsSecureCoding: Bool { true } 10 | 11 | override public init(with representation: HTMLRepresentation? = nil) { 12 | super.init(with: representation) 13 | } 14 | 15 | required public init?(coder aDecoder: NSCoder){ 16 | super.init(coder: aDecoder) 17 | } 18 | 19 | static func ==(lhs: HTMLPre, rhs: HTMLPre) -> Bool { 20 | return lhs === rhs 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Logic/CSS/BoldCSSAttributeMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class BoldCSSAttributeMatcher: CSSAttributeMatcher { 4 | 5 | public init() {} 6 | 7 | public func check(_ cssAttribute: CSSAttribute) -> Bool { 8 | guard let value = cssAttribute.value, 9 | cssAttribute.type == .fontWeight else { 10 | return false 11 | } 12 | 13 | if let weight = FontWeight(rawValue: value) { 14 | return weight.isBold() 15 | } else if let weight = Int(value) { 16 | return FontWeightNumeric.isBold(weight) 17 | } 18 | 19 | return false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/GUI/FormatBar/FormatBarDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | public enum FormatBarOverflowState { 5 | case hidden 6 | case visible 7 | } 8 | 9 | public protocol FormatBarDelegate : NSObjectProtocol { 10 | /// Informs the delegate that a touch down event was received on the format bar. 11 | /// 12 | func formatBarTouchesBegan(_ formatBar: FormatBar) 13 | 14 | /// Called when the overflow items in the format bar are either shown or hidden 15 | /// as a result of the user tapping the toggle button. 16 | /// 17 | func formatBar(_ formatBar: FormatBar, didChangeOverflowState overflowState: FormatBarOverflowState) 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/HTMLDiv.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | // MARK: - HTMLDiv 5 | // 6 | class HTMLDiv: ParagraphProperty { 7 | 8 | override public func encode(with aCoder: NSCoder) { 9 | super.encode(with: aCoder) 10 | } 11 | 12 | override open class var supportsSecureCoding: Bool { true } 13 | 14 | override public init(with representation: HTMLRepresentation? = nil) { 15 | super.init(with: representation) 16 | } 17 | 18 | required public init?(coder aDecoder: NSCoder){ 19 | super.init(coder: aDecoder) 20 | } 21 | 22 | static func ==(lhs: HTMLDiv, rhs: HTMLDiv) -> Bool { 23 | return lhs === rhs 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Extensions/MediaAttachment+WordPress.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Aztec 3 | 4 | 5 | // MARK: - MediaAttachment 6 | // 7 | extension MediaAttachment { 8 | 9 | public static let uploadKey = "data-wp_upload_id" 10 | 11 | public var uploadID: String? { 12 | get { 13 | return extraAttributes[MediaAttachment.uploadKey]?.toString() 14 | } 15 | set { 16 | if let nonNilValue = newValue { 17 | extraAttributes[MediaAttachment.uploadKey] = .string(nonNilValue) 18 | } else { 19 | extraAttributes.remove(named: MediaAttachment.uploadKey) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/Example/UIImage+SaveTo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | extension UIImage { 5 | 6 | func saveToTemporaryFile() -> URL { 7 | let fileName = "\(ProcessInfo.processInfo.globallyUniqueString)_file.jpg" 8 | 9 | guard let data = self.jpegData(compressionQuality: 0.9) else { 10 | fatalError("Could not conert image to JPEG.") 11 | } 12 | 13 | let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) 14 | 15 | guard (try? data.write(to: fileURL, options: [.atomic])) != nil else { 16 | fatalError("Could not write the image to disk.") 17 | } 18 | 19 | return fileURL 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSBundle+AztecBundle.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | @objc public class var aztecBundle: Bundle { 5 | #if SWIFT_PACKAGE 6 | return .module 7 | #else 8 | let defaultBundle = Bundle(for: EditorView.self) 9 | // If installed with CocoaPods, resources will be in WordPress-Aztec-iOS.bundle 10 | if let bundleURL = defaultBundle.resourceURL, 11 | let resourceBundle = Bundle(url: bundleURL.appendingPathComponent("WordPress-Aztec-iOS.bundle")) { 12 | return resourceBundle 13 | } 14 | // Otherwise, the default bundle is used for resources 15 | return defaultBundle 16 | #endif 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/CiteFormatter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class CiteFormatter: FontFormatter { 5 | 6 | // MARK: - Init 7 | 8 | init() { 9 | super.init(traits: .traitItalic, htmlRepresentationKey: .citeHtmlRepresentation) 10 | } 11 | 12 | override func applicationRange(for range: NSRange, in text: NSAttributedString) -> NSRange { 13 | var effectiveRange = NSRange() 14 | 15 | let location = min(range.location, max(text.length - 1, 0)) 16 | 17 | text.attribute(.citeHtmlRepresentation, at: location, effectiveRange: &effectiveRange) 18 | 19 | return effectiveRange 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/Extensions/StringRegExTests.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import XCTest 3 | @testable import WordPressEditor 4 | 5 | class StringRegExTests: XCTestCase { 6 | 7 | /// This test checks if `replacingMatches(of:options:using:)` crashes when the match finishes with 8 | /// a character modified by "Mark, nonspacing"-class unicode characters. 9 | /// 10 | /// More info here: https://github.com/wordpress-mobile/WordPress-iOS/issues/9941 11 | /// 12 | func testReplacingMatchesWithMnClassUnicodeCharacters() { 13 | let string = "a\u{0309}" 14 | 15 | XCTAssertNoThrow(string.replacingMatches(of: "a", options: [], using: { _, _ in return "ignored!" })); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSAttributedString+Archive.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | // MARK: - NSAttributedString Archive methods 5 | // 6 | extension NSAttributedString 7 | { 8 | static let pasteboardUTI = UIPasteboard.UTType(identifier: "com.wordpress.aztec.attributedString") 9 | 10 | func archivedData() throws -> Data { 11 | return try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) 12 | } 13 | 14 | static func unarchive(with data: Data) throws -> NSAttributedString? { 15 | return try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSAttributedString.self, HTMLRepresentation.self], from: data) as? NSAttributedString 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/HTMLParagraph.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | // MARK: - HTMLParagraph 5 | // 6 | class HTMLParagraph: ParagraphProperty { 7 | 8 | override public func encode(with aCoder: NSCoder) { 9 | super.encode(with: aCoder) 10 | } 11 | 12 | override open class var supportsSecureCoding: Bool { true } 13 | 14 | override public init(with representation: HTMLRepresentation? = nil) { 15 | super.init(with: representation) 16 | } 17 | 18 | required public init?(coder aDecoder: NSCoder){ 19 | super.init(coder: aDecoder) 20 | } 21 | 22 | static func ==(lhs: HTMLParagraph, rhs: HTMLParagraph) -> Bool { 23 | return lhs === rhs 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Base/MainAttributesConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class MainAttributesConverter { 4 | 5 | private let converters: [ElementAttributeConverter] 6 | 7 | init(_ converters: [ElementAttributeConverter]) { 8 | self.converters = converters 9 | } 10 | 11 | func convert( 12 | _ attributes: [Attribute], 13 | inheriting inheritedAttributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 14 | 15 | return converters.reduce(inheritedAttributes) { (previous, converter) -> [NSAttributedString.Key: Any] in 16 | return converter.convert(attributes, inheriting: previous) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/FontProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | public class FontProvider { 5 | 6 | private init() { 7 | 8 | } 9 | 10 | public static var shared = FontProvider() 11 | 12 | public lazy var monospaceFont: UIFont = { 13 | let baseFont = UIFont(descriptor:UIFontDescriptor(name: "Menlo", size: 14), size:14) 14 | let font: UIFont 15 | font = UIFontMetrics.default.scaledFont(for: baseFont) 16 | return font 17 | }() 18 | 19 | public lazy var defaultFont: UIFont = { 20 | let baseFont = UIFont.systemFont(ofSize: 14) 21 | let font: UIFont 22 | font = UIFontMetrics.default.scaledFont(for: baseFont) 23 | return font 24 | }() 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/NSAttributedString/Conversions/AttachmentToElementConverter/LineAttachmentToElementConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class LineAttachmentToElementConverter: AttachmentToElementConverter { 5 | func convert(_ attachment: LineAttachment, attributes: [NSAttributedString.Key : Any]) -> [Node] { 6 | let element: ElementNode 7 | 8 | if let representation = attributes[.hrHtmlRepresentation] as? HTMLRepresentation, 9 | case let .element(representationElement) = representation.kind { 10 | 11 | element = representationElement.toElementNode() 12 | } else { 13 | element = ElementNode(type: .hr) 14 | } 15 | 16 | return [element] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Implementations/UnderlineElementAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class UnderlineElementAttributesConverter: ElementAttributeConverter { 5 | 6 | let cssAttributeMatcher = UnderlineCSSAttributeMatcher() 7 | 8 | func convert( 9 | _ attribute: Attribute, 10 | inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 11 | 12 | guard attribute.containsCSSAttribute(matching: cssAttributeMatcher) else { 13 | return attributes 14 | } 15 | 16 | var attributes = attributes 17 | 18 | attributes[.underlineStyle] = 1 19 | 20 | return attributes 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/BRElementConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | /// Converts `
` elements into a `String(.lineSeparator)`. 5 | /// 6 | class BRElementConverter: ElementConverter { 7 | 8 | // MARK: - ElementConverter 9 | 10 | func convert( 11 | _ element: ElementNode, 12 | inheriting attributes: [NSAttributedString.Key: Any], 13 | contentSerializer serialize: ContentSerializer) -> NSAttributedString { 14 | 15 | precondition(element.type == .br) 16 | 17 | let intrinsicRepresentation = NSAttributedString(.lineSeparator, attributes: attributes) 18 | 19 | return serialize(element, intrinsicRepresentation, attributes, false) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Example/AztecUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/WordPressPlugin/Calypso/GalleryShortcode/GalleryElementToTagConverterTests.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import XCTest 3 | @testable import WordPressEditor 4 | 5 | class GalleryElementToTagConverterTests: XCTestCase { 6 | 7 | let converter = GalleryElementToTagConverter() 8 | 9 | func testGalleryElementConverterWithOnlyColumns() { 10 | let attributes = [ 11 | Attribute(name: "columns", value: .string("4")), 12 | Attribute(name: "ids", value: .string("4, 5, 6, 7")) 13 | ] 14 | let element = ElementNode(type: .gallery, attributes: attributes) 15 | 16 | let (tag, _) = converter.convert(element) 17 | 18 | XCTAssertEqual(tag, "[gallery columns=\"4\" ids=\"4, 5, 6, 7\"]") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/HTMLParser/ElementsToHTML/Base/ElementToStringConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Converts an `ElementNode` into its `String` representation. 4 | /// 5 | public protocol ElementToTagConverter { 6 | 7 | /// Pair containing a mandatory opening tag, and an optional closing 8 | /// one. 9 | /// 10 | typealias Tag = (opening: String, closing: String?) 11 | 12 | /// Converts an instance of `ElementNode` into its `Tag` representation. 13 | /// 14 | /// - Parameters: 15 | /// - elementNode: ElementNode that's about to be converted. 16 | /// - childrenSerializer: Callback to serialize child elements. 17 | /// 18 | /// - Returns: the tag that represents this element. 19 | /// 20 | func convert(_ elementNode: ElementNode) -> Tag 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSAttributedString+ReplaceOcurrences.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSAttributedString { 4 | 5 | /// Convenience initializer for text replacement in an `NSAttributedString`. 6 | /// 7 | /// - Parameters: 8 | /// - stringToFind: the string to replace. 9 | /// - replacementString: the string to replace all matching occurrences with. 10 | /// 11 | convenience init(with attributedString: NSAttributedString, replacingOcurrencesOf string: String, with replacementString: String) { 12 | 13 | let mutableString = attributedString.mutableCopy() as! NSMutableAttributedString 14 | 15 | mutableString.replaceOcurrences(of: string, with: replacementString) 16 | 17 | self.init(attributedString: mutableString) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/WordPressPlugin/Gutenberg/GutenblockTests.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import XCTest 3 | @testable import WordPressEditor 4 | 5 | class GutenblockTests: XCTestCase { 6 | 7 | func testInitWithRepresentation() { 8 | let gutenblockElement = ElementNode(type: .gutenblock) 9 | let elementRepresentation = HTMLElementRepresentation(gutenblockElement) 10 | let representation = HTMLRepresentation(for: .element(elementRepresentation)) 11 | let gutenblock = Gutenblock(storing: representation) 12 | 13 | XCTAssertEqual(gutenblock.representation, representation) 14 | } 15 | 16 | func testInitWithCoder() { 17 | let gutenblock = Gutenblock() 18 | 19 | XCTAssertEqual(gutenblock.representation, nil) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/UITextView+Delegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | extension UITextView { 5 | 6 | /// Notifies the delegate of a text change. 7 | /// 8 | final func notifyTextViewDidChange() { 9 | if let textView = self as? TextView, !textView.shouldNotifyOfNonUserChanges { 10 | return 11 | } 12 | delegate?.textViewDidChange?(self) 13 | NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: self) 14 | } 15 | 16 | final func shouldChangeText(in range: NSRange, with text:String) -> Bool { 17 | guard let result = self.delegate?.textView?(self, shouldChangeTextIn: range, replacementText: text) else { 18 | return true 19 | } 20 | return result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.buildkite/publish-aztec-pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | PODSPEC_PATH="WordPress-Aztec-iOS.podspec" 4 | SPECS_REPO="git@github.com:wordpress-mobile/cocoapods-specs.git" 5 | SLACK_WEBHOOK=$PODS_SLACK_WEBHOOK 6 | 7 | echo "--- :rubygems: Setting up Gems" 8 | install_gems 9 | 10 | echo "--- :cocoapods: Publishing Pod to CocoaPods CDN" 11 | # Using `--synchronous` here because Editor depends on Aztec, and we need 12 | # to be able to `pod trunk push` the Editor pod immediately after the Aztec 13 | # pod has been published, without being hindered by the CDN propagation time. 14 | publish_pod --synchronous $PODSPEC_PATH 15 | 16 | echo "--- :cocoapods: Publishing Pod to WP Specs Repo" 17 | publish_private_pod $PODSPEC_PATH $SPECS_REPO "$SPEC_REPO_PUBLIC_DEPLOY_KEY" 18 | 19 | echo "--- :slack: Notifying Slack" 20 | slack_notify_pod_published $PODSPEC_PATH $SLACK_WEBHOOK 21 | -------------------------------------------------------------------------------- /.buildkite/publish-editor-pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | PODSPEC_PATH="WordPress-Editor-iOS.podspec" 4 | SPECS_REPO="git@github.com:wordpress-mobile/cocoapods-specs.git" 5 | SLACK_WEBHOOK=$PODS_SLACK_WEBHOOK 6 | 7 | echo "--- :rubygems: Setting up Gems" 8 | install_gems 9 | 10 | echo "--- :cocoapods: Publishing Pod to CocoaPods CDN" 11 | # Using `--synchronous` here because Editor depends on Aztec, and we need 12 | # to be able to `pod trunk push` the Editor pod immediately after the Aztec 13 | # pod has been published, without being hindered by the CDN propagation time. 14 | publish_pod --synchronous $PODSPEC_PATH 15 | 16 | echo "--- :cocoapods: Publishing Pod to WP Specs Repo" 17 | publish_private_pod $PODSPEC_PATH $SPECS_REPO "$SPEC_REPO_PUBLIC_DEPLOY_KEY" 18 | 19 | echo "--- :slack: Notifying Slack" 20 | slack_notify_pod_published $PODSPEC_PATH $SLACK_WEBHOOK 21 | -------------------------------------------------------------------------------- /Example/Example/SampleContent/gallery.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/WordPressPlugin.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import UIKit 3 | 4 | /// WordPress Plugin for Aztec. 5 | /// 6 | open class WordPressPlugin: Plugin { 7 | 8 | typealias GutenbergContentVerifier = (String) -> Bool 9 | 10 | public init() { 11 | let isGutenbergContent: GutenbergContentVerifier = { content -> Bool in 12 | return content.contains(" 9 |  قسم شنط  الكايلي   10 | 11 | 12 | لوقو انستجرام 13 | -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/WordPressPlugin/Calypso/AutopRemovep/RemovePProcessorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressEditor 3 | 4 | class RemovePProcessorTests: XCTestCase { 5 | 6 | let processor = RemovePProcessor() 7 | 8 | /// Verifies that strings containing emoji characters do not result in data loss. 9 | /// 10 | /// Reference: https://github.com/wordpress-mobile/AztecEditor-iOS/issues/885 11 | /// 12 | /// Input: 13 | /// - "Test & Test 😊
lalalalala" 14 | /// 15 | /// Expected Output: 16 | /// - "Test & Test 😊\nlalalalala" 17 | /// 18 | func testStringsWithEmojiDoNotResultInDataLoss() { 19 | let input = "Test & Test 😊
lalalalala" 20 | let expected = "Test & Test 😊\nlalalalala" 21 | let output = processor.process(input) 22 | 23 | XCTAssertEqual(output, expected) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSLayoutManager+Attachments.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | // MARK: - NSLayoutManager Helpers 5 | // 6 | extension NSLayoutManager { 7 | 8 | /// Invalidates the layout for an attachment when some change happened to it. 9 | /// 10 | func invalidateLayout(for attachment: NSTextAttachment) { 11 | guard let ranges = textStorage?.ranges(forAttachment: attachment) else { 12 | return 13 | } 14 | 15 | for range in ranges { 16 | invalidateLayout(forCharacterRange: range, actualCharacterRange: nil) 17 | invalidateDisplay(forCharacterRange: range) 18 | } 19 | } 20 | 21 | /// Ensures the layout for all of the TextContainers. 22 | /// 23 | func ensureLayoutForContainers() { 24 | for textContainer in textContainers { 25 | ensureLayout(for: textContainer) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/AztecTests/Aztec.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "B60FC877-ADB8-4C48-B26F-F4F63AED1F77", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : false 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:", 18 | "identifier" : "HTMLParserTests", 19 | "name" : "HTMLParserTests" 20 | } 21 | }, 22 | { 23 | "target" : { 24 | "containerPath" : "container:", 25 | "identifier" : "AztecTests", 26 | "name" : "AztecTests" 27 | } 28 | }, 29 | { 30 | "target" : { 31 | "containerPath" : "container:", 32 | "identifier" : "WordPressEditorTests", 33 | "name" : "WordPressEditorTests" 34 | } 35 | } 36 | ], 37 | "version" : 1 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/CiteElementConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | class CiteElementConverter: FormatterElementConverter { 5 | 6 | lazy var citeFormatter = CiteFormatter() 7 | 8 | // MARK: - ElementConverter 9 | 10 | func convert( 11 | _ element: ElementNode, 12 | inheriting inheritedAttributes: [NSAttributedString.Key: Any], 13 | contentSerializer serialize: ContentSerializer) -> NSAttributedString { 14 | 15 | precondition(element.type == .cite) 16 | 17 | let childrenAttributes = attributes(for: element, inheriting: inheritedAttributes) 18 | 19 | return serialize(element, nil, childrenAttributes, false) 20 | } 21 | 22 | // MARK: - FormatterElementConverter 23 | 24 | func formatter() -> AttributeFormatter { 25 | return citeFormatter 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Base/ElementAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | public protocol ElementAttributeConverter { 5 | func convert( 6 | _ attributes: [Attribute], 7 | inheriting inheritedAttributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] 8 | 9 | func convert( 10 | _ attribute: Attribute, 11 | inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] 12 | } 13 | 14 | extension ElementAttributeConverter { 15 | 16 | func convert( 17 | _ attributes: [Attribute], 18 | inheriting inheritedAttributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 19 | 20 | return attributes.reduce(inheritedAttributes, { (previous, attribute) -> [NSAttributedString.Key: Any] in 21 | return convert(attribute, inheriting: previous) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/Blockquote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class Blockquote: ParagraphProperty { 4 | 5 | public override func encode(with aCoder: NSCoder) { 6 | super.encode(with: aCoder) 7 | } 8 | 9 | override open class var supportsSecureCoding: Bool { true } 10 | 11 | override public init(with representation: HTMLRepresentation? = nil) { 12 | super.init(with: representation) 13 | } 14 | 15 | required public init?(coder aDecoder: NSCoder){ 16 | super.init(coder: aDecoder) 17 | } 18 | 19 | static func ==(lhs: Blockquote, rhs: Blockquote) -> Bool { 20 | return lhs.representation == rhs.representation 21 | } 22 | 23 | override func isEqual(_ object: Any?) -> Bool { 24 | guard let rightBlockquote = object as? Blockquote else { 25 | return false 26 | } 27 | 28 | return self == rightBlockquote 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Implementations/ForegroundColorElementAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class ForegroundColorElementAttributesConverter: ElementAttributeConverter { 5 | 6 | let cssAttributeMatcher = ForegroundColorCSSAttributeMatcher() 7 | 8 | func convert( 9 | _ attribute: Attribute, 10 | inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 11 | 12 | guard let cssColor = attribute.firstCSSAttribute(ofType: .foregroundColor), 13 | let colorValue = cssColor.value, 14 | let color = ColorProvider.shared.color(named: colorValue) ?? UIColor(hexString: colorValue) else { 15 | return attributes 16 | } 17 | 18 | var attributes = attributes 19 | 20 | attributes[.foregroundColor] = color 21 | 22 | return attributes 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Plugin/Plugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | /// Plugin base class. You can implement a subclass to customize some of the behavior in Aztec. 5 | /// 6 | open class Plugin { 7 | 8 | // MARK: - Customizers 9 | 10 | public let inputCustomizer: PluginInputCustomizer? 11 | public let outputCustomizer: PluginOutputCustomizer? 12 | 13 | // MARK: - Initializers 14 | 15 | public init(inputCustomizer: PluginInputCustomizer? = nil, outputCustomizer: PluginOutputCustomizer? = nil) { 16 | self.inputCustomizer = inputCustomizer 17 | self.outputCustomizer = outputCustomizer 18 | } 19 | 20 | /// Method plugins can use to execute extra code when loaded. 21 | /// 22 | open func loaded(textView: TextView) {} 23 | 24 | // MARK: - Equatable 25 | 26 | public static func ==(lhs: Plugin, rhs: Plugin) -> Bool { 27 | return type(of: lhs) == type(of: rhs) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/TextKit/ParagraphProperty/Figcaption.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class Figcaption: ParagraphProperty { 5 | let defaultFont: UIFont 6 | 7 | public init(defaultFont: UIFont, storing representation: HTMLRepresentation? = nil) { 8 | self.defaultFont = defaultFont 9 | super.init(with: representation) 10 | } 11 | 12 | required public init?(coder aDecoder: NSCoder){ 13 | defaultFont = aDecoder.decodeObject(of: UIFont.self, forKey: CodingKeys.defaultFont)! 14 | super.init(coder: aDecoder) 15 | } 16 | 17 | public override func encode(with aCoder: NSCoder) { 18 | super.encode(with: aCoder) 19 | aCoder.encode(defaultFont, forKey: CodingKeys.defaultFont) 20 | } 21 | 22 | override open class var supportsSecureCoding: Bool { true } 23 | } 24 | 25 | private extension Figcaption { 26 | struct CodingKeys { 27 | static let defaultFont = "defaultFont" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSMutableAttributedString+ParagraphProperty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSMutableAttributedString { 4 | 5 | /// Returns a copy of the provided attributes appending the specified `ParagraphProperty` to the paragraph style. 6 | /// 7 | /// - Parameters: 8 | /// - property: the property to append to the paragraph styles. 9 | /// - attributes: the base attributes. 10 | /// 11 | /// - Returns: the final string attributes. 12 | /// 13 | func append(paragraphProperty property: ParagraphProperty) { 14 | enumerateParagraphRanges(spanning: self.rangeOfEntireString) { (_, range) in 15 | let attributes = self.attributes(at: range.lowerBound, effectiveRange: nil) 16 | let paragraphStyle = attributes.paragraphStyle() 17 | 18 | paragraphStyle.appendProperty(property) 19 | 20 | self.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Formatters/Implementations/UnderlineFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class UnderlineFormatter: StandardAttributeFormatter { 4 | 5 | init() { 6 | super.init(attributeKey: .underlineStyle, 7 | attributeValue: NSUnderlineStyle.single.rawValue, 8 | htmlRepresentationKey: .underlineHtmlRepresentation) 9 | } 10 | } 11 | 12 | class SpanUnderlineFormatter: UnderlineFormatter { 13 | 14 | override func apply(to attributes: [NSAttributedString.Key : Any], andStore representation: HTMLRepresentation?) -> [NSAttributedString.Key : Any] { 15 | 16 | let cssAttribute = CSSAttribute.underline 17 | let underlineStyleAttribute = Attribute(type: .style, value: .inlineCss([cssAttribute])) 18 | let spanRepresentation = HTMLElementRepresentation(ElementNode.init(type: .span, attributes: [underlineStyleAttribute], children: [])) 19 | 20 | return super.apply(to: attributes, andStore: HTMLRepresentation(for: .element(spanRepresentation))) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/AztecTests/Extensions/UIStackViewHelpersTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Aztec 3 | 4 | class UIStackViewHelpersTests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testArrangedSubviews() { 17 | let subviews = [ 18 | UIView(), 19 | UIView(), 20 | UIView() 21 | ] 22 | let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) 23 | 24 | stackView.addArrangedSubviews(subviews) 25 | XCTAssertEqual(stackView.arrangedSubviews, subviews) 26 | 27 | stackView.removeArrangedSubviews(subviews) 28 | XCTAssertEqual(stackView.arrangedSubviews, []) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/Array+ShortcodeAttribute.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Array where Element == ShortcodeAttribute { 4 | 5 | subscript(_ key: String) -> ShortcodeAttribute? { 6 | get { 7 | return first() { $0.key == key } 8 | } 9 | } 10 | 11 | mutating func set(_ value: String, forKey key: String) { 12 | set(.string(value), forKey: key) 13 | } 14 | 15 | mutating func set(_ value: ShortcodeAttribute.Value, forKey key: String) { 16 | let newAttribute = ShortcodeAttribute(key: key, value: value) 17 | 18 | guard let attributeIndex = firstIndex(where: { $0.key == key }) else { 19 | append(newAttribute) 20 | return 21 | } 22 | 23 | self[attributeIndex] = newAttribute 24 | } 25 | 26 | mutating func remove(key: String) { 27 | guard let attributeIndex = firstIndex(where: { $0.key == key }) else { 28 | return 29 | } 30 | 31 | remove(at: attributeIndex) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Example/Example/SampleContent/video.html: -------------------------------------------------------------------------------- 1 |

Standard Video

2 | 3 | 4 |

Standard Video no poster image

5 | 6 | 7 |

Standard Video with source element

8 | 11 | 12 |

Standard Video with multiple source elements

13 | 17 | 18 | -------------------------------------------------------------------------------- /docs/resources/opensans.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: local('Open Sans Light'), local('OpenSans-Light'), url(http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTaRDOzjiPcYnFooOUGCOsRk.woff) format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Open Sans'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3bO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); 12 | } 13 | @font-face { 14 | font-family: 'Open Sans'; 15 | font-style: normal; 16 | font-weight: 600; 17 | src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqRDOzjiPcYnFooOUGCOsRk.woff) format('woff'); 18 | } 19 | @font-face { 20 | font-family: 'PT Mono'; 21 | font-style: normal; 22 | font-weight: 400; 23 | src: local('PT Mono'), local('PTMono-Regular'), url(https://fonts.gstatic.com/s/ptmono/v4/RTA0Dhj_HESY0h-ax44VkwLUuEpTyoUstqEm5AMlJo4.woff) format('woff'); 24 | } 25 | -------------------------------------------------------------------------------- /Tests/WordPressEditorTests/WordPressPlugin/Calypso/GalleryShortcode/GalleryShortcodeInputProcessorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressEditor 3 | 4 | class GalleryShortcodeInputProcessorTests: XCTestCase { 5 | 6 | let processor = GalleryShortcodeInputProcessor() 7 | 8 | /// Verifies that a Caption Shortcode with a columns attribute is properly pre-processed. 9 | /// 10 | func testGalleryShortcodeWithOnlyColumns() { 11 | let input = "[gallery columns=\"4\"]" 12 | let expected = "" 13 | 14 | let output = processor.process(input) 15 | 16 | XCTAssertEqual(output, expected) 17 | } 18 | 19 | /// Verifies that a Caption Shortcode with an IDs attribute is properly pre-processed. 20 | /// 21 | func testGalleryShortcodeWithOnlyIDs() { 22 | let input = "[gallery ids=\"4,10,22,11\"]" 23 | let expected = "" 24 | 25 | let output = processor.process(input) 26 | 27 | XCTAssertEqual(output, expected) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/Example/SampleContent/imagesOverlays.html: -------------------------------------------------------------------------------- 1 |

Overlay on Media:

2 |

3 | 20px Wide Image: 4 | Plane

5 | 50px Wide Image: 6 | Plane

7 | 70px Wide Image: 8 | Plane

9 | 90px Wide Image: 10 | Plane

11 | 120px Wide Image: 12 | Plane

13 | 200px Wide Image: 14 | Plane

15 | 300px Wide Image: 16 | Plane 17 |

18 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Calypso/GalleryShortcode/GalleryShortcodeInputProcessor.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import Foundation 3 | 4 | /// Handler Gallery Shortcodes pre-processing by converting them to nodes. 5 | /// 6 | class GalleryShortcodeInputProcessor: Processor { 7 | static let tag = "gallery" 8 | 9 | private lazy var galleryShortcodeProcessor: ShortcodeProcessor = { 10 | return ShortcodeProcessor(tag: GalleryShortcodeInputProcessor.tag) { [unowned self] (shortcode) -> String? in 11 | return self.process(shortcode) 12 | } 13 | }() 14 | 15 | let serializer = ShortcodeAttributeSerializer() 16 | 17 | // MARK: - Processor 18 | 19 | func process(_ text: String) -> String { 20 | return galleryShortcodeProcessor.process(text) 21 | } 22 | 23 | // MARK: - Gallery Tag Processing Logic 24 | 25 | func process(_ shortcode: Shortcode) -> String { 26 | let attributes = serializer.serialize(shortcode.attributes) 27 | 28 | return "" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/ElementsToAttributedString/Base/AttachmentElementConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | /// For any object that converts an element into an attachment. 5 | /// 6 | public protocol AttachmentElementConverter: ElementConverter { 7 | associatedtype AttachmentType: NSTextAttachment 8 | 9 | func convert( 10 | _ element: ElementNode, 11 | inheriting attributes: [NSAttributedString.Key: Any], 12 | contentSerializer serialize: ContentSerializer) -> (attachment: AttachmentType, string: NSAttributedString) 13 | } 14 | 15 | public extension AttachmentElementConverter { 16 | 17 | /// For most classes implementing this protocol, this is the default `convert` implementation from `ElementConverter`. 18 | /// 19 | func convert( 20 | _ element: ElementNode, 21 | inheriting attributes: [NSAttributedString.Key: Any], 22 | contentSerializer serialize: ContentSerializer) -> NSAttributedString { 23 | 24 | let (_, output) = convert(element, inheriting: attributes, contentSerializer: serialize) 25 | 26 | return output 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Gutenberg/GutenbergAttributeDecoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Aztec 3 | 4 | /// Class to facilitate decoding of Gutenberg comments data from Element attributes 5 | /// 6 | class GutenbergAttributeDecoder { 7 | 8 | // MARK: - Attribute Data 9 | 10 | func attribute(_ gutenbergAttribute: GutenbergAttribute, from element: ElementNode) -> String? { 11 | guard let attribute = element.attribute(named: gutenbergAttribute.rawValue), 12 | let opener = decode(attribute) else { 13 | return nil 14 | } 15 | 16 | return opener 17 | } 18 | 19 | // MARK: - Base64 Decoding 20 | 21 | private func decode(_ attribute: Attribute) -> String? { 22 | guard let base64Gutenblock = attribute.value.toString() else { 23 | return nil 24 | } 25 | 26 | return decode(base64Gutenblock: base64Gutenblock) 27 | } 28 | 29 | private func decode(base64Gutenblock: String) -> String { 30 | let data = Data(base64Encoded: base64Gutenblock)! 31 | return String(data: data, encoding: .utf16)! 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/UIColor+Parsers.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIColor { 4 | 5 | /// Creates a color based on a hexString. If the string is not a valid hexColor it return nil 6 | /// Example of colors: #FF0000, #00FF0000 7 | /// 8 | convenience init?(hexString: String) { 9 | 10 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 11 | var int = UInt64() 12 | if !Scanner(string: hex).scanHexInt64(&int) { 13 | return nil 14 | } 15 | let a, r, g, b: UInt64 16 | switch hex.count { 17 | case 3: // RGB (12-bit) 18 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 19 | case 6: // RGB (24-bit) 20 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 21 | case 8: // ARGB (32-bit) 22 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 23 | default: 24 | return nil 25 | } 26 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Data/CommentNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | /// Comment nodes use to hold HTML comments like this: 5 | /// 6 | public class CommentNode: Node { 7 | 8 | public var comment: String 9 | 10 | // MARK: - CustomReflectable 11 | 12 | override public var customMirror: Mirror { 13 | get { 14 | return Mirror(self, children: ["type": "comment", "name": name, "comment": comment, "parent": parent.debugDescription], ancestorRepresentation: .suppressed) 15 | } 16 | } 17 | 18 | // MARK: - Initializers 19 | 20 | public init(text: String) { 21 | comment = text 22 | 23 | super.init(name: "comment") 24 | } 25 | 26 | // MARK - Hashable 27 | 28 | override public func hash(into hasher: inout Hasher) { 29 | hasher.combine(name) 30 | hasher.combine(comment) 31 | } 32 | 33 | // MARK: - Equatable 34 | 35 | override public func isEqual(_ object: Any?) -> Bool { 36 | guard let rhs = object as? CommentNode else { 37 | return false 38 | } 39 | 40 | return name == rhs.name && comment == rhs.comment 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/AztecTests/Extensions/ArrayHelperTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Aztec 3 | 4 | 5 | // MARK: - Array Helper Tests 6 | // 7 | class ArrayHelperTests: XCTestCase { 8 | 9 | /// Verifies that the `lastIndex` helper effectively returns the position of the last satisfying 10 | /// element within the collection. 11 | /// 12 | func testLastIndexEffectivelyReturnsTheLastSatisfyingElementIndex() { 13 | let collection = [9,8,7,6,5,4,3,2,1,9,0,9,0] 14 | 15 | guard let index = collection.lastIndex(where: { $0 == 9 }) else { 16 | XCTFail() 17 | return 18 | } 19 | 20 | XCTAssert(index == (collection.count - 2)) 21 | } 22 | 23 | /// Verifies that the `lastIndex` helper effectively returns nil whenever there is just no single 24 | /// element within the collection that would satisfy the specified condition. 25 | /// 26 | func testLastIndexEffectivelyReturnsNilWheneverThereIsNoSatisfyingElement() { 27 | let collection = [9,8,7,6,5,4,3,2,1,9,0,9,0] 28 | 29 | guard let _ = collection.lastIndex(where: { $0 == 15 }) else { 30 | return 31 | } 32 | 33 | XCTFail() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSTextingResult+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension NSTextCheckingResult { 4 | 5 | /// Returns the match for the corresponding capture group position in a text 6 | /// 7 | /// - Parameters: 8 | /// - position: the capture group position 9 | /// - text: the string where the match was detected 10 | /// - Returns: the string with the captured group text 11 | /// 12 | func captureGroup(in position: Int, text: String) -> String? { 13 | guard position < numberOfRanges else { 14 | return nil 15 | } 16 | 17 | #if swift(>=4.0) 18 | let nsrange = self.range(at: position) 19 | #else 20 | let nsrange = self.rangeAt(position) 21 | #endif 22 | 23 | guard nsrange.location != NSNotFound else { 24 | return nil 25 | } 26 | 27 | let range = text.range(fromUTF16NSRange: nsrange) 28 | 29 | #if swift(>=4.0) 30 | let captureGroup = String(text[range]) 31 | #else 32 | let captureGroup = text.substring(with: range) 33 | #endif 34 | 35 | return captureGroup 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Calypso/GalleryShortcode/GalleryElementToTagConverter.swift: -------------------------------------------------------------------------------- 1 | import Aztec 2 | import Foundation 3 | 4 | class GalleryElementToTagConverter: ElementToTagConverter { 5 | func convert(_ elementNode: ElementNode) -> ElementToTagConverter.Tag { 6 | let shortcode = self.shortcode(for: elementNode) 7 | 8 | return (shortcode, nil) 9 | } 10 | } 11 | 12 | // MARK: - Shortcode Construction 13 | 14 | private extension GalleryElementToTagConverter { 15 | private func shortcode(for elementNode: ElementNode) -> String { 16 | let attributes = serialize(attributes: elementNode.attributes) 17 | 18 | return "[gallery " + attributes + "]" 19 | } 20 | } 21 | 22 | // MARK: - Attribute Conversion Logic 23 | 24 | private extension GalleryElementToTagConverter { 25 | /// Serializes an array of attributes into their HTML representation 26 | /// 27 | private func serialize(attributes: [Attribute]) -> String { 28 | return attributes.reduce("") { (html, attribute) in 29 | let prefix = html.count > 0 ? html + " " : "" 30 | 31 | return prefix + attribute.toString() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/HTMLParserTests/HTML/Nodes/TextNodeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | // See comment in BoldElementToAttributeConverterTests.swift to understand the conditional import below. 3 | #if SWIFT_PACKAGE 4 | @testable import HTMLParser 5 | #else 6 | @testable import Aztec 7 | #endif 8 | 9 | 10 | // MARK: - TextNodeTests 11 | // 12 | class TextNodeTests: XCTestCase { 13 | 14 | /// Verifies that two different TextNode(s) return false when equality is checked. 15 | /// 16 | func testEqualityOperatorEffectivelyReturnsFalseWhenNodesDiffer() { 17 | let text1 = TextNode(text: "First Children Here") 18 | let text2 = TextNode(text: "Second Child!") 19 | 20 | XCTAssert((text1 == text2) == false) 21 | XCTAssert(text1 != text2) 22 | } 23 | 24 | /// Verifies that two different ElementNode(s) instances, with the same name and the exact same Children array 25 | /// return true when equality is checked. 26 | /// 27 | func testEqualityOperatorEffectivelyReturnsTrueWhenNodesAreEqual() { 28 | let text1 = TextNode(text: "First Children Here") 29 | let text2 = TextNode(text: "First Children Here") 30 | 31 | XCTAssert(text1 == text2) 32 | XCTAssert(text1 !== text2) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Extensions/VideoAttachment+WordPress.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Aztec 3 | 4 | 5 | // MARK: - VideoAttachment 6 | // 7 | extension VideoAttachment { 8 | 9 | @objc public var videoPressID: String? { 10 | get { 11 | return extraAttributes[VideoShortcodeProcessor.videoPressHTMLAttribute]?.toString() 12 | } 13 | set { 14 | if let nonNilValue = newValue { 15 | extraAttributes[VideoShortcodeProcessor.videoPressHTMLAttribute] = .string(nonNilValue) 16 | } else { 17 | extraAttributes.remove(named: VideoShortcodeProcessor.videoPressHTMLAttribute) 18 | } 19 | } 20 | } 21 | 22 | @objc public var isShortcode: Bool { 23 | get { 24 | return extraAttributes[VideoShortcodeProcessor.videoWPShortcodeHTMLAttribute]?.toString() == "true" 25 | } 26 | set { 27 | if newValue { 28 | extraAttributes[VideoShortcodeProcessor.videoWPShortcodeHTMLAttribute] = .string(String("true")) 29 | } else { 30 | extraAttributes.remove(named: VideoShortcodeProcessor.videoWPShortcodeHTMLAttribute) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Extensions/NSMutableAttributedString+ReplaceAttributes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | public extension NSMutableAttributedString { 5 | 6 | /// Replace all instances of the .font attribute that belong to the same family for the new font, trying to keep the same symbolic traits 7 | /// - Parameter font: the original font to be replaced 8 | /// - Parameter newFont: the new font to use. 9 | func replace(font: UIFont, with newFont: UIFont) { 10 | let fullRange = NSRange(location: 0, length: length) 11 | 12 | beginEditing() 13 | enumerateAttributes(in: fullRange, options: []) { (attributes, subrange, stop) in 14 | guard let currentFont = attributes[.font] as? UIFont, currentFont.familyName == font.familyName else { 15 | return 16 | } 17 | var replacementFont = newFont 18 | if let fontDescriptor = newFont.fontDescriptor.withSymbolicTraits(currentFont.fontDescriptor.symbolicTraits) { 19 | replacementFont = UIFont(descriptor: fontDescriptor, size: currentFont.pointSize) 20 | } 21 | addAttribute(.font, value: replacementFont, range: subrange) 22 | } 23 | endEditing() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/HTMLParser/DOM/Data/AttributeType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | public struct AttributeType: RawRepresentable, Hashable { 5 | 6 | public typealias RawValue = String 7 | 8 | public let rawValue: String 9 | 10 | // MARK: - CSS Support 11 | 12 | public static var cssAttributeTypes: Set = [.style] 13 | 14 | // MARK: - Initializers 15 | 16 | public init?(rawValue: RawValue) { 17 | self.init(rawValue) 18 | } 19 | 20 | public init(_ rawValue: RawValue) { 21 | self.rawValue = rawValue 22 | } 23 | 24 | // MARK: - Equatable 25 | 26 | public static func ==(lhs: AttributeType, rhs: AttributeType) -> Bool{ 27 | return lhs.rawValue.lowercased() == rhs.rawValue.lowercased() 28 | } 29 | } 30 | 31 | 32 | public extension AttributeType { 33 | static let `class` = AttributeType("class") 34 | static let href = AttributeType("href") 35 | static let rel = AttributeType("rel") 36 | static let src = AttributeType("src") 37 | static let style = AttributeType("style") 38 | static let target = AttributeType("target") 39 | static let reversed = AttributeType("reversed") 40 | static let start = AttributeType("start") 41 | } 42 | -------------------------------------------------------------------------------- /Sources/WordPressEditor/Classes/Plugins/WordPressPlugin/Calypso/Embeds/WordPressPasteboardDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Aztec 3 | 4 | class WordPressTextViewPasteboardDelegate: AztecTextViewPasteboardDelegate { 5 | 6 | override func tryPastingURL(in textView: TextView) -> Bool { 7 | 8 | let selectedRange = textView.selectedRange 9 | 10 | /// TODO: support pasting multiple URLs 11 | guard UIPasteboard.general.hasURLs, // There are URLs on the pasteboard 12 | let url = UIPasteboard.general.url, // We can get the first one 13 | selectedRange.length == 0, // No text is selected in the TextView 14 | EmbedURLProcessor(url: url).isValidEmbed // The pasteboard contains an embeddable URL 15 | else { 16 | return super.tryPastingURL(in: textView) 17 | } 18 | 19 | let result = super.tryPastingString(in: textView) 20 | if result { 21 | // Bump the input to the next line – we need the embed link to be the only 22 | // text on this line – otherwise it can't be autoconverted. 23 | textView.insertText(String(.lineSeparator)) 24 | } 25 | 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/FigcaptionElementConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | /// Returns a specialised representation for a `
` element. 5 | /// 6 | class FigcaptionElementConverter: ElementConverter { 7 | 8 | let figcaptionFormatter = FigcaptionFormatter() 9 | 10 | // MARK: - ElementConverter 11 | 12 | func convert( 13 | _ element: ElementNode, 14 | inheriting attributes: [NSAttributedString.Key: Any], 15 | contentSerializer serialize: ContentSerializer) -> NSAttributedString { 16 | 17 | precondition(element.type == .figcaption) 18 | 19 | let attributes = self.attributes(for: element, inheriting: attributes) 20 | 21 | return serialize(element, nil, attributes, false) 22 | } 23 | 24 | private func attributes(for element: ElementNode, inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 25 | let elementRepresentation = HTMLElementRepresentation(element) 26 | let representation = HTMLRepresentation(for: .element(elementRepresentation)) 27 | 28 | return figcaptionFormatter.apply(to: attributes, andStore: representation) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Implementations/BoldElementAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class BoldElementAttributesConverter: ElementAttributeConverter { 5 | 6 | let cssAttributeMatcher = BoldCSSAttributeMatcher() 7 | 8 | func convert( 9 | _ attribute: Attribute, 10 | inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 11 | 12 | guard attribute.containsCSSAttribute(matching: cssAttributeMatcher) else { 13 | return attributes 14 | } 15 | 16 | var attributes = attributes 17 | 18 | // The default font should already be in the attributes. But in case it's nil 19 | // we should have some way to figure out the default font. Honestly it feels like 20 | // this configuration should come from elsewhere, but we'll just default to the 21 | // default system font of size 14 for now. 22 | // 23 | let font = attributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: 14) 24 | let newFont = font.modifyTraits([.traitBold], enable: true) 25 | 26 | attributes[.font] = newFont 27 | 28 | return attributes 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/AttributesToStringAttributes/Implementations/ItalicElementAttributeConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class ItalicElementAttributesConverter: ElementAttributeConverter { 5 | 6 | let cssAttributeMatcher = ItalicCSSAttributeMatcher() 7 | 8 | func convert( 9 | _ attribute: Attribute, 10 | inheriting attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { 11 | 12 | guard attribute.containsCSSAttribute(matching: cssAttributeMatcher) else { 13 | return attributes 14 | } 15 | 16 | var attributes = attributes 17 | 18 | // The default font should already be in the attributes. But in case it's nil 19 | // we should have some way to figure out the default font. Honestly it feels like 20 | // this configuration should come from elsewhere, but we'll just default to the 21 | // default system font of size 14 for now. 22 | // 23 | let font = attributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: 14) 24 | let newFont = font.modifyTraits([.traitItalic], enable: true) 25 | 26 | attributes[.font] = newFont 27 | 28 | return attributes 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Aztec/Classes/Converters/ElementsToAttributedString/Base/ElementConverter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// ElementConverters take an HTML Element that don't have a textual representation and return a special value to 4 | /// represent it (e.g. `` or `