From a2d0837eacb36e45b29b4672cc22d841f054af86 Mon Sep 17 00:00:00 2001 From: Jocs Date: Fri, 17 Nov 2017 00:31:47 +0800 Subject: [PATCH] feat: nest list --- src/editor/index.js | 97 ++++++++++++++++++++++++++++----------------- src/editor/utils.js | 28 +++++++++---- 2 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index 8073989c..9797f694 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -1,27 +1,13 @@ import { - updateBlock, - checkLineBreakUpdate, - createEmptyElement, - checkInlineUpdate, - checkMarkedTextUpdate, - isAganippeEditorElement, - findNearestParagraph, - markedText2Html, - operateClassName, - insertBefore, - insertAfter, - removeNode, - isFirstChildElement, - wrapperElementWithTag, - nestElementWithTag, - chopHeader -} from './utils.js' + updateBlock, checkLineBreakUpdate, createEmptyElement, checkInlineUpdate, checkMarkedTextUpdate, + isAganippeEditorElement, findNearestParagraph, markedText2Html, operateClassName, insertBefore, + insertAfter, removeNode, isFirstChildElement, wrapperElementWithTag, nestElementWithTag, chopHeader +} from './utils' import { keys, - activeClassName, - paragraphClassName // eslint-disable-line no-unused-vars -} from './config.js' + activeClassName +} from './config' import Selection from './selection' import Event from './event' @@ -138,7 +124,12 @@ class Aganippe { subscribeEnter (event) { event.preventDefault() 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 preTagName = paragraph.tagName.toLowerCase() const attrs = paragraph.attributes @@ -151,14 +142,19 @@ class Aganippe { tagName = preTagName const { pre, post } = selection.chopHtmlByCursor(paragraph) newParagraph = createEmptyElement(this.ids, tagName, attrs) - paragraph.innerHTML = markedText2Html(pre) - newParagraph.innerHTML = markedText2Html(post, { start: 0, end: 0 }) + if (tagName === 'li') { + paragraph.children[0].innerHTML = markedText2Html(pre) + newParagraph.children[0].innerHTML = markedText2Html(post, { start: 0, end: 0 }) + } else { + paragraph.innerHTML = markedText2Html(pre) + newParagraph.innerHTML = markedText2Html(post, { start: 0, end: 0 }) + } insertAfter(newParagraph, paragraph) selection.moveCursor(newParagraph, 0) return false case left === 0 && right === 0: // paragraph is empty if (isFirstChildElement(paragraph) && preTagName === 'li') { - tagName = 'li' + tagName = preTagName newParagraph = createEmptyElement(this.ids, tagName, attrs) insertAfter(newParagraph, paragraph) selection.moveCursor(newParagraph, 0) @@ -177,7 +173,7 @@ class Aganippe { return false case left !== 0 && right === 0: // cursor at end 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 newParagraph = createEmptyElement(this.ids, tagName, attrs) if (left === 0 && right !== 0) { @@ -215,8 +211,11 @@ class Aganippe { } subscribeElementUpdate (inlineUpdate, selectionState, paragraph) { + const { start, end } = selectionState 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) let newElement if (/^h/.test(inlineUpdate.type)) { @@ -224,23 +223,47 @@ class Aganippe { selection.importSelection(selectionState, newElement) } else if (inlineUpdate.type === 'blockquote') { if (preTagName === 'p') { - const { start, end } = selectionState newElement = updateBlock(paragraph, inlineUpdate.type) - nestElementWithTag(newElement, 'p') - selection.importSelection({ start: start - 1, end: end - 1 }, newElement) // `1` is length of `>` + nestElementWithTag(this.ids, newElement, 'p') + selection.importSelection({ + start: start - chopedLength, + end: end - chopedLength + }, newElement) // `1` is length of `>` } else { // TODO li - const nestElement = nestElementWithTag(paragraph, 'p') - newElement = wrapperElementWithTag(nestElement, 'blockquote') + const nestElement = nestElementWithTag(this.ids, paragraph, 'p') + newElement = wrapperElementWithTag(this.ids, nestElement, 'blockquote') } } else if (inlineUpdate.type === 'li') { switch (inlineUpdate.info) { + case 'order': // fallthrough case 'disorder': - - break - case 'order': + 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 + case 'tasklist': + // TODO break } } @@ -273,7 +296,10 @@ class Aganippe { const changeHandler = event => { const { id: preId, paragraph: preParagraph } = this.activeParagraph 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 if (newId !== preId) { eventCenter.dispatch('paragraphChange', paragraph, preParagraph) @@ -290,7 +316,6 @@ class Aganippe { } subscribeParagraphChange (newParagraph, oldParagraph) { - console.log(newParagraph.id, oldParagraph.id) operateClassName(oldParagraph, 'remove', activeClassName) operateClassName(newParagraph, 'add', activeClassName) oldParagraph.innerHTML = markedText2Html(oldParagraph.textContent) diff --git a/src/editor/utils.js b/src/editor/utils.js index c3c736de..c36b92eb 100644 --- a/src/editor/utils.js +++ b/src/editor/utils.js @@ -17,7 +17,7 @@ const EMPHASIZE_REG_G = /(\*{1,3})([^*]+)(\1)/g const EMPHASIZE_REG = /(\*{1,3})([^*]+)(\1)/ const LINE_BREAK_BLOCK_REG = /^(?:`{3,}(.*))/ 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 /** @@ -233,13 +233,21 @@ export const replaceElement = (origin, alt) => { export const createEmptyElement = (ids, tagName, attrs) => { const id = getUniqueId(ids) const element = document.createElement(tagName) + element.innerHTML = '
' if (attrs) { Array.from(attrs).forEach(attr => { element.setAttribute(attr.name, attr.value) }) } - if (!element.classList.contains(paragraphClassName)) element.classList.add(paragraphClassName) - element.innerHTML = '
' + if (tagName === 'li') { + const pid = getUniqueId(ids) + const p = document.createElement('p') + p.innerHTML = '
' + operateClassName(p, 'add', paragraphClassName) + p.id = pid + element.innerHTML = p.outerHTML + } + operateClassName(element, 'add', paragraphClassName) element.id = id return element } @@ -250,19 +258,25 @@ export const removeNode = node => { } // is firstChildElement 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 id = getUniqueId(ids) + operateClassName(wrapper, 'add', paragraphClassName) + wrapper.id = id wrapper.innerHTML = element.outerHTML replaceElement(element, wrapper) return wrapper } -export const nestElementWithTag = (element, tagName) => { +export const nestElementWithTag = (ids, element, tagName) => { + const id = getUniqueId(ids) const wrapper = document.createElement(tagName) - wrapper.innerHTML = element.innerHTML + operateClassName(wrapper, 'add', paragraphClassName) + wrapper.id = id + wrapper.innerHTML = element.innerHTML || '
' element.innerHTML = wrapper.outerHTML return element }