Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(markdown): Markdown support #326

Merged
merged 17 commits into from
Sep 17, 2017
Merged

feat(markdown): Markdown support #326

merged 17 commits into from
Sep 17, 2017

Conversation

machour
Copy link
Member

@machour machour commented Sep 12, 2017

I looked at #106 and #202 and implemented a better support for README.md rendering in the app, by introducing a GitHub flavoured MarkDown component:

MarkDown

It takes care of decorating the markup html with a stylesheet, fix a weird rendering issue with Android, and renders it in a WebView, with basePath set in order to handle internal links correctly.

Having it as component mean that we can later re-use it to render .md files in the Code section of the app :)

I'm just facing two issues if someone can advise:

  1. github-markdown.css is from this package: https://github.com/sindresorhus/github-markdown-css. It should be included as an asset in the app, but I couldn't do it as WebView is kind of "tricky" to stay polite.
  2. on Android, it's impossible to horizontally scroll as it's intercepted by the app navigation and we move away from the screen. Seems to be correctly handled on the iOS simulator

And one design concern:

If you navigate inside the WebView using a link, and hit the back arrow on top of the screen, you'll get back to the repository screen. Do we want to intercept the click instead and navigate back in history? And is letting the user navigating freely in this WebView is a good thing? 🤔

Copy link
Member

@andrewda andrewda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😲 This is just awesome!!! I'll take a closer look into the issues you're having tomorrow if they're not already resolved. Fantastic work!! 👏

baseUrl: string,
};

componentDidMount() {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed, I believe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oupsy, done :)

import React, { Component } from 'react';
import { WebView, Platform } from 'react-native';

export class MarkDown extends Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be named Markdown.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to MarkdownWebview in last commit

