Coder Social home page Coder Social logo

marky-mark's Introduction

Marky Mark

build status codecov Marky Mark is a parser written in Swift that converts markdown into native views. The way it looks it highly customizable and the supported markdown syntax is easy to extend.

Screenshot Screenshot Screenshot

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

  • iOS 8.0+
  • Xcode 8.0+

Installation

CocoaPods 1.0.0+ is required to build MarkyMark

To integrate MarkyMark into your Xcode project using CocoaPods, specify it in your Podfile:

pod "markymark"

Alternatively, add MarkyMark to your project using Swift Package Manager using:

https://github.com/M2Mobi/Marky-Mark

Simple usage

View with default styling

let markDownView = MarkDownTextView()
markDownView.text = "# Header\nParagraph"

View with modified styling

Markymark has many styling options, please check the examples in the styling section of this readme. A simple example:

let markDownView = MarkDownTextView()
markDownView.styling.headingStyling.textColorsForLevels = [
	.orange, //H1 (i.e. # Title)
	.black,  //H2, ... (i.e. ## Subtitle, ### Sub subtitle)
]

markDownView.styling.linkStyling.textColor = .blue
markDownView.styling.paragraphStyling.baseFont = .systemFont(ofSize: 14)
markDownView.text = "# Header\nParagraph"

Supported tags in the Default Flavor

Note: Different tags can be supported by either extending the ContentfulFlavor (default) or by implementing a class that comforms to Flavor and implement the required Rule's

Headings
# H1
## H2
### H3
#### H4
##### H5
###### H6

Lists
- item
	- item
* item
	* item
+ item
	+ item
a. item
b. item
1. item
2. item

Emphasis
*Em*
_Em_
**Strong**
__Strong__
~~Strike through~~

Images
![Alternative text](image.png)

Links
[Link text](https://www.example.net)

Code 
`code`
```code```

Customizing default style

Default Styling instance

var styling = DefaultStyling()

Paragraphs (regular text)

Markdown example: Some text

styling.paragraphStyling.baseFont = .systemFont(ofSize: 14)
styling.paragraphStyling.textColor = .black
styling.paragraphStyling.contentInsets = UIEdgeInsets(top:0, left: 0, bottom: 5, right: 0)  
styling.paragraphStyling.lineHeight = 4
styling.paragraphStyling.isBold = false
styling.paragraphStyling.isItalic = false
styling.paragraphStyling.textAlignment = .left

Headings

Markdown example: # Title or ## Subtitle etc.

styling.headingStyling.fontsForLevels = [
	UIFont.boldSystemFontOfSize(24), //H1
	UIFont.systemFontOfSize(18),     //H2
	UIFont.systemFontOfSize(16)      //H3, ... (last item will be next levels as well)
]

styling.headingStyling.colorsForLevels = [
	.red, //H1
	.black, //H2, ... (last item will be next levels as well)
]

// Margins
styling.headingStyling.contentInsetsForLevels = [
	UIEdgeInsets(top: 5, left: 0, bottom: 15, right: 10), // H1
	UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 10) //H2, ... (last item will be next levels as well)
]

styling.headingStyling.isBold = false
styling.headingStyling.isItalic = false
styling.headingStyling.isUnderlined = false
styling.headingStyling.textAlignment = .left

linkStyling

