@milkdown/plugin-emoji 6.5.2 → 6.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/constant.d.ts.map +1 -1
- package/lib/filter/helper.d.ts +2 -2
- package/lib/filter/helper.d.ts.map +1 -1
- package/lib/filter/index.d.ts +1 -1
- package/lib/filter/index.d.ts.map +1 -1
- package/lib/filter/style.d.ts +1 -1
- package/lib/filter/style.d.ts.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +158 -170
- package/lib/index.es.js.map +1 -1
- package/lib/node.d.ts +2 -2
- package/lib/node.d.ts.map +1 -1
- package/lib/parse.d.ts.map +1 -1
- package/lib/remark-twemoji.d.ts +1 -1
- package/lib/remark-twemoji.d.ts.map +1 -1
- package/package.json +20 -15
- package/src/constant.ts +3 -3
- package/src/filter/helper.ts +66 -77
- package/src/filter/index.ts +208 -210
- package/src/filter/style.ts +22 -20
- package/src/index.ts +4 -4
- package/src/node.ts +89 -88
- package/src/parse.ts +3 -3
- package/src/remark-twemoji.ts +57 -57
package/src/node.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { RemarkPlugin } from '@milkdown/core'
|
|
3
|
-
import { expectDomTypeError } from '@milkdown/exception'
|
|
4
|
-
import { InputRule } from '@milkdown/prose/inputrules'
|
|
5
|
-
import { createNode } from '@milkdown/utils'
|
|
6
|
-
import nodeEmoji from 'node-emoji'
|
|
7
|
-
import remarkEmoji from 'remark-emoji'
|
|
2
|
+
import type { RemarkPlugin } from '@milkdown/core'
|
|
3
|
+
import { expectDomTypeError } from '@milkdown/exception'
|
|
4
|
+
import { InputRule } from '@milkdown/prose/inputrules'
|
|
5
|
+
import { createNode } from '@milkdown/utils'
|
|
6
|
+
import nodeEmoji from 'node-emoji'
|
|
7
|
+
import remarkEmoji from 'remark-emoji'
|
|
8
8
|
|
|
9
|
-
import { input } from './constant'
|
|
10
|
-
import { filter } from './filter'
|
|
11
|
-
import { parse } from './parse'
|
|
12
|
-
import { twemojiPlugin } from './remark-twemoji'
|
|
9
|
+
import { input } from './constant'
|
|
10
|
+
import { filter } from './filter'
|
|
11
|
+
import { parse } from './parse'
|
|
12
|
+
import { twemojiPlugin } from './remark-twemoji'
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
14
|
+
export interface EmojiOptions {
|
|
15
|
+
maxListSize: number
|
|
16
|
+
twemojiOptions: TwemojiOptions
|
|
17
|
+
}
|
|
18
18
|
|
|
19
19
|
export const emojiNode = createNode<string, EmojiOptions>((utils, options) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const getStyle = () =>
|
|
21
|
+
utils.getStyle(
|
|
22
|
+
({ css }) => css`
|
|
23
23
|
.emoji {
|
|
24
24
|
height: 1em;
|
|
25
25
|
width: 1em;
|
|
@@ -27,76 +27,77 @@ export const emojiNode = createNode<string, EmojiOptions>((utils, options) => {
|
|
|
27
27
|
vertical-align: -1.5px;
|
|
28
28
|
}
|
|
29
29
|
`,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return { html: dom.innerHTML };
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
toDOM: (node) => {
|
|
54
|
-
const span = document.createElement('span');
|
|
55
|
-
span.classList.add('emoji-wrapper');
|
|
56
|
-
span.dataset['type'] = 'emoji';
|
|
57
|
-
utils.themeManager.onFlush(() => {
|
|
58
|
-
const style = getStyle();
|
|
59
|
-
if (style) {
|
|
60
|
-
span.classList.add(style);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
span.innerHTML = node.attrs['html'];
|
|
64
|
-
return { dom: span };
|
|
65
|
-
},
|
|
66
|
-
parseMarkdown: {
|
|
67
|
-
match: ({ type }) => type === 'emoji',
|
|
68
|
-
runner: (state, node, type) => {
|
|
69
|
-
state.addNode(type, { html: node['value'] as string });
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
toMarkdown: {
|
|
73
|
-
match: (node) => node.type.name === 'emoji',
|
|
74
|
-
runner: (state, node) => {
|
|
75
|
-
const span = document.createElement('span');
|
|
76
|
-
span.innerHTML = node.attrs['html'];
|
|
77
|
-
const img = span.querySelector('img');
|
|
78
|
-
const title = img?.title;
|
|
79
|
-
span.remove();
|
|
80
|
-
state.addNode('text', undefined, title);
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
}),
|
|
84
|
-
inputRules: (nodeType) => [
|
|
85
|
-
new InputRule(input, (state, match, start, end) => {
|
|
86
|
-
const content = match[0];
|
|
87
|
-
if (!content) return null;
|
|
88
|
-
const got = nodeEmoji.get(content);
|
|
89
|
-
if (!got || content.includes(got)) return null;
|
|
30
|
+
)
|
|
31
|
+
return {
|
|
32
|
+
id: 'emoji',
|
|
33
|
+
schema: () => ({
|
|
34
|
+
group: 'inline',
|
|
35
|
+
inline: true,
|
|
36
|
+
atom: true,
|
|
37
|
+
attrs: {
|
|
38
|
+
html: {
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
parseDOM: [
|
|
43
|
+
{
|
|
44
|
+
tag: 'span[data-type="emoji"]',
|
|
45
|
+
getAttrs: (dom) => {
|
|
46
|
+
if (!(dom instanceof HTMLElement))
|
|
47
|
+
throw expectDomTypeError(dom)
|
|
90
48
|
|
|
91
|
-
|
|
49
|
+
return { html: dom.innerHTML }
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
toDOM: (node) => {
|
|
54
|
+
const span = document.createElement('span')
|
|
55
|
+
span.classList.add('emoji-wrapper')
|
|
56
|
+
span.dataset.type = 'emoji'
|
|
57
|
+
utils.themeManager.onFlush(() => {
|
|
58
|
+
const style = getStyle()
|
|
59
|
+
if (style)
|
|
60
|
+
span.classList.add(style)
|
|
61
|
+
})
|
|
62
|
+
span.innerHTML = node.attrs.html
|
|
63
|
+
return { dom: span }
|
|
64
|
+
},
|
|
65
|
+
parseMarkdown: {
|
|
66
|
+
match: ({ type }) => type === 'emoji',
|
|
67
|
+
runner: (state, node, type) => {
|
|
68
|
+
state.addNode(type, { html: node.value as string })
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
toMarkdown: {
|
|
72
|
+
match: node => node.type.name === 'emoji',
|
|
73
|
+
runner: (state, node) => {
|
|
74
|
+
const span = document.createElement('span')
|
|
75
|
+
span.innerHTML = node.attrs.html
|
|
76
|
+
const img = span.querySelector('img')
|
|
77
|
+
const title = img?.title
|
|
78
|
+
span.remove()
|
|
79
|
+
state.addNode('text', undefined, title)
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
inputRules: nodeType => [
|
|
84
|
+
new InputRule(input, (state, match, start, end) => {
|
|
85
|
+
const content = match[0]
|
|
86
|
+
if (!content)
|
|
87
|
+
return null
|
|
88
|
+
const got = nodeEmoji.get(content)
|
|
89
|
+
if (!got || content.includes(got))
|
|
90
|
+
return null
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
const html = parse(got, options?.twemojiOptions)
|
|
93
|
+
|
|
94
|
+
return state.tr
|
|
95
|
+
.setMeta('emoji', true)
|
|
96
|
+
.replaceRangeWith(start, end, nodeType.create({ html }))
|
|
97
|
+
.scrollIntoView()
|
|
98
|
+
}),
|
|
99
|
+
],
|
|
100
|
+
remarkPlugins: () => [remarkEmoji as RemarkPlugin, twemojiPlugin(options?.twemojiOptions)],
|
|
101
|
+
prosePlugins: () => [filter(utils, options?.maxListSize ?? 6, options?.twemojiOptions)],
|
|
102
|
+
}
|
|
103
|
+
})
|
package/src/parse.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import twemoji from 'twemoji'
|
|
2
|
+
import twemoji from 'twemoji'
|
|
3
3
|
|
|
4
|
-
const setAttr = (text: string) => ({ title: text })
|
|
4
|
+
const setAttr = (text: string) => ({ title: text })
|
|
5
5
|
|
|
6
6
|
export const parse = (emoji: string, twemojiOptions?: TwemojiOptions): string =>
|
|
7
|
-
|
|
7
|
+
twemoji.parse(emoji, { attributes: setAttr, ...twemojiOptions }) as unknown as string
|
package/src/remark-twemoji.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { RemarkPlugin } from '@milkdown/core'
|
|
3
|
-
import emojiRegex from 'emoji-regex'
|
|
4
|
-
import { Literal, Node, Parent } from 'unist'
|
|
2
|
+
import type { RemarkPlugin } from '@milkdown/core'
|
|
3
|
+
import emojiRegex from 'emoji-regex'
|
|
4
|
+
import type { Literal, Node, Parent } from 'unist'
|
|
5
5
|
|
|
6
|
-
import { parse } from './parse'
|
|
6
|
+
import { parse } from './parse'
|
|
7
7
|
|
|
8
|
-
const regex = emojiRegex()
|
|
8
|
+
const regex = emojiRegex()
|
|
9
9
|
|
|
10
|
-
const isParent = (node: Node): node is Parent => !!(node as Parent).children
|
|
11
|
-
const isLiteral = (node: Node): node is Literal => !!(node as Literal).value
|
|
10
|
+
const isParent = (node: Node): node is Parent => !!(node as Parent).children
|
|
11
|
+
const isLiteral = (node: Node): node is Literal => !!(node as Literal).value
|
|
12
12
|
|
|
13
13
|
function flatMap(ast: Node, fn: (node: Node, index: number, parent: Node | null) => Node[]) {
|
|
14
|
-
|
|
14
|
+
return transform(ast, 0, null)[0]
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
16
|
+
function transform(node: Node, index: number, parent: Node | null) {
|
|
17
|
+
if (isParent(node)) {
|
|
18
|
+
const out = []
|
|
19
|
+
for (let i = 0, n = node.children.length; i < n; i++) {
|
|
20
|
+
const nthChild = node.children[i]
|
|
21
|
+
if (nthChild) {
|
|
22
|
+
const xs = transform(nthChild, i, node)
|
|
23
|
+
if (xs) {
|
|
24
|
+
for (let j = 0, m = xs.length; j < m; j++) {
|
|
25
|
+
const item = xs[j]
|
|
26
|
+
if (item)
|
|
27
|
+
out.push(item)
|
|
32
28
|
}
|
|
33
|
-
|
|
29
|
+
}
|
|
34
30
|
}
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
}
|
|
32
|
+
node.children = out
|
|
37
33
|
}
|
|
34
|
+
|
|
35
|
+
return fn(node, index, parent)
|
|
36
|
+
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
export const twemojiPlugin: (twemojiOptions?: TwemojiOptions) => RemarkPlugin =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
39
|
+
export const twemojiPlugin: (twemojiOptions?: TwemojiOptions) => RemarkPlugin = twemojiOptions => () => {
|
|
40
|
+
function transformer(tree: Node) {
|
|
41
|
+
flatMap(tree, (node) => {
|
|
42
|
+
if (!isLiteral(node))
|
|
43
|
+
return [node]
|
|
44
|
+
|
|
45
|
+
const value = node.value as string
|
|
46
|
+
const output: Literal<string>[] = []
|
|
47
|
+
let match
|
|
48
|
+
let str = value
|
|
49
|
+
// eslint-disable-next-line no-cond-assign
|
|
50
|
+
while ((match = regex.exec(str))) {
|
|
51
|
+
const { index } = match
|
|
52
|
+
const emoji = match[0]
|
|
53
|
+
if (emoji) {
|
|
54
|
+
if (index > 0)
|
|
55
|
+
output.push({ ...node, value: str.slice(0, index) })
|
|
56
|
+
|
|
57
|
+
output.push({ ...node, value: parse(emoji, twemojiOptions), type: 'emoji' })
|
|
58
|
+
str = str.slice(index + emoji.length)
|
|
59
|
+
}
|
|
60
|
+
regex.lastIndex = 0
|
|
61
|
+
}
|
|
62
|
+
if (str.length)
|
|
63
|
+
output.push({ ...node, value: str })
|
|
64
|
+
|
|
65
|
+
return output
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
return transformer
|
|
69
|
+
}
|