-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #180 from Automattic/develop
Simplenote Mark 1.3
- Loading branch information
Showing
47 changed files
with
2,212 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// | ||
// Element.swift | ||
// Notepad | ||
// | ||
// Created by Rudd Fawcett on 10/14/16. | ||
// Copyright © 2016 Rudd Fawcett. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
|
||
/// A String type enum to keep track of the different elements we're tracking with regex. | ||
public enum Element: String { | ||
case unknown = "x^" | ||
|
||
case h1 = "^(\\#[^\\#](.*))$" | ||
case h2 = "^(\\#{2}(.*))$" | ||
|
||
case body = ".*" | ||
|
||
case bold = "(^|[\\W_])(?:(?!\\1)|(?=^))(\\*|_)\\2(?=\\S)(.*?\\S)\\2\\2(?!\\2)(?=[\\W_]|$)" | ||
case italic = "(^|[\\W_])(?:(?!\\1)|(?=^))(\\*|_)(?=\\S)((?:(?!\\2).)*?\\S)\\2(?!\\2)(?=[\\W_]|$)" | ||
case boldItalic = "(\\*\\*\\*\\w+(\\s\\w+)*\\*\\*\\*)" | ||
case inlineCode = "\\`([^\\`].*?)\\`" | ||
|
||
case url = "\\[([^\\]]+)\\]\\(([^\\)\"\\s]+)(?:\\s+\"(.*)\")?\\)" | ||
case image = "\\!\\[([^\\]]+)\\]\\(([^\\)\"\\s]+)(?:\\s+\"(.*)\")?\\)" | ||
case quote = "^(\\>[^\\>](.*))$" | ||
case firstLine = "\\A.*" | ||
|
||
/// Converts an enum value (type String) to a NSRegularExpression. | ||
/// | ||
/// - returns: The NSRegularExpression. | ||
func toRegex() -> NSRegularExpression { | ||
return self.rawValue.toRegex() | ||
} | ||
|
||
/// Returns an Element enum based upon a String. | ||
/// | ||
/// - parameter string: The String representation of the enum. | ||
/// | ||
/// - returns: The Element enum match. | ||
func from(string: String) -> Element { | ||
switch string { | ||
case "h1": return .h1 | ||
case "h2": return .h2 | ||
case "body": return .body | ||
case "bold": return .bold | ||
case "italic": return .italic | ||
case "boldItalic": return .boldItalic | ||
case "url": return .url | ||
case "image": return .image | ||
case "quote": return .quote | ||
case "firstLine": return .firstLine | ||
case "inlineCode": return .inlineCode | ||
default: return .unknown | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// | ||
// Extensions.swift | ||
// Notepad | ||
// | ||
// Created by Rudd Fawcett on 10/14/16. | ||
// Copyright © 2016 Rudd Fawcett. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
#if os(iOS) | ||
import struct UIKit.CGFloat | ||
#elseif os(macOS) | ||
import struct AppKit.CGFloat | ||
#endif | ||
|
||
extension UniversalColor { | ||
/// Converts a hex color code to UIColor. | ||
/// http://stackoverflow.com/a/33397427/6669540 | ||
/// | ||
/// - parameter hexString: The hex code. | ||
convenience init(hexString: String) { | ||
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) | ||
var int = UInt32() | ||
Scanner(string: hex).scanHexInt32(&int) | ||
let a, r, g, b: UInt32 | ||
switch hex.count { | ||
case 3: // RGB (12-bit) | ||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) | ||
case 6: // RGB (24-bit) | ||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) | ||
case 8: // ARGB (32-bit) | ||
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) | ||
default: | ||
(a, r, g, b) = (255, 0, 0, 0) | ||
} | ||
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) | ||
} | ||
} | ||
|
||
extension String { | ||
/// Converts a String to a NSRegularExpression. | ||
/// | ||
/// - returns: The NSRegularExpression. | ||
func toRegex() -> NSRegularExpression { | ||
var pattern: NSRegularExpression = NSRegularExpression() | ||
|
||
do { | ||
try pattern = NSRegularExpression(pattern: self, options: .anchorsMatchLines) | ||
} catch { | ||
print(error) | ||
} | ||
|
||
return pattern | ||
} | ||
|
||
/// Converts a NSRange to a Range<String.Index>. | ||
/// http://stackoverflow.com/a/30404532/6669540 | ||
/// | ||
/// - parameter range: The NSRange. | ||
/// | ||
/// - returns: The equivalent Range<String.Index>. | ||
func range(from nsRange: NSRange) -> Range<String.Index>? { | ||
guard | ||
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), | ||
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), | ||
let from = String.Index(from16, within: self), | ||
let to = String.Index(to16, within: self) | ||
else { return nil } | ||
return from ..< to | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
<key>NSPrincipalClass</key> | ||
<string></string> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// Notepad-macOS.swift | ||
// Notepad | ||
// | ||
// Created by Christian Tietze on 2017-07-21. | ||
// Copyright © 2017 Rudd Fawcett. All rights reserved. | ||
// | ||
|
||
#if os(macOS) | ||
import AppKit | ||
|
||
public class Notepad: NSTextView { | ||
|
||
var storage: Storage = Storage() | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// | ||
// Storage.swift | ||
// Notepad | ||
// | ||
// Created by Rudd Fawcett on 10/14/16. | ||
// Copyright © 2016 Rudd Fawcett. All rights reserved. | ||
// | ||
|
||
#if os(iOS) | ||
import UIKit | ||
#elseif os(macOS) | ||
import AppKit | ||
#endif | ||
|
||
@objc public class Storage: NSTextStorage { | ||
/// The Theme for the Notepad. | ||
public var theme: Theme? { | ||
didSet { | ||
let wholeRange = NSRange(location: 0, length: (self.string as NSString).length) | ||
|
||
self.beginEditing() | ||
self.applyStyles(wholeRange) | ||
self.edited(.editedAttributes, range: wholeRange, changeInLength: 0) | ||
self.endEditing() | ||
} | ||
} | ||
|
||
/// The underlying text storage implementation. | ||
var backingStore = NSTextStorage() | ||
|
||
var markdownEnabled = false | ||
|
||
override public var string: String { | ||
get { | ||
return backingStore.string | ||
} | ||
} | ||
|
||
override public init() { | ||
super.init() | ||
} | ||
|
||
@objc public class func newInstance() -> Storage { | ||
let storage = Storage() | ||
storage.theme = Theme(markdownEnabled: false) | ||
return storage | ||
} | ||
|
||
override public init(attributedString attrStr: NSAttributedString) { | ||
super.init(attributedString:attrStr) | ||
backingStore.setAttributedString(attrStr) | ||
} | ||
|
||
required public init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
required public init(itemProviderData data: Data, typeIdentifier: String) throws { | ||
fatalError("init(itemProviderData:typeIdentifier:) has not been implemented") | ||
} | ||
|
||
#if os(macOS) | ||
required public init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) { | ||
fatalError("init(pasteboardPropertyList:ofType:) has not been implemented") | ||
} | ||
#endif | ||
|
||
/// Finds attributes within a given range on a String. | ||
/// | ||
/// - parameter location: How far into the String to look. | ||
/// - parameter range: The range to find attributes for. | ||
/// | ||
/// - returns: The attributes on a String within a certain range. | ||
override public func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedStringKey : Any] { | ||
return backingStore.attributes(at: location, effectiveRange: range) | ||
} | ||
|
||
/// Replaces edited characters within a certain range with a new string. | ||
/// | ||
/// - parameter range: The range to replace. | ||
/// - parameter str: The new string to replace the range with. | ||
override public func replaceCharacters(in range: NSRange, with str: String) { | ||
self.beginEditing() | ||
backingStore.replaceCharacters(in: range, with: str) | ||
let change = str.utf16.count - range.length | ||
self.edited(.editedCharacters, range: range, changeInLength: change) | ||
self.endEditing() | ||
} | ||
|
||
override public func addAttribute(_ name: NSAttributedStringKey, value: Any, range: NSRange) { | ||
self.beginEditing() | ||
backingStore.addAttribute(name, value: value, range: range) | ||
self.endEditing() | ||
} | ||
|
||
override public func removeAttribute(_ name: NSAttributedStringKey, range: NSRange) { | ||
self.beginEditing() | ||
backingStore.removeAttribute(name, range: range) | ||
self.edited(.editedAttributes, range: range, changeInLength: 0) | ||
self.endEditing() | ||
} | ||
|
||
/// Sets the attributes on a string for a particular range. | ||
/// | ||
/// - parameter attrs: The attributes to add to the string for the range. | ||
/// - parameter range: The range in which to add attributes. | ||
override public func setAttributes(_ attrs: [NSAttributedStringKey : Any]?, range: NSRange) { | ||
self.beginEditing() | ||
backingStore.setAttributes(attrs, range: range) | ||
self.edited(.editedAttributes, range: range, changeInLength: 0) | ||
self.endEditing() | ||
} | ||
|
||
/// Processes any edits made to the text in the editor. | ||
override public func processEditing() { | ||
let backingString = backingStore.string | ||
let nsRange = backingString.range(from: NSMakeRange(NSMaxRange(editedRange), 0))! | ||
let indexRange = backingString.lineRange(for: nsRange) | ||
let extendedRange: NSRange = NSUnionRange(editedRange, NSRange(indexRange, in: backingString)) | ||
|
||
applyStyles(extendedRange) | ||
super.processEditing() | ||
} | ||
|
||
/// Applies styles to a range on the backingString. | ||
/// | ||
/// - parameter range: The range in which to apply styles. | ||
func applyStyles(_ range: NSRange) { | ||
guard let theme = self.theme else { return } | ||
|
||
let backingString = backingStore.string | ||
backingStore.setAttributes(theme.body.attributes, range: range) | ||
|
||
for (style) in theme.styles { | ||
style.regex.enumerateMatches(in: backingString, options: .withoutAnchoringBounds, range: range, using: { (match, flags, stop) in | ||
guard let match = match else { return } | ||
backingStore.addAttributes(style.attributes, range: match.range(at: 0)) | ||
}) | ||
} | ||
} | ||
|
||
@objc public func applyStyle(markdownEnabled: Bool) { | ||
self.theme = Theme(markdownEnabled: markdownEnabled) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// | ||
// Style.swift | ||
// Notepad | ||
// | ||
// Created by Rudd Fawcett on 10/14/16. | ||
// Copyright © 2016 Rudd Fawcett. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct Style { | ||
var regex: NSRegularExpression! | ||
var attributes: [NSAttributedStringKey: AnyObject] = [:] | ||
|
||
init(element: Element, attributes: [NSAttributedStringKey: AnyObject]) { | ||
self.regex = element.toRegex() | ||
self.attributes = attributes | ||
} | ||
|
||
init(regex: NSRegularExpression, attributes: [NSAttributedStringKey: AnyObject]) { | ||
self.regex = regex | ||
self.attributes = attributes | ||
} | ||
|
||
init() { | ||
self.regex = Element.unknown.toRegex() | ||
} | ||
} |
Oops, something went wrong.