Markdown Example [Google](http://www.google.com)

styling.linkStyling.textColor = .black
styling.linkStyling.baseFont = nil // Default: nil. Setting baseFont to nil will inherit font from paragraphStyling

styling.linkStyling.isBold = false
styling.linkStyling.isItalic = false
styling.linkStyling.isUnderlined = true

List styling

Markdown Example:

- List item 1
- List item 2
- Nested List item
// By default a font will be used with the bullet character `•`. Use the follow properties to configure it's size and color:
styling.listStyling.bulletFont = .systemFont(ofSize: 14)
styling.listStyling.bulletColor = .black

// Bullets can also be images for more complex styling. When setting images, bullet font and color won't be used anymore
// Array of images used as bullet for each level of nested list items
styling.listStyling.bulletImages = [
	UIImage(named: "circle"),
	UIImage(named: "emptyCircle"),
	UIImage(named: "line"),
	UIImage(named: "square")
]

// Size of the images
styling.listStyling.bulletViewSize = CGSize(width: 16, height: 16)

styling.listStyling.baseFont = .systemFont(ofSize: 14)
styling.listStyling.contentInsets = UIEdgeInsets(top: 0, left:  0, bottom: 10, right: 10)

//Amount of space underneath each list item
styling.listStyling.bottomListItemSpacing = 5

// Number of pixels to indent for each nested list level
styling.listStyling.listIdentSpace = 15

styling.listStyling.textColor = .black

Styling is also possible for:

styling.paragraphStyling
styling.italicStyling
styling.boldStyling
styling.strikeThroughStyling
styling.imageStyling
styling.linkStyling
styling.horizontalLineStyling
styling.codeBlockStyling
styling.inlineCodeBlockStyling
styling.quoteStyling

Please check the DefaultStyling class for more information

Accessibility: Dynamic Type (font scaling)

To enable Dynamic Type on MarkDownTextView:

    markDownView.hasScalableFonts = true

In some cases you may want to configure a maximum size for each font (default is nil). An example on how to limit the font-size for paragraphs

    markDownView.styling.paragraphStyling.maximumPointSize = 26

To set maximumPointSize for headings you can set 'maximumPointSizeForLevels' on headingStyling. The following example sets the maximum size for heading1, heading2, heading3:

    markDownView.styling.headingStyling.maximumPointSizeForLevels = [36, 20, 18]

Advanced usage

Advanced usage is only needed for very specific cases. Making subsets of styling, making different styling combinations, supporting different Markdown rules (syntax) or modifying certain views after that have been generated.

Custom styling objects

struct CustomMarkyMarkStyling: Styling {
	var headerStyling = CustomHeaderStyling()
	var paragraphStyling = ParagraphStyling()
	var linkStyling = ListStyling()

	var itemStylingRules: [ItemStyling] {
		return [headerStyling, paragraphStyling, linkStyling]  
	}
}

You can implement CustomHeaderStyling by checking how other Styling objects have been implemented, like HeaderStyling. Make sure your CustomHeaderStyling comforms to all styling rules you'd like your custom styling to support. i.e. comform to TextColorStylingRule to support textStyle of your custom styling.

Each styling rule can be applied to a markDownItem by comforming to ItemStyling and implement the required method like this:

public func isApplicableOn(_ markDownItem: MarkDownItem) -> Bool {
	return markDownItem is HeaderMarkDownItem
}

This will let the mechanism know it should apply your styling to a HeaderMarkDownItem

You can inject your new styling object by passing it to the constructor of the MarkdownTextView

MarkDownTextView(styling: CustomMarkyMarkStyling())

Adding your own rules

Adding a new rule requires three new classes of based on the following protocol:

  • Rule that can recoginizes the desired markdown syntax
  • MarkDownItem for your new element that will be created by your new rule
  • LayoutBlockBuilder that can convert your MarkDownItem to layout

Add the rule to MarkyMark

markyMark.addRule(MyCustomRule())

Or when using the MarkdownTextView:

markdownTextView.add(rule: MyCustomRule())

Add the block builder to your layout converter

converter.addLayoutBlockBuilder(MyCustomLayoutBlockBuilder())

Or when using the MarkdownTextView use either of these options (depending on the configuration view or attributedString):

markdownTextView.addViewLayoutBlockBuilder(MyCustomLayoutBlockBuilder())
markdownTextView.addAttributedStringLayoutBlockBuilder(MyCustomLayoutBlockBuilder())

If needed you can also add a custom styling class to the default styling

styling.addStyling(MyCustomStyling())

Converter hook

The converter has a callback method which is called every time a MarkDownItem is converted to layout.

converter.didConvertElement = {
	markDownItem, view in
	// Do something with markDownItem and / or view here
}

When using the MarkdownTextView

markDownTextView.onDidConvertMarkDownItemToView = {
    markDownItem, view in

}

Link behavior

By default Markymark opens URL's using UIApplication.shared.delegate.open(_:open:options). links will only be openened when this method is implemented. Markymark allows changing this behavior by passing a custom URLOpener, an object that conforms to the URLOpener protocol.

let markDownView = MarkDownTextView()
markDownTextView?.urlOpener = MyCustomerURLOpener()

Using Markymark in Extensions

Markymark also supports usage the a Today extension. By default tapping url's is not working, since Extensions don't have access to UIApplication.shared, in order to support links you can pass a different url opener to a MarkyDownTextView. See the Example project for a working example:

markDownTextView?.urlOpener = ExtensionContextURLOpener(extensionContext: self.extensionContext)

Author

M2mobi, [email protected]

License

MarkyMark is available under the MIT license. See the LICENSE file for more info.

marky-mark's People

Contributors

basca avatar basthomas avatar bram-l avatar edenman avatar edwinveger avatar erikpoort avatar jimvanzummeren avatar jvanzummeren avatar krs1w avatar loudmouth avatar maren-osnabrug avatar marenosnabrug avatar mennom2mobi avatar nathanfallet avatar neoneye avatar quynhnguyen avatar smillerdev avatar steefys avatar tomvanzummeren avatar tpgmeligmeyling avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

marky-mark's Issues

LineBreaks not consistent

Marky-Mark information

  • Marky-Mark version: 9.1.3
  • MarkDownConfiguration: AttributedString

Describe the bug

I have a test that consists of single words per line. Marky-Mark will ignore the newlines.
If on the other hand I have more than one word per line, it will respect the newlines in the AttributedString output.

Used markdown text

Word
Word
Word

which will result in

WordWordWord

vs

Word with something else behind it
Word
Word

which will result in

Word with something else behind it
WordWord

Is there any setting that I am missing for this issue? Cause I suppose it's now expected behaviour, is it?

Paragraph-Style for Bullet-Point-Lists not working

I seems that it's not properly respecting lineSpacings of Bullet-Point-Lists?!?

We are using non-default lineSpacings and noticed that the lists aren't quite the same. So I dug a bit into the code and noticed in ListAttributedStringLayoutBlockBuilder.swift in function getBulletIndentingAttributesForLevel when creating the paragraphStyle the lineSpacing isn't set. I fixed it by adding the line

paragraphStyle.lineSpacing = listStyling?.neededLineHeight() ?? 0

and now it's working as expected.

I guess it's a bug in marky-mark?!?
Also... shouldn't lineHeight in LineHeightStylingRule be called lineSpacing?

How do we detect when the layout changes?

For long loading content like images, when they eventually pop in, the layout can get messed up with overlapping or squashed views everywhere.

Is there a way to detect these changes and update the containing view to fit the new content?

No support for Bold Italic

Currently the library does not support inline bold italic text, denoted by ***text***. I've seen a BoldItalicMarkDownItem class, but full support does not seem to have been added yet.

Marky-Mark is not detecting url automatically. How to enable that??

Marky-Mark information

  • Marky-Mark version: _____
  • MarkDownConfiguration: _____ (View, AttributedString)

Describe the bug

A clear and concise description of what the bug is.

Used markdown text

What markdown text did you use that caused this issue?
Try to isolate the part causing the issue.

Screenshots

If applicable, add screenshots to help explain your problem.

Bold Italic sequence is working. But when you try to do italic bold sequence its not workin.

Im using marky-mark for a long time. but i notice when comes to bold italic style sometimes its not working. so I investigate it why. i notice this.

1st sequence is press bold then italic -working
sample
How does the grading affect the price?

2nd sequence is press italic then bold - not working
sample
Ah. There we have a fine subject for the next article. At Rat Records, we have a creative approach…

sample

Won't open in Xcode 11.4

Project /Users/blah/Development/github/iOS/Marky-Mark/MarkyMark.xcodeproj cannot be opened because it is missing its project.pbxproj file.

Tried cloning and downloading zip

[Q] - Using Local Images

I am using Marky-Mark with an internal online help system in my app. I am retrieving text in markdown format, using Marky-Mark to render it to an attributed string, then displaying it. But now I want to include an image, which is going to be stored in the application's internal structure (not an external URL).

So I need to statically specify a file that Marky-Mark can find, but that references a location that is in the iOS app bundle. How can I do that? I don't (and can't) know the full path for the image file as it will exist in my customer's device.

