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; pointer-events: none;
} }
hr {
cursor: default;
}
.ag-emoji-box { .ag-emoji-box {
position: absolute; position: absolute;
display: none; display: none;

View File

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

View File

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

View File

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