Skip to content

Commit 5d9b2cd

Browse files
authored
Merge pull request #1204 from wordpress-mobile/release/1.8.0
Release/1.8.0
2 parents ba57c9d + b8c5376 commit 5d9b2cd

12 files changed

Lines changed: 137 additions & 23 deletions

Aztec/Classes/Extensions/NSMutableAttributedString+ReplaceOcurrences.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ extension NSMutableAttributedString {
3232

3333
assert(!replacementString.contains(stringToFind),
3434
"Allowing the replacement string to contain the original string would result in a ininite loop.")
35-
36-
let swiftUTF16Range = string.utf16.range(from: range)
35+
let rangeDifference = (replacementString as NSString).length - (stringToFind as NSString).length
36+
var swiftUTF16Range = string.utf16.range(from: range)
3737
var swiftRange = string.range(from: swiftUTF16Range)
38-
3938
while let matchRange = string.range(of: stringToFind, options: [], range: swiftRange, locale: nil) {
4039
let matchNSRange = string.utf16NSRange(from: matchRange)
4140

4241
replaceCharacters(in: matchNSRange, with: replacementString)
4342

4443
// Since the new string may invalidate the initial swift range, we want to update it.
45-
swiftRange = swiftRange.lowerBound ..< string.index(matchRange.lowerBound, offsetBy: replacementString.count)
44+
// And because the string that backs the NSAttributeString will change after the replace we need to recalculate the index from scratch
45+
let updatedRange = range.extendedRight(by: rangeDifference)
46+
swiftUTF16Range = string.utf16.range(from: updatedRange)
47+
swiftRange = string.range(from: swiftUTF16Range)
4648
}
4749
}
4850
}

Aztec/Classes/Extensions/UITextView+Delegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ extension UITextView {
66
/// Notifies the delegate of a text change.
77
///
88
final func notifyTextViewDidChange() {
9+
if let textView = self as? TextView, !textView.shouldNotifyOfNonUserChanges {
10+
return
11+
}
912
delegate?.textViewDidChange?(self)
1013
NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: self)
1114
}

Aztec/Classes/NSAttributedString/Conversions/HTMLConverter.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ public class HTMLConverter {
2020
}
2121

2222
// MARK: - Converters: HTML -> AttributedString
23-
23+
24+
/// If a value is set the character set will be used to replace any last empty line created by the converter.
25+
///
26+
open var characterToReplaceLastEmptyLine: Character?
27+
2428
let htmlToTree = HTMLParser()
2529