Can you help?

emoji issue

It will crash at String.subString if text contains emoji.

After building getting many errors

I have downloaded the github repo and ran the pod install on Example folder.
After building it i am getting many errors. Any idea why ?

screen shot 2017-09-30 at 7 50 04 pm

Dark Mode Support

As of 9.2.1, Marky-Mark does not support dark mode. I have made this change in my local copy, but suggest that adding this to Marky-Mark on an official basis would be both simple and valuable.

The approach I used was to change all references to specific colors in the Marky-Mark code to new custom colors. For example, I replaced all references to UIColor.black to UIColor.mmText, where mmText is defined below:

extension UIColor {
	static let mmLigthTranslucentGray : UIColor = {
		if #available(iOS 13, *) {
			return UIColor.systemGray4.withAlphaComponent(0.25)
		} else {
			return UIColor.lightGray.withAlphaComponent(0.25)
		}
	}()
	
	static let mmGray : UIColor = {
		if #available(iOS 13, *) {
			return UIColor.systemGray
		} else {
			return UIColor.gray
		}
	}()
	
	static let mmText : UIColor = {
		if #available(iOS 13, *) {
			return UIColor.label
		} else {
			return .black
		}
	}()
	
	static let mmBackground : UIColor = {
		if #available(iOS 13, *) {
			return UIColor.secondarySystemBackground
		} else {
			return UIColor.gray
		}
	}()
	
	static let mmGrayText : UIColor = {
		if #available(iOS 13, *) {
			return UIColor.secondaryLabel
		} else {
			return UIColor.gray
		}
	}()
}

