feat: handle hr edit

This commit is contained in:
Jocs 2017-11-21 15:36:16 +08:00
parent 78bace484f
commit 5aeb8dfa65
4 changed files with 96 additions and 56 deletions

View File

@ -24,6 +24,10 @@ a {
pointer-events: none;
}
hr {
cursor: default;
}
.ag-emoji-box {
position: absolute;
display: none;

View File

@ -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) {

View File

@ -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

View File

@ -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']