2630
private(set) lazy var treeToAttributedString: AttributedStringSerializer = {
@@ -54,11 +58,27 @@ public class HTMLConverter {
5458
pluginManager.process(htmlTree: rootNode)
5559

5660
let defaultAttributes = defaultAttributes ?? [:]
57-
let attributedString = treeToAttributedString.serialize(rootNode, defaultAttributes: defaultAttributes)
61+
var attributedString = treeToAttributedString.serialize(rootNode, defaultAttributes: defaultAttributes)
62+
63+
if let characterToUse = characterToReplaceLastEmptyLine {
64+
attributedString = replaceLastEmptyLine(in: attributedString, with: characterToUse)
65+
}
5866

5967
return attributedString
6068
}
6169

70+
func replaceLastEmptyLine(in attributedString: NSAttributedString, with replacement: Character) -> NSAttributedString {
71+
var result = attributedString
72+
let string = attributedString.string
73+
if !string.isEmpty, string.isEmptyLineAtEndOfFile(at: string.count), string.hasSuffix(String(.paragraphSeparator)), let location = string.location(before: attributedString.length) {
74+
let mutableString = NSMutableAttributedString(attributedString: attributedString)
75+
let attributes = mutableString.attributes(at: location, effectiveRange: nil)
76+
mutableString.replaceCharacters(in: NSRange(location: location, length: attributedString.length-location), with: NSAttributedString(string: String(replacement), attributes: attributes))
77+
result = mutableString
78+
}
79+
return result
80+
}
81+
6282

6383
/// Check if the given html string is supported to be parsed into Attributed Strings.
6484
///

Aztec/Classes/TextKit/TextStorage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ open class TextStorage: NSTextStorage {
8181

8282
// MARK: - HTML Conversion
8383

84-
let htmlConverter = HTMLConverter()
84+
public let htmlConverter = HTMLConverter()
8585

8686
// MARK: - PluginManager
8787

Aztec/Classes/TextKit/TextView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ open class TextView: UITextView {
173173
///
174174
open var pasteboardDelegate: TextViewPasteboardDelegate = AztecTextViewPasteboardDelegate()
175175

176+
177+
/// If this is true the text view will notify is delegate and notification system when changes happen by calls to methods like setHTML
178+
///
179+
open var shouldNotifyOfNonUserChanges = true
180+
176181
// MARK: - Customizable Input VC
177182

178183
private var customInputViewController: UIInputViewController?
@@ -267,7 +272,7 @@ open class TextView: UITextView {
267272

268273
// MARK: - TextKit Aztec Subclasses
269274

270-
var storage: TextStorage {
275+
public var storage: TextStorage {
271276
return textStorage as! TextStorage
272277
}
273278

AztecTests/Extensions/NSMutableAttributedStringReplaceOcurrencesTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,14 @@ class NSMutableAttributedStringReplaceOcurrencesTests: XCTestCase {
8888

8989
XCTAssertEqual(newAttrString, NSAttributedString(string: "🌎💚💚😬💚🌎"))
9090
}
91+
92+
/// Tests that replacing ocurrences on the end of a range work correctly
93+
///
94+
func testReplaceOcurrencesThatAreOnEndOfRange() {
95+
let attrString = NSAttributedString(string: "Hello"+String(.paragraphSeparator)+"Amazing"+String(.paragraphSeparator)+"World"+String(.paragraphSeparator))
96+
let newAttrString = NSMutableAttributedString(attributedString: attrString)
97+
newAttrString.replaceOcurrences(of: String(.paragraphSeparator), with: String(.lineFeed), within: NSRange(location:6, length: 8))
98+
99+
XCTAssertEqual(newAttrString, NSAttributedString(string: "Hello"+String(.paragraphSeparator)+"Amazing"+String(.lineFeed)+"World"+String(.paragraphSeparator)))
100+
}
91101
}

AztecTests/TextKit/TextStorageTests.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,48 @@ class TextStorageTests: XCTestCase {
494494

495495
XCTAssertEqual(expectedResult, result)
496496
}
497+
498+
func testEmptyListOutput() {
499+
let initialHTML = "<ul><li></li></ul>"
500+
501+
// Setup
502+
let defaultAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 14),
503+
.paragraphStyle: ParagraphStyle.default]
504+
505+
storage.setHTML(initialHTML, defaultAttributes: defaultAttributes)
506+
var expectedResult = String(.paragraphSeparator)
507+
var result = String(storage.string)
508+
509+
XCTAssertEqual(expectedResult, result)
510+
511+
storage.htmlConverter.characterToReplaceLastEmptyLine = Character(.zeroWidthSpace)
512+
513+
storage.setHTML(initialHTML, defaultAttributes: defaultAttributes)
514+
expectedResult = String(.zeroWidthSpace)
515+
result = String(storage.string)
516+
517+
XCTAssertEqual(expectedResult, result)
518+
}
519+
520+
func testCiteOutput() {
521+
let initialHTML = "<cite>Hello<br></cite>"
522+
523+
// Setup
524+
let defaultAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 14),
525+
.paragraphStyle: ParagraphStyle.default]
526+
527+
storage.setHTML(initialHTML, defaultAttributes: defaultAttributes)
528+
var expectedResult = String("Hello")+String(.lineSeparator)
529+
var result = String(storage.string)
530+
531+
XCTAssertEqual(expectedResult, result)
532+
533+
storage.htmlConverter.characterToReplaceLastEmptyLine = Character(.zeroWidthSpace)
534+
535+
storage.setHTML(initialHTML, defaultAttributes: defaultAttributes)
536+
expectedResult = String("Hello")+String(.lineSeparator)
537+
result = String(storage.string)
538+
539+
XCTAssertEqual(expectedResult, result)
540+
}
497541
}

AztecTests/TextKit/TextViewTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,22 @@ class TextViewTests: XCTestCase {
12541254
XCTAssertEqual(textView.text, Constants.sampleText0 + Constants.sampleText1 + String(.lineFeed) + String(.lineFeed))
12551255
}
12561256

1257+
/// Verifies that toggling a Blockquote paragraph in the middle of paragraph does not crash system.
1258+
///
1259+
func testTogglingBlockquoteOnMiddleOfRangeDoesNotCrash() {
1260+
let initialQuote = """
1261+
<blockquote><strong>Quote One</strong></blockquote>
1262+
<blockquote><strong>Quote Two</strong></blockquote>
1263+
<blockquote><strong>Quote Three</strong></blockquote>
1264+
"""
1265+
let textView = TextViewStub(withHTML: initialQuote)
1266+
1267+
textView.selectedTextRange = textView.textRange(from: textView.endOfDocument, to: textView.endOfDocument)
1268+
textView.toggleBlockquote(range: NSRange(location: 11, length: 0))
1269+
1270+
XCTAssertEqual(textView.text, "Quote One" + String(.paragraphSeparator)+"Quote Two"+String(.lineFeed)+"Quote Three")
1271+
}
1272+
12571273

12581274
// MARK: - Pre
12591275