Image rule breaks with any non-newline characters after closing parenthesis

I've discovered a very strange bug here that I can't understand. Basically, with markymark, if there is any non-newline character after the closing parenthesis ) for the ImageRule (i.e. )C is an example), then the Markdown converter will omit the TextAttachment that would normally be inserted into the NSAttributedString

Any ideas what may be going on?

Stepping through with the debugger, it looks like markymark is still correctly identifying the markdown image with the regex and returning the correct [MarkDownItem] array—in this case, the relevant image item will be picked up as a nested item: the data structure looks like this after exiting the markyMark.parseMarkDown(text) method (printed from lldb):

(lldb) po markdownItems
▿ 9 elements
  - 0 : <ParagraphMarkDownItem: 0x280261320>
  - 1 : <ParagraphMarkDownItem: 0x280261800>
  - 2 : <ParagraphMarkDownItem: 0x280262430>
  - 3 : <ParagraphMarkDownItem: 0x280262760>
  - 4 : <ParagraphMarkDownItem: 0x280262a90>
  - 5 : <ParagraphMarkDownItem: 0x280262dc0>
  - 6 : <ParagraphMarkDownItem: 0x2802630f0>
  - 7 : <ParagraphMarkDownItem: 0x280263420>
  - 8 : <ParagraphMarkDownItem: 0x280263720>

(lldb) po markdownItems.first!.markDownItems
▿ Optional<Array<MarkDownItem>>
  ▿ some : 2 elements
    ▿ 0 : <ImageMarkDownItem: 0x282ff84b0>
    - 1 : <InlineTextMarkDownItem: 0x280261710>

The ImageMarkDownItem is nested as the first item of the first ParagraphMarkDownItem in the array that is returned.

