mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 05:21:41 +08:00
feat: handle hr edit
This commit is contained in:
parent
78bace484f
commit
5aeb8dfa65
@ -24,6 +24,10 @@ a {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ag-emoji-box {
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
@ -3,7 +3,8 @@ import {
|
||||
operateClassName, insertBefore, insertAfter, removeNode, isFirstChildElement,
|
||||
wrapperElementWithTag, nestElementWithTag, isOnlyChildElement, isLastChildElement,
|
||||
chopBlockQuote, removeAndInsertBefore, removeAndInsertPreList, replaceElement,
|
||||
replacementLists, insertBeforeBlockQuote, isAganippeEditorElement
|
||||
replacementLists, insertBeforeBlockQuote, isAganippeEditorElement,
|
||||
findOutMostParagraph
|
||||
} from './utils/domManipulate'
|
||||
|
||||
import {
|
||||
@ -16,7 +17,7 @@ import {
|
||||
} from './utils'
|
||||
|
||||
import {
|
||||
CLASS_OR_ID, EVENT_KEYS, LOWERCASE_TAGS
|
||||
CLASS_OR_ID, LOWERCASE_TAGS, EVENT_KEYS
|
||||
} from './config'
|
||||
|
||||
import Selection from './selection'
|
||||
@ -57,12 +58,11 @@ class Aganippe {
|
||||
eventCenter.subscribe('elementUpdate', this.subscribeElementUpdate.bind(this))
|
||||
this.dispatchElementUpdate()
|
||||
|
||||
eventCenter.subscribe('arrow', this.subscribeArrow.bind(this))
|
||||
this.dispatchArrow()
|
||||
|
||||
eventCenter.bind('enter', this.enterKeyHandler.bind(this))
|
||||
eventCenter.bind('backspace', this.backspaceKeyHandler.bind(this))
|
||||
|
||||
this.handlerSelectHr()
|
||||
|
||||
this.generateLastEmptyParagraph()
|
||||
}
|
||||
/**
|
||||
@ -103,6 +103,10 @@ class Aganippe {
|
||||
dispatchEditeEmoji () {
|
||||
const { container, eventCenter } = this
|
||||
const changeHandler = event => {
|
||||
const target = event.target
|
||||
if (event.type === 'click' && target.tagName.toLowerCase() === LOWERCASE_TAGS.hr) {
|
||||
return false
|
||||
}
|
||||
const node = selection.getSelectionStart()
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
@ -143,7 +147,10 @@ class Aganippe {
|
||||
dispatchMarkedText () {
|
||||
const { container, eventCenter } = this
|
||||
const changeHandler = event => {
|
||||
// TODO: Handler hr
|
||||
const target = event.target
|
||||
if (event.type === 'click' && target.tagName.toLowerCase() === LOWERCASE_TAGS.hr) {
|
||||
return false
|
||||
}
|
||||
const node = selection.getSelectionStart()
|
||||
const paragraph = findNearestParagraph(node)
|
||||
const text = paragraph.textContent
|
||||
@ -253,47 +260,59 @@ class Aganippe {
|
||||
paragraph: newElement
|
||||
}
|
||||
}
|
||||
|
||||
dispatchArrow () {
|
||||
const { eventCenter, container } = this
|
||||
const changeHandler = event => {
|
||||
if (event.key) {
|
||||
if (
|
||||
event.key === EVENT_KEYS.ArrowLeft ||
|
||||
event.key === EVENT_KEYS.ArrowRight ||
|
||||
event.key === EVENT_KEYS.ArrowUp ||
|
||||
event.key === EVENT_KEYS.ArrowDown
|
||||
) {
|
||||
eventCenter.dispatch('arrow', event)
|
||||
// add handler to select hr element. and translate it to a p element
|
||||
handlerSelectHr () {
|
||||
const { container, eventCenter } = this
|
||||
let newElement
|
||||
const changeHr2P = (event, target, preParagraph) => {
|
||||
newElement = updateBlock(target, LOWERCASE_TAGS.p)
|
||||
newElement.textContent = '---'
|
||||
selection.importSelection({
|
||||
start: 3,
|
||||
end: 3
|
||||
}, newElement)
|
||||
this.activeParagraph = {
|
||||
id: newElement.id,
|
||||
paragraph: newElement
|
||||
}
|
||||
eventCenter.dispatch('paragraphChange', newElement, preParagraph)
|
||||
event.preventDefault()
|
||||
}
|
||||
const handler = event => {
|
||||
switch (event.type) {
|
||||
case 'click': {
|
||||
const target = event.target
|
||||
if (target.tagName.toLowerCase() === LOWERCASE_TAGS.hr) {
|
||||
changeHr2P(event, target, this.activeParagraph.paragraph)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'keydown': {
|
||||
const node = selection.getSelectionStart()
|
||||
const outmostParagraph = findOutMostParagraph(node)
|
||||
const preSibling = outmostParagraph.previousElementSibling
|
||||
const nextSibling = outmostParagraph.nextElementSibling
|
||||
if (
|
||||
event.key === EVENT_KEYS.ArrowUp &&
|
||||
preSibling &&
|
||||
preSibling.tagName.toLowerCase() === LOWERCASE_TAGS.hr
|
||||
) {
|
||||
changeHr2P(event, preSibling, outmostParagraph)
|
||||
}
|
||||
if (
|
||||
event.key === EVENT_KEYS.ArrowDown &&
|
||||
nextSibling &&
|
||||
nextSibling.tagName.toLowerCase() === LOWERCASE_TAGS.hr
|
||||
) {
|
||||
changeHr2P(event, nextSibling, outmostParagraph)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
eventCenter.attachDOMEvent(container, 'keydown', changeHandler)
|
||||
}
|
||||
|
||||
subscribeArrow () {
|
||||
// switch (event.key) {
|
||||
// case EVENT_KEYS.ArrowUp:
|
||||
// if (this.activeEmojiItem > 0) {
|
||||
// event.preventDefault()
|
||||
// this.activeEmojiItem = this.activeEmojiItem - 1
|
||||
// if (this.emojiList) {
|
||||
// this.emoji.box.setOptions(this.emojiList, this.activeEmojiItem)
|
||||
// }
|
||||
// }
|
||||
// break
|
||||
// case EVENT_KEYS.ArrowDown:
|
||||
// console.log(this.activeEmojiItem, this.emojiList.length)
|
||||
// if (this.activeEmojiItem < this.emojiList.length - 1) {
|
||||
// event.preventDefault()
|
||||
// this.activeEmojiItem = this.activeEmojiItem + 1
|
||||
// if (this.emojiList) {
|
||||
// this.emoji.box.setOptions(this.emojiList, this.activeEmojiItem)
|
||||
// }
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
// TODO
|
||||
eventCenter.attachDOMEvent(container, 'keydown', handler)
|
||||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||||
}
|
||||
|
||||
dispatchParagraphChange () {
|
||||
@ -333,16 +352,19 @@ class Aganippe {
|
||||
break
|
||||
}
|
||||
case LOWERCASE_TAGS.hr: {
|
||||
updateBlock(oldParagraph, LOWERCASE_TAGS.hr)
|
||||
oldParagraph = updateBlock(oldParagraph, LOWERCASE_TAGS.hr)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// set and remove active className
|
||||
operateClassName(oldParagraph, 'remove', CLASS_OR_ID['AG_ACTIVE'])
|
||||
operateClassName(newParagraph, 'add', CLASS_OR_ID['AG_ACTIVE'])
|
||||
oldParagraph.innerHTML = markedText2Html(oldParagraph.textContent)
|
||||
if (oldContext) {
|
||||
oldParagraph.innerHTML = markedText2Html(oldParagraph.textContent)
|
||||
}
|
||||
}
|
||||
console.log(newParagraph, oldParagraph)
|
||||
// set and remove active className
|
||||
operateClassName(oldParagraph, 'remove', CLASS_OR_ID['AG_ACTIVE'])
|
||||
operateClassName(newParagraph, 'add', CLASS_OR_ID['AG_ACTIVE'])
|
||||
}
|
||||
|
||||
enterKeyHandler (event) {
|
||||
|
@ -14,19 +14,20 @@ import {
|
||||
*/
|
||||
|
||||
const fragments = [
|
||||
'^#{1,6}', // Header
|
||||
'^#{1,6}[^#]?', // Header
|
||||
'(\\*{1,3}|_{1,3})[^*_]+\\1', // Emphasize
|
||||
'(`{1,3})([^`]+?|.{2,})\\2', // inline code
|
||||
'\\[[^\\[\\]]+\\]\\(.*?\\)', // link
|
||||
'\\[\\]\\([^\\(\\)]*?\\)', // no text link
|
||||
':[^:]+?:', // emoji
|
||||
'~{2}[^~]+~{2}', // line through
|
||||
'https?://[^\\s]+(?=\\s|$)' // auto link
|
||||
'https?://[^\\s]+(?=\\s|$)', // auto link
|
||||
'^\\*{3,}|^\\-{3,}|^\\_{3,}' // hr
|
||||
]
|
||||
|
||||
const CHOP_REG = new RegExp(fragments.join('|'), 'g') // eslint-disable-line no-useless-escape
|
||||
const HEAD_REG_G = /^(#{1,6})([^#]*)$/g
|
||||
const HEAD_REG = /^(#{1,6})([^#]*)$/
|
||||
const HEAD_REG = /^#{1,6}[^#]*$/
|
||||
const EMPHASIZE_REG_G = /(\*{1,3}|_{1,3})([^*]+)(\1)/g
|
||||
const EMPHASIZE_REG = /(\*{1,3}|_{1,3})([^*]+)(\1)/
|
||||
const INLINE_CODE_REG_G = /(`{1,3})([^`]+?|.{2,})(\1)/g
|
||||
@ -42,6 +43,8 @@ const LINE_THROUGH_REG_G = /(~{2})([^~]+?)(~{2})/g
|
||||
const LINE_THROUGH_REG = /~{2}[^~]+?~{2}/
|
||||
const AUTO_LINK_G = /(https?:\/\/[^\\s]+)(?=\s|$)/g
|
||||
const AUTO_LINK = /https?:\/\/[^\s]+(?=\s|$)/
|
||||
const HR_REG_G = /(^\*{3,}|^-{3,}|^_{3,})/g
|
||||
const HR_REG = /^\*{3,}|^-{3,}|^_{3,}/
|
||||
// const SIMPLE_LINK_G = /(<)([^<>]+?)(>)/g
|
||||
// const SIMPLE_LINK = /<[^<>]+?>/g
|
||||
const LINE_BREAK_BLOCK_REG = /^(?:`{3,}([^`]*))|[\*\-\_]{3,}/ // eslint-disable-line no-useless-escape
|
||||
@ -56,6 +59,8 @@ const conflict = (arr1, arr2) => {
|
||||
}
|
||||
|
||||
const chunk2html = ({ chunk, index, lastIndex }, { start, end } = {}) => {
|
||||
// `###h` should be corrected to `###` to judge the confliction.
|
||||
if (/^#{1,6}[^#]/.test(chunk)) lastIndex = lastIndex - 1
|
||||
// if no positionState provided, no conflict.
|
||||
const isConflicted = start !== undefined && end !== undefined
|
||||
? conflict([index, lastIndex], [start, end])
|
||||
@ -65,7 +70,11 @@ const chunk2html = ({ chunk, index, lastIndex }, { start, end } = {}) => {
|
||||
// handle head mark symble
|
||||
if (HEAD_REG.test(chunk)) {
|
||||
return chunk.replace(HEAD_REG_G, (match, p1, p2) => {
|
||||
return `<a href="#" class="${className}">${p1}</a>${p2}`
|
||||
if (p2) {
|
||||
return `<a href="#" class="${className}">${p1}</a>${p2}`
|
||||
} else {
|
||||
return `<a href="#" class="${CLASS_OR_ID['AG_GRAY']}">${p1}</a>${p2}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -182,6 +191,12 @@ const chunk2html = ({ chunk, index, lastIndex }, { start, end } = {}) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (HR_REG.test(chunk)) {
|
||||
return chunk.replace(HR_REG_G, (match, p1) => {
|
||||
return `<a href="#" class="${CLASS_OR_ID['AG_GRAY']}">${p1}</a>`
|
||||
})
|
||||
}
|
||||
|
||||
// handle picture
|
||||
// TODO
|
||||
// handle auto link: markdown text: `<this is a auto link>`
|
||||
@ -227,7 +242,7 @@ export const markedText2Html = (markedText, positionState) => {
|
||||
result = result.replace(c.chunk, c.html)
|
||||
})
|
||||
}
|
||||
|
||||
console.log(chunks)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -316,7 +331,6 @@ export const checkBackspaceCase = (startNode, selection) => {
|
||||
}
|
||||
if (parTagName === LOWERCASE_TAGS.blockquote && inLeft === 0) {
|
||||
if (isOnlyChildElement(nearestParagraph)) {
|
||||
console.log('xx')
|
||||
return { type: 'BLOCKQUOTE', info: 'REPLACEMENT' }
|
||||
} else if (isFirstChildElement(nearestParagraph)) {
|
||||
return { type: 'BLOCKQUOTE', info: 'INSERT_BEFORE' }
|
||||
@ -335,7 +349,7 @@ export const checkLineBreakUpdate = text => {
|
||||
switch (true) {
|
||||
case /^`{3,}.*/.test(match):
|
||||
return { type: 'pre', info: token[1] }
|
||||
case /^[*_-]{3,}/.test(match):
|
||||
case HR_REG.test(match):
|
||||
return { type: 'hr' }
|
||||
default:
|
||||
return false
|
||||
|
@ -347,7 +347,7 @@ export const updateBlock = (origin, tagName) => {
|
||||
const json = html2json(origin.outerHTML)
|
||||
|
||||
json.child[0].tag = tagName
|
||||
if (/^h/.test(tagName)) {
|
||||
if (/^h\d$/.test(tagName)) {
|
||||
json.child[0].attr['data-head-level'] = tagName
|
||||
} else if (json.child[0].attr['data-head-level']) {
|
||||
delete json.child[0].attr['data-head-level']
|
||||
|
Loading…
Reference in New Issue
Block a user