From a9d570c6864d8ecf9ddef09687dfcd1498988ea9 Mon Sep 17 00:00:00 2001 From: Jocs Date: Sat, 9 Dec 2017 01:47:30 +0800 Subject: [PATCH] image click --- TODO.md | 3 +- src/editor/contentState/enterCtrl.js | 2 +- src/editor/index.js | 22 +- src/editor/oldindex.js | 354 --------------------------- src/editor/utils/domManipulate.js | 6 +- 5 files changed, 25 insertions(+), 362 deletions(-) delete mode 100644 src/editor/oldindex.js diff --git a/TODO.md b/TODO.md index 96eb85af..cb16ad84 100644 --- a/TODO.md +++ b/TODO.md @@ -66,4 +66,5 @@ Bugs: * [ ] 处理嵌套在 item 中的 codeblock 的方向键。 * [ ] codeblock 中光标在 begining 的时候,向上箭头失效。 -* [ ] codeblock 中 language input 输入,enter 后,codeblock 没有自动获取焦点。 \ No newline at end of file +* [ ] codeblock 中 language input 输入,enter 后,codeblock 没有自动获取焦点。 +* [ ] 图片段落下面有新的段落,编辑跳动现象。 \ No newline at end of file diff --git a/src/editor/contentState/enterCtrl.js b/src/editor/contentState/enterCtrl.js index b22402bf..72eac7ca 100644 --- a/src/editor/contentState/enterCtrl.js +++ b/src/editor/contentState/enterCtrl.js @@ -81,7 +81,7 @@ const enterCtrl = ContentState => { } this.removeBlock(block) - } else if (parent.type === 'li') { + } else if (parent && parent.type === 'li') { newBlock = this.createBlockLi() this.insertAfter(newBlock, parent) const index = this.findIndex(parent.children, block) diff --git a/src/editor/index.js b/src/editor/index.js index 6e7bd9d9..f93ef445 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -7,7 +7,7 @@ import { search } from './codeMirror' import { checkEditLanguage, replaceLanguage } from './codeMirror/language' import Emoji, { checkEditEmoji, setInlineEmoji } from './emojis' import floatBox from './floatBox' -import { findNearestParagraph } from './utils/domManipulate' +import { findNearestParagraph, operateClassName } from './utils/domManipulate' class Aganippe { constructor (container, options) { @@ -42,6 +42,7 @@ class Aganippe { // if you dont click the keyboard after 1 second, the garbageCollection will run. eventCenter.attachDOMEvent(container, 'keydown', debounce(() => this.contentState.garbageCollection(), 1000)) + this.imageClick() this.dispatchArrow() this.dispatchBackspace() this.dispatchEnter() @@ -231,6 +232,25 @@ class Aganippe { eventCenter.attachDOMEvent(container, 'keyup', changeHandler) } + imageClick () { + const { container, eventCenter } = this + const handler = event => { + const target = event.target + const markedImageText = target.previousElementSibling + if (markedImageText && markedImageText.classList.contains(CLASS_OR_ID['AG_IMAGE_MARKED_TEXT'])) { + const textLen = markedImageText.textContent.length + operateClassName(markedImageText, 'remove', CLASS_OR_ID['AG_HIDE']) + operateClassName(markedImageText, 'add', CLASS_OR_ID['AG_GRAY']) + selection.importSelection({ + start: textLen, + end: textLen + }, markedImageText) + } + } + + eventCenter.attachDOMEvent(container, 'click', handler) + } + destroy () { this.eventCenter.detachAllDomEvents() this.emoji.clear() // clear emoji cache for memory recycle diff --git a/src/editor/oldindex.js b/src/editor/oldindex.js deleted file mode 100644 index 682871c1..00000000 --- a/src/editor/oldindex.js +++ /dev/null @@ -1,354 +0,0 @@ -import { - updateBlock, createEmptyElement, findNearestParagraph, - operateClassName, insertBefore, insertAfter, removeNode, isFirstChildElement, - wrapperElementWithTag, nestElementWithTag, isOnlyChildElement, isLastChildElement, - chopBlockQuote, removeAndInsertBefore, removeAndInsertPreList, replaceElement, - replacementLists, insertBeforeBlockQuote, isAganippeEditorElement, - findOutMostParagraph, createInputInCodeBlock, isCodeBlockParagraph, hr2P -} from './utils/domManipulate' - -import codeMirror, { setMode, search, setCursorAtLastLine, - isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, // eslint-disable-line no-unused-vars - isCursorAtEnd, setCursorAtFirstLine, onlyHaveOneLine // eslint-disable-line no-unused-vars -} from './codeMirror' - -import FloatBox from './floatBox' - -import { - checkInlineUpdate, checkMarkedTextUpdate, markedText2Html, checkLineBreakUpdate, - chopHeader, checkEditEmoji, setInlineEmoji, checkBackspaceCase, checkEditLanguage, - replaceLanguage -} from './syntax' - -import { - throttle, - debounce -} from './utils' - -import { - CLASS_OR_ID, LOWERCASE_TAGS, EVENT_KEYS, codeMirrorConfig -} from './config' - -import Selection from './selection' -import Event from './event' -import Emoji from './emojis' - -const selection = new Selection(document) - -class Aganippe { - constructor (container, options) { - this.container = container - this.activeParagraph = null - this.ids = new Set() // use to store element's id - this.codeBlocks = new Map() - this.eventCenter = new Event() - this.emoji = new Emoji(this.eventCenter) // emoji instance: has search(text) clear() methods. - this.floatBox = new FloatBox(this.eventCenter) - this.init() - } - - init () { - this.ensureContainerDiv() - const { container, eventCenter } = this - - container.setAttribute('contenteditable', true) - container.setAttribute(CLASS_OR_ID['AG_EDITOR_ATTR'], true) - container.classList.add(CLASS_OR_ID['mousetrap']) // for use of mousetrap - container.id = CLASS_OR_ID['AG_EDITOR_ID'] - - // listen to customEvent `markedTextChange` event, and change markedText to html. - eventCenter.subscribe('markedTextChange', this.subscribeMarkedText.bind(this)) - this.dispatchMarkedText() - - eventCenter.subscribe('editEmoji', throttle(this.subscribeEditEmoji.bind(this), 200)) - this.dispatchEditeEmoji() - - eventCenter.subscribe('paragraphChange', this.subscribeParagraphChange.bind(this)) - this.dispatchParagraphChange() - - eventCenter.subscribe('elementUpdate', this.subscribeElementUpdate.bind(this)) - this.dispatchElementUpdate() - eventCenter.subscribe('editLanguage', throttle(this.subscribeEditLanguage.bind(this))) - this.dispatchEditLanguage() - - eventCenter.subscribe('hideFloatBox', this.subscribeHideFloatBox.bind(this)) - this.dispatchHideFloatBox() - - eventCenter.bind('enter', this.enterKeyHandler.bind(this)) - - eventCenter.subscribe('backspace', this.backspaceHandler.bind(this)) - this.dispatchBackspace() - - eventCenter.subscribe('arrow', this.arrowHander.bind(this)) - this.dispatchArrow() - // if you dont click the keyboard after 1 second, the garbageCollection will run. - eventCenter.attachDOMEvent(container, 'keydown', debounce(this.garbageCollection.bind(this), 1000)) - - this.handlerSelectHr() - this.imageClick() - this.generateLastEmptyParagraph() - } - - dispatchParagraphChange () { - const { container, eventCenter } = this - - const changeHandler = event => { - const { id: preId, paragraph: preParagraph } = this.activeParagraph - let node = selection.getSelectionStart() - if (isAganippeEditorElement(node)) { - event.preventDefault() - return false - } - // handle click img - if (event.type === 'click') { - const target = event.target - if (target.tagName.toLowerCase() === LOWERCASE_TAGS.img) { - node = target - } - } - let paragraph = findNearestParagraph(node) - if (paragraph.tagName.toLowerCase() === LOWERCASE_TAGS.li) { - paragraph = paragraph.children[0] - } - - const id = paragraph.id - if (id !== preId) { - const autoFocus = event.key && event.key === EVENT_KEYS.Enter - eventCenter.dispatch('paragraphChange', paragraph, preParagraph, autoFocus) - } - } - - eventCenter.attachDOMEvent(container, 'click', changeHandler) - eventCenter.attachDOMEvent(container, 'keyup', changeHandler) - } - // newParagrpha and oldParagraph must be h1~6\p\pre element. can not be `li` or `blockquote` - subscribeParagraphChange (newParagraph, oldParagraph, autofocus) { - const { eventCenter, ids } = this - const oldContext = oldParagraph.textContent - const oldTagName = oldParagraph.tagName.toLowerCase() - const lineBreakUpdate = checkLineBreakUpdate(oldContext) - - if (oldParagraph.classList.contains(CLASS_OR_ID['AG_TEMP'])) { - if (!oldContext) { - removeNode(oldParagraph) - } else { - operateClassName(oldParagraph, 'remove', CLASS_OR_ID['AG_TEMP']) - } - } else if (lineBreakUpdate && oldTagName !== lineBreakUpdate.type) { - switch (lineBreakUpdate.type) { - case LOWERCASE_TAGS.pre: { - // exchange of newParagraph and oldParagraph - const codeMirrorWrapper = updateBlock(oldParagraph, lineBreakUpdate.type) - operateClassName(codeMirrorWrapper, 'add', CLASS_OR_ID['AG_CODE_BLOCK']) - - codeMirrorWrapper.innerHTML = '' - const config = Object.assign(codeMirrorConfig, { - autofocus - }) - const codeBlock = codeMirror(codeMirrorWrapper, config) - const input = createInputInCodeBlock(codeMirrorWrapper) - - const handler = langMode => { - const { mode } = langMode - setMode(codeBlock, mode) - .then(mode => { - codeMirrorWrapper.setAttribute('lang', mode.name) - input.value = mode.name - input.blur() - setCursorAtLastLine(codeBlock) - }) - .catch(err => { - console.warn(err) - }) - this.floatBox.hideIfNeeded() - } - - handler({mode: lineBreakUpdate.info}) - - eventCenter.attachDOMEvent(input, 'keyup', () => { - const value = input.value - eventCenter.dispatch('editLanguage', input, value.trim(), handler) - }) - - if (!isLastChildElement(newParagraph) && autofocus) { - removeNode(newParagraph) - } - if (autofocus) { - operateClassName(codeMirrorWrapper, 'add', CLASS_OR_ID['AG_ACTIVE']) - this.activeParagraph = { - id: codeMirrorWrapper.id, - paragraph: codeMirrorWrapper - } - } - this.codeBlocks.set(codeMirrorWrapper.id, codeBlock) - return false - } - case LOWERCASE_TAGS.hr: { - oldParagraph = updateBlock(oldParagraph, LOWERCASE_TAGS.hr) - break - } - } - } else { - if (oldContext && oldTagName !== LOWERCASE_TAGS.pre) { - oldParagraph.innerHTML = markedText2Html(ids, oldParagraph.textContent) - } - } - // set and remove active className - if (oldParagraph) { - operateClassName(oldParagraph, 'remove', CLASS_OR_ID['AG_ACTIVE']) - } - if (newParagraph) { - operateClassName(newParagraph, 'add', CLASS_OR_ID['AG_ACTIVE']) - this.activeParagraph = { - id: newParagraph.id, - paragraph: newParagraph - } - } - } - - // dispach arrow event - dispatchArrow () { - const { container, eventCenter } = this - const handler = event => { - switch (event.key) { - case EVENT_KEYS.ArrowUp: // fallthrough - case EVENT_KEYS.ArrowDown: // fallthrough - case EVENT_KEYS.ArrowLeft: // fallthrough - case EVENT_KEYS.ArrowRight: // fallthrough - eventCenter.dispatch('arrow', event) - break - } - } - eventCenter.attachDOMEvent(container, 'keydown', handler) - } - - arrowHander (event) { - // when the float box is show, use up and down to select item. - const { list, index, show } = this.floatBox - const node = selection.getSelectionStart() - const paragraph = findNearestParagraph(node) - console.log(node) - const outMostParagraph = findOutMostParagraph(node) - const { left, right } = selection.getCaretOffsets(paragraph) - let preParagraph = outMostParagraph.previousElementSibling - let nextParagraph = outMostParagraph.nextElementSibling - if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) { - event.preventDefault() - switch (event.key) { - case EVENT_KEYS.ArrowDown: - if (index < list.length - 1) { - this.floatBox.setOptions(list, index + 1) - } - break - case EVENT_KEYS.ArrowUp: - if (index > 0) { - this.floatBox.setOptions(list, index - 1) - } - break - } - } else if (isCodeBlockParagraph(paragraph)) { - // handle cursor in code block. the case at firstline or lastline. - const codeBlockId = paragraph.id - const cm = this.codeBlocks.get(codeBlockId) - - event.preventDefault() - switch (event.key) { - case EVENT_KEYS.ArrowLeft: // fallthrough - case EVENT_KEYS.ArrowUp: - if ( - (event.key === EVENT_KEYS.ArrowUp && isCursorAtFirstLine(cm) && preParagraph) || - (event.key === EVENT_KEYS.ArrowLeft && isCursorAtBegin(cm) && preParagraph) - ) { - if (isCodeBlockParagraph(preParagraph)) { - const newParagraph = createEmptyElement(this.ids, LOWERCASE_TAGS.p) - operateClassName(newParagraph, 'add', CLASS_OR_ID['AG_TEMP']) - insertAfter(newParagraph, preParagraph) - preParagraph = newParagraph - } - - if (preParagraph.tagName.toLowerCase() === LOWERCASE_TAGS.hr) { - hr2P(preParagraph, selection) - } else { - selection.importSelection({ - start: preParagraph.textContent.length, - end: preParagraph.textContent.length - }, preParagraph) - } - } - break - case EVENT_KEYS.ArrowRight: // fallthrough - case EVENT_KEYS.ArrowDown: - if ( - (event.key === EVENT_KEYS.ArrowDown && isCursorAtLastLine(cm) && nextParagraph) || - (event.key === EVENT_KEYS.ArrowRight && isCursorAtEnd(cm) && nextParagraph) - ) { - if (isCodeBlockParagraph(nextParagraph)) { - const newParagraph = createEmptyElement(this.ids, LOWERCASE_TAGS.p) - operateClassName(newParagraph, 'add', CLASS_OR_ID['AG_TEMP']) - insertBefore(newParagraph, nextParagraph) - nextParagraph = newParagraph - } - if (nextParagraph.tagName.toLowerCase() === LOWERCASE_TAGS.hr) { - hr2P(nextParagraph, selection) - } else { - selection.importSelection({ - start: 0, - end: 0 - }, nextParagraph) - } - } else if (!nextParagraph) { - const newParagraph = createEmptyElement(this.ids, LOWERCASE_TAGS.p) - insertAfter(newParagraph, paragraph) - selection.moveCursor(newParagraph, 0) - } - break - } - } else if ( - (isCodeBlockParagraph(preParagraph) && event.key === EVENT_KEYS.ArrowUp) || - (isCodeBlockParagraph(preParagraph) && event.key === EVENT_KEYS.ArrowLeft && left === 0) - ) { - event.preventDefault() - const codeBlockId = preParagraph.id - const cm = this.codeBlocks.get(codeBlockId) - return setCursorAtLastLine(cm) - } else if ( - (isCodeBlockParagraph(nextParagraph) && event.key === EVENT_KEYS.ArrowDown) || - (isCodeBlockParagraph(nextParagraph) && event.key === EVENT_KEYS.ArrowRight && right === 0) - ) { - event.preventDefault() - const codeBlockId = nextParagraph.id - const cm = this.codeBlocks.get(codeBlockId) - return setCursorAtFirstLine(cm) - } - } - - imageClick () { - const { container, eventCenter } = this - const handler = event => { - const target = event.target - const markedImageText = target.previousElementSibling - if (markedImageText && markedImageText.classList.contains(CLASS_OR_ID['AG_IMAGE_MARKED_TEXT'])) { - const textLen = markedImageText.textContent.length - operateClassName(markedImageText, 'remove', CLASS_OR_ID['AG_HIDE']) - operateClassName(markedImageText, 'add', CLASS_OR_ID['AG_GRAY']) - selection.importSelection({ - start: textLen, - end: textLen - }, markedImageText) - } - } - - eventCenter.attachDOMEvent(container, 'click', handler) - } - - - getMarkdown () { - // TODO - } - getHtml () { - // TODO - } - -} - -export default Aganippe diff --git a/src/editor/utils/domManipulate.js b/src/editor/utils/domManipulate.js index 147eb0f0..0e573353 100644 --- a/src/editor/utils/domManipulate.js +++ b/src/editor/utils/domManipulate.js @@ -105,7 +105,7 @@ export const findPreviousSibling = node => { return false } - var previousSibling = node.previousSibling + let previousSibling = node.previousSibling while (!previousSibling && !isAganippeEditorElement(node.parentNode)) { node = node.parentNode previousSibling = node.previousSibling @@ -159,10 +159,6 @@ export const createInputInCodeBlock = codeEle => { return input } -export const isCodeBlockParagraph = paragraph => { - return paragraph && paragraph.classList.contains(CLASS_OR_ID['AG_CODE_BLOCK']) -} - // DOM operations export const insertAfter = (newNode, originNode) => { const parentNode = originNode.parentNode