I think the issue is with the MarkDownConverter.convert(markdownItems) instance method (in this case I'm converting to NSAttributedString).

Here is my code:

static func attributedText(text: String, styling: Styling = styling()) -> NSAttributedString {
    let markyMark = MarkyMark() { $0.setFlavor(ContentfulFlavor()) }
    let markdownItems = markyMark.parseMarkDown(text)
    let config = MarkDownToAttributedStringConverterConfiguration(styling: styling)

    let converter = MarkDownConverter(configuration: config)
    let attributedText = converter.convert(markdownItems)
    return attributedText
}

[iOS ]Unable to add <br/> tag functionality on MarkDowbTextView(). <br/> as it is display on UI.

Please provide your solution. Is it supported or need to find another way ?

Marky-Mark information

  • Marky-Mark version: _____
  • MarkDownConfiguration: _____ (View, AttributedString)

Describe the bug

A clear and concise description of what the bug is.

Used markdown text

What markdown text did you use that caused this issue?
Try to isolate the part causing the issue.

Screenshots

If applicable, add screenshots to help explain your problem.

Damaged MarkyMark.xcodeproj

I downloaded the framework source code and try to figure out how to use the framework with Carthage. Unfortunately, I can't open the Xcode project file. Something is missing.

Project ~/Marky-Mark-master/MarkyMark.xcodeproj cannot be opened because it is missing its project.pbxproj file.

Can you update the MarkyMark.xcodeproj please? Thanks.

codeBlockStyling.contentInset not respected

👋

When doing something like this:

let styling = DefaultStyling()
styling.codeBlockStyling.contentInsets = UIEdgeInsets(top: 5.0, left: 15.0, bottom: 5.0, right: 15.0)

and then applying that styling, I have noticed that the contentInsets aren't rendered. Other attributes of codeBlockStyling are rendered properly.

Ability to intercept links

Currently, markymark handles opening all links internally in AttributedInteractiveLabel with:

 @objc func didTap(_ tapGesture:UITapGestureRecognizer) {
        guard let view = tapGesture.view else { return }

        let locationInView = tapGesture.location(in: view)
        if let url = getUrlAtLocationInView(locationInView) {
            UIApplication.shared.openURL(url)
        }
    }

I would like to be able to intercept link handling and handle them myself. This would let me use SFSafariViewController to open the link or perform other actions. What would be the be the best way to bring this capability to markymark? Originally I was thinking of adding a link handler on AttributedInteractiveLabel like so:

let linkHandler: ((_ link: URL) -> Void)? = nil

...

@objc func didTap(_ tapGesture:UITapGestureRecognizer) {
    guard let view = tapGesture.view else { return }

    let locationInView = tapGesture.location(in: view)
    if let url = getUrlAtLocationInView(locationInView) {
        if let handler = linkHandler {
            handler(url)
        } else {
            UIApplication.shared.openURL(url)
        }
    }
}

Given how it's used internally I'm not sure that's the right approach.

Adding Swift 2.0.x compatibility.

Hi!

Thanks for solving the last query that I had about Swift 2.2.

I just tried to integrate this into a Swift 2.0 project. It didn't work.

I want to fork the repo and do it myself from the Tag 1.0.0 release you made. The project files are missing so I cannot build the project myself, sadly 👎

I'm happy to help maintain this. This library is ridiculously useful! Could you upload the Xcodeproj and associated items? Or is that too much to ask of you? 😂

Thanks again

MarkDownToAttributedStringConverter Swift 2

pod install could not find the MarkDownToAttributedStringConverterConfiguration in Swift 2.2. I don't want to use Swift 2.3 or 3. This project is dependent on Swift 2.2 at the moment. We are using Version 0.1.3. Can you assist? Thanks

Handle tap on elements

Hi,

I'm currently using Marky-Mark and I love it, it saves me a lot of time.

I need to handle touches on markdown elements (in my case, I need to open a new view controller on click on some images). What would be the simplest way to do that?

I was thinking using a custom LayoutBlockBuilder with a delegate and a rule which overrides the default "image" rule. But I somehow need to pass my delegate to the custom LayoutBlockBuilder. Or maybe it could be a native feature for every items?

Thanks a lot !

Image loading blocking UI

While rendering big size image, app got freezed because it's blocked the UI. It is possible to render images in background without blocking the UI?

Awesome library! Any chance of SVG support

Firstly, I just wanted to say I really think this library is awesome! I love that i don't have to deal with all the asynchronous loading that web views require and that I can throw either NSAttributedString or UIView into places like UITableViewCell—something that web views make less feasible. I also like how you've added functionality for easy extension.

My question is about SVG support for inline images...while I realize I can make my own LayoutBlockBuilder to extend functionality, the issue is that if I want to associate my SVGAttributedStringBlockBuilder with an ImageMarkDownItem, I can't grab the file or altText from the markdown item as they missing accessibility identifiers and therefore default to internal.

Additionally, the niceties you have implemented in your own TextAttachment class are also internal and therefore my SVG block builder can't take advantage of that either without copy/pasting the class.

Can, at minimum, the accessibility identifiers be made public? Additionally, I'm curious if you have any thoughts about adding an SVG sub-spec that depends on another framework like SVGKit (enables extraction of UIImage for injecting for injecting to NSTextAttachment), or SwiftSVG (seems to have active maintenance, but no simple UIImage extraction).

URLs containing underscores are being italicized

Marky-Mark information

  • Marky-Mark version: 9.1.0 (seen since 6.0)
  • MarkDownConfiguration: AttributedString

Describe the bug

URLs containing underscores or other markdown characters are being formatted (italicized in this case) when they shouldn't

I'm using the Example App to create this issue.

Used markdown text

<https://test.com/adsf_qwer_dg_dfg_swert_dfg>

[https://test.com/adsf_qwer_dg_dfg_swert_dfg]

https://test.com/adsf_qwer_dg_dfg_swert_dfg

Screenshots

image

Setting MarkDownTextView.onDidConvertMarkDownItemToView does not work

Marky-Mark information

  • Marky-Mark version: 10.1.4
  • MarkDownConfiguration: view (View, AttributedString)

Describe the bug

I have set up a MarkDownTextView via xib, and am attempting to set onDidConvertMarkDownItemToView via the didSet of the outlet as so:

    @IBOutlet weak var markDownTextView: MarkDownTextView! {
        didSet {
            self.markDownTextView.onDidConvertMarkDownItemToView = { item, view in
               // doesn't get called
            }
        }
    }

Unfortunately onDidConvertMarkDownItemToView never gets called. Am I doing something wrong? I have made a PR to fix this: #118

Allow custom inline rules.

There is no public API that allows you to add a custom LayoutBlockBuilder into MarkDownToInlineAttributedStringConverterConfiguration. In a result, I don't know a convenient way of adding a custom inline rule.

Maybe add an option to inject it into MarkdownToViewConverterConfiguration or into MarkDownToAttributedStringConverterConfiguration?

There might exist other issues related to internal access controls.

List View has incorrect height

Hey, I'm using the markdown text view inside a tableview cell.
The list view and list view item views don't have any sort of constraints or pinning, so in a cell are squashed to the nib height rather then having an intrinsic size.

Let me know if you need any further information.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.