return `<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://machour.idk.tn/github-markdown.css" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you're working on this, but for the sake of documenting it, https://machour.idk.tn/github-markdown.css should probably be changed to a local file eventually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's one of the issue I was facing.
I added the file to src/assets/css for linking.

@machour
Copy link
Member Author

machour commented Sep 12, 2017

@andrewda Thanks a lot for the positive feedback! I took care of your two comments and implemented markdown rendering for issues comments, with images and emojis handling 🕺

issueception

I first was tempted to get rid of MarkdownWebview all together in favor of MarkdownHtmlview, but images in .md files can be hosted anywhere (https:// and non http://) and I don't think we want to allow arbitrary hosts in the app. Security would become a nightmare.

For now, MarkdownHtmlview only supports contents from https://user-images.githubusercontent.com (and I have no clue where this host is authorized in GitPoint)

Now, thinking about properly rendering the .md files in the code section, I think we could use MarkdownWebview and the Markdown API from GitHub but that might be kind of slow (one request to get the file contents, another one to transform it, and then WebView).

Maybe we could have gitpoint.co hosting a simple transformer and point our WebView to it whenever needed? 🤔

@machour
Copy link
Member Author

machour commented Sep 12, 2017

By the way, here are a few things to tweak in MarkdownHtmlview later on:

  • transform @user to a link to a profile
  • transform #ID to a link to an issue/pr
  • follow internal links and go to Code section with them

},
em: {
...textStyle,
...fonts.fontPrimaryBold, // FIXME: we need an italic font
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should italic font go here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, to render this markdown correctly. We need to bundle it like we do for other variants


if (Platform.OS === 'android') {
// Android's webview won't correctly render new lines in <pre> leading
// to code being rendered on a single line.. we convert those to <br />s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@housseindjirdeh
Copy link
Member

This is great @machour 👏 👏 Tomorrow I'll pull this down and test it 😍

Still trying to understand why MarkdownWebview and the Markdown API from GitHub will be slow 🤔 And what do you mean by a transformer? Sorry for the silly questions 😭

@machour
Copy link
Member Author

machour commented Sep 13, 2017

@housseindjirdeh I'm just worried about an extra API call, that's all 😄
Forget about the transformer, that was me thinking out loud and in the wrong direction 😉

Let me know when you check this PR, and if you're able to use a bundled css file in the Webview rather than the external one I'm using now. Couldn't achieve that on my own.

@machour machour changed the title feat(markdown): Implement Markdown component and use it for README files feat(markdown): Markdown support Sep 13, 2017
Copy link
Member

@alejandronanez alejandronanez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please re evaluate the switch statement?

const mapStateToProps = state => ({
authUser: state.auth.user,
});

class CommentListItemComponent extends Component {
props: {
comment: Object,
onLinkPress: Function,
// onLinkPress: Function,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! I think you missed this one

const textStyleLight = {
fontSize: Platform.OS === 'ios' ? normalize(11) : normalize(12),
color: colors.primaryDark,
...lightFont,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a really controlled environment but you should write this like:

const textStyleLight = {
  ...lightFont,
  fontSize: Platform.OS === 'ios' ? normalize(11) : normalize(12),
  color: colors.primaryDark
}

By doing that you'll avoid potential overwrites in the new object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely (wasn't well aware of that, thanks)

/* eslint-disable no-param-reassign */
// Convert checkboxes
node.data = node.data.replace(
/^\s*\[\s?\]/,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about moving this regexp into a constant in order to improve its meaning?

Something like const checkBoxes = /^..... after doing that you can remove the comment. =]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great readability advice, thanks!

break;

case 'tag':
switch (node.name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm not a big fan of switch statements (also they add complexity to the codebase).
What do you think about moving this into some kind of function? Or maybe use the strategy pattern to solve this?

I'd love to see a solution like that :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like this? http://robdodson.me/javascript-design-patterns-strategy/ (seems indeed way more readable)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

@alejandronanez
Copy link
Member

alejandronanez commented Sep 13, 2017 via email

@machour
Copy link
Member Author

machour commented Sep 14, 2017

@alejandronanez thanks a lot, I applied your remarks in last commit.

Could you tell me if this is how the strategy pattern should be implemented?
I had to use /* eslint-disable no-unused-vars */, I hope that's okay.

Also, if you can advise: I think I'm doing something really wrong, if you look at navigateToIssue and navigateToProfile :
https://github.com/gitpoint/git-point/pull/326/files#diff-30d90f2bbbe7ca7893404044e8662a80R136

I'm passing navigation from MakdownHtmlview as I don't have access to this.props anymore. Is that okay ?

And for the missing onLinkPress implementation you spotted, it was originally defined here, and I'd like to integrate it into MakdownHtmlview, but it requires the authUser.. I'm unsure if I need to propagate it as a prop from IssueScreen => CommentListItem => MarkdownHtml => as a callback parameter, or if there's a nicer way of getting this global state..
https://github.com/gitpoint/git-point/blob/master/src/issue/screens/issue.screen.js#L106

@housseindjirdeh
Copy link
Member

Just pulled it down to test, all in all README's seem to look just great 👍

image

Issue comments are also getting there as well. However I am noticing that there seems to be some slight font differences and styles with different element nodes:

image

as well as the size seems a little too big, we might need a little more padding for some breathing room and for some reason: there seems to be line breaks when it seems there shouldn't 🤔

image

So all in all some smoothing needs to be done but it's really looking solid and almost there 😍

And to help answer your questions mate:

I'm passing navigation from MakdownHtmlview as I don't have access to this.props anymore. Is that okay ?

Yep there are instances of the app where navigation is being passed down. However is there a particular reason why you can't access this.props in here?

And for the missing onLinkPress implementation you spotted, it was originally defined here, and I'd like to integrate it into MakdownHtmlview, but it requires the authUser.. I'm unsure if I need to propagate it as a prop from IssueScreen => CommentListItem => MarkdownHtml => as a callback parameter, or if there's a nicer way of getting this global state..

CommentListItem is already stateful and is already referencing authUser from the state (https://github.com/gitpoint/git-point/blob/master/src/components/comment-list-item.component.js#L123). You should be able to just pass it down a single level chain from here :)

@machour
Copy link
Member Author

machour commented Sep 14, 2017

@housseindjirdeh I can't access this.props cause I'm outside of the MarkdownHtmlview object: https://github.com/gitpoint/git-point/pull/326/files#diff-30d90f2bbbe7ca7893404044e8662a80R136 I'm sure there's a better way to handle that :/

@machour
Copy link
Member Author

machour commented Sep 14, 2017

@housseindjirdeh I fixed the font issues and greatly simplified my work by understanding better how HtmlView works.

I needed to use only <Text> everywhere as the <View>'s reserved boxes and that what was making the weird wrapping.

I'm now struggling with two issues UI wise:

  • I can't seem to place a View under h1 or h2 to emulate the divided line. No matter what I tried, the dividers if floating to the right of the text
  • Same for blockquote, I can't draw a line to its right. I think I'm misunderstanding something with the flex logic (tried column/row/grow/shrink, nothing worked)

I also started supporting tables 🕺

@machour
Copy link
Member Author

machour commented Sep 14, 2017

Here's a big markdown chunk to showcase all features :

It's very easy to make some words bold and other words italic with Markdown.

You can even link to Google!

Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

Sometimes you want numbered lists:

  1. One
  2. Two
  3. Three

Sometimes you want bullet points:

  • Start a line with a star
  • Profit!

Alternatively,

  • Dashes work just as well
  • And if you have sub points, put two spaces before the dash or star:
    • Like this
    • And this

If you want to embed images, this is how you do it:
Image of Yaktocat

h1

h2

h3

h4

h5
h6

If you'd like to quote someone, use the > character before the line:

Coffee. The finest organic suspension ever devised... I beat the Borg with it.

  • Captain Janeway

There are many different ways to style code with GitHub's markdown. If you have inline code blocks, wrap them in backticks: var example = true. If you've got a longer block of code, you can indent with four spaces:

if (isAwesome){
  return true
}

GitHub also supports something called code fencing, which allows for multiple lines without indentation:

if (isAwesome){
  return true
}

And if you'd like to use syntax highlighting, include the language:

if (isAwesome){
  return true
}

GitHub supports many extras in Markdown that help you reference and link to people. If you ever want to direct a comment at someone, you can prefix their name with an @ symbol: Hey @kneath — love your sweater!

But I have to admit, tasks lists are my favorite:

  • This is a complete item
  • This is an incomplete item

When you include a task list in the first comment of an Issue, you will see a helpful progress bar in your list of issues. It works in Pull Requests, too!

And, of course emoji! ✨ 🐫 💥 :notanemoji:

Copy link
Member

@andrewda andrewda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a few minor things to tweak, but overall looks absolutely fantastic!!

@machour
Copy link
Member Author

machour commented Sep 15, 2017

Ok, I think this is as far as I can go for now on Markdown support inside comments before madness kicks in 😆

screen shot 2017-09-16 at 12 29 23 am
screen shot 2017-09-16 at 12 30 32 am
screen shot 2017-09-16 at 12 30 13 am
screen shot 2017-09-16 at 12 29 55 am

Putting this for review and adoption (knowing that I still need someone to properly bundle the CSS file for MarkdownWebview which is used to render the README.md file through a WebView).

Cheers !

let rendered = marked(md);

rendered = rendered
.replace(new RegExp(/<p>*>/g), '<span>')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, should be better use Just /<p>*>/g, I think new RegExp is not necessary :)

const todoItem = /<li>\s*\[(x|\s)?\](.*)<\/li>/g;
const emojiMarkup = /:(\w+):/g;

let rendered = marked(md);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this let is not necessary, you can just

return marked(md)
   .replace...

This way we can avoid using let :) What do you think about?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm all for it. Was keeping it in a variable to be able to console.log() it ;)

@@ -120,12 +120,21 @@ class Issue extends Component {
node.attribs.class &&
node.attribs.class.includes('issue-link')
) {
navigation.navigate('Issue', {
issueURL: node.attribs['data-url'].replace(
let issueURL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic could be transformed in a method in order to avoid let

@jouderianjr
Copy link
Member

jouderianjr commented Sep 16, 2017

Hey, I'm not a reviewer but I've sent some comments in the code :) I hope that's not a problem

@housseindjirdeh
Copy link
Member

Welcome to the team @jouderianjr :) You should be able to submit reviews now 🙌

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lord have mercy @machour, this is one outstanding PR. Can't even express how amazing this is :O

Left some tiny tiny nitpick comments but can't find anything else I would change. Really solid job mate, this makes markdown look sooo good in the app

Once you get the minor nits that I and @jouderianjr mentioned (all really shouldn't take long at all), we'll merge this baby in

);
};

export class MarkdownHtmlview extends Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the most minor nitpick of all nitpicks, but do you think we should name this MarkdownHtmlView? :)

a: linkStyle,
};

const stylessheet = StyleSheet.create(styles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another minor nitpick 👀, but could we keep the variable camelcase like so: styleSheet. Also think it might be nicer to name it styleSheet rather than stylesSheet since it matches the RN API

import React, { Component } from 'react';
import { WebView, Platform } from 'react-native';

export class MarkdownWebview extends Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, MarkdownWebView since RN API also camel-cases WebView

@machour
Copy link
Member Author

machour commented Sep 16, 2017

@jouderianjr your comments are really appreciated and have been taken care of. Thanks a lot! ✋

@housseindjirdeh All done. I also embedded OpenSans-Italic.ttf for android, and loaded the css file from file:///android_assets/.

I'm still unable to figure out how to do load a bundled CSS for iOS thought, so I'm still pointing to an external url, could you guys have a look at it ?

And last but not least, there's still the problem in Android webview where you can't scroll horizontally (to read a code sample), because the swipe is intercepted by the NavigationTab.. @jouderianjr is also having this issue for his PR : #327 (comment)

Can we haz disable? 😆

@housseindjirdeh
Copy link
Member

housseindjirdeh commented Sep 17, 2017

@machour Thank you sooo much matey. Unfortunately, that's something I don't think we should be disabling 😞 I'm quite used to navigating that way and I'm sure quite a few folks are the same (discussion continued in #327)

Isn't it only the far left of the screen that this swipeEnabled kicks in? Does it completely disrupt how WebView scrolls in Android even if my finger is on the middle of the screen?

And are you considering replacing AvenirNext for iOS with OpenSans throughout the app?

@machour
Copy link
Member Author

machour commented Sep 17, 2017

@housseindjirdeh just tried on a real Android device, no matter where I start scrolling, the swipeEnabled kicks in. The only was to scroll is to long press the <pre> until you get the copy/paste native popup, and extend the selection to the right, which is far from ideal.

If we want to keep swipeEnabled, here's a few options we might consider:

  • tweak the CSS to make the <pre> contents wrap
  • ditch MarkdownWebView completely and use MarkdownHtmlView to render the README, and fine tune <pre> handling to embed it in a scroll view.

(later option is something I'm considering for another PR, to have a strong MarkdownView component, dealing with all md related stuff in the app, and why not, extract it to git-point/react-native-markdown-view in the future).

But let's not make the scroll issue a show-stopper for now, especially if we're implementing #285 soon ;)

For the fonts, I wouldn't dare messig with AvenirNext :D
I'm using AvenirNext-Italic for iOS which was already bundled but not defined in fonts.js. I bundled OpenSans-Italic for Android: https://github.com/gitpoint/git-point/pull/326/files#diff-a0fa44442811d58ef50c6de7283b8916

@housseindjirdeh
Copy link
Member

@machour Nice thank you, appreciate you including OpenSans- Italic and sorry for not including it myself. Yep AvenirNext is included as system font for iOS.

100% agreed. i'm more than happy to merge this in for now and we can open an issue to fix the scroll issue on Android in the near future ⭐

@housseindjirdeh housseindjirdeh merged commit 2b09dc7 into gitpoint:master Sep 17, 2017
@machour machour deleted the markdown-renderer branch September 18, 2017 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants