refactor: markdown editor
This commit is contained in:
@@ -6,8 +6,8 @@ export class Markdown {
|
||||
this.api = api
|
||||
this.data = data || {}
|
||||
this.config = config || {}
|
||||
this.text = data.text || ''
|
||||
this.readOnly = readOnly
|
||||
this.text = data.text || ''
|
||||
this.placeholder = __("Type '/' for commands or select text to format")
|
||||
}
|
||||
|
||||
@@ -30,65 +30,28 @@ export class Markdown {
|
||||
|
||||
const div = document.createElement('div')
|
||||
app.mount(div)
|
||||
return {
|
||||
title: '',
|
||||
icon: div.innerHTML,
|
||||
}
|
||||
}
|
||||
|
||||
onPaste(event) {
|
||||
const data = {
|
||||
text: event.detail.data.innerHTML,
|
||||
}
|
||||
|
||||
this.data = data
|
||||
window.requestAnimationFrame(() => {
|
||||
if (!this.wrapper) {
|
||||
return
|
||||
}
|
||||
this.wrapper.innerHTML = this.data.text || ''
|
||||
})
|
||||
return { title: '', icon: div.innerHTML }
|
||||
}
|
||||
|
||||
static get pasteConfig() {
|
||||
return {
|
||||
tags: ['P'],
|
||||
}
|
||||
return { tags: ['P'] }
|
||||
}
|
||||
|
||||
render() {
|
||||
this.wrapper = document.createElement('div')
|
||||
this.wrapper.classList.add('cdx-block', 'ce-paragraph')
|
||||
this.wrapper.contentEditable = !this.readOnly
|
||||
this.wrapper.dataset.placeholder = this.placeholder
|
||||
this.wrapper.innerHTML = this.text
|
||||
|
||||
if (!this.readOnly) {
|
||||
this.wrapper.contentEditable = true
|
||||
this.wrapper.innerHTML = this.text
|
||||
|
||||
this.wrapper.addEventListener('focus', () =>
|
||||
this._togglePlaceholder()
|
||||
)
|
||||
this.wrapper.addEventListener('blur', () =>
|
||||
this._togglePlaceholder()
|
||||
)
|
||||
|
||||
this.wrapper.addEventListener('input', (event) => {
|
||||
this._togglePlaceholder()
|
||||
let value = event.target.textContent
|
||||
if (event.keyCode === 32 && value.startsWith('#')) {
|
||||
this.convertToHeader(event, value)
|
||||
} else if (event.keyCode == 189) {
|
||||
this.convertBlock('list', {
|
||||
style: 'unordered',
|
||||
})
|
||||
} else if (/^[a-zA-Z]/.test(event.key)) {
|
||||
this.convertBlock('paragraph', {
|
||||
text: value,
|
||||
})
|
||||
} else if (event.keyCode === 13 || event.keyCode === 190) {
|
||||
this.parseContent(event)
|
||||
}
|
||||
})
|
||||
this.wrapper.addEventListener('keydown', (e) => this._onKeyDown(e))
|
||||
}
|
||||
|
||||
return this.wrapper
|
||||
@@ -99,10 +62,9 @@ export class Markdown {
|
||||
'.cdx-block.ce-paragraph[data-placeholder]'
|
||||
)
|
||||
blocks.forEach((block) => {
|
||||
if (block !== this.wrapper) {
|
||||
delete block.dataset.placeholder
|
||||
}
|
||||
if (block !== this.wrapper) delete block.dataset.placeholder
|
||||
})
|
||||
|
||||
if (this.wrapper.innerHTML.trim() === '') {
|
||||
this.wrapper.dataset.placeholder = this.placeholder
|
||||
} else {
|
||||
@@ -110,102 +72,107 @@ export class Markdown {
|
||||
}
|
||||
}
|
||||
|
||||
convertToHeader(event, value) {
|
||||
event.preventDefault()
|
||||
if (['#', '##', '###', '####', '#####', '######'].includes(value)) {
|
||||
let level = value.length
|
||||
event.target.textContent = ''
|
||||
this.convertBlock('header', {
|
||||
level: level,
|
||||
})
|
||||
}
|
||||
}
|
||||
_onKeyDown(event) {
|
||||
const text = this.wrapper.textContent
|
||||
|
||||
parseContent(event) {
|
||||
event.preventDefault()
|
||||
let previousLine = this.wrapper.textContent
|
||||
if (event.keyCode === 190) {
|
||||
previousLine = previousLine + '.'
|
||||
}
|
||||
|
||||
if (previousLine && this.hasImage(previousLine)) {
|
||||
if (event.key === ' ' && /^#{1,6}$/.test(text)) {
|
||||
event.preventDefault()
|
||||
const level = text.length
|
||||
this.wrapper.textContent = ''
|
||||
this.convertBlock('image')
|
||||
} else if (previousLine && this.hasLink(previousLine)) {
|
||||
const { text, url } = this.extractLink(previousLine)
|
||||
const anchorTag = `<a href="${url}" target="_blank">${text}</a>`
|
||||
this.convertBlock('paragraph', {
|
||||
text: previousLine.replace(/\[.+?\]\(.+?\)/, anchorTag),
|
||||
})
|
||||
} else if (previousLine && previousLine.startsWith('- ')) {
|
||||
this.convertBlock('list', {
|
||||
this._convertBlock('header', { level })
|
||||
} else if (event.key === ' ' && text === '-') {
|
||||
event.preventDefault()
|
||||
this.wrapper.textContent = ''
|
||||
this._convertBlock('list', {
|
||||
style: 'unordered',
|
||||
items: [
|
||||
{
|
||||
content: previousLine.replace('- ', ''),
|
||||
},
|
||||
],
|
||||
items: [{ content: '' }],
|
||||
})
|
||||
} else if (previousLine && previousLine.startsWith('1.')) {
|
||||
this.convertBlock('list', {
|
||||
style: 'ordered',
|
||||
items: [
|
||||
{
|
||||
content: previousLine.replace('1.', ''),
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if (previousLine && this.canBeEmbed(previousLine)) {
|
||||
} else if (event.key === ' ' && /^1\.$/.test(text)) {
|
||||
event.preventDefault()
|
||||
this.wrapper.textContent = ''
|
||||
this.convertBlock('embed', {
|
||||
source: previousLine,
|
||||
this._convertBlock('list', {
|
||||
style: 'ordered',
|
||||
items: [{ content: '' }],
|
||||
})
|
||||
} else {
|
||||
this.convertBlock('paragraph', {
|
||||
text: previousLine,
|
||||
} else if (this._isEmbed(text) && event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.wrapper.textContent = ''
|
||||
this._convertBlock('embed', { source: text })
|
||||
} else if (event.key === 'Enter') {
|
||||
setTimeout(() => this._checkMarkdownAfterEnter(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
_checkMarkdownAfterEnter() {
|
||||
const text = this.wrapper.textContent.trim()
|
||||
|
||||
if (this._isImage(text)) {
|
||||
this._convertBlock('image', {
|
||||
file: { url: this._extractImage(text).url },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async convertBlock(type, data, index = null) {
|
||||
async _convertBlock(type, data) {
|
||||
const currentIndex = this.api.blocks.getCurrentBlockIndex()
|
||||
const currentBlock = this.api.blocks.getBlockByIndex(currentIndex)
|
||||
|
||||
if (!currentBlock) return
|
||||
|
||||
await this.api.blocks.convert(currentBlock.id, type, data)
|
||||
this.api.caret.focus(true)
|
||||
|
||||
setTimeout(() => {
|
||||
const newIndex = this.api.blocks.getCurrentBlockIndex()
|
||||
const newBlock = this.api.blocks.getBlockByIndex(newIndex)
|
||||
|
||||
if (newBlock && newBlock.holder) {
|
||||
const holder = newBlock.holder.querySelector(
|
||||
'[contenteditable="true"]'
|
||||
)
|
||||
if (holder) {
|
||||
holder.focus()
|
||||
// Place caret at end
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(holder)
|
||||
range.collapse(false)
|
||||
const sel = window.getSelection()
|
||||
sel.removeAllRanges()
|
||||
sel.addRange(range)
|
||||
} else {
|
||||
this.api.caret.focus(true)
|
||||
}
|
||||
} else {
|
||||
this.api.caret.focus(true)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
return {
|
||||
text: blockContent.innerHTML,
|
||||
}
|
||||
return { text: blockContent.innerHTML }
|
||||
}
|
||||
|
||||
hasImage(line) {
|
||||
return /!\[.+?\]\(.+?\)/.test(line)
|
||||
_isImage(text) {
|
||||
return /!\[.+?\]\(.+?\)/.test(text)
|
||||
}
|
||||
|
||||
extractImage(line) {
|
||||
const match = line.match(/!\[(.+?)\]\((.+?)\)/)
|
||||
if (match) {
|
||||
return { alt: match[1], url: match[2] }
|
||||
}
|
||||
_extractImage(text) {
|
||||
const match = text.match(/!\[(.+?)\]\((.+?)\)/)
|
||||
if (match) return { alt: match[1], url: match[2] }
|
||||
return { alt: '', url: '' }
|
||||
}
|
||||
|
||||
hasLink(line) {
|
||||
return /\[.+?\]\(.+?\)/.test(line)
|
||||
_isLink(text) {
|
||||
return /\[.+?\]\(.+?\)/.test(text)
|
||||
}
|
||||
|
||||
extractLink(line) {
|
||||
const match = line.match(/\[(.+?)\]\((.+?)\)/)
|
||||
if (match) {
|
||||
return { text: match[1], url: match[2] }
|
||||
}
|
||||
_extractLink(text) {
|
||||
const match = text.match(/\[(.+?)\]\((.+?)\)/)
|
||||
if (match) return { text: match[1], url: match[2] }
|
||||
return { text: '', url: '' }
|
||||
}
|
||||
|
||||
canBeEmbed(line) {
|
||||
return /^https?:\/\/.+/.test(line.trim())
|
||||
_isEmbed(text) {
|
||||
return /^https?:\/\/.+/.test(text.trim())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user