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

Aggressive emoji support with twemoji #19

Merged
merged 11 commits into from
Aug 19, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Upgrade dependencies to latest ([#16](https://github.com/marp-team/marp-core/pull/16))
- Support fitting header ([#17](https://github.com/marp-team/marp-core/pull/17))
- Add `uncover` theme ([#18](https://github.com/marp-team/marp-core/pull/18))
- Add emoji support with twemoji ([#19](https://github.com/marp-team/marp-core/pull/19))

## v0.0.1 - 2018-08-10

Expand Down
58 changes: 55 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ document.addEventListener('DOMContentLoaded', () => {

_We will only explain features extended in marp-core._ Please refer to [@marp-team/marpit](https://github.com/marp-team/marpit) repository if you want to know the basic feature of Marpit framework.

### Marp Markdown

Marp Markdown is based on [Marpit](https://github.com/marp-team/marpit) and [CommonMark](https://commonmark.org/), and there are these additional features:

- **Marpit**
- Enable [inline SVG mode](https://github.com/marp-team/marpit#inline-svg-slide-experimental) and lazy YAML parsing by default.
- **CommonMark**
- For security reason, HTML tag is disabled by default.
- [Support table syntax based on GitHub Flavored Markdown.](https://help.github.com/articles/organizing-information-with-tables/)
- Line breaks in paragraph will convert to `<br>` tag.
- Auto convert URL like text into hyperlink.

### Emoji support

Emoji shortcode (like `:smile:`) and Unicode emoji 😄 will convert into the SVG vector image provided by [twemoji](https://github.com/twitter/twemoji) <img src="https://twemoji.maxcdn.com/2/svg/1f604.svg" alt="😄" width="16" height="16" />. It could render emoji with high resolution.

### Math typesetting

We have [Pandoc's Markdown style](https://pandoc.org/MANUAL.html#math) math typesetting support by [KaTeX](https://khan.github.io/KaTeX/). Surround your formula by `$...$` to render math as inline, and `$$...$$` to render as block.
Expand Down Expand Up @@ -91,16 +107,26 @@ This syntax is similar to [Deckset's `[fit]` keyword](https://docs.decksetapp.co

You can customize a behavior of Marp parser by passing an options object to the constructor. You can also pass together with [Marpit constructor options](https://marpit.netlify.com/marpit#Marpit).

> :information_source: [Marpit's `markdown` option](https://github.com/marp-team/marpit/blob/6cec8177b1c296c6df4ec8c917e7c780940ad3bf/src/marpit.js#L58-L59) is accepted only object options because of always using CommonMark.

```javascript
const marp = new Marp({
// marp-core constructor options
html: false,
html: true,
emoji: {
shortcode: true,
unicode: false,
twemojiBase: '/resources/twemoji/',
},
math: {
katexFontPath: '/resources/fonts/',
},

// Marpit constructor options
inlineSVG: true,
// It can be included Marpit constructor options
lazyYAML: false,
markdown: {
breaks: false,
},
})
```

Expand All @@ -110,15 +136,41 @@ Setting whether to render raw HTML in Markdown. The default value is **`false`**

Even if you are setting `false`, `<!-- HTML comment -->` is always parsed by Marpit for directives. When you are not disabled [Marpit's `inlineStyle` option](https://marpit.netlify.com/marpit#Marpit) by `false`, `<style>` tags are parsed too for tweaking theme style.

> :information_source: `html` flag in `markdown` option cannot use because of overridden by this.

### `emoji`: _`object`_

Setting about emoji conversions.

- **`shortcode`**: _`boolean` | `"twemoji"`_

- By setting `false`, it does not convert any emoji shortcodes.
- By setting `true`, it converts emoji shortcodes into Unicode emoji. `:dog:` → 🐶
- By setting `"twemoji"` string, it converts into twemoji vector image. `:dog:` → <img src="https://twemoji.maxcdn.com/2/svg/1f436.svg" alt="🐶" width="16" height="16" valign="middle" /> _(default)_

- **`unicode`**: _`boolean` | `"twemoji"`_

- It can convert Unicode emoji into twemoji when setting `"twemoji"`. 🐶 → <img src="https://twemoji.maxcdn.com/2/svg/1f436.svg" alt="🐶" width="16" height="16" valign="middle" /> _(default)_
- If you not want this aggressive conversion, please set `false`.

- **`twemojiBase`**: _`string`_

- It is corresponded to [twemoji's `base` option](https://github.com/twitter/twemoji#object-as-parameter). By default, marp-core will use online emoji images [through MaxCDN (twemoji's default)](https://github.com/twitter/twemoji#cdn-support).

> **For developers:** When you setting `unicode` option as `true`, Markdown parser will convert Unicode emoji into tokens internally. The rendering result is same as in `true`.

### `math`: _`boolean` | `object`_

Enable or disable [math typesetting](#math-typesetting) syntax. The default value is `true`.

You can modify KaTeX further settings by passing an object of sub-options.

- **`katexOption`**: _`object`_

- The options passing to KaTeX. Please refer to [KaTeX document](https://khan.github.io/KaTeX/docs/options.html).

- **`katexFontPath`**: _`string` | `false`_

- By default, marp-core will use [online web-font resources through jsDelivr CDN](https://cdn.jsdelivr.net/npm/katex@latest/dist/fonts/). You have to set path to fonts directory if you want to use local resources. If you set `false`, we will not manipulate the path (Use KaTeX's original path: `fonts/KaTeX_***-***.woff2`).

<!-- ## [Work in progress] Themes -->
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@
"uglify-es": "^3.3.9"
},
"dependencies": {
"@marp-team/marpit": "^0.0.11",
"@marp-team/marpit": "^0.0.12",
"emoji-regex": "^7.0.0",
"highlight.js": "^9.12.0",
"katex": "^0.10.0-beta",
"markdown-it": "^8.4.2",
"markdown-it-emoji": "^1.4.0",
"markdown-it-katex": "^2.0.3",
"postcss": "^7.0.2"
"postcss": "^7.0.2",
"twemoji": "^11.0.1"
},
"publishConfig": {
"access": "public"
Expand Down
96 changes: 96 additions & 0 deletions src/emoji/emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import emojiRegex from 'emoji-regex'
import Token from 'markdown-it/lib/token'
import markdownItEmoji from 'markdown-it-emoji'
import twemoji from 'twemoji'
import twemojiCSS from './twemoji.scss'

export interface EmojiOptions {
shortcode?: boolean | 'twemoji'
twemojiBase?: string
unicode?: boolean | 'twemoji'
}

const regexForSplit = new RegExp(`(${emojiRegex().source})`, 'g')

export const css = (opts: EmojiOptions) =>
opts.shortcode === 'twemoji' || opts.unicode === 'twemoji'
? twemojiCSS
: undefined

export function markdown(md, opts: EmojiOptions): void {
const twemojiParse = (content: string): string =>
twemoji
.parse(content, {
base: opts.twemojiBase,
className: '__placeholder__',
ext: '.svg',
size: 'svg',
})
.replace('class="__placeholder__"', 'data-marp-twemoji')

const twemojiRenderer = (token: any[], idx: number): string =>
twemojiParse(token[idx].content)

if (opts.shortcode) {
md.use(markdownItEmoji, { shortcuts: {} })
if (opts.shortcode === 'twemoji') md.renderer.rules.emoji = twemojiRenderer
}

if (opts.unicode) {
md.core.ruler.after('inline', 'marp_unicode_emoji', ({ tokens }) => {
tokens.forEach(token => {
if (token.type !== 'inline') return

token.children = token.children.reduce((arr, t) => {
if (t.type !== 'text') return [...arr, t]

return [
...arr,
...t.content.split(regexForSplit).reduce(
(splitedArr, text, idx) =>
text.length === 0
? splitedArr
: [
...splitedArr,
Object.assign(new Token(), {
...t,
content: text,
type: idx % 2 ? 'unicode_emoji' : 'text',
}),
],
[]
),
]
}, [])
})
})

md.renderer.rules.unicode_emoji = (token: any[], idx: number): string =>
token[idx].content

const { code_block, code_inline, fence } = md.renderer.rules

if (opts.unicode === 'twemoji') {
const wrap = text =>
text
.split(/(<[^>]*>)/g)
.reduce(
(ret, part, idx) =>
`${ret}${
idx % 2
? part
: part.replace(regexForSplit, ([emoji]) =>
twemojiParse(emoji)
)
}`,
''
)

md.renderer.rules.unicode_emoji = twemojiRenderer

md.renderer.rules.code_inline = (...args) => wrap(code_inline(...args))
md.renderer.rules.code_block = (...args) => wrap(code_block(...args))
md.renderer.rules.fence = (...args) => wrap(fence(...args))
}
}
}
7 changes: 7 additions & 0 deletions src/emoji/twemoji.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
img[data-marp-twemoji] {
background: transparent;
height: 1em;
margin: 0 0.05em 0 0.1em;
vertical-align: -0.1em;
width: 1em;
}
36 changes: 23 additions & 13 deletions src/marp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Marpit, MarpitOptions, ThemeSetPackOptions } from '@marp-team/marpit'
import highlightjs from 'highlight.js'
import { version } from 'katex/package.json'
import markdownItEmoji from 'markdown-it-emoji'
import browser from './browser'
import * as emojiPlugin from './emoji/emoji'
import * as fittingPlugin from './fitting/fitting'
import * as mathPlugin from './math/math'
import defaultTheme from '../themes/default.scss'
Expand All @@ -12,7 +12,9 @@ import uncoverTheme from '../themes/uncover.scss'
const marpObservedSymbol = Symbol('marpObserved')

export interface MarpOptions extends MarpitOptions {
emoji?: emojiPlugin.EmojiOptions
html?: boolean
markdown?: object
math?:
| boolean
| {
Expand All @@ -27,22 +29,29 @@ export class Marp extends Marpit {
private renderedMath: boolean = false

constructor(opts: MarpOptions = {}) {
super({
super(<MarpOptions>{
inlineSVG: true,
lazyYAML: true,
math: true,
...opts,
markdown: [
'commonmark',
{
breaks: true,
linkify: true,
...(typeof opts.markdown === 'object' ? opts.markdown : {}),
highlight: (code: string, lang: string) =>
this.highlighter(code, lang),
html: opts.html !== undefined ? opts.html : false,
linkify: true,
},
],
math: true,
...opts,
} as MarpitOptions)
emoji: {
shortcode: 'twemoji',
twemojiBase: undefined,
unicode: 'twemoji',
...(opts.emoji || {}),
},
})

// Enable table
this.markdown.enable(['table', 'linkify'])
Expand All @@ -56,12 +65,10 @@ export class Marp extends Marpit {
applyMarkdownItPlugins(md = this.markdown) {
super.applyMarkdownItPlugins(md)

const { inlineSVG, math } = this.options
const { emoji, inlineSVG, math } = this.options

// Emoji shorthand
md.use(markdownItEmoji, { shortcuts: {} })
md.renderer.rules.emoji = (token, idx) =>
`<span data-marpit-emoji>${token[idx].content}</span>`
// Emoji support
md.use(emojiPlugin.markdown, emoji)

// Math typesetting
if (math) {
Expand All @@ -88,9 +95,12 @@ export class Marp extends Marpit {

protected themeSetPackOptions(): ThemeSetPackOptions {
const base = { ...super.themeSetPackOptions() }
const prependCSS = css => (base.before = `${css}\n${base.before || ''}`)
const { math } = this.options
const prependCSS = css => {
if (css) base.before = `${css}\n${base.before || ''}`
}
const { emoji, math } = this.options

prependCSS(emojiPlugin.css(emoji!))
prependCSS(fittingPlugin.css)

if (math && this.renderedMath) {
Expand Down
Loading