mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 17:01:47 +08:00
feat: nest list
This commit is contained in:
parent
5b77a3bbdb
commit
a2d0837eac
@ -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)
|
||||||
paragraph.innerHTML = markedText2Html(pre)
|
if (tagName === 'li') {
|
||||||
newParagraph.innerHTML = markedText2Html(post, { start: 0, end: 0 })
|
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)
|
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)
|
||||||
break
|
newElement = nestElementWithTag(this.ids, newElement, 'p')
|
||||||
case 'order':
|
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 '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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user