From 8e0d3bd1e20deb059236a4b6242b2239ea047a76 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 15:49:09 +0200
Subject: [PATCH 1/9] Replace roaster by marked
This allows us to use marked directly, but we need to implement two
things that were provided by roaster:
- Handle the yaml frontmatter tags.
- Convert github emoji tags (e.g :rocket:) to the actual emojis.
---
lib/renderer.js | 44 ++++++++++++++++++++++++--------------------
package.json | 2 +-
2 files changed, 25 insertions(+), 21 deletions(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index 2847a12..50c9478 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -2,7 +2,8 @@ const { TextEditor } = require('atom')
const path = require('path')
const createDOMPurify = require('dompurify')
const fs = require('fs-plus')
-let roaster = null // Defer until used
+let marked = null // Defer until used
+
const { scopeForFenceName } = require('./extension-helper')
const { resourcePath } = atom.getLoadSettings()
const packagePath = path.dirname(__dirname)
@@ -50,36 +51,39 @@ exports.toHTML = function (text, filePath, grammar, callback) {
}
var render = function (text, filePath, callback) {
- if (roaster == null) {
- roaster = require('roaster')
+ if (marked == null) {
+ marked = require('marked')
}
- const options = {
+
+ marked.setOptions({
sanitize: false,
breaks: atom.config.get('markdown-preview.breakOnSingleNewline')
- }
+ })
// Remove the since otherwise marked will escape it
// https://github.com/chjj/marked/issues/354
text = text.replace(/^\s*\s*/i, '')
- return roaster(text, options, function (error, html) {
- if (error != null) {
- return callback(error)
- }
-
- html = createDOMPurify().sanitize(html, {
- ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
- 'markdown-preview.allowUnsafeProtocols'
- )
- })
+ let html
- const template = document.createElement('template')
- template.innerHTML = html.trim()
- const fragment = template.content.cloneNode(true)
+ try {
+ html = marked(text)
+ } catch (error) {
+ return callback(error)
+ }
- resolveImagePaths(fragment, filePath)
- return callback(null, fragment)
+ html = createDOMPurify().sanitize(html, {
+ ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
+ 'markdown-preview.allowUnsafeProtocols'
+ )
})
+
+ const template = document.createElement('template')
+ template.innerHTML = html.trim()
+ const fragment = template.content.cloneNode(true)
+
+ resolveImagePaths(fragment, filePath)
+ callback(null, fragment)
}
var resolveImagePaths = function (element, filePath) {
diff --git a/package.json b/package.json
index 31b2a4e..42c9fd2 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"dompurify": "^1.0.2",
"fs-plus": "^3.0.0",
- "roaster": "^1.2.1",
+ "marked": "^0.6.2",
"underscore-plus": "^1.0.0"
},
"devDependencies": {
From 05ac5bbeb4f8e340843072aa7de9cbcead03eac8 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 16:17:07 +0200
Subject: [PATCH 2/9] Fix sanitization test
---
spec/markdown-preview-spec.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/spec/markdown-preview-spec.js b/spec/markdown-preview-spec.js
index 2434794..22bfea6 100644
--- a/spec/markdown-preview-spec.js
+++ b/spec/markdown-preview-spec.js
@@ -649,10 +649,10 @@ var x = y;
runs(() =>
expect(preview.element.innerHTML).toBe(`\
hello
-
-
+
+
-world
\
+world\
`)
)
})
From acad6c340b89cac0f46c4d7bc61dba0ef2d28552 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 16:24:26 +0200
Subject: [PATCH 3/9] Change spec since now all doctypes are removed
---
lib/renderer.js | 4 ----
spec/markdown-preview-spec.js | 4 ++--
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index 50c9478..08760e5 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -60,10 +60,6 @@ var render = function (text, filePath, callback) {
breaks: atom.config.get('markdown-preview.breakOnSingleNewline')
})
- // Remove the since otherwise marked will escape it
- // https://github.com/chjj/marked/issues/354
- text = text.replace(/^\s*\s*/i, '')
-
let html
try {
diff --git a/spec/markdown-preview-spec.js b/spec/markdown-preview-spec.js
index 22bfea6..0cf4d5e 100644
--- a/spec/markdown-preview-spec.js
+++ b/spec/markdown-preview-spec.js
@@ -657,7 +657,7 @@ world\
)
})
- it('remove the first tag at the beginning of the file', function () {
+ it('remove any tag on markdown files', function () {
waitsForPromise(() => atom.workspace.open('subdir/doctype-tag.md'))
runs(() =>
atom.commands.dispatch(
@@ -670,7 +670,7 @@ world\
runs(() =>
expect(preview.element.innerHTML).toBe(`\
content
-<!doctype html>
\
+
\
`)
)
})
From 943e3c3475df9ab7beb5798a2018cd1f48608283 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 16:32:32 +0200
Subject: [PATCH 4/9] Fix syntax highlighting
---
lib/renderer.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index 08760e5..235aab7 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -150,7 +150,8 @@ var highlightCodeBlocks = function (domFragment, grammar, editorCallback) {
: preElement
const className = codeBlock.getAttribute('class')
const fenceName =
- className != null ? className.replace(/^lang-/, '') : defaultLanguage
+ className != null ? className.replace(/^language-/, '') : defaultLanguage
+
const editor = new TextEditor({
readonly: true,
keyboardInputEnabled: false
From 51b6496fa28dca6ef2c137bd920fdc569d5fea0d Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 18:13:31 +0200
Subject: [PATCH 5/9] Refactor logic to use async/await
---
lib/main.js | 22 +++-----
lib/markdown-preview-view.js | 97 ++++++++++++++++--------------------
lib/renderer.js | 60 ++++++++--------------
3 files changed, 73 insertions(+), 106 deletions(-)
diff --git a/lib/main.js b/lib/main.js
index c057de5..0b87479 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -178,7 +178,7 @@ module.exports = {
})
},
- copyHTML () {
+ async copyHTML () {
const editor = atom.workspace.getActiveTextEditor()
if (editor == null) {
return
@@ -188,19 +188,13 @@ module.exports = {
renderer = require('./renderer')
}
const text = editor.getSelectedText() || editor.getText()
- return new Promise(function (resolve) {
- renderer.toHTML(text, editor.getPath(), editor.getGrammar(), function (
- error,
- html
- ) {
- if (error) {
- console.warn('Copying Markdown as HTML failed', error)
- } else {
- atom.clipboard.write(html)
- resolve()
- }
- })
- })
+ const html = await renderer.toHTML(
+ text,
+ editor.getPath(),
+ editor.getGrammar()
+ )
+
+ atom.clipboard.write(html)
},
saveAsHTML () {
diff --git a/lib/markdown-preview-view.js b/lib/markdown-preview-view.js
index c6c70d9..40ee87b 100644
--- a/lib/markdown-preview-view.js
+++ b/lib/markdown-preview-view.js
@@ -287,40 +287,35 @@ module.exports = class MarkdownPreviewView {
}
}
- getHTML (callback) {
- return this.getMarkdownSource().then(source => {
- if (source == null) {
- return
- }
+ async getHTML () {
+ const source = await this.getMarkdownSource()
- return renderer.toHTML(
- source,
- this.getPath(),
- this.getGrammar(),
- callback
- )
- })
+ if (source == null) {
+ return
+ }
+
+ return renderer.toHTML(source, this.getPath(), this.getGrammar())
}
- renderMarkdownText (text) {
+ async renderMarkdownText (text) {
const { scrollTop } = this.element
- return renderer.toDOMFragment(
- text,
- this.getPath(),
- this.getGrammar(),
- (error, domFragment) => {
- if (error) {
- this.showError(error)
- } else {
- this.loading = false
- this.loaded = true
- this.element.textContent = ''
- this.element.appendChild(domFragment)
- this.emitter.emit('did-change-markdown')
- this.element.scrollTop = scrollTop
- }
- }
- )
+
+ try {
+ const domFragment = await renderer.toDOMFragment(
+ text,
+ this.getPath(),
+ this.getGrammar()
+ )
+
+ this.loading = false
+ this.loaded = true
+ this.element.textContent = ''
+ this.element.appendChild(domFragment)
+ this.emitter.emit('did-change-markdown')
+ this.element.scrollTop = scrollTop
+ } catch (error) {
+ this.showError(error)
+ }
}
getTitle () {
@@ -439,7 +434,7 @@ module.exports = class MarkdownPreviewView {
selection.addRange(range)
}
- copyToClipboard () {
+ async copyToClipboard () {
if (this.loading) {
return
}
@@ -456,16 +451,16 @@ module.exports = class MarkdownPreviewView {
) {
atom.clipboard.write(selectedText)
} else {
- return this.getHTML(function (error, html) {
- if (error != null) {
- atom.notifications.addError('Copying Markdown as HTML failed', {
- dismissable: true,
- detail: error.message
- })
- } else {
- atom.clipboard.write(html)
- }
- })
+ try {
+ const html = await this.getHTML()
+
+ atom.clipboard.write(html)
+ } catch (error) {
+ atom.notifications.addError('Copying Markdown as HTML failed', {
+ dismissable: true,
+ detail: error.message
+ })
+ }
}
}
@@ -484,7 +479,7 @@ module.exports = class MarkdownPreviewView {
return { defaultPath }
}
- saveAs (htmlFilePath) {
+ async saveAs (htmlFilePath) {
if (this.loading) {
atom.notifications.addWarning(
'Please wait until the Markdown Preview has finished loading before saving'
@@ -498,13 +493,10 @@ module.exports = class MarkdownPreviewView {
title = path.parse(filePath).name
}
- return new Promise((resolve, reject) => {
- return this.getHTML((error, htmlBody) => {
- if (error != null) {
- throw error
- } else {
- const html =
- `\
+ const htmlBody = await this.getHTML()
+
+ const html =
+ `\
@@ -515,10 +507,7 @@ module.exports = class MarkdownPreviewView {
${htmlBody}
` + '\n' // Ensure trailing newline
- fs.writeFileSync(htmlFilePath, html)
- return atom.workspace.open(htmlFilePath).then(resolve)
- }
- })
- })
+ fs.writeFileSync(htmlFilePath, html)
+ return atom.workspace.open(htmlFilePath)
}
}
diff --git a/lib/renderer.js b/lib/renderer.js
index 235aab7..7d28f6a 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -8,49 +8,38 @@ const { scopeForFenceName } = require('./extension-helper')
const { resourcePath } = atom.getLoadSettings()
const packagePath = path.dirname(__dirname)
-exports.toDOMFragment = function (text, filePath, grammar, callback) {
+exports.toDOMFragment = async function (text, filePath, grammar, callback) {
if (text == null) {
text = ''
}
- render(text, filePath, function (error, domFragment) {
- if (error != null) {
- return callback(error)
- }
- highlightCodeBlocks(
- domFragment,
- grammar,
- makeAtomEditorNonInteractive
- ).then(() => callback(null, domFragment))
- })
+ const domFragment = render(text, filePath)
+
+ await highlightCodeBlocks(domFragment, grammar, makeAtomEditorNonInteractive)
+
+ return domFragment
}
-exports.toHTML = function (text, filePath, grammar, callback) {
+exports.toHTML = async function (text, filePath, grammar) {
if (text == null) {
text = ''
}
- return render(text, filePath, function (error, domFragment) {
- if (error != null) {
- return callback(error)
- }
+ const domFragment = render(text, filePath)
+ const div = document.createElement('div')
- const div = document.createElement('div')
- div.appendChild(domFragment)
- document.body.appendChild(div)
-
- return highlightCodeBlocks(
- div,
- grammar,
- convertAtomEditorToStandardElement
- ).then(function () {
- callback(null, div.innerHTML)
- return div.remove()
- })
- })
+ div.appendChild(domFragment)
+ document.body.appendChild(div)
+
+ await highlightCodeBlocks(div, grammar, convertAtomEditorToStandardElement)
+
+ const result = div.innerHTML
+ div.remove()
+
+ return result
}
-var render = function (text, filePath, callback) {
+var render = function (text, filePath) {
if (marked == null) {
marked = require('marked')
}
@@ -60,13 +49,7 @@ var render = function (text, filePath, callback) {
breaks: atom.config.get('markdown-preview.breakOnSingleNewline')
})
- let html
-
- try {
- html = marked(text)
- } catch (error) {
- return callback(error)
- }
+ let html = marked(text)
html = createDOMPurify().sanitize(html, {
ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
@@ -79,7 +62,8 @@ var render = function (text, filePath, callback) {
const fragment = template.content.cloneNode(true)
resolveImagePaths(fragment, filePath)
- callback(null, fragment)
+
+ return fragment
}
var resolveImagePaths = function (element, filePath) {
From 5c97c141a578d9191c40409814f88b42b3cfd15a Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 19:17:07 +0200
Subject: [PATCH 6/9] Add yaml frontmatter support
---
lib/renderer.js | 22 +++++++++++++++++++++-
package.json | 3 ++-
spec/fixtures/subdir/file.markdown | 7 +++++++
spec/markdown-preview-view-spec.js | 21 +++++++++++++++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index 7d28f6a..05a989d 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -3,6 +3,7 @@ const path = require('path')
const createDOMPurify = require('dompurify')
const fs = require('fs-plus')
let marked = null // Defer until used
+const yamlFrontMatter = require('yaml-front-matter')
const { scopeForFenceName } = require('./extension-helper')
const { resourcePath } = atom.getLoadSettings()
@@ -49,7 +50,8 @@ var render = function (text, filePath) {
breaks: atom.config.get('markdown-preview.breakOnSingleNewline')
})
- let html = marked(text)
+ const { __content, ...vars } = yamlFrontMatter.loadFront(text)
+ let html = marked(renderYamlTable(vars) + __content)
html = createDOMPurify().sanitize(html, {
ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
@@ -66,6 +68,24 @@ var render = function (text, filePath) {
return fragment
}
+function renderYamlTable (variables) {
+ const entries = Object.entries(variables)
+
+ if (!entries.length) {
+ return ''
+ }
+
+ const markdownRows = [
+ entries.map(entry => entry[0]),
+ entries.map(entry => '--'),
+ entries.map(entry => entry[1])
+ ]
+
+ return (
+ markdownRows.map(row => '| ' + row.join(' | ') + ' |').join('\n') + '\n'
+ )
+}
+
var resolveImagePaths = function (element, filePath) {
const [rootDirectory] = atom.project.relativizePath(filePath)
diff --git a/package.json b/package.json
index 42c9fd2..66b5583 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
"dompurify": "^1.0.2",
"fs-plus": "^3.0.0",
"marked": "^0.6.2",
- "underscore-plus": "^1.0.0"
+ "underscore-plus": "^1.0.0",
+ "yaml-front-matter": "^4.0.0"
},
"devDependencies": {
"coffeelint": "^1.9.7",
diff --git a/spec/fixtures/subdir/file.markdown b/spec/fixtures/subdir/file.markdown
index d8ec5df..ac8dc01 100644
--- a/spec/fixtures/subdir/file.markdown
+++ b/spec/fixtures/subdir/file.markdown
@@ -1,3 +1,10 @@
+---
+variable1: value1
+array:
+ - foo
+ - bar
+---
+
## File.markdown
:cool:
diff --git a/spec/markdown-preview-view-spec.js b/spec/markdown-preview-view-spec.js
index 5dbcfbb..45a81ea 100644
--- a/spec/markdown-preview-view-spec.js
+++ b/spec/markdown-preview-view-spec.js
@@ -327,6 +327,27 @@ end\
})
})
+ describe('yaml front matter', function () {
+ it('creates a table with the YAML variables', function () {
+ atom.config.set('markdown-preview.breakOnSingleNewline', true)
+
+ waitsForPromise(() => preview.renderMarkdown())
+
+ runs(() => {
+ expect(
+ [...preview.element.querySelectorAll('table th')].map(
+ el => el.textContent
+ )
+ ).toEqual(['variable1', 'array'])
+ expect(
+ [...preview.element.querySelectorAll('table td')].map(
+ el => el.textContent
+ )
+ ).toEqual(['value1', 'foo,bar'])
+ })
+ })
+ })
+
describe('text selections', function () {
it('adds the `has-selection` class to the preview depending on if there is a text selection', function () {
expect(preview.element.classList.contains('has-selection')).toBe(false)
From 1445a643042f8de65741e6855978762905025025 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 19:58:51 +0200
Subject: [PATCH 7/9] Render lists with checkboxes correctly
---
lib/renderer.js | 10 +++++++++-
styles/markdown-preview.less | 8 +++-----
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index 05a989d..b8c3638 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -3,6 +3,7 @@ const path = require('path')
const createDOMPurify = require('dompurify')
const fs = require('fs-plus')
let marked = null // Defer until used
+let renderer = null
const yamlFrontMatter = require('yaml-front-matter')
const { scopeForFenceName } = require('./extension-helper')
@@ -43,11 +44,18 @@ exports.toHTML = async function (text, filePath, grammar) {
var render = function (text, filePath) {
if (marked == null) {
marked = require('marked')
+ renderer = new marked.Renderer()
+ renderer.listitem = function (text, isTask) {
+ const listAttributes = isTask ? ' class="task-list-item"' : ''
+
+ return `${text}\n`
+ }
}
marked.setOptions({
sanitize: false,
- breaks: atom.config.get('markdown-preview.breakOnSingleNewline')
+ breaks: atom.config.get('markdown-preview.breakOnSingleNewline'),
+ renderer
})
const { __content, ...vars } = yamlFrontMatter.loadFront(text)
diff --git a/styles/markdown-preview.less b/styles/markdown-preview.less
index b242f04..bff8cec 100644
--- a/styles/markdown-preview.less
+++ b/styles/markdown-preview.less
@@ -20,15 +20,13 @@
}
// move task list checkboxes
- .task-list-item-checkbox {
+ .task-list-item input[type=checkbox] {
position: absolute;
margin: .25em 0 0 -1.4em;
}
- // hide bullet for task lists
- // TODO: Use more specific selector once https://github.com/gjtorikian/task-lists-js/issues/5 gets fixed
- ul label {
- vertical-align: top;
+ .task-list-item {
+ list-style-type: none;
}
}
From ca190429277cafbaaa8b02f5bab2fc1b1b1359df Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 20:20:22 +0200
Subject: [PATCH 8/9] Add emoji support to markdown previews
---
lib/renderer.js | 5 +++++
package.json | 1 +
2 files changed, 6 insertions(+)
diff --git a/lib/renderer.js b/lib/renderer.js
index b8c3638..f94498b 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -1,6 +1,7 @@
const { TextEditor } = require('atom')
const path = require('path')
const createDOMPurify = require('dompurify')
+const emoji = require('emoji-images')
const fs = require('fs-plus')
let marked = null // Defer until used
let renderer = null
@@ -10,6 +11,8 @@ const { scopeForFenceName } = require('./extension-helper')
const { resourcePath } = atom.getLoadSettings()
const packagePath = path.dirname(__dirname)
+const emojiFolder = path.join(path.dirname(require.resolve('emoji-images') ), "pngs")
+
exports.toDOMFragment = async function (text, filePath, grammar, callback) {
if (text == null) {
text = ''
@@ -59,7 +62,9 @@ var render = function (text, filePath) {
})
const { __content, ...vars } = yamlFrontMatter.loadFront(text)
+
let html = marked(renderYamlTable(vars) + __content)
+ html = emoji(html, emojiFolder, 20)
html = createDOMPurify().sanitize(html, {
ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
diff --git a/package.json b/package.json
index 66b5583..0c3537d 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"dompurify": "^1.0.2",
+ "emoji-images": "^0.1.1",
"fs-plus": "^3.0.0",
"marked": "^0.6.2",
"underscore-plus": "^1.0.0",
From 32453355657bafde0c5feb2ce41a98f0c3cb0405 Mon Sep 17 00:00:00 2001
From: Rafael Oleza
Date: Tue, 23 Apr 2019 20:26:54 +0200
Subject: [PATCH 9/9] Do not add emojis on code tags
---
lib/renderer.js | 19 +++++++++++++++++--
package.json | 1 +
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/lib/renderer.js b/lib/renderer.js
index f94498b..e507026 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -5,13 +5,17 @@ const emoji = require('emoji-images')
const fs = require('fs-plus')
let marked = null // Defer until used
let renderer = null
+let cheerio = null
const yamlFrontMatter = require('yaml-front-matter')
const { scopeForFenceName } = require('./extension-helper')
const { resourcePath } = atom.getLoadSettings()
const packagePath = path.dirname(__dirname)
-const emojiFolder = path.join(path.dirname(require.resolve('emoji-images') ), "pngs")
+const emojiFolder = path.join(
+ path.dirname(require.resolve('emoji-images')),
+ 'pngs'
+)
exports.toDOMFragment = async function (text, filePath, grammar, callback) {
if (text == null) {
@@ -46,6 +50,7 @@ exports.toHTML = async function (text, filePath, grammar) {
var render = function (text, filePath) {
if (marked == null) {
+ cheerio = require('cheerio')
marked = require('marked')
renderer = new marked.Renderer()
renderer.listitem = function (text, isTask) {
@@ -64,7 +69,17 @@ var render = function (text, filePath) {
const { __content, ...vars } = yamlFrontMatter.loadFront(text)
let html = marked(renderYamlTable(vars) + __content)
- html = emoji(html, emojiFolder, 20)
+
+ // emoji-images is too aggressive, so replace images in monospace tags with the actual emoji text.
+ const $ = cheerio.load(emoji(html, emojiFolder, 20))
+ $('pre img').each((index, element) =>
+ $(element).replaceWith($(element).attr('title'))
+ )
+ $('code img').each((index, element) =>
+ $(element).replaceWith($(element).attr('title'))
+ )
+
+ html = $.html()
html = createDOMPurify().sanitize(html, {
ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
diff --git a/package.json b/package.json
index 0c3537d..5aa6f2d 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"atom": "*"
},
"dependencies": {
+ "cheerio": "^1.0.0-rc.3",
"dompurify": "^1.0.2",
"emoji-images": "^0.1.1",
"fs-plus": "^3.0.0",