Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

[iOS] Enable indent/unindent #519

Merged
merged 4 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions platforms/ios/example/Shared/View+Accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public enum WysiwygSharedAccessibilityIdentifier: String {
case redoButton = "WysiwygRedoButton"
case orderedListButton = "WysiwygOrderedListButton"
case unorderedListButton = "WysiwygUnorderedListButton"
case indentButton = "WysiwygIndentButton"
case unIndentButton = "WysiwygUnIndentButton"
case codeBlockButton = "WysiwygCodeBlockButton"
case quoteButton = "WysiwygQuoteButton"
case sendButton = "WysiwygSendButton"
Expand Down
8 changes: 8 additions & 0 deletions platforms/ios/example/Wysiwyg.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
A64AB144296C747C00F08494 /* WysiwygUITests+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB143296C747C00F08494 /* WysiwygUITests+Format.swift */; };
A64AB146296C759A00F08494 /* WysiwygUITests+Quotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */; };
A64AB148296C769000F08494 /* WysiwygUITests+CodeBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB147296C769000F08494 /* WysiwygUITests+CodeBlocks.swift */; };
A6852F032981643900632252 /* WysiwygUITests+Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6852F022981643900632252 /* WysiwygUITests+Lists.swift */; };
A6852F052981661000632252 /* WysiwygUITests+Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6852F042981661000632252 /* WysiwygUITests+Indent.swift */; };
A68E713C291D34A50023CC04 /* View+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2157428C0E95C00C8E727 /* View+Accessibility.swift */; };
A68E7140291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A68E713F291D40710023CC04 /* WysiwygSharedConstants.swift */; };
A68E7141291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A68E713F291D40710023CC04 /* WysiwygSharedConstants.swift */; };
Expand Down Expand Up @@ -56,6 +58,8 @@
A64AB143296C747C00F08494 /* WysiwygUITests+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Format.swift"; sourceTree = "<group>"; };
A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Quotes.swift"; sourceTree = "<group>"; };
A64AB147296C769000F08494 /* WysiwygUITests+CodeBlocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+CodeBlocks.swift"; sourceTree = "<group>"; };
A6852F022981643900632252 /* WysiwygUITests+Lists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Lists.swift"; sourceTree = "<group>"; };
A6852F042981661000632252 /* WysiwygUITests+Indent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Indent.swift"; sourceTree = "<group>"; };
A68E713F291D40710023CC04 /* WysiwygSharedConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygSharedConstants.swift; sourceTree = "<group>"; };
A6C2157428C0E95C00C8E727 /* View+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Accessibility.swift"; sourceTree = "<group>"; };
A6C2157828C0F62000C8E727 /* WysiwygActionToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygActionToolbar.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,8 +136,10 @@
A6472CC52886CF840021A0E8 /* WysiwygUITests.swift */,
A64AB147296C769000F08494 /* WysiwygUITests+CodeBlocks.swift */,
A64AB143296C747C00F08494 /* WysiwygUITests+Format.swift */,
A6852F042981661000632252 /* WysiwygUITests+Indent.swift */,
A64AB141296C744200F08494 /* WysiwygUITests+InlineCode.swift */,
A64AB13F296C73CE00F08494 /* WysiwygUITests+Links.swift */,
A6852F022981643900632252 /* WysiwygUITests+Lists.swift */,
A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */,
A64AB13D296C732500F08494 /* WysiwygUITests+Typing.swift */,
);
Expand Down Expand Up @@ -350,11 +356,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A6852F052981661000632252 /* WysiwygUITests+Indent.swift in Sources */,
A64AB13E296C732500F08494 /* WysiwygUITests+Typing.swift in Sources */,
A6472CC62886CF840021A0E8 /* WysiwygUITests.swift in Sources */,
A64AB140296C73CE00F08494 /* WysiwygUITests+Links.swift in Sources */,
A68E7141291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */,
A64AB144296C747C00F08494 /* WysiwygUITests+Format.swift in Sources */,
A6852F032981643900632252 /* WysiwygUITests+Lists.swift in Sources */,
A68E713C291D34A50023CC04 /* View+Accessibility.swift in Sources */,
A64AB146296C759A00F08494 /* WysiwygUITests+Quotes.swift in Sources */,
A64AB142296C744200F08494 /* WysiwygUITests+InlineCode.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import WysiwygComposer
extension WysiwygAction: CaseIterable, Identifiable {
public static var allCases: [WysiwygAction] = [
.bold, .italic, .strikeThrough, .underline, .inlineCode,
.link, .undo, .redo, .orderedList, .unorderedList, .codeBlock, .quote,
.link, .undo, .redo, .orderedList, .unorderedList, .indent, .unIndent, .codeBlock, .quote,
]

public var id: String {
Expand Down Expand Up @@ -72,6 +72,10 @@ extension WysiwygAction: CaseIterable, Identifiable {
return .orderedListButton
case .unorderedList:
return .unorderedListButton
case .indent:
return .indentButton
case .unIndent:
return .unIndentButton
case .codeBlock:
return .codeBlockButton
case .quote:
Expand Down Expand Up @@ -102,6 +106,10 @@ extension WysiwygAction: CaseIterable, Identifiable {
return "list.number"
case .unorderedList:
return "list.bullet"
case .indent:
return "increase.indent"
case .unIndent:
return "decrease.indent"
case .codeBlock:
return "note.text"
case .quote:
Expand Down Expand Up @@ -133,6 +141,10 @@ private extension WysiwygAction {
return .orderedList
case .unorderedList:
return .unorderedList
case .indent:
return .indent
case .unIndent:
return .unIndent
case .codeBlock:
return .codeBlock
case .quote:
Expand Down
64 changes: 64 additions & 0 deletions platforms/ios/example/WysiwygUITests/WysiwygUITests+Indent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest

extension WysiwygUITests {
func testIndent() {
textView.typeTextCharByChar("Item 1")
button(.orderedListButton).tap()
textView.typeTextCharByChar("\nItem 2")
// Indent second list item.
button(.indentButton).tap()
assertTreeEquals(
"""
└>ol
└>li
├>p
│ └>"Item 1"
└>ol
└>li
└>"Item 2"
"""
)
XCTAssertFalse(button(.indentButton).isEnabled)
// Transform indented list item into unordered.
button(.unorderedListButton).tap()
assertTreeEquals(
"""
└>ol
└>li
├>p
│ └>"Item 1"
└>ul
└>li
└>"Item 2"
"""
)
// Unindent second list item.
button(.unIndentButton).tap()
assertTreeEquals(
"""
└>ol
├>li
│ └>"Item 1"
└>li
└>"Item 2"
"""
)
XCTAssertFalse(button(.unIndentButton).isEnabled)
}
}
46 changes: 46 additions & 0 deletions platforms/ios/example/WysiwygUITests/WysiwygUITests+Lists.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest

extension WysiwygUITests {
func testList() {
textView.typeTextCharByChar("Item 1")
// Create list and add a second item.
button(.orderedListButton).tap()
textView.typeTextCharByChar("\nItem 2")
assertTreeEquals(
"""
└>ol
├>li
│ └>"Item 1"
└>li
└>"Item 2"
"""
)
// Transform list into unordered.
button(.unorderedListButton).tap()
assertTreeEquals(
"""
└>ul
├>li
│ └>"Item 1"
└>li
└>"Item 2"
"""
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ extension NSMutableAttributedString {
switch textBlock.backgroundColor {
case TempColor.codeBlock:
addAttribute(.backgroundStyle, value: style.codeBlockBackgroundStyle, range: range)
addAttribute(.paragraphStyle, value: NSParagraphStyle.default, range: range)
case TempColor.quote:
addAttribute(.backgroundStyle, value: style.quoteBackgroundStyle, range: range)
addAttribute(.paragraphStyle, value: NSParagraphStyle.default, range: range)
default:
break
}
Expand Down Expand Up @@ -59,4 +61,15 @@ extension NSMutableAttributedString {
self.deleteCharacters(in: range)
}
}

/// Remove the vertical spacing for paragraphs in the entire attributed string.
func removeParagraphVerticalSpacing() {
enumerateTypedAttribute(.paragraphStyle) { (style: NSParagraphStyle, range: NSRange, _) in
guard let mutableStyle = style.mutableCopy() as? NSMutableParagraphStyle else { return }

mutableStyle.paragraphSpacing = 0
mutableStyle.paragraphSpacingBefore = 0
addAttribute(.paragraphStyle, value: mutableStyle as Any, range: range)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ public final class HTMLParser {
mutableAttributedString.applyBackgroundStyles(style: style)
mutableAttributedString.applyInlineCodeBackgroundStyle(codeBackgroundColor: style.codeBackgroundColor)
mutableAttributedString.removeDiscardableText()

// FIXME: This solution might not fit for everything.
mutableAttributedString.addAttribute(.paragraphStyle,
value: NSParagraphStyle.default,
range: .init(location: 0, length: mutableAttributedString.length))
mutableAttributedString.removeParagraphVerticalSpacing()

removeTrailingNewlineIfNeeded(from: mutableAttributedString, given: html)
return mutableAttributedString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public enum WysiwygAction: Equatable {
case orderedList
/// Create an unordered list.
case unorderedList
case indent
case unIndent
case codeBlock
case quote
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ extension ComposerModel {
update = orderedList()
case .unorderedList:
update = unorderedList()
case .indent:
update = indent()
case .unIndent:
update = unIndent()
case .codeBlock:
update = codeBlock()
case .quote:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,19 @@ final class ListsSnapshotTests: SnapshotTests {
record: isRecord
)
}

func testIndentedListContent() throws {
viewModel.setHtmlContent(
"""
<ol><li>Item 1</li><li><p>Item 2</p>\
<ol><li>Item 2A</li><li>Item 2B</li><li>Item 2C</li></ol>\
</li><li>Item 3</li></ul>
"""
)
assertSnapshot(
matching: hostingController,
as: .image(on: .iPhone13),
record: isRecord
)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

@testable import WysiwygComposer
import XCTest

private enum Constants {
/// A list with three items.
static let sampleListHtml = "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol>"
/// A list with three items. Second item is indented, Third item is indented twice.
static let indentedSampleListHtml = """
<ol><li><p>Item 1</p>\
<ol><li><p>Item 2</p>\
<ol><li>Item 3</li></ol></li></ol></li></ol>
"""
}

extension WysiwygComposerTests {
func testIndent() {
let composer = newComposerModel()
_ = composer.setContentFromHtml(html: Constants.sampleListHtml)
// Select somewhere on item 2
_ = composer.select(startUtf16Codeunit: 9, endUtf16Codeunit: 9)
_ = composer.indent()
XCTAssertTrue(composer.actionStates()[.indent] == .disabled)
// Select somewhere on item 3
_ = composer.select(startUtf16Codeunit: 18, endUtf16Codeunit: 18)
_ = composer.indent()
_ = composer.indent()
XCTAssertTrue(composer.actionStates()[.indent] == .disabled)
XCTAssertEqual(
composer.getContentAsHtml(),
Constants.indentedSampleListHtml
)
}

func testUnIndent() {
let composer = newComposerModel()
_ = composer.setContentFromHtml(html: Constants.indentedSampleListHtml)
// Select somewhere on item 3
_ = composer.select(startUtf16Codeunit: 18, endUtf16Codeunit: 18)
_ = composer.unIndent()
_ = composer.unIndent()
XCTAssertTrue(composer.actionStates()[.unIndent] == .disabled)
// Select somewhere on item 2
_ = composer.select(startUtf16Codeunit: 9, endUtf16Codeunit: 9)
_ = composer.unIndent()
XCTAssertTrue(composer.actionStates()[.unIndent] == .disabled)
XCTAssertEqual(
composer.getContentAsHtml(),
Constants.sampleListHtml
)
}
}