Skip to content
This repository was archived by the owner on Apr 28, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions app/assets/javascripts/components/highlight.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// @flow
import Prism from 'prismjs';
import escapeTextContentForBrowser from 'escape-html';
import apacheconf from "prismjs/components/prism-apacheconf"
import bash from "prismjs/components/prism-bash"
import brainfuck from "prismjs/components/prism-brainfuck"
import c from "prismjs/components/prism-c"
import coffeescript from "prismjs/components/prism-coffeescript"
import cpp from "prismjs/components/prism-cpp"
import csharp from "prismjs/components/prism-csharp"
import d from "prismjs/components/prism-d"
import diff from "prismjs/components/prism-diff"
import docker from "prismjs/components/prism-docker"
import elixir from "prismjs/components/prism-elixir"
import erlang from "prismjs/components/prism-erlang"
import go from "prismjs/components/prism-go"
import graphql from "prismjs/components/prism-graphql"
import haml from "prismjs/components/prism-haml"
import handlebars from "prismjs/components/prism-handlebars"
import haskell from "prismjs/components/prism-haskell"
import java from "prismjs/components/prism-java"
import json from "prismjs/components/prism-json"
import jsx from "prismjs/components/prism-jsx"
import julia from "prismjs/components/prism-julia"
import kotlin from "prismjs/components/prism-kotlin"
import lua from "prismjs/components/prism-lua"
import markdown from "prismjs/components/prism-markdown"
import nginx from "prismjs/components/prism-nginx"
import objectivec from "prismjs/components/prism-objectivec"
import ocaml from "prismjs/components/prism-ocaml"
import perl from "prismjs/components/prism-perl"
import php from "prismjs/components/prism-php"
import processing from "prismjs/components/prism-processing"
import python from "prismjs/components/prism-python"
import r from "prismjs/components/prism-r"
import ruby from "prismjs/components/prism-ruby"
import rust from "prismjs/components/prism-rust"
import sass from "prismjs/components/prism-sass"
import scala from "prismjs/components/prism-scala"
import scheme from "prismjs/components/prism-scheme"
import scss from "prismjs/components/prism-scss"
import smalltalk from "prismjs/components/prism-smalltalk"
import sql from "prismjs/components/prism-sql"
import swift from "prismjs/components/prism-swift"
import typescript from "prismjs/components/prism-typescript"
import yaml from "prismjs/components/prism-yaml"

Prism.languages.extend({bash, brainfuck, c, cpp, csharp, d, diff, docker, elixir, erlang, go, graphql, haml, handlebars, haskell, java, json, jsx, julia, kotlin, lua, markdown, nginx, objectivec, ocaml, perl, php, processing, python, r, ruby, rust, sass, scala, scheme, scss, smalltalk, sql, swift, typescript, yaml});

Prism.languages.rb = Prism.languages.ruby;

type Code = {|
text: string,
language: ?string,
|}

export const createCodeElement = (doc: Document, {text, language}: Code): HTMLPreElement => {
const pre = doc.createElement('pre');
if(language) pre.className = `language-${language}`;
const code = doc.createElement('code');
const syntax = Prism.languages[language];
const highlighted = syntax ? Prism.highlight(text, syntax) : escapeTextContentForBrowser(text);
code.innerHTML = highlighted;
pre.appendChild(code);
return pre;
}

const removeNode = (node: Node) => {
node.parentNode.removeChild(node);
return true
}

const trimBR = (node: Node) => {
while(node.firstChild && node.firstChild.tagName === 'BR') removeNode(node.firstChild);
while(node.lastChild && node.lastChild.tagName === 'BR') removeNode(node.lastChild);
}

const previousSiblingsParagraph = (doc: Document, node: Node) => {
const p = doc.createElement('p');
while(node.parentNode.firstChild !== node) p.appendChild(node.parentNode.firstChild);
trimBR(p);
return p;
}

