diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index af5d2e44..6d9fadb1 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -56,6 +56,7 @@ describe("Course Creation", () => { .parent() .within(() => { cy.get("input").click().type("frappe"); + cy.wait(500); cy.get("input") .invoke("attr", "aria-controls") .as("instructor_list_id"); diff --git a/frontend/src/utils/markdownParser.js b/frontend/src/utils/markdownParser.js index f4a335a1..a5b10ae7 100644 --- a/frontend/src/utils/markdownParser.js +++ b/frontend/src/utils/markdownParser.js @@ -6,8 +6,8 @@ export class Markdown { this.api = api this.data = data || {} this.config = config || {} - this.text = data.text || '' this.readOnly = readOnly + this.text = data.text || '' this.placeholder = __("Type '/' for commands or select text to format") } @@ -30,65 +30,28 @@ export class Markdown { const div = document.createElement('div') app.mount(div) - return { - title: '', - icon: div.innerHTML, - } - } - - onPaste(event) { - const data = { - text: event.detail.data.innerHTML, - } - - this.data = data - window.requestAnimationFrame(() => { - if (!this.wrapper) { - return - } - this.wrapper.innerHTML = this.data.text || '' - }) + return { title: '', icon: div.innerHTML } } static get pasteConfig() { - return { - tags: ['P'], - } + return { tags: ['P'] } } render() { this.wrapper = document.createElement('div') this.wrapper.classList.add('cdx-block', 'ce-paragraph') + this.wrapper.contentEditable = !this.readOnly + this.wrapper.dataset.placeholder = this.placeholder this.wrapper.innerHTML = this.text if (!this.readOnly) { - this.wrapper.contentEditable = true - this.wrapper.innerHTML = this.text - this.wrapper.addEventListener('focus', () => this._togglePlaceholder() ) this.wrapper.addEventListener('blur', () => this._togglePlaceholder() ) - - this.wrapper.addEventListener('input', (event) => { - this._togglePlaceholder() - let value = event.target.textContent - if (event.keyCode === 32 && value.startsWith('#')) { - this.convertToHeader(event, value) - } else if (event.keyCode == 189) { - this.convertBlock('list', { - style: 'unordered', - }) - } else if (/^[a-zA-Z]/.test(event.key)) { - this.convertBlock('paragraph', { - text: value, - }) - } else if (event.keyCode === 13 || event.keyCode === 190) { - this.parseContent(event) - } - }) + this.wrapper.addEventListener('keydown', (e) => this._onKeyDown(e)) } return this.wrapper @@ -99,10 +62,9 @@ export class Markdown { '.cdx-block.ce-paragraph[data-placeholder]' ) blocks.forEach((block) => { - if (block !== this.wrapper) { - delete block.dataset.placeholder - } + if (block !== this.wrapper) delete block.dataset.placeholder }) + if (this.wrapper.innerHTML.trim() === '') { this.wrapper.dataset.placeholder = this.placeholder } else { @@ -110,102 +72,107 @@ export class Markdown { } } - convertToHeader(event, value) { - event.preventDefault() - if (['#', '##', '###', '####', '#####', '######'].includes(value)) { - let level = value.length - event.target.textContent = '' - this.convertBlock('header', { - level: level, - }) - } - } + _onKeyDown(event) { + const text = this.wrapper.textContent - parseContent(event) { - event.preventDefault() - let previousLine = this.wrapper.textContent - if (event.keyCode === 190) { - previousLine = previousLine + '.' - } - - if (previousLine && this.hasImage(previousLine)) { + if (event.key === ' ' && /^#{1,6}$/.test(text)) { + event.preventDefault() + const level = text.length this.wrapper.textContent = '' - this.convertBlock('image') - } else if (previousLine && this.hasLink(previousLine)) { - const { text, url } = this.extractLink(previousLine) - const anchorTag = `${text}` - this.convertBlock('paragraph', { - text: previousLine.replace(/\[.+?\]\(.+?\)/, anchorTag), - }) - } else if (previousLine && previousLine.startsWith('- ')) { - this.convertBlock('list', { + this._convertBlock('header', { level }) + } else if (event.key === ' ' && text === '-') { + event.preventDefault() + this.wrapper.textContent = '' + this._convertBlock('list', { style: 'unordered', - items: [ - { - content: previousLine.replace('- ', ''), - }, - ], + items: [{ content: '' }], }) - } else if (previousLine && previousLine.startsWith('1.')) { - this.convertBlock('list', { - style: 'ordered', - items: [ - { - content: previousLine.replace('1.', ''), - }, - ], - }) - } else if (previousLine && this.canBeEmbed(previousLine)) { + } else if (event.key === ' ' && /^1\.$/.test(text)) { + event.preventDefault() this.wrapper.textContent = '' - this.convertBlock('embed', { - source: previousLine, + this._convertBlock('list', { + style: 'ordered', + items: [{ content: '' }], }) - } else { - this.convertBlock('paragraph', { - text: previousLine, + } else if (this._isEmbed(text) && event.key === 'Enter') { + event.preventDefault() + this.wrapper.textContent = '' + this._convertBlock('embed', { source: text }) + } else if (event.key === 'Enter') { + setTimeout(() => this._checkMarkdownAfterEnter(), 0) + } + } + + _checkMarkdownAfterEnter() { + const text = this.wrapper.textContent.trim() + + if (this._isImage(text)) { + this._convertBlock('image', { + file: { url: this._extractImage(text).url }, }) } } - async convertBlock(type, data, index = null) { + async _convertBlock(type, data) { const currentIndex = this.api.blocks.getCurrentBlockIndex() const currentBlock = this.api.blocks.getBlockByIndex(currentIndex) + + if (!currentBlock) return + await this.api.blocks.convert(currentBlock.id, type, data) - this.api.caret.focus(true) + + setTimeout(() => { + const newIndex = this.api.blocks.getCurrentBlockIndex() + const newBlock = this.api.blocks.getBlockByIndex(newIndex) + + if (newBlock && newBlock.holder) { + const holder = newBlock.holder.querySelector( + '[contenteditable="true"]' + ) + if (holder) { + holder.focus() + // Place caret at end + const range = document.createRange() + range.selectNodeContents(holder) + range.collapse(false) + const sel = window.getSelection() + sel.removeAllRanges() + sel.addRange(range) + } else { + this.api.caret.focus(true) + } + } else { + this.api.caret.focus(true) + } + }, 0) } save(blockContent) { - return { - text: blockContent.innerHTML, - } + return { text: blockContent.innerHTML } } - hasImage(line) { - return /!\[.+?\]\(.+?\)/.test(line) + _isImage(text) { + return /!\[.+?\]\(.+?\)/.test(text) } - extractImage(line) { - const match = line.match(/!\[(.+?)\]\((.+?)\)/) - if (match) { - return { alt: match[1], url: match[2] } - } + _extractImage(text) { + const match = text.match(/!\[(.+?)\]\((.+?)\)/) + if (match) return { alt: match[1], url: match[2] } return { alt: '', url: '' } } - hasLink(line) { - return /\[.+?\]\(.+?\)/.test(line) + _isLink(text) { + return /\[.+?\]\(.+?\)/.test(text) } - extractLink(line) { - const match = line.match(/\[(.+?)\]\((.+?)\)/) - if (match) { - return { text: match[1], url: match[2] } - } + _extractLink(text) { + const match = text.match(/\[(.+?)\]\((.+?)\)/) + if (match) return { text: match[1], url: match[2] } return { text: '', url: '' } } - canBeEmbed(line) { - return /^https?:\/\/.+/.test(line.trim()) + _isEmbed(text) { + return /^https?:\/\/.+/.test(text.trim()) } }