X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/c81cb6f2afd79c33104671ea31f4b0a0e9f85eba..refs/heads/development:/resources/js/markdown/actions.js diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js deleted file mode 100644 index 9faf43de3..000000000 --- a/resources/js/markdown/actions.js +++ /dev/null @@ -1,458 +0,0 @@ -import DrawIO from "../services/drawio"; - -export class Actions { - /** - * @param {MarkdownEditor} editor - */ - constructor(editor) { - this.editor = editor; - this.lastContent = { - html: '', - markdown: '', - }; - } - - updateAndRender() { - const content = this.editor.cm.getValue(); - this.editor.config.inputEl.value = content; - - const html = this.editor.markdown.render(content); - window.$events.emit('editor-html-change', ''); - window.$events.emit('editor-markdown-change', ''); - this.lastContent.html = html; - this.lastContent.markdown = content; - this.editor.display.patchWithHtml(html); - } - - getContent() { - return this.lastContent; - } - - insertImage() { - const cursorPos = this.editor.cm.getCursor('from'); - /** @type {ImageManager} **/ - const imageManager = window.$components.first('image-manager'); - imageManager.show(image => { - const imageUrl = image.thumbs.display || image.url; - let selectedText = this.editor.cm.getSelection(); - let newText = "[![" + (selectedText || image.name) + "](" + imageUrl + ")](" + image.url + ")"; - this.editor.cm.focus(); - this.editor.cm.replaceSelection(newText); - this.editor.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); - }, 'gallery'); - } - - insertLink() { - const cursorPos = this.editor.cm.getCursor('from'); - const selectedText = this.editor.cm.getSelection() || ''; - const newText = `[${selectedText}]()`; - this.editor.cm.focus(); - this.editor.cm.replaceSelection(newText); - const cursorPosDiff = (selectedText === '') ? -3 : -1; - this.editor.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length+cursorPosDiff); - } - - showImageManager() { - const cursorPos = this.editor.cm.getCursor('from'); - /** @type {ImageManager} **/ - const imageManager = window.$components.first('image-manager'); - imageManager.show(image => { - this.insertDrawing(image, cursorPos); - }, 'drawio'); - } - - // Show the popup link selector and insert a link when finished - showLinkSelector() { - const cursorPos = this.editor.cm.getCursor('from'); - /** @type {EntitySelectorPopup} **/ - const selector = window.$components.first('entity-selector-popup'); - selector.show(entity => { - let selectedText = this.editor.cm.getSelection() || entity.name; - let newText = `[${selectedText}](${entity.link})`; - this.editor.cm.focus(); - this.editor.cm.replaceSelection(newText); - this.editor.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); - }); - } - - // Show draw.io if enabled and handle save. - startDrawing() { - const url = this.editor.config.drawioUrl; - if (!url) return; - - const cursorPos = this.editor.cm.getCursor('from'); - - DrawIO.show(url,() => { - return Promise.resolve(''); - }, (pngData) => { - - const data = { - image: pngData, - uploaded_to: Number(this.editor.config.pageId), - }; - - window.$http.post("/images/drawio", data).then(resp => { - this.insertDrawing(resp.data, cursorPos); - DrawIO.close(); - }).catch(err => { - this.handleDrawingUploadError(err); - }); - }); - } - - insertDrawing(image, originalCursor) { - const newText = `
`; - this.editor.cm.focus(); - this.editor.cm.replaceSelection(newText); - this.editor.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length); - } - - // Show draw.io if enabled and handle save. - editDrawing(imgContainer) { - const drawioUrl = this.editor.config.drawioUrl; - if (!drawioUrl) { - return; - } - - const cursorPos = this.editor.cm.getCursor('from'); - const drawingId = imgContainer.getAttribute('drawio-diagram'); - - DrawIO.show(drawioUrl, () => { - return DrawIO.load(drawingId); - }, (pngData) => { - - const data = { - image: pngData, - uploaded_to: Number(this.editor.config.pageId), - }; - - window.$http.post("/images/drawio", data).then(resp => { - const newText = `
`; - const newContent = this.editor.cm.getValue().split('\n').map(line => { - if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) { - return newText; - } - return line; - }).join('\n'); - this.editor.cm.setValue(newContent); - this.editor.cm.setCursor(cursorPos); - this.editor.cm.focus(); - DrawIO.close(); - }).catch(err => { - this.handleDrawingUploadError(err); - }); - }); - } - - handleDrawingUploadError(error) { - if (error.status === 413) { - window.$events.emit('error', this.editor.config.text.serverUploadLimit); - } else { - window.$events.emit('error', this.editor.config.text.imageUploadError); - } - console.log(error); - } - - // Make the editor full screen - fullScreen() { - const container = this.editor.config.container; - const alreadyFullscreen = container.classList.contains('fullscreen'); - container.classList.toggle('fullscreen', !alreadyFullscreen); - document.body.classList.toggle('markdown-fullscreen', !alreadyFullscreen); - } - - // Scroll to a specified text - scrollToText(searchText) { - if (!searchText) { - return; - } - - const content = this.editor.cm.getValue(); - const lines = content.split(/\r?\n/); - let lineNumber = lines.findIndex(line => { - return line && line.indexOf(searchText) !== -1; - }); - - if (lineNumber === -1) { - return; - } - - this.editor.cm.scrollIntoView({ - line: lineNumber, - }, 200); - this.editor.cm.focus(); - // set the cursor location. - this.editor.cm.setCursor({ - line: lineNumber, - char: lines[lineNumber].length - }) - } - - focus() { - this.editor.cm.focus(); - } - - /** - * Insert content into the editor. - * @param {String} content - */ - insertContent(content) { - this.editor.cm.replaceSelection(content); - } - - /** - * Prepend content to the editor. - * @param {String} content - */ - prependContent(content) { - const cursorPos = this.editor.cm.getCursor('from'); - const newContent = content + '\n' + this.editor.cm.getValue(); - this.editor.cm.setValue(newContent); - const prependLineCount = content.split('\n').length; - this.editor.cm.setCursor(cursorPos.line + prependLineCount, cursorPos.ch); - } - - /** - * Append content to the editor. - * @param {String} content - */ - appendContent(content) { - const cursorPos = this.editor.cm.getCursor('from'); - const newContent = this.editor.cm.getValue() + '\n' + content; - this.editor.cm.setValue(newContent); - this.editor.cm.setCursor(cursorPos.line, cursorPos.ch); - } - - /** - * Replace the editor's contents - * @param {String} content - */ - replaceContent(content) { - this.editor.cm.setValue(content); - } - - /** - * @param {String|RegExp} search - * @param {String} replace - */ - findAndReplaceContent(search, replace) { - const text = this.editor.cm.getValue(); - const cursor = this.editor.cm.listSelections(); - this.editor.cm.setValue(text.replace(search, replace)); - this.editor.cm.setSelections(cursor); - } - - /** - * Replace the start of the line - * @param {String} newStart - */ - replaceLineStart(newStart) { - const cursor = this.editor.cm.getCursor(); - let lineContent = this.editor.cm.getLine(cursor.line); - const lineLen = lineContent.length; - const lineStart = lineContent.split(' ')[0]; - - // Remove symbol if already set - if (lineStart === newStart) { - lineContent = lineContent.replace(`${newStart} `, ''); - this.editor.cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); - this.editor.cm.setCursor({line: cursor.line, ch: cursor.ch - (newStart.length + 1)}); - return; - } - - const alreadySymbol = /^[#>`]/.test(lineStart); - let posDif = 0; - if (alreadySymbol) { - posDif = newStart.length - lineStart.length; - lineContent = lineContent.replace(lineStart, newStart).trim(); - } else if (newStart !== '') { - posDif = newStart.length + 1; - lineContent = newStart + ' ' + lineContent; - } - this.editor.cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); - this.editor.cm.setCursor({line: cursor.line, ch: cursor.ch + posDif}); - } - - /** - * Wrap the line in the given start and end contents. - * @param {String} start - * @param {String} end - */ - wrapLine(start, end) { - const cursor = this.editor.cm.getCursor(); - const lineContent = this.editor.cm.getLine(cursor.line); - const lineLen = lineContent.length; - let newLineContent = lineContent; - - if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) { - newLineContent = lineContent.slice(start.length, lineContent.length - end.length); - } else { - newLineContent = `${start}${lineContent}${end}`; - } - - this.editor.cm.replaceRange(newLineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); - this.editor.cm.setCursor({line: cursor.line, ch: cursor.ch + start.length}); - } - - /** - * Wrap the selection in the given contents start and end contents. - * @param {String} start - * @param {String} end - */ - wrapSelection(start, end) { - const selection = this.editor.cm.getSelection(); - if (selection === '') return this.wrapLine(start, end); - - let newSelection = selection; - const frontDiff = 0; - let endDiff; - - if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) { - newSelection = selection.slice(start.length, selection.length - end.length); - endDiff = -(end.length + start.length); - } else { - newSelection = `${start}${selection}${end}`; - endDiff = start.length + end.length; - } - - const selections = this.editor.cm.listSelections()[0]; - this.editor.cm.replaceSelection(newSelection); - const headFirst = selections.head.ch <= selections.anchor.ch; - selections.head.ch += headFirst ? frontDiff : endDiff; - selections.anchor.ch += headFirst ? endDiff : frontDiff; - this.editor.cm.setSelections([selections]); - } - - replaceLineStartForOrderedList() { - const cursor = this.editor.cm.getCursor(); - const prevLineContent = this.editor.cm.getLine(cursor.line - 1) || ''; - const listMatch = prevLineContent.match(/^(\s*)(\d)([).])\s/) || []; - - const number = (Number(listMatch[2]) || 0) + 1; - const whiteSpace = listMatch[1] || ''; - const listMark = listMatch[3] || '.' - - const prefix = `${whiteSpace}${number}${listMark}`; - return this.replaceLineStart(prefix); - } - - /** - * Cycles through the type of callout block within the selection. - * Creates a callout block if none existing, and removes it if cycling past the danger type. - */ - cycleCalloutTypeAtSelection() { - const selectionRange = this.editor.cm.listSelections()[0]; - const lineContent = this.editor.cm.getLine(selectionRange.anchor.line); - const lineLength = lineContent.length; - const contentRange = { - anchor: {line: selectionRange.anchor.line, ch: 0}, - head: {line: selectionRange.anchor.line, ch: lineLength}, - }; - - const formats = ['info', 'success', 'warning', 'danger']; - const joint = formats.join('|'); - const regex = new RegExp(`class="((${joint})\\s+callout|callout\\s+(${joint}))"`, 'i'); - const matches = regex.exec(lineContent); - const format = (matches ? (matches[2] || matches[3]) : '').toLowerCase(); - - if (format === formats[formats.length - 1]) { - this.wrapLine(`

`, '

'); - } else if (format === '') { - this.wrapLine('

', '

'); - } else { - const newFormatIndex = formats.indexOf(format) + 1; - const newFormat = formats[newFormatIndex]; - const newContent = lineContent.replace(matches[0], matches[0].replace(format, newFormat)); - this.editor.cm.replaceRange(newContent, contentRange.anchor, contentRange.head); - - const chDiff = newContent.length - lineContent.length; - selectionRange.anchor.ch += chDiff; - if (selectionRange.anchor !== selectionRange.head) { - selectionRange.head.ch += chDiff; - } - this.editor.cm.setSelection(selectionRange.anchor, selectionRange.head); - } - } - - /** - * Handle image upload and add image into markdown content - * @param {File} file - */ - uploadImage(file) { - if (file === null || file.type.indexOf('image') !== 0) return; - let ext = 'png'; - - if (file.name) { - let fileNameMatches = file.name.match(/\.(.+)$/); - if (fileNameMatches.length > 1) ext = fileNameMatches[1]; - } - - // Insert image into markdown - const id = "image-" + Math.random().toString(16).slice(2); - const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`); - const selectedText = this.editor.cm.getSelection(); - const placeHolderText = `![${selectedText}](${placeholderImage})`; - const cursor = this.editor.cm.getCursor(); - this.editor.cm.replaceSelection(placeHolderText); - this.editor.cm.setCursor({line: cursor.line, ch: cursor.ch + selectedText.length + 3}); - - const remoteFilename = "image-" + Date.now() + "." + ext; - const formData = new FormData(); - formData.append('file', file, remoteFilename); - formData.append('uploaded_to', this.editor.config.pageId); - - window.$http.post('/images/gallery', formData).then(resp => { - const newContent = `[![${selectedText}](${resp.data.thumbs.display})](${resp.data.url})`; - this.findAndReplaceContent(placeHolderText, newContent); - }).catch(err => { - window.$events.emit('error', this.editor.config.text.imageUploadError); - this.findAndReplaceContent(placeHolderText, selectedText); - console.log(err); - }); - } - - syncDisplayPosition() { - // Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html - const scroll = this.editor.cm.getScrollInfo(); - const atEnd = scroll.top + scroll.clientHeight === scroll.height; - if (atEnd) { - this.editor.display.scrollToIndex(-1); - return; - } - - const lineNum = this.editor.cm.lineAtHeight(scroll.top, 'local'); - const range = this.editor.cm.getRange({line: 0, ch: null}, {line: lineNum, ch: null}); - const parser = new DOMParser(); - const doc = parser.parseFromString(this.editor.markdown.render(range), 'text/html'); - const totalLines = doc.documentElement.querySelectorAll('body > *'); - this.editor.display.scrollToIndex(totalLines.length); - } - - /** - * Fetch and insert the template of the given ID. - * The page-relative position provided can be used to determine insert location if possible. - * @param {String} templateId - * @param {Number} posX - * @param {Number} posY - */ - insertTemplate(templateId, posX, posY) { - const cursorPos = this.editor.cm.coordsChar({left: posX, top: posY}); - this.editor.cm.setCursor(cursorPos); - window.$http.get(`/templates/${templateId}`).then(resp => { - const content = resp.data.markdown || resp.data.html; - this.editor.cm.replaceSelection(content); - }); - } - - /** - * Insert multiple images from the clipboard. - * @param {File[]} images - */ - insertClipboardImages(images) { - const cursorPos = this.editor.cm.coordsChar({left: event.pageX, top: event.pageY}); - this.editor.cm.setCursor(cursorPos); - for (const image of images) { - this.uploadImage(image); - } - } -} \ No newline at end of file