mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 22:41:53 +08:00
670 lines
20 KiB
JavaScript
670 lines
20 KiB
JavaScript
import ContentState from './contentState'
|
||
import selection from './selection'
|
||
import EventCenter from './event'
|
||
import { LOWERCASE_TAGS, EVENT_KEYS, CLASS_OR_ID, codeMirrorConfig } from './config'
|
||
import { throttle, wordCount } from './utils'
|
||
import { search } from './codeMirror'
|
||
import { checkEditLanguage } from './codeMirror/language'
|
||
import Emoji, { checkEditEmoji, setInlineEmoji } from './emojis'
|
||
import FloatBox from './floatBox'
|
||
import { findNearestParagraph, operateClassName, isInElement } from './utils/domManipulate'
|
||
import ExportMarkdown from './utils/exportMarkdown'
|
||
import ExportHtml from './utils/exportHtml'
|
||
import { checkEditImage } from './utils/checkEditImage'
|
||
import TablePicker from './tablePicker'
|
||
|
||
import './assets/symbolIcon' // import symbol icons
|
||
import './assets/symbolIcon/index.css'
|
||
import './assets/styles/index.css'
|
||
|
||
class Muya {
|
||
constructor (container, options) {
|
||
const {
|
||
focusMode = false, theme = 'light', markdown = '', preferLooseListItem = true,
|
||
autoPairBracket = true, autoPairMarkdownSyntax = true, autoPairQuote = true,
|
||
bulletListMarker = '-', tabSize = 4
|
||
} = options
|
||
this.container = container
|
||
const eventCenter = this.eventCenter = new EventCenter()
|
||
const floatBox = this.floatBox = new FloatBox(eventCenter)
|
||
const tablePicker = this.tablePicker = new TablePicker(eventCenter)
|
||
this.contentState = new ContentState({
|
||
eventCenter,
|
||
floatBox,
|
||
tablePicker,
|
||
preferLooseListItem,
|
||
autoPairBracket,
|
||
autoPairMarkdownSyntax,
|
||
autoPairQuote,
|
||
bulletListMarker,
|
||
tabSize
|
||
})
|
||
this.emoji = new Emoji() // emoji instance: has search(text) clear() methods.
|
||
this.focusMode = focusMode
|
||
this.theme = theme
|
||
this.markdown = markdown
|
||
this.fontSize = 16
|
||
this.lineHeight = 1.6
|
||
this.preferLooseListItem = preferLooseListItem
|
||
// private property
|
||
this._isEditChinese = false // true or false
|
||
this._copyType = 'normal' // `normal` or `copyAsMarkdown` or `copyAsHtml`
|
||
this._pasteType = 'normal' // `normal` or `pasteAsPlainText`
|
||
this.init()
|
||
}
|
||
|
||
init () {
|
||
this.ensureContainerDiv()
|
||
const { container, contentState, eventCenter } = this
|
||
contentState.stateRender.setContainer(container.children[0])
|
||
|
||
eventCenter.subscribe('editEmoji', throttle(this.subscribeEditEmoji.bind(this), 200))
|
||
this.dispatchEditEmoji()
|
||
eventCenter.subscribe('editLanguage', throttle(this.subscribeEditLanguage.bind(this)))
|
||
this.dispatchEditLanguage()
|
||
|
||
eventCenter.subscribe('hideFloatBox', this.subscribeHideFloatBox.bind(this))
|
||
this.dispatchHideFloatBox()
|
||
|
||
eventCenter.subscribe('stateChange', this.dispatchChange.bind(this))
|
||
|
||
eventCenter.attachDOMEvent(container, 'paste', event => {
|
||
contentState.pasteHandler(event, this._pasteType)
|
||
this._pasteType = 'normal'
|
||
})
|
||
|
||
eventCenter.attachDOMEvent(container, 'contextmenu', event => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
const sectionChanges = this.contentState.selectionChange(undefined, undefined, this.contentState.cursor)
|
||
eventCenter.dispatch('contextmenu', event, sectionChanges)
|
||
})
|
||
|
||
this.recordEditChinese()
|
||
this.imageClick()
|
||
this.listItemCheckBoxClick()
|
||
this.dispatchArrow()
|
||
this.dispatchBackspace()
|
||
this.dispatchEnter()
|
||
this.dispatchSelection()
|
||
this.dispatchUpdateState()
|
||
this.dispatchCopyCut()
|
||
this.dispatchTableToolBar()
|
||
this.dispatchCodeBlockClick()
|
||
this.htmlPreviewClick()
|
||
this.mathPreviewClick()
|
||
|
||
contentState.listenForPathChange()
|
||
|
||
const { theme, focusMode, markdown } = this
|
||
this.setTheme(theme)
|
||
this.setMarkdown(markdown)
|
||
this.setFocusMode(focusMode)
|
||
}
|
||
|
||
/**
|
||
* [ensureContainerDiv ensure container element is div]
|
||
*/
|
||
ensureContainerDiv () {
|
||
const { container } = this
|
||
const div = document.createElement(LOWERCASE_TAGS.div)
|
||
const rootDom = document.createElement(LOWERCASE_TAGS.div)
|
||
const attrs = container.attributes
|
||
const parentNode = container.parentNode
|
||
// copy attrs from origin container to new div element
|
||
Array.from(attrs).forEach(attr => {
|
||
div.setAttribute(attr.name, attr.value)
|
||
})
|
||
div.setAttribute('contenteditable', true)
|
||
div.classList.add('mousetrap')
|
||
div.appendChild(rootDom)
|
||
parentNode.insertBefore(div, container)
|
||
parentNode.removeChild(container)
|
||
this.container = div
|
||
}
|
||
|
||
dispatchChange () {
|
||
const { eventCenter } = this
|
||
const markdown = this.markdown = this.getMarkdown()
|
||
const wordCount = this.getWordCount(markdown)
|
||
const cursor = this.getCursor()
|
||
const history = this.getHistory()
|
||
eventCenter.dispatch('change', { markdown, wordCount, cursor, history })
|
||
}
|
||
|
||
dispatchCopyCut () {
|
||
const { container, eventCenter, contentState } = this
|
||
const handler = event => {
|
||
contentState.copyHandler(event, this._copyType)
|
||
if (event.type === 'cut') {
|
||
// when user use `cut` function, the dom has been deleted by default.
|
||
// But should update content state manually.
|
||
contentState.cutHandler()
|
||
}
|
||
this._copyType = 'normal'
|
||
}
|
||
eventCenter.attachDOMEvent(container, 'cut', handler)
|
||
eventCenter.attachDOMEvent(container, 'copy', handler)
|
||
}
|
||
|
||
/**
|
||
* dispatchEditEmoji
|
||
*/
|
||
dispatchEditEmoji () {
|
||
const { container, eventCenter } = this
|
||
const changeHandler = event => {
|
||
const node = selection.getSelectionStart()
|
||
const emojiNode = checkEditEmoji(node)
|
||
if (emojiNode && event.key !== EVENT_KEYS.Enter) {
|
||
eventCenter.dispatch('editEmoji', emojiNode)
|
||
}
|
||
}
|
||
eventCenter.attachDOMEvent(container, 'keyup', changeHandler) // don't listen `input` event
|
||
}
|
||
|
||
subscribeEditEmoji (emojiNode) {
|
||
const text = emojiNode.textContent.trim()
|
||
if (text) {
|
||
const list = this.emoji.search(text).map(l => {
|
||
return Object.assign(l, { text: l.aliases[0] })
|
||
})
|
||
const cb = item => {
|
||
setInlineEmoji(emojiNode, item, selection)
|
||
this.floatBox.hideIfNeeded()
|
||
}
|
||
if (list.length) {
|
||
this.floatBox.showIfNeeded(emojiNode, cb)
|
||
this.floatBox.setOptions(list)
|
||
} else {
|
||
this.floatBox.hideIfNeeded()
|
||
}
|
||
}
|
||
}
|
||
|
||
dispatchHideFloatBox () {
|
||
const { container, eventCenter } = this
|
||
let cacheTop = null
|
||
const handler = event => {
|
||
if (event.type === 'scroll') {
|
||
const scrollTop = container.scrollTop
|
||
if (cacheTop && Math.abs(scrollTop - cacheTop) > 10) {
|
||
cacheTop = null
|
||
return eventCenter.dispatch('hideFloatBox')
|
||
} else {
|
||
cacheTop = scrollTop
|
||
return
|
||
}
|
||
}
|
||
if (event.target && event.target.classList.contains(CLASS_OR_ID['AG_LANGUAGE_INPUT'])) {
|
||
return
|
||
}
|
||
if (event.type === 'click') return eventCenter.dispatch('hideFloatBox')
|
||
const node = selection.getSelectionStart()
|
||
const paragraph = findNearestParagraph(node)
|
||
const selectionState = selection.exportSelection(paragraph)
|
||
const lang = checkEditLanguage(paragraph, selectionState)
|
||
const emojiNode = node && checkEditEmoji(node)
|
||
const editImage = checkEditImage()
|
||
|
||
if (!emojiNode && !lang && !editImage) {
|
||
eventCenter.dispatch('hideFloatBox')
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
eventCenter.attachDOMEvent(container, 'keyup', handler)
|
||
eventCenter.attachDOMEvent(container, 'scroll', throttle(handler, 200))
|
||
}
|
||
|
||
subscribeHideFloatBox () {
|
||
this.floatBox.hideIfNeeded()
|
||
}
|
||
|
||
/**
|
||
* dispatchIsEditLanguage
|
||
*/
|
||
dispatchEditLanguage () {
|
||
const { container, eventCenter } = this
|
||
const inputHandler = event => {
|
||
const node = selection.getSelectionStart()
|
||
const paragraph = findNearestParagraph(node)
|
||
const selectionState = selection.exportSelection(paragraph)
|
||
const lang = checkEditLanguage(paragraph, selectionState)
|
||
if (lang) {
|
||
eventCenter.dispatch('editLanguage', paragraph, lang)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'input', inputHandler)
|
||
}
|
||
|
||
subscribeEditLanguage (paragraph, lang, cb) {
|
||
const modes = search(lang).map(mode => {
|
||
return Object.assign(mode, { text: mode.name })
|
||
})
|
||
|
||
const callback = item => {
|
||
this.contentState.selectLanguage(paragraph, item.name)
|
||
}
|
||
if (modes.length) {
|
||
this.floatBox.showIfNeeded(paragraph, cb || callback)
|
||
this.floatBox.setOptions(modes)
|
||
} else {
|
||
this.floatBox.hideIfNeeded()
|
||
}
|
||
}
|
||
|
||
dispatchBackspace () {
|
||
const { container, eventCenter } = this
|
||
|
||
const handler = event => {
|
||
if (event.key === EVENT_KEYS.Backspace) {
|
||
this.contentState.backspaceHandler(event)
|
||
} else if (event.key === EVENT_KEYS.Delete) {
|
||
this.contentState.deleteHandler(event)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'keydown', handler)
|
||
}
|
||
|
||
recordEditChinese () {
|
||
const { container, eventCenter } = this
|
||
const handler = event => {
|
||
if (event.type === 'compositionstart') {
|
||
this._isEditChinese = true
|
||
} else if (event.type === 'compositionend') {
|
||
this._isEditChinese = false
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'compositionend', handler)
|
||
eventCenter.attachDOMEvent(container, 'compositionstart', handler)
|
||
}
|
||
|
||
dispatchEnter (event) {
|
||
const { container, eventCenter } = this
|
||
|
||
const handler = event => {
|
||
if (event.key === EVENT_KEYS.Enter && !this._isEditChinese) {
|
||
this.contentState.enterHandler(event)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'keydown', handler)
|
||
}
|
||
|
||
dispatchSelection (event) {
|
||
const { container, eventCenter } = this
|
||
const handler = event => {
|
||
if (event.ctrlKey && event.key === 'a') {
|
||
this.contentState.tableCellHandler(event)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'keydown', handler)
|
||
}
|
||
|
||
// dispatch arrow event
|
||
dispatchArrow () {
|
||
const { container, eventCenter } = this
|
||
const handler = event => {
|
||
if (this._isEditChinese) return
|
||
switch (event.key) {
|
||
case EVENT_KEYS.ArrowUp: // fallthrough
|
||
case EVENT_KEYS.ArrowDown: // fallthrough
|
||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||
case EVENT_KEYS.ArrowRight: // fallthrough
|
||
this.contentState.arrowHandler(event)
|
||
break
|
||
case EVENT_KEYS.Tab:
|
||
this.contentState.tabHandler(event)
|
||
break
|
||
}
|
||
}
|
||
eventCenter.attachDOMEvent(container, 'keydown', handler)
|
||
}
|
||
|
||
dispatchCodeBlockClick () {
|
||
const { container, eventCenter } = this
|
||
const handler = event => {
|
||
const target = event.target
|
||
if (target.tagName === 'PRE' && target.classList.contains(CLASS_OR_ID['AG_CODE_BLOCK'])) {
|
||
this.contentState.focusCodeBlock(event)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
dispatchTableToolBar () {
|
||
const { container, eventCenter } = this
|
||
const getToolItem = target => {
|
||
// poor implement, fix me @jocs
|
||
const parent = target.parentNode
|
||
const grandPa = parent && parent.parentNode
|
||
if (target.hasAttribute('data-label')) return target
|
||
if (parent && parent.hasAttribute('data-label')) return parent
|
||
if (grandPa && grandPa.hasAttribute('data-label')) return grandPa
|
||
return null
|
||
}
|
||
const handler = event => {
|
||
const target = event.target
|
||
const toolItem = getToolItem(target)
|
||
if (toolItem) {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
const type = toolItem.getAttribute('data-label')
|
||
const grandPa = toolItem.parentNode.parentNode
|
||
if (grandPa.classList.contains('ag-tool-table')) {
|
||
this.contentState.tableToolBarClick(type)
|
||
} else if (grandPa.classList.contains('ag-tool-html')) {
|
||
this.contentState.htmlToolBarClick(type)
|
||
}
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
dispatchUpdateState () {
|
||
const { container, eventCenter } = this
|
||
let timer = null
|
||
const changeHandler = event => {
|
||
const target = event.target
|
||
if (event.type === 'click' && target.classList.contains(CLASS_OR_ID['AG_FUNCTION_HTML'])) return
|
||
if (!this._isEditChinese) {
|
||
this.contentState.updateState(event)
|
||
}
|
||
if (event.type === 'click' || event.type === 'keyup') {
|
||
if (timer) clearTimeout(timer)
|
||
timer = setTimeout(() => {
|
||
const selectionChanges = this.getSelection()
|
||
const { formats } = this.contentState.selectionFormats()
|
||
eventCenter.dispatch('selectionChange', selectionChanges)
|
||
eventCenter.dispatch('selectionFormats', formats)
|
||
this.dispatchChange()
|
||
})
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', changeHandler)
|
||
eventCenter.attachDOMEvent(container, 'keyup', changeHandler)
|
||
eventCenter.attachDOMEvent(container, 'input', changeHandler)
|
||
}
|
||
|
||
imageClick () {
|
||
const { container, eventCenter } = this
|
||
const selectionText = node => {
|
||
const textLen = node.textContent.length
|
||
operateClassName(node, 'remove', CLASS_OR_ID['AG_HIDE'])
|
||
operateClassName(node, 'add', CLASS_OR_ID['AG_GRAY'])
|
||
selection.importSelection({
|
||
start: textLen,
|
||
end: textLen
|
||
}, node)
|
||
}
|
||
|
||
const handler = event => {
|
||
const target = event.target
|
||
const markedImageText = target.previousElementSibling
|
||
const mathRender = isInElement(target, CLASS_OR_ID['AG_MATH_RENDER'])
|
||
const mathText = mathRender && mathRender.previousElementSibling
|
||
if (markedImageText && markedImageText.classList.contains(CLASS_OR_ID['AG_IMAGE_MARKED_TEXT'])) {
|
||
selectionText(markedImageText)
|
||
} else if (mathText) {
|
||
selectionText(mathText)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
htmlPreviewClick () {
|
||
const { eventCenter, container } = this
|
||
const handler = event => {
|
||
const target = event.target
|
||
const htmlPreview = isInElement(target, 'ag-function-html')
|
||
if (htmlPreview && !htmlPreview.classList.contains(CLASS_OR_ID['AG_ACTIVE'])) {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
this.contentState.handleHtmlBlockClick(htmlPreview)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
mathPreviewClick () {
|
||
const { eventCenter, container } = this
|
||
const handler = event => {
|
||
const target = event.target
|
||
const mathFigure = isInElement(target, 'ag-multiple-math-block')
|
||
if (mathFigure && !mathFigure.classList.contains(CLASS_OR_ID['AG_ACTIVE'])) {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
this.contentState.handleMathBlockClick(mathFigure)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
listItemCheckBoxClick () {
|
||
const { container, eventCenter } = this
|
||
const handler = event => {
|
||
const target = event.target
|
||
if (target.tagName === 'INPUT' && target.classList.contains(CLASS_OR_ID['AG_TASK_LIST_ITEM_CHECKBOX'])) {
|
||
this.contentState.listItemCheckBoxClick(target)
|
||
}
|
||
}
|
||
|
||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||
}
|
||
|
||
getMarkdown () {
|
||
const blocks = this.contentState.getBlocks()
|
||
return new ExportMarkdown(blocks).generate()
|
||
}
|
||
|
||
getHistory () {
|
||
return this.contentState.getHistory()
|
||
}
|
||
|
||
setHistory (history) {
|
||
return this.contentState.setHistory(history)
|
||
}
|
||
|
||
exportStyledHTML (filename) {
|
||
const { markdown } = this
|
||
return new ExportHtml(markdown).generate(filename)
|
||
}
|
||
|
||
exportHtml () {
|
||
const { markdown } = this
|
||
return new ExportHtml(markdown).renderHtml()
|
||
}
|
||
|
||
getWordCount (markdown) {
|
||
return wordCount(markdown)
|
||
}
|
||
|
||
getCursor () {
|
||
return this.contentState.getCodeMirrorCursor()
|
||
}
|
||
|
||
clearHistory () {
|
||
this.contentState.history.clearHistory()
|
||
}
|
||
|
||
setMarkdown (markdown, cursor, isRenderCursor = true) {
|
||
let newMarkdown = markdown
|
||
if (cursor) {
|
||
newMarkdown = this.contentState.addCursorToMarkdown(markdown, cursor)
|
||
}
|
||
this.contentState.importMarkdown(newMarkdown)
|
||
this.contentState.importCursor(cursor)
|
||
this.contentState.render(isRenderCursor)
|
||
this.dispatchChange()
|
||
}
|
||
|
||
createTable (tableChecker) {
|
||
const { eventCenter } = this
|
||
this.contentState.createFigure(tableChecker)
|
||
const selectionChanges = this.getSelection()
|
||
eventCenter.dispatch('selectionChange', selectionChanges)
|
||
}
|
||
|
||
getSelection () {
|
||
const { fontSize, lineHeight } = this
|
||
return this.contentState.selectionChange(fontSize, lineHeight)
|
||
}
|
||
|
||
setFocusMode (bool) {
|
||
const { container, focusMode } = this
|
||
if (bool && !focusMode) {
|
||
container.classList.add(CLASS_OR_ID['AG_FOCUS_MODE'])
|
||
} else {
|
||
container.classList.remove(CLASS_OR_ID['AG_FOCUS_MODE'])
|
||
}
|
||
this.focusMode = bool
|
||
}
|
||
|
||
setTheme (name) {
|
||
if (!name) return
|
||
if (name === 'dark') {
|
||
codeMirrorConfig.theme = 'railscasts'
|
||
} else {
|
||
delete codeMirrorConfig.theme
|
||
}
|
||
this.theme = name
|
||
// Render cursor and refresh code block
|
||
this.contentState.render(true, true)
|
||
}
|
||
|
||
setFont ({ fontSize, lineHeight }) {
|
||
if (fontSize) this.fontSize = parseInt(fontSize, 10)
|
||
if (lineHeight) this.lineHeight = lineHeight
|
||
}
|
||
|
||
setListItemPreference (preferLooseListItem) {
|
||
this.preferLooseListItem = preferLooseListItem
|
||
this.contentState.preferLooseListItem = preferLooseListItem
|
||
}
|
||
|
||
setTabSize (tabSize) {
|
||
if (!tabSize || typeof tabSize !== 'number') {
|
||
tabSize = 4
|
||
} else if (tabSize < 1) {
|
||
tabSize = 1
|
||
}
|
||
this.tabSize = tabSize
|
||
this.contentState.tabSize = tabSize
|
||
}
|
||
|
||
updateParagraph (type) {
|
||
this.contentState.updateParagraph(type)
|
||
}
|
||
|
||
insertParagraph (location) {
|
||
this.contentState.insertParagraph(location)
|
||
}
|
||
|
||
editTable (data) {
|
||
this.contentState.editTable(data)
|
||
}
|
||
|
||
blur () {
|
||
this.container.blur()
|
||
}
|
||
|
||
showAutoImagePath (files) {
|
||
const list = files.map(f => {
|
||
const iconClass = f.type === 'directory' ? 'icon-folder' : 'icon-image'
|
||
return Object.assign(f, { iconClass, text: f.file + (f.type === 'directory' ? '/' : '') })
|
||
})
|
||
this.contentState.showAutoImagePath(list)
|
||
}
|
||
|
||
format (type) {
|
||
this.contentState.format(type)
|
||
}
|
||
|
||
insertImage (url) {
|
||
this.contentState.insertImage(url)
|
||
}
|
||
|
||
search (value, opt) {
|
||
const { selectHighlight } = opt
|
||
this.contentState.search(value, opt)
|
||
this.contentState.render(!!selectHighlight)
|
||
return this.contentState.searchMatches
|
||
}
|
||
|
||
replace (value, opt) {
|
||
this.contentState.replace(value, opt)
|
||
this.contentState.render(false)
|
||
return this.contentState.searchMatches
|
||
}
|
||
|
||
find (action/* pre or next */) {
|
||
this.contentState.find(action)
|
||
this.contentState.render(false)
|
||
return this.contentState.searchMatches
|
||
}
|
||
|
||
on (event, listener) {
|
||
this.eventCenter.subscribe(event, listener)
|
||
}
|
||
|
||
undo () {
|
||
this.contentState.history.undo()
|
||
}
|
||
|
||
redo () {
|
||
this.contentState.history.redo()
|
||
}
|
||
|
||
copyAsMarkdown () {
|
||
this._copyType = 'copyAsMarkdown'
|
||
document.execCommand('copy')
|
||
}
|
||
|
||
copyAsHtml () {
|
||
this._copyType = 'copyAsHtml'
|
||
document.execCommand('copy')
|
||
}
|
||
|
||
pasteAsPlainText () {
|
||
this._pasteType = 'pasteAsPlainText'
|
||
document.execCommand('paste')
|
||
}
|
||
|
||
copy (name) {
|
||
switch (name) {
|
||
case 'table':
|
||
this._copyType = 'copyTable'
|
||
document.execCommand('copy')
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
|
||
destroy () {
|
||
this.emoji.clear() // clear emoji cache for memory recycle
|
||
this.contentState.clear()
|
||
this.floatBox.destroy()
|
||
this.tablePicker.destroy()
|
||
this.container = null
|
||
this.contentState = null
|
||
this.emoji = null
|
||
this.floatBox = null
|
||
this.tablePicker = null
|
||
this.eventCenter.detachAllDomEvents()
|
||
this.eventCenter = null
|
||
}
|
||
}
|
||
|
||
export default Muya
|