CONTRIBUTING.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
1-
# Contributing to Aztec
1+
# How to Contribute
22

3-
Hi! Thank you for your interest in contributing to the app, we really appreciate it.
3+
First off, thank you for contributing! We're excited to collaborate with you! 🎉
44

5-
There are many ways to contribute – reporting bugs, feature suggestions, fixing bugs, submitting pull requests for enhancements.
5+
The following is a set of guidelines for the many ways you can join our collective effort.
66

7-
## Reporting Bugs, Asking Questions, Sending Suggestions
7+
Before anything else, please take a moment to read our [Code of Conduct](CODE-OF-CONDUCT.md). We expect all participants, from full-timers to occasional tinkerers, to uphold it.
88

9-
Just [file a GitHub issue](https://github.com/wordpress-mobile/WordPress-Aztec-iOS/issues), that’s all. If you want to prefix the title with a “Question:”, “Bug:”, "Feature Request:", or the general area of the application, that would be helpful, but by no means mandatory. If you have write access, add the appropriate labels.
9+
## Reporting Bugs, Asking Questions, and Suggesting Features
1010

11-
If you’re filing a bug, specific steps to reproduce are helpful. Please include the part of the app that has the bug, along with what you expected to see and what happened instead. A screenshot is always helpful as well.
11+
Have a suggestion or feedback? Please go to [Issues](https://github.com/wordpress-mobile/AztecEditor-iOS/issues) and [open a new issue](https://github.com/wordpress-mobile/AztecEditor-iOS/issues/new). Prefix the title with a category like _"Bug:"_, _"Question:"_, or _"Feature Request:"_. Screenshots help us resolve issues and answer questions faster, so thanks for including some if you can.
1212

13-
## Submitting code changes
13+
## Submitting Code Changes
1414

15-
We welcome Pull Requests for bug fixes on any open issues that you'd like to contribute! More details on our process can be found in the [Make WordPress Mobile handbook](https://make.wordpress.org/mobile/handbook/pathways/ios/), specifically [here](https://make.wordpress.org/mobile/handbook/pathways/ios/how-to-contribute/).
15+
If you're just getting started and want to familiarize yourself with the app’s code, we suggest looking at [these issues](https://github.com/wordpress-mobile/AztecEditor-iOS/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) with the **good first issue** label. But if you’d like to tackle something different, you're more than welcome to visit the [Issues](https://github.com/wordpress-mobile/AztecEditor-iOS/issues) page and pick an item that interests you.
1616

17-
## Discussion
17+
We always try to avoid duplicating efforts, so if you decide to work on an issue, leave a comment to state your intent. If you choose to focus on a new feature or the change you’re proposing is significant, we recommend waiting for a response before proceeding. The issue may no longer align with project goals.
1818

19-
Aside from writing issues and pull requests, you can also hang out in the #mobile WordPress.org Slack channel with other WordPress Mobile developers/contributors. Check out chat.wordpress.org for instructions on how to create an account in Slack.
19+
If the change is trivial, feel free to send a pull request without notifying us.
2020

21-
The purpose of the Slack chat is to provide updates about ongoing initiatives regarding the mobile apps and to discuss, prioritize and coordinate future changes. If you have questions, Slack chat is a great place to start.
21+
### Pull Requests and Code Reviews
2222

23-
## Contribute to translations
23+
All code contributions pass through pull requests. If you haven't created a pull request before, we recommend this free video series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).
2424

25-
We use a tool called GlotPress to manage translations. The WordPress-iOS GlotPress instance lives here: http://translate.wordpress.org/projects/apps/ios/dev. To add new translations or fix existing ones, create an account over at GlotPress and submit your changes over at the GlotPress site.
25+
The core team monitors and reviews all pull requests. Depending on the changes, we will either approve them or close them with an explanation. We might also work with you to improve a pull request before approval.
26+
27+
We do our best to respond quickly to all pull requests. If you don't get a response from us after a week, feel free to reach out to us via Slack.
28+
29+
## Getting in Touch
30+
31+
If you have questions or just want to say hi, join the [WordPress Slack](https://make.wordpress.org/chat/) and drop a message on the `#mobile` channel.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ let textView = Aztec.TextView(
8383
defaultMissingImage: UIImage) {
8484
```
8585

86+
## Contributing
87+
88+
Read our [Contributing Guide](CONTRIBUTING.md) to learn about reporting issues, contributing code, and more ways to contribute.
89+
90+
## Getting in Touch
91+
92+
If you have questions about getting setup or just want to say hi, join the [WordPress Slack](https://chat.wordpress.org) and drop a message on the `#mobile` channel.
93+
8694
## License
8795

8896
WordPress-Aztec-iOS is available under the GPLv2 license. See the [LICENSE file](./LICENSE) for more info.

0 commit comments

Comments
 (0)