mirror of
https://github.com/jakejarvis/imagemoji.git
synced 2025-04-28 07:00:30 -04:00
initial commit 🎉
This commit is contained in:
commit
6119fe84c0
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
|
||||||
|
# this file is the top-most editorconfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# all files
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# site content
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
16
.eslintrc.json
Normal file
16
.eslintrc.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"@jakejarvis/eslint-config",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"dist/**"
|
||||||
|
]
|
||||||
|
}
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
* text=auto eol=lf
|
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
versioning-strategy: increase
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "📦 npm:"
|
21
.github/workflows/ci.yml
vendored
Normal file
21
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16.x
|
||||||
|
cache: yarn
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
- run: yarn test
|
||||||
|
- run: yarn build
|
21
.github/workflows/release.yml
vendored
Normal file
21
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
npm:
|
||||||
|
name: Publish to NPM
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16.x
|
||||||
|
registry-url: https://registry.npmjs.org/
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
- run: yarn publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.npmrc
|
||||||
|
.vscode/
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2021 Jake Jarvis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# 🖼️ imagemoji
|
||||||
|
|
||||||
|
[](https://github.com/jakejarvis/imagemoji/actions/workflows/ci.yml)
|
||||||
|
[](https://www.npmjs.com/package/imagemoji)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
Replaces emojis in strings or DOM nodes with corresponding images of your choosing. A barebones, mostly drop-in replacement for Twemoji's [`twemoji.parse()`](https://github.com/twitter/twemoji#twemojiparse---v1) (and heavily cherry-picked from Twitter's [original script](https://github.com/twitter/twemoji/blob/master/scripts/build.js)) to cut some cruft and save a few bytes.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### via [unpkg](https://unpkg.com/browse/imagemoji/):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>I ❤️ emoji!</p>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/imagemoji/dist/imagemoji.min.js"></script>
|
||||||
|
<script>
|
||||||
|
imagemoji.parse(document.body);
|
||||||
|
//=> <p>I <img class="emoji" draggable="false" alt="❤️" src="https://twemoji.maxcdn.com/v/latest/svg/2764.svg"/> emoji!</p>
|
||||||
|
|
||||||
|
imagemoji.parse(document.body, (icon) => `/assets/emoji/${icon}.png`);
|
||||||
|
//=> <p>I <img class="emoji" draggable="false" alt="❤️" src="/assets/emoji/2764.png"/> emoji!</p>
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### via NPM:
|
||||||
|
|
||||||
|
`npm install imagemoji` or `yarn add imagemoji`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import imagemoji from "imagemoji";
|
||||||
|
// or:
|
||||||
|
// const imagemoji = require("imagemoji");
|
||||||
|
|
||||||
|
imagemoji.parse(document.body);
|
||||||
|
imagemoji.parse(document.body, (icon) => `/assets/emoji/${icon}.png`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### .parse(what, how?)
|
||||||
|
|
||||||
|
#### what
|
||||||
|
|
||||||
|
Type: `string` or `Node`
|
||||||
|
|
||||||
|
Either a plain string or a DOM node (e.g. `document.body`) containing emojis to replace with `<img>`s.
|
||||||
|
|
||||||
|
#### how
|
||||||
|
|
||||||
|
Type: `function`\
|
||||||
|
Default: `(icon: string): string => "https://twemoji.maxcdn.com/v/latest/svg/" + icon + ".svg"`
|
||||||
|
|
||||||
|
A callback function to determine the image source URL of a given emoji codepoint (always lowercase, e.g. `1f4a9` for 💩, and variations are joined with dashes, e.g. `1f468-200d-1f4bb` for 👨💻). Defaults to pulling SVGs from the [Twemoji CDN](https://github.com/twitter/twemoji#cdn-support).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
55
package.json
Normal file
55
package.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "imagemoji",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "🖼️ Replaces emojis in strings or DOM nodes with corresponding images",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": {
|
||||||
|
"name": "Jake Jarvis",
|
||||||
|
"email": "jake@jarv.is",
|
||||||
|
"url": "https://jarv.is/"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jakejarvis/imagemoji.git"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"source": "./src/imagemoji.ts",
|
||||||
|
"main": "./dist/imagemoji.cjs",
|
||||||
|
"module": "./dist/imagemoji.esm.js",
|
||||||
|
"unpkg": "./dist/imagemoji.min.js",
|
||||||
|
"exports": {
|
||||||
|
"require": "./dist/imagemoji.cjs",
|
||||||
|
"import": "./dist/imagemoji.esm.js",
|
||||||
|
"browser": "./dist/imagemoji.min.js"
|
||||||
|
},
|
||||||
|
"types": "./dist/imagemoji.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "microbundle --format cjs,esm,umd",
|
||||||
|
"test": "eslint .",
|
||||||
|
"prepublishOnly": "yarn build"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@jakejarvis/eslint-config": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.3.1",
|
||||||
|
"@typescript-eslint/parser": "^5.3.1",
|
||||||
|
"eslint": "^8.2.0",
|
||||||
|
"microbundle": "^0.14.1",
|
||||||
|
"twemoji-parser": "13.1.0",
|
||||||
|
"typescript": "^4.4.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"front-end",
|
||||||
|
"emoji",
|
||||||
|
"twemoji",
|
||||||
|
"html",
|
||||||
|
"dom",
|
||||||
|
"unicode"
|
||||||
|
]
|
||||||
|
}
|
246
src/imagemoji.ts
Normal file
246
src/imagemoji.ts
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// NOTE: A lot of this logic was cherry-picked from Twitter's original script:
|
||||||
|
// https://github.com/twitter/twemoji/blob/master/scripts/build.js
|
||||||
|
// As such...
|
||||||
|
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
|
||||||
|
|
||||||
|
// RegExp based on emoji's official Unicode standards
|
||||||
|
// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
|
||||||
|
import regex from "twemoji-parser/dist/lib/regex";
|
||||||
|
|
||||||
|
// avoid using a string literal like '\u200D' here because minifiers expand it inline
|
||||||
|
const zeroWidthJoiner = String.fromCharCode(0x200d);
|
||||||
|
|
||||||
|
// nodes with type 1 which should **not** be parsed
|
||||||
|
const skipTags = /^(?:style|script|noscript|iframe|noframes|select|textarea)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut to create text nodes.
|
||||||
|
*
|
||||||
|
* @param text text used to create DOM text node
|
||||||
|
* @param clean strip any variation selectors found
|
||||||
|
* @return a DOM node with the given text
|
||||||
|
*/
|
||||||
|
const createText = function (text: string, clean: boolean): Text {
|
||||||
|
return document.createTextNode(clean ? text.replace(/\ufe0f/g, "") : text);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given UTF16 surrogate pairs, returns the equivalent HEX codepoint.
|
||||||
|
*
|
||||||
|
* @param unicodeSurrogates generic utf16 surrogates pair, i.e. `\uD83D\uDCA9`
|
||||||
|
* @param separator optional separator for double code points, default: `-`
|
||||||
|
* @return UTF16 transformed into codepoint string, i.e. `1F4A9`
|
||||||
|
*/
|
||||||
|
const toCodePoint = function (unicodeSurrogates: string, separator = "-"): string {
|
||||||
|
// remove possible variants (if there is a zero-width-joiner (U+200D), leave them in)
|
||||||
|
if (unicodeSurrogates.indexOf(zeroWidthJoiner) < 0) {
|
||||||
|
unicodeSurrogates = unicodeSurrogates.replace(/\ufe0f/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const points: string[] = [];
|
||||||
|
let char = 0;
|
||||||
|
let previous = 0;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < unicodeSurrogates.length) {
|
||||||
|
char = unicodeSurrogates.charCodeAt(i++);
|
||||||
|
|
||||||
|
if (previous) {
|
||||||
|
points.push((0x10000 + (previous - 0xd800 << 10) + (char - 0xdc00)).toString(16));
|
||||||
|
previous = 0;
|
||||||
|
} else if (char > 0xd800 && char <= 0xdbff) {
|
||||||
|
previous = char;
|
||||||
|
} else {
|
||||||
|
points.push(char.toString(16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return points.join(separator);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a generic DOM nodeType 1, walk through all children and store every
|
||||||
|
* nodeType 3 (#text) found in the tree.
|
||||||
|
*
|
||||||
|
* @param node a DOM Element with probably some text in it
|
||||||
|
* @param allText the list of previously discovered text nodes
|
||||||
|
* @return same list with new discovered nodes, if any
|
||||||
|
*/
|
||||||
|
const getAllTextNodes = function (node: Node, allText: Node[] = []): Node[] {
|
||||||
|
const { childNodes } = node;
|
||||||
|
let { length } = childNodes;
|
||||||
|
|
||||||
|
while (length--) {
|
||||||
|
const subnode = childNodes[length];
|
||||||
|
const { nodeType } = subnode;
|
||||||
|
|
||||||
|
// parse emoji only in text nodes
|
||||||
|
if (nodeType === 3) {
|
||||||
|
// collect them to process emoji later
|
||||||
|
allText.push(subnode);
|
||||||
|
} else if (
|
||||||
|
nodeType === 1 &&
|
||||||
|
!("ownerSVGElement" in subnode) &&
|
||||||
|
!skipTags.test(subnode.nodeName.toLowerCase())
|
||||||
|
) {
|
||||||
|
// ignore all nodes that are not type 1, that are svg, or that
|
||||||
|
// should not be parsed as script, style, and others
|
||||||
|
getAllTextNodes(subnode, allText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allText;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOM version of the same logic / parser: emojify all found sub-text nodes by
|
||||||
|
* placing image nodes instead.
|
||||||
|
*
|
||||||
|
* @param node generic DOM node with some text in some child node
|
||||||
|
* @param srcGenerator the callback to invoke per each found emoji to get the image source
|
||||||
|
* @return same generic node with emoji in place, if any.
|
||||||
|
*/
|
||||||
|
const parseNode = function (node: Node, srcGenerator: (icon: string) => string): Node {
|
||||||
|
const allText = getAllTextNodes(node);
|
||||||
|
let { length } = allText;
|
||||||
|
|
||||||
|
while (length--) {
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
const subnode = allText[length];
|
||||||
|
const text = subnode.nodeValue || "";
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
let modified = false;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while ((match = regex.exec(text))) {
|
||||||
|
const { index } = match;
|
||||||
|
const rawEmoji = match[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
const icon = toCodePoint(rawEmoji);
|
||||||
|
const src = srcGenerator(icon);
|
||||||
|
i = index + rawEmoji.length;
|
||||||
|
|
||||||
|
if (index !== i) {
|
||||||
|
fragment.appendChild(createText(text.slice(i, index), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon && src) {
|
||||||
|
const img = new Image();
|
||||||
|
img.setAttribute("draggable", "false");
|
||||||
|
img.className = "emoji";
|
||||||
|
img.alt = rawEmoji;
|
||||||
|
img.src = src;
|
||||||
|
img.onerror = function () {
|
||||||
|
// remove missing images to preserve the original text intent when
|
||||||
|
// a fallback for network problems is desired
|
||||||
|
if (this.parentNode) {
|
||||||
|
this.parentNode.replaceChild(createText(this.alt, false), this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
modified = true;
|
||||||
|
|
||||||
|
fragment.appendChild(img);
|
||||||
|
} else {
|
||||||
|
fragment.appendChild(createText(rawEmoji, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is there actually anything to replace in here?
|
||||||
|
if (modified) {
|
||||||
|
// any text left to be added?
|
||||||
|
if (i < text.length) {
|
||||||
|
fragment.appendChild(createText(text.slice(i), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace the text node only, leave intact anything else surrounding such text
|
||||||
|
subnode.parentNode?.replaceChild(fragment, subnode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String/HTML version of the same logic / parser: emojify a generic text placing images tags
|
||||||
|
* instead of surrogates pair.
|
||||||
|
*
|
||||||
|
* @param str generic string with possibly some emoji in it
|
||||||
|
* @param srcGenerator the callback to invoke per each found emoji to get the image source
|
||||||
|
* @return the string with `<img>` tags replacing all found and parsed emoji
|
||||||
|
*/
|
||||||
|
const parseString = function (str: string, srcGenerator: (icon: string) => string): string {
|
||||||
|
return str.replace(regex, function (rawEmoji: string): string {
|
||||||
|
const icon = toCodePoint(rawEmoji);
|
||||||
|
const src = srcGenerator(icon);
|
||||||
|
|
||||||
|
// recycle the match string replacing the emoji with its image counterpart
|
||||||
|
return (icon && src) ? `<img class="emoji" draggable="false" alt="${rawEmoji}" src="${src}"/>` : rawEmoji;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default callback used to generate emoji image source based on Twitter CDN.
|
||||||
|
*
|
||||||
|
* @param icon the emoji codepoint string
|
||||||
|
* @return the corresponding image URL to use
|
||||||
|
*/
|
||||||
|
const getTwemojiSvg = function (icon: string): string {
|
||||||
|
return `https://twemoji.maxcdn.com/v/latest/svg/${icon}.svg`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Emojify" a generic text or DOM Element with `<img>` tags or HTMLImage nodes.
|
||||||
|
*
|
||||||
|
* @overloads
|
||||||
|
*
|
||||||
|
* String replacement for `innerHTML` or server side operations:
|
||||||
|
* `imagemoji.parse(string)`
|
||||||
|
* `imagemoji.parse(string, Function)`
|
||||||
|
*
|
||||||
|
* HTML element tree parsing for safer operations over existing DOM:
|
||||||
|
* `imagemoji.parse(Node)`
|
||||||
|
* `imagemoji.parse(Node, Function)`
|
||||||
|
*
|
||||||
|
* @param what The source to parse and enrich with emoji
|
||||||
|
*
|
||||||
|
* string: Replace emoji matches with `<img>` tags.
|
||||||
|
* Mainly used to inject emoji via `innerHTML`
|
||||||
|
* It does **not** parse the string or validate it,
|
||||||
|
* it simply replaces found emoji with a tag.
|
||||||
|
* NOTE: be sure this won't affect security.
|
||||||
|
*
|
||||||
|
* Node: Walk through the DOM tree and find emoji
|
||||||
|
* that are inside **text node only** (nodeType === 3)
|
||||||
|
* Mainly used to put emoji in already generated DOM
|
||||||
|
* without compromising surrounding nodes and
|
||||||
|
* **avoiding** the usage of `innerHTML`.
|
||||||
|
* NOTE: Using DOM elements instead of strings should
|
||||||
|
* improve security without compromising too much
|
||||||
|
* performance compared with a less safe `innerHTML`.
|
||||||
|
*
|
||||||
|
* @param how If specified, this will be invoked per each emoji that has been
|
||||||
|
* found through the RegExp except those follwed by the invariant
|
||||||
|
* \uFE0E ("as text"). Once invoked, parameters will be:
|
||||||
|
*
|
||||||
|
* icon {string} the lower case HEX code point
|
||||||
|
* i.e. "1f4a9"
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* imagemoji.parse("I \u2764\uFE0F emoji!");
|
||||||
|
* //=> I <img class="emoji" draggable="false" alt="❤️" src="https://twemoji.maxcdn.com/v/latest/svg/2764.svg"/> emoji!
|
||||||
|
*
|
||||||
|
* imagemoji.parse("I \u2764\uFE0F emoji!", (icon) => "/assets/emoji/" + icon + ".png");
|
||||||
|
* //=> I <img class="emoji" draggable="false" alt="❤️" src="/assets/emoji/2764.png"/> emoji!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const parse = function (what: string | Node, how?: (icon: string) => string): string | Node {
|
||||||
|
// only accept custom how if it's a function
|
||||||
|
how = (typeof how === "function") ? how : getTwemojiSvg;
|
||||||
|
|
||||||
|
// if first argument is string, return html <img> tags
|
||||||
|
// otherwise use the DOM tree and parse text nodes only to inject images
|
||||||
|
return (typeof what === "string") ? parseString(what, how) : parseNode(what, how);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { parse };
|
Loading…
x
Reference in New Issue
Block a user