const accumulateOrTerminateCode = (doc: Document, current: Node, code: Code, top: ?Node): ?Code => {
if(current.textContent !== '```') {
code.text += current.tagName === 'BR' && code.text !== '' ? "\n" : current.textContent;
return removeNode(current) && code;
}
let p = current;
while(p.parentNode !== top) p = p.parentNode;
if(top.tagName === 'P') {
// p can not include block element
// <p>text1<pre></pre>text2</p> => <p>text1</p><pre></pre><p>text</p>
const prev = previousSiblingsParagraph(doc, current)
if(prev.childNodes.length !== 0) top.parentNode.insertBefore(prev, top)
top.parentNode.insertBefore(createCodeElement(doc, code), top)
removeNode(current);
} else {
top.insertBefore(createCodeElement(doc, code), p);
removeNode(current);
}
return null;
}

const findStartDelimiter = (current: Node): ?Code => {
const match = current.textContent.match(/^```([a-z]*)$/)
if(match) {
removeNode(current);
return {language: match[1], text: ''};
}
return null;
}

const handleLeaf = (doc: Document, current: Node, code: ?Code, top: ?Node): ?Code => {
if(code) return accumulateOrTerminateCode(doc, current, code, top);
if(!(current instanceof window.Text)) return null;
return findStartDelimiter(current)
}

const transform = (doc, current: Node, code: ?Code, top: ?Node): ?Code => {
if(current.childNodes.length === 0) return handleLeaf(doc, current, code, top);

// childNodes might be modified inside loop
const arr = Array.from(current.childNodes);
for(let i = 0, l = arr.length; i < l; i++) {
const outside = !code;
code = transform(doc, arr[i], code, top);
if(outside && code) top = current;
}

if(current.tagName === 'P') {
if(code) code.text += code.text === '' ? "\n" : "\n\n";
trimBR(current)
if(current.childNodes.length === 0) removeNode(current);
}
return code;
}

const parse = (text: string): ?Document => {
try {
const doc = new window.DOMParser().parseFromString(text, 'text/html');
if(doc.getElementsByTagName("parsererror").length) return null;
return doc;
} catch (e) {
return null;
}
}

const highlight = (text: string): string => {
const doc = parse(text);
if(!doc) return escapeTextContentForBrowser(text);
const incomplete = transform(doc, doc.body, null, null);
if(incomplete) return text;
return doc.body.innerHTML;
}

export default highlight;
3 changes: 2 additions & 1 deletion app/assets/javascripts/components/reducers/statuses.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ import {
} from '../actions/favourites';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import Immutable from 'immutable';
import highlight from '../highlight';

const normalizeStatus = (state, status) => {
if (!status) {
return state;
}

const normalStatus = { ...status };
const normalStatus = { ...status, content: highlight(status.content) };
normalStatus.account = status.account.id;

if (status.reblog && status.reblog.id) {
Expand Down
6 changes: 6 additions & 0 deletions app/assets/javascripts/extras.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import emojify from './components/emoji'
import highlight from './components/highlight'

$(() => {
$.each($('.emojify'), (_, content) => {
Expand Down Expand Up @@ -37,4 +38,9 @@ $(() => {
$(e.target).parent().attr('style', null);
}
});

$.each($('.highlight'), (_, content) => {
const $content = $(content);
$content.html(highlight($content.html()));
});
});
10 changes: 10 additions & 0 deletions app/assets/stylesheets/components.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import 'variables';
@import 'prism';

.app-body{
-webkit-overflow-scrolling: touch;
Expand Down Expand Up @@ -286,6 +287,15 @@
overflow: hidden;
white-space: pre-wrap;

pre {
@extend pre[class*="language-"];
border-width: 2px;
position: relative;
padding: 0.4em 0.8em;
> code {
font-size: 0.8em;
}
}
.emojione {
width: 18px;
height: 18px;
Expand Down
Loading