Skip to content

Commit

Permalink
(GLI-3964) Able to bold and italicize the same word when editing a no…
Browse files Browse the repository at this point in the history
…te. (#3)
  • Loading branch information
aaronmartinez6 authored and stephanheilner committed Jun 22, 2018
1 parent 7abbd35 commit 332b9bb
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 40 deletions.
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.0
8 changes: 4 additions & 4 deletions TSMarkdownParser.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Pod::Spec.new do |s|
s.license = "MIT"
s.authors = { "Tobias Sundstrand" => "[email protected]", "Antoine Cœur" => "" }
s.social_media_url = "https://twitter.com/laptobbe"
s.ios.deployment_target = "6.0"
s.tvos.deployment_target = "9.0"
s.osx.deployment_target = "10.7"
s.ios.deployment_target = "8.0"
s.tvos.deployment_target = "10.0"
s.osx.deployment_target = "10.9"
s.source = { :git => "https://github.com/laptobbe/TSMarkdownParser.git", :tag => s.version.to_s }
s.source_files = "TSMarkdownParser/**/*.{h,m}"
s.source_files = "TSMarkdownParser/**/*.{h,m,swift}"
s.requires_arc = true
s.ios.framework = 'UIKit'
s.tvos.framework = 'UIKit'
Expand Down
26 changes: 22 additions & 4 deletions TSMarkdownParser.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
CC3C7D016BD3BAD6F43D40B3 /* UIImage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3C729D5C0DBD73FCA4990E /* UIImage+Tests.m */; };
CC3C7DB793F3604B20BACFA2 /* TSMarkdownParser.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3C79659073897FDCACEC17 /* TSMarkdownParser.h */; };
CF40360D1BA06F0600C67594 /* markdown_test_image.png in Resources */ = {isa = PBXBuildFile; fileRef = CF40360C1BA06F0600C67594 /* markdown_test_image.png */; };
D4AA0C0C1FA79CEE0019DA54 /* NSAttributedString+Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AA0C0B1FA79CEE0019DA54 /* NSAttributedString+Markdown.swift */; };
F5A4F45B1B36EF4A00C4F56C /* TSMarkdownParser.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3C79659073897FDCACEC17 /* TSMarkdownParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
F5A4F45C1B36EF4A00C4F56C /* TSMarkdownParser.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3C7B7AEA9B50B9BF782FC5 /* TSMarkdownParser.m */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -135,6 +136,8 @@
CC3C7B7AEA9B50B9BF782FC5 /* TSMarkdownParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSMarkdownParser.m; sourceTree = "<group>"; };
CC3C7D01D183A61FD440A876 /* TSMarkdownParser-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TSMarkdownParser-Prefix.pch"; sourceTree = "<group>"; };
CF40360C1BA06F0600C67594 /* markdown_test_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = markdown_test_image.png; sourceTree = "<group>"; };
D4AA0C0A1FA79CEE0019DA54 /* TSMarkdownParserLib-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TSMarkdownParserLib-Bridging-Header.h"; sourceTree = "<group>"; };
D4AA0C0B1FA79CEE0019DA54 /* NSAttributedString+Markdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Markdown.swift"; sourceTree = "<group>"; };
F5A4F4421B36EF4100C4F56C /* TSMarkdownParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TSMarkdownParser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F5A4F4451B36EF4100C4F56C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F6F149C11A7CFB4200A24213 /* TSMarkdownParser.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = TSMarkdownParser.podspec; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -308,6 +311,8 @@
CC3C7B7AEA9B50B9BF782FC5 /* TSMarkdownParser.m */,
C81B78CF1C5483DF00A1DE36 /* TSBaseParser.h */,
C81B78D01C5483DF00A1DE36 /* TSBaseParser.m */,
D4AA0C0B1FA79CEE0019DA54 /* NSAttributedString+Markdown.swift */,
D4AA0C0A1FA79CEE0019DA54 /* TSMarkdownParserLib-Bridging-Header.h */,
);
path = TSMarkdownParser;
sourceTree = "<group>";
Expand Down Expand Up @@ -564,6 +569,9 @@
C8414BC41CB114BC000C7921 = {
CreatedOnToolsVersion = 7.3;
};
CC3C704FF16FF7C75C832F3A = {
LastSwiftMigration = 0900;
};
F5A4F4411B36EF4100C4F56C = {
CreatedOnToolsVersion = 6.3.2;
};
Expand Down Expand Up @@ -728,6 +736,7 @@
buildActionMask = 2147483647;
files = (
C81B78D21C5483DF00A1DE36 /* TSBaseParser.m in Sources */,
D4AA0C0C1FA79CEE0019DA54 /* NSAttributedString+Markdown.swift in Sources */,
CC3C78BB152404965D77F4DA /* TSMarkdownParser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1045,18 +1054,23 @@
CC3C730126B95B11146003EF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DSTROOT = /tmp/TSMarkdownParser.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TSMarkdownParser/TSMarkdownParser-Prefix.pch";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = TSMarkdownParser;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "TSMarkdownParser/TSMarkdownParserLib-Bridging-Header.h";
SWIFT_VERSION = 4.0;
};
name = Release;
};
CC3C75B20E774F01AA9D507C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TSMarkdownParser/TSMarkdownParser-Prefix.pch";
INFOPLIST_FILE = "TSMarkdownParserTests/TSMarkdownParserTests-Info.plist";
Expand Down Expand Up @@ -1112,8 +1126,7 @@
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
MACOSX_DEPLOYMENT_TARGET = 10.7;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "se.computertalk.$(PRODUCT_NAME:rfc1034identifier)";
SDKROOT = iphoneos;
Expand All @@ -1125,12 +1138,17 @@
CC3C7A22C12A4CACA538788A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DSTROOT = /tmp/TSMarkdownParser.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TSMarkdownParser/TSMarkdownParser-Prefix.pch";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = TSMarkdownParser;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "TSMarkdownParser/TSMarkdownParserLib-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
Expand Down Expand Up @@ -1173,8 +1191,7 @@
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
MACOSX_DEPLOYMENT_TARGET = 10.7;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
PRODUCT_BUNDLE_IDENTIFIER = "se.computertalk.$(PRODUCT_NAME:rfc1034identifier)";
SDKROOT = iphoneos;
TVOS_DEPLOYMENT_TARGET = 9.0;
Expand All @@ -1186,6 +1203,7 @@
CC3C7FD1E0A254C798F8235C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TSMarkdownParser/TSMarkdownParser-Prefix.pch";
INFOPLIST_FILE = "TSMarkdownParserTests/TSMarkdownParserTests-Info.plist";
Expand Down
159 changes: 159 additions & 0 deletions TSMarkdownParser/NSAttributedString+Markdown.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//
// NSAttributedString+Markdown.swift
// TSMarkdownParserLib
//
// Created by Stephan Heilner on 10/30/17.
// Copyright © 2017 Computertalk Sweden. All rights reserved.
//

#if !os(OSX)

import UIKit
import Foundation

public extension NSAttributedString {

public func markdownString() -> String {
let bulletCharacter = Character("\u{2022}")
let nonBreakingSpaceCharacter = Character("\u{00A0}")

var markdownString = ""

enum FormattingChange {
case enable
case disable
case keep

static func getFormattingChange(_ before: Bool, after: Bool) -> FormattingChange {
if !before && after { return .enable }
if before && !after { return .disable }
return .keep
}
}

var stringHasBoldEnabled = false
var stringHasItalicEnabled = false
var closingString = ""
var characterOnBulletedListLine = false
var openedNumberedListStarter = false
var characterOnNumberedListLine = false
var numberedListIsFirstLine = false
var previousCharacter: Character?

enumerateAttributes(in: NSRange(location: 0, length: length), options: []) { attributes, range, shouldStop in
if let traits = (attributes[NSAttributedStringKey.font] as? UIFont)?.fontDescriptor.symbolicTraits {
let boldChange = FormattingChange.getFormattingChange(stringHasBoldEnabled, after: traits.contains(.traitBold))
let italicChange = FormattingChange.getFormattingChange(stringHasItalicEnabled, after: traits.contains(.traitItalic))
var formatString = ""
switch boldChange {
case .enable:
formatString += "**"
closingString = "**\(closingString)"
case .disable:
if stringHasItalicEnabled && italicChange == .keep {
formatString += "_**_"
closingString = "_"
} else {
formatString += "**"
closingString = ""
}
case .keep:
break
}

switch italicChange {
case .enable:
formatString += "_"
closingString = "_\(closingString)"
case .disable:
if stringHasBoldEnabled && boldChange == .keep {
formatString = "**_**\(formatString)"
closingString = "**"
} else {
formatString = "_\(formatString)"
closingString = ""
}
case .keep:
break
}

markdownString += formatString

stringHasBoldEnabled = traits.contains(.traitBold)
stringHasItalicEnabled = traits.contains(.traitItalic)
}

let preprocessedString = (self.string as NSString).substring(with: range)
let processedString = preprocessedString.characters.reduce("") { resultString, character in
var stringToAppend = ""

switch character {
case "\\", "`", "*", "_", "{", "}", "[", "]", "(", ")", "#", "+", "-", "!":
stringToAppend = "\\\(character)"
case "\n", "\u{2028}":
stringToAppend = "\(closingString)\(character)"
if !characterOnBulletedListLine && !characterOnNumberedListLine {
stringToAppend += String(closingString.characters.reversed())
}

characterOnBulletedListLine = false
characterOnNumberedListLine = false
case "1", "2", "3", "4", "5", "6", "7", "8", "9", "0":
if previousCharacter == "\n" || previousCharacter == nil || previousCharacter == nonBreakingSpaceCharacter {
openedNumberedListStarter = true
}

numberedListIsFirstLine = previousCharacter == nil ? true : numberedListIsFirstLine
stringToAppend = "\(character)"
case bulletCharacter:
characterOnBulletedListLine = true
stringToAppend = "+ \(previousCharacter != nil ? String(closingString.characters.reversed()) : markdownString)"
markdownString = previousCharacter == nil ? "" : markdownString
case ".":
if openedNumberedListStarter {
openedNumberedListStarter = false
characterOnNumberedListLine = true

stringToAppend = "\(character) \(!numberedListIsFirstLine ? String(closingString.characters.reversed()) : markdownString)"

if numberedListIsFirstLine {
markdownString = ""
numberedListIsFirstLine = false
}
break
}
stringToAppend = "\\\(character)"
case nonBreakingSpaceCharacter:
if characterOnBulletedListLine || characterOnNumberedListLine {
break
}
stringToAppend = " "
default:
if (previousCharacter == "\n" || previousCharacter == "\u{2028}") && characterOnBulletedListLine {
characterOnBulletedListLine = false
stringToAppend = "\(String(closingString.characters.reversed()))\(character)"
} else {
stringToAppend = "\(character)"
}
}

previousCharacter = character
return "\(resultString)\(stringToAppend)"
}
markdownString += processedString
}
markdownString += closingString
markdownString = markdownString.replacingOccurrences(of: "**__**", with: "").replacingOccurrences(of: "****", with: "")
.replacingOccurrences(of: "__", with: "")
// Help the user because they probably didn't intend to have empty bullets and it will make markdown have a + if we leave them
markdownString = markdownString.replacingOccurrences(of: "+ \n", with: "")
if markdownString.hasSuffix("+ ") {
markdownString = (markdownString as NSString).substring(to: markdownString.characters.count - 2)
}

return markdownString
}

}

#endif
2 changes: 2 additions & 0 deletions TSMarkdownParser/TSMarkdownParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ typedef void (^TSMarkdownParserLinkFormattingBlock)(NSMutableAttributedString *a
/// accepts "***text***", "___text___"
- (void)addStrongAndEmphasisParsingWithFormattingBlock:(TSMarkdownParserFormattingBlock)formattingBlock;

- (void)addStrongAndEmphasisParsingWithFormattingBlock:(TSMarkdownParserFormattingBlock)formattingBlock;

/* 7. examples unescaping parsing */
/* to use together with `addEscapingParsing` or `addCodeEscapingParsing` */

Expand Down
Loading

0 comments on commit 332b9bb

Please sign in to comment.