react-native-markdown-renderer icon indicating copy to clipboard operation
react-native-markdown-renderer copied to clipboard

Q: How can i use Syntax highlighting in React Native?

Open syedabuthahirm opened this issue 7 years ago • 4 comments
trafficstars

I can't find any examples for syntax highlighting in react native? How can i convert https://github.com/markdown-it/markdown-it#syntax-highlighting this example to react native?

syedabuthahirm avatar May 04 '18 15:05 syedabuthahirm

I dont know what highlightjs is returning as a syntax. i need some time to delf in to this.

mientjan avatar May 08 '18 13:05 mientjan

Thanks, @mientjan. I am also looking into that. I will tell if I figure it out.

syedabuthahirm avatar May 09 '18 15:05 syedabuthahirm

@syedabuthahirm any news?

mientjan avatar Jun 04 '18 13:06 mientjan

This is example code @mientjan. I changed syntax highlight rules to get the correct design.

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Image,
  Text,
  View,
  ScrollView
} from 'react-native';


import Tip from "./tip";
import Markdown, {getUniqueID} from 'react-native-markdown-renderer'
import NativeSyntaxHighlighter from "./Higlighter"
import FitImage from 'react-native-fit-image'
const a = require('./2.gif')
const rules = {
  fence: (node, children, parent, styles) =>
  <View style={{ margin:5 }} key={getUniqueID()}>
    <NativeSyntaxHighlighter
      language="python"
      customStyle={{ marginHorizontal:5, padding:7, borderRadius: 5 }}
      highlighter="prism">
        {node.content}
    </NativeSyntaxHighlighter>
  </View>,
  image: (node, children, parent, styles) => {
    return <Image style={{width: '50%', height: 250, borderColor: 'red', borderWidth: 4}} resizeMode="contain" key={getUniqueID()} source={node.attributes.src}/>
  }
}

export default class App extends Component{
  render() {
    return (
      <ScrollView>
        <Markdown rules={rules}>{Tip}</Markdown>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  
});

This is our core package file but I modified some imports statements to load the highlight content faster.

import React from 'react';
import { Text, ScrollView, Platform, View } from 'react-native';
import SyntaxHighlighterPrism, { registerLanguage } from "react-syntax-highlighter/prism-light";
import { createStyleObject } from 'react-syntax-highlighter/create-element';
import python from 'react-syntax-highlighter/languages/prism/python';
import dark from 'react-syntax-highlighter/styles/prism/tomorrow'; 
const styleCache = new Map();

registerLanguage('python', python);

const topLevelPropertiesToRemove = [
  "Opacity",
  "color", 
  "textShadow", 
  "textAlign", 
  "whiteSpace", 
  "wordSpacing",
  "wordBreak",
  "wordWrap",
  "lineHeight",
  "MozTabSize",
  "OTabSize",
  "tabSize",
  "WebkitHyphens",
  "MozHyphens",
  "msHyphens",
  "hyphens",
  "fontFamily",
];

function generateNewStylesheet({ stylesheet, highlighter }) {
  if (styleCache.has(stylesheet)) {
    return styleCache.get(stylesheet);
  }
  const transformedStyle = Object.entries(stylesheet).reduce((newStylesheet, [className, style]) => {
    newStylesheet[className] = Object.entries(style).reduce((newStyle, [key, value]) => {
      if (key === 'overflowX' || key === "overflow") {
        newStyle.overflow = value === 'auto' ? 'scroll' : value;
      }
      else if (value.includes('em')) {
        const [num] = value.split('em');
        newStyle[key] = Number(num) * 16;
      }
      else if (key === 'background') {
        newStyle.backgroundColor = value;
      }
      else if (key === 'display') {
        return newStyle;
      }
      else {
        newStyle[key] = value;
      }
      return newStyle;
    }, {});
    return newStylesheet;
  }, {});
  const topLevel = highlighter === "prism" ?  transformedStyle['pre[class*=\"language-\"]'] :  transformedStyle.hljs;
  const defaultColor = topLevel && topLevel.color || "#000";
  topLevelPropertiesToRemove.forEach(property => {
    if (topLevel[property]) {
      delete topLevel[property];
    }
  });
  if (topLevel.backgroundColor === "none") {
    delete topLevel.backgroundColor;
  }
  const codeLevel =  transformedStyle['code[class*=\"language-\"]'];
  if (highlighter === "prism" && !!codeLevel) {
    topLevelPropertiesToRemove.forEach(property => {
      if (codeLevel[property]) {
        delete codeLevel[property];
      }
    });
    if (codeLevel.backgroundColor === "none") {
      delete codeLevel.backgroundColor;
    }
  }
  styleCache.set(stylesheet, { transformedStyle, defaultColor });
  return  { transformedStyle, defaultColor };
}

function createChildren({ stylesheet, fontSize, fontFamily }) {
  let childrenCount = 0;
  return (children, defaultColor) => {
    childrenCount += 1;
    return children.map((child, i) => createNativeElement({
      node: child,
      stylesheet,
      key:`code-segment-${childrenCount}-${i}`,
      defaultColor,
      fontSize,
      fontFamily
    }));
  }
}

function createNativeElement({ node, stylesheet, key, defaultColor, fontFamily, fontSize = 12 }) {
  const { properties, type, tagName: TagName, value } = node;
  const startingStyle = { fontFamily, fontSize, height: fontSize + 5 };
  if (type === 'text') {
    return (
      <Text
        key={key}
        style={Object.assign({ color: defaultColor }, startingStyle)}
      >
        {value}
      </Text>
    );
  } else if (TagName) {
    const childrenCreator = createChildren({ stylesheet, fontSize, fontFamily });
    const style = createStyleObject(
      properties.className,
      Object.assign(
        { color: defaultColor },
        properties.style,
        startingStyle
      ),
      stylesheet
    );
    const children = childrenCreator(node.children, style.color || defaultColor);
    return <Text key={key} style={style}>{children}</Text>;
  }
}

function nativeRenderer({ defaultColor, fontFamily, fontSize }) {
  return ({ rows, stylesheet }) => rows.map((node, i) => createNativeElement({
    node,
    stylesheet,
    key: `code-segment-${i}`,
    defaultColor,
    fontFamily,
    fontSize
  }))
}


function NativeSyntaxHighlighter({ 
  fontFamily, 
  fontSize, 
  children, 
  highlighter = "highlightjs", 
  style = dark,
  ...rest
}) {
  const { transformedStyle, defaultColor } = generateNewStylesheet({
    stylesheet: style,
    highlighter
  });
  const Highlighter = (
    highlighter === "prism" 
    ? 
    SyntaxHighlighterPrism 
    : 
    SyntaxHighlighter
  );
  return (
    <Highlighter
      {...rest}
      style={transformedStyle}
      horizontal={true}
      renderer={(nativeRenderer({
        defaultColor,
        fontFamily,
        fontSize
      }))}
    >
      {children}
    </Highlighter>
  );
}

NativeSyntaxHighlighter.defaultProps = {
  fontFamily: Platform.OS === 'ios' ? 'Menlo-Regular' : 'monospace',
  fontSize: 12,
  PreTag: ScrollView,
  CodeTag: View
};

export default NativeSyntaxHighlighter;

syedabuthahirm avatar Jun 06 '18 15:06 syedabuthahirm