feat: nest list

This commit is contained in:
Jocs 2017-11-17 00:31:47 +08:00
parent 5b77a3bbdb
commit a2d0837eac
2 changed files with 82 additions and 43 deletions

View File

@ -1,27 +1,13 @@
import { import {
updateBlock, updateBlock, checkLineBreakUpdate, createEmptyElement, checkInlineUpdate, checkMarkedTextUpdate,
checkLineBreakUpdate, isAganippeEditorElement, findNearestParagraph, markedText2Html, operateClassName, insertBefore,
createEmptyElement, insertAfter, removeNode, isFirstChildElement, wrapperElementWithTag, nestElementWithTag, chopHeader
checkInlineUpdate, } from './utils'
checkMarkedTextUpdate,
isAganippeEditorElement,
findNearestParagraph,
markedText2Html,
operateClassName,
insertBefore,
insertAfter,
removeNode,
isFirstChildElement,
wrapperElementWithTag,
nestElementWithTag,
chopHeader
} from './utils.js'
import { import {
keys, keys,
activeClassName, activeClassName
paragraphClassName // eslint-disable-line no-unused-vars } from './config'
} from './config.js'
import Selection from './selection' import Selection from './selection'
import Event from './event' import Event from './event'
@ -138,7 +124,12 @@ class Aganippe {
subscribeEnter (event) { subscribeEnter (event) {
event.preventDefault() event.preventDefault()
const node = selection.getSelectionStart() const node = selection.getSelectionStart()
const paragraph = findNearestParagraph(node) let paragraph = findNearestParagraph(node)
const parentNode = paragraph.parentNode
const parTagName = parentNode.tagName.toLowerCase()
if (parTagName === 'li' && isFirstChildElement(paragraph)) {
paragraph = parentNode
}
const { left, right } = selection.getCaretOffsets(paragraph) const { left, right } = selection.getCaretOffsets(paragraph)
const preTagName = paragraph.tagName.toLowerCase() const preTagName = paragraph.tagName.toLowerCase()
const attrs = paragraph.attributes const attrs = paragraph.attributes
@ -151,14 +142,19 @@ class Aganippe {
tagName = preTagName tagName = preTagName
const { pre, post } = selection.chopHtmlByCursor(paragraph) const { pre, post } = selection.chopHtmlByCursor(paragraph)
newParagraph = createEmptyElement(this.ids, tagName, attrs) newParagraph = createEmptyElement(this.ids, tagName, attrs)
if (tagName === 'li') {
paragraph.children[0].innerHTML = markedText2Html(pre)
newParagraph.children[0].innerHTML = markedText2Html(post, { start: 0, end: 0 })
} else {
paragraph.innerHTML = markedText2Html(pre) paragraph.innerHTML = markedText2Html(pre)
newParagraph.innerHTML = markedText2Html(post, { start: 0, end: 0 }) newParagraph.innerHTML = markedText2Html(post, { start: 0, end: 0 })
}
insertAfter(newParagraph, paragraph) insertAfter(newParagraph, paragraph)
selection.moveCursor(newParagraph, 0) selection.moveCursor(newParagraph, 0)
return false return false
case left === 0 && right === 0: // paragraph is empty case left === 0 && right === 0: // paragraph is empty
if (isFirstChildElement(paragraph) && preTagName === 'li') { if (isFirstChildElement(paragraph) && preTagName === 'li') {
tagName = 'li' tagName = preTagName
newParagraph = createEmptyElement(this.ids, tagName, attrs) newParagraph = createEmptyElement(this.ids, tagName, attrs)
insertAfter(newParagraph, paragraph) insertAfter(newParagraph, paragraph)
selection.moveCursor(newParagraph, 0) selection.moveCursor(newParagraph, 0)
@ -177,7 +173,7 @@ class Aganippe {
return false return false
case left !== 0 && right === 0: // cursor at end of paragraph case left !== 0 && right === 0: // cursor at end of paragraph
case left === 0 && right !== 0: // cursor at begin of paragraph case left === 0 && right !== 0: // cursor at begin of paragraph
if (preTagName === 'li') tagName = 'li' if (preTagName === 'li') tagName = preTagName
else tagName = 'p' // insert after or before else tagName = 'p' // insert after or before
newParagraph = createEmptyElement(this.ids, tagName, attrs) newParagraph = createEmptyElement(this.ids, tagName, attrs)
if (left === 0 && right !== 0) { if (left === 0 && right !== 0) {
@ -215,8 +211,11 @@ class Aganippe {
} }
subscribeElementUpdate (inlineUpdate, selectionState, paragraph) { subscribeElementUpdate (inlineUpdate, selectionState, paragraph) {
const { start, end } = selectionState
const preTagName = paragraph.tagName.toLowerCase() const preTagName = paragraph.tagName.toLowerCase()
const chopedText = chopHeader(paragraph.textContent) const markedText = paragraph.textContent
const chopedText = chopHeader(markedText)
const chopedLength = markedText.length - chopedText.length
paragraph.innerHTML = markedText2Html(chopedText) paragraph.innerHTML = markedText2Html(chopedText)
let newElement let newElement
if (/^h/.test(inlineUpdate.type)) { if (/^h/.test(inlineUpdate.type)) {
@ -224,23 +223,47 @@ class Aganippe {
selection.importSelection(selectionState, newElement) selection.importSelection(selectionState, newElement)
} else if (inlineUpdate.type === 'blockquote') { } else if (inlineUpdate.type === 'blockquote') {
if (preTagName === 'p') { if (preTagName === 'p') {
const { start, end } = selectionState
newElement = updateBlock(paragraph, inlineUpdate.type) newElement = updateBlock(paragraph, inlineUpdate.type)
nestElementWithTag(newElement, 'p') nestElementWithTag(this.ids, newElement, 'p')
selection.importSelection({ start: start - 1, end: end - 1 }, newElement) // `1` is length of `>` selection.importSelection({
start: start - chopedLength,
end: end - chopedLength
}, newElement) // `1` is length of `>`
} else { } else {
// TODO li // TODO li
const nestElement = nestElementWithTag(paragraph, 'p') const nestElement = nestElementWithTag(this.ids, paragraph, 'p')
newElement = wrapperElementWithTag(nestElement, 'blockquote') newElement = wrapperElementWithTag(this.ids, nestElement, 'blockquote')
} }
} else if (inlineUpdate.type === 'li') { } else if (inlineUpdate.type === 'li') {
switch (inlineUpdate.info) { switch (inlineUpdate.info) {
case 'order': // fallthrough
case 'disorder': case 'disorder':
newElement = updateBlock(paragraph, inlineUpdate.type)
newElement = nestElementWithTag(this.ids, newElement, 'p')
const id = newElement.querySelector('p').id
const altTagName = inlineUpdate.info === 'order' ? 'ol' : 'ul'
const parentNode = newElement.parentNode
const parentTagName = parentNode.tagName.toLowerCase()
const previousElement = newElement.previousElementSibling
const preViousTagName = previousElement && previousElement.tagName.toLowerCase()
if (parentTagName !== altTagName && preViousTagName !== altTagName) {
newElement = wrapperElementWithTag(this.ids, newElement, altTagName)
}
if (preViousTagName === altTagName) {
previousElement.appendChild(newElement)
}
// has bug: cursor postion disorder
const cursorElement = newElement.querySelector(`#${id}`)
console.log(cursorElement, chopedLength, start, end)
selection.importSelection({
start: start - chopedLength,
end: end - chopedLength
}, cursorElement)
break break
case 'order':
break
case 'tasklist': case 'tasklist':
// TODO
break break
} }
} }
@ -273,7 +296,10 @@ class Aganippe {
const changeHandler = event => { const changeHandler = event => {
const { id: preId, paragraph: preParagraph } = this.activeParagraph const { id: preId, paragraph: preParagraph } = this.activeParagraph
const node = selection.getSelectionStart() const node = selection.getSelectionStart()
const paragraph = findNearestParagraph(node) let paragraph = findNearestParagraph(node)
if (paragraph.tagName.toLowerCase() === 'li') {
paragraph = paragraph.children[0]
}
const newId = paragraph.id const newId = paragraph.id
if (newId !== preId) { if (newId !== preId) {
eventCenter.dispatch('paragraphChange', paragraph, preParagraph) eventCenter.dispatch('paragraphChange', paragraph, preParagraph)
@ -290,7 +316,6 @@ class Aganippe {
} }
subscribeParagraphChange (newParagraph, oldParagraph) { subscribeParagraphChange (newParagraph, oldParagraph) {
console.log(newParagraph.id, oldParagraph.id)
operateClassName(oldParagraph, 'remove', activeClassName) operateClassName(oldParagraph, 'remove', activeClassName)
operateClassName(newParagraph, 'add', activeClassName) operateClassName(newParagraph, 'add', activeClassName)
oldParagraph.innerHTML = markedText2Html(oldParagraph.textContent) oldParagraph.innerHTML = markedText2Html(oldParagraph.textContent)

View File

@ -17,7 +17,7 @@ const EMPHASIZE_REG_G = /(\*{1,3})([^*]+)(\1)/g
const EMPHASIZE_REG = /(\*{1,3})([^*]+)(\1)/ const EMPHASIZE_REG = /(\*{1,3})([^*]+)(\1)/
const LINE_BREAK_BLOCK_REG = /^(?:`{3,}(.*))/ const LINE_BREAK_BLOCK_REG = /^(?:`{3,}(.*))/
const INLINE_BLOCK_REG = /^(?:[*+-]\s(\[\s\]\s)?|\d+\.\s|(#{1,6})[^#]+|>.+)/ const INLINE_BLOCK_REG = /^(?:[*+-]\s(\[\s\]\s)?|\d+\.\s|(#{1,6})[^#]+|>.+)/
const CHOP_HEADER_REG = /^([*+-]\s(?:\[\s\]\s)?|>)/ const CHOP_HEADER_REG = /^([*+-]\s(?:\[\s\]\s)?|>|\d+\.\s)/
// help functions // help functions
/** /**
@ -233,13 +233,21 @@ export const replaceElement = (origin, alt) => {
export const createEmptyElement = (ids, tagName, attrs) => { export const createEmptyElement = (ids, tagName, attrs) => {
const id = getUniqueId(ids) const id = getUniqueId(ids)
const element = document.createElement(tagName) const element = document.createElement(tagName)
element.innerHTML = '<br>'
if (attrs) { if (attrs) {
Array.from(attrs).forEach(attr => { Array.from(attrs).forEach(attr => {
element.setAttribute(attr.name, attr.value) element.setAttribute(attr.name, attr.value)
}) })
} }
if (!element.classList.contains(paragraphClassName)) element.classList.add(paragraphClassName) if (tagName === 'li') {
element.innerHTML = '<br>' const pid = getUniqueId(ids)
const p = document.createElement('p')
p.innerHTML = '<br>'
operateClassName(p, 'add', paragraphClassName)
p.id = pid
element.innerHTML = p.outerHTML
}
operateClassName(element, 'add', paragraphClassName)
element.id = id element.id = id
return element return element
} }
@ -250,19 +258,25 @@ export const removeNode = node => {
} }
// is firstChildElement // is firstChildElement
export const isFirstChildElement = node => { export const isFirstChildElement = node => {
return !!node.previousElementSibling return !node.previousElementSibling
} }
export const wrapperElementWithTag = (element, tagName) => { export const wrapperElementWithTag = (ids, element, tagName) => {
const wrapper = document.createElement(tagName) const wrapper = document.createElement(tagName)
const id = getUniqueId(ids)
operateClassName(wrapper, 'add', paragraphClassName)
wrapper.id = id
wrapper.innerHTML = element.outerHTML wrapper.innerHTML = element.outerHTML
replaceElement(element, wrapper) replaceElement(element, wrapper)
return wrapper return wrapper
} }
export const nestElementWithTag = (element, tagName) => { export const nestElementWithTag = (ids, element, tagName) => {
const id = getUniqueId(ids)
const wrapper = document.createElement(tagName) const wrapper = document.createElement(tagName)
wrapper.innerHTML = element.innerHTML operateClassName(wrapper, 'add', paragraphClassName)
wrapper.id = id
wrapper.innerHTML = element.innerHTML || '<br>'
element.innerHTML = wrapper.outerHTML element.innerHTML = wrapper.outerHTML
return element return element
} }