diff --git a/src/editor/index.css b/src/editor/index.css
index 8cf160cb..d10c51f3 100644
--- a/src/editor/index.css
+++ b/src/editor/index.css
@@ -24,6 +24,10 @@ a {
pointer-events: none;
}
+hr {
+ cursor: default;
+}
+
.ag-emoji-box {
position: absolute;
display: none;
diff --git a/src/editor/index.js b/src/editor/index.js
index 0b9e96e3..850ecae9 100644
--- a/src/editor/index.js
+++ b/src/editor/index.js
@@ -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) {
diff --git a/src/editor/syntax/index.js b/src/editor/syntax/index.js
index 5845ef0e..e8e43911 100644
--- a/src/editor/syntax/index.js
+++ b/src/editor/syntax/index.js
@@ -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 `${p1}${p2}`
+ if (p2) {
+ return `${p1}${p2}`
+ } else {
+ return `${p1}${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 `${p1}`
+ })
+ }
+
// handle picture
// TODO
// handle auto link: markdown text: ``
@@ -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
diff --git a/src/editor/utils/domManipulate.js b/src/editor/utils/domManipulate.js
index a7aa4d9e..8b1b5d49 100644
--- a/src/editor/utils/domManipulate.js
+++ b/src/editor/utils/domManipulate.js
@@ -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']