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