diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-31 18:09:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-31 18:09:10 +0000 |
commit | 62ddd3a00522d62ab23c7804e24dbe1c941bc0a7 (patch) | |
tree | 71f2cf18cdc83a3fce5221d05df3a792a562dd4e /app | |
parent | 1e6a9268646e7346519610492fc2a02d6655a663 (diff) | |
download | gitlab-ce-62ddd3a00522d62ab23c7804e24dbe1c941bc0a7.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/behaviors/markdown/paste_markdown_table.js | 88 | ||||
-rw-r--r-- | app/assets/javascripts/dropzone_input.js | 24 |
2 files changed, 106 insertions, 6 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js b/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js new file mode 100644 index 00000000000..d14799c976b --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js @@ -0,0 +1,88 @@ +export default class PasteMarkdownTable { + constructor(clipboardData) { + this.data = clipboardData; + } + + static maxColumnWidth(rows, columnIndex) { + return Math.max.apply(null, rows.map(row => row[columnIndex].length)); + } + + // To determine whether the cut data is a table, the following criteria + // must be satisfied with the clipboard data: + // + // 1. MIME types "text/plain" and "text/html" exist + // 2. The "text/html" data must have a single <table> element + static isTable(data) { + const types = new Set(data.types); + + if (!types.has('text/html') || !types.has('text/plain')) { + return false; + } + + const htmlData = data.getData('text/html'); + const doc = new DOMParser().parseFromString(htmlData, 'text/html'); + + // We're only looking for exactly one table. If there happens to be + // multiple tables, it's possible an application copied data into + // the clipboard that is not related to a simple table. It may also be + // complicated converting multiple tables into Markdown. + if (doc.querySelectorAll('table').length === 1) { + return true; + } + + return false; + } + + convertToTableMarkdown() { + const text = this.data.getData('text/plain').trim(); + this.rows = text.split(/[\n\u0085\u2028\u2029]|\r\n?/g).map(row => row.split('\t')); + this.normalizeRows(); + this.calculateColumnWidths(); + + const markdownRows = this.rows.map( + row => + // | Name | Title | Email Address | + // |--------------|-------|----------------| + // | Jane Atler | CEO | jane@acme.com | + // | John Doherty | CTO | john@acme.com | + // | Sally Smith | CFO | sally@acme.com | + `| ${row.map((column, index) => this.formatColumn(column, index)).join(' | ')} |`, + ); + + // Insert a header break (e.g. -----) to the second row + markdownRows.splice(1, 0, this.generateHeaderBreak()); + + return markdownRows.join('\n'); + } + + // Ensure each row has the same number of columns + normalizeRows() { + const rowLengths = this.rows.map(row => row.length); + const maxLength = Math.max(...rowLengths); + + this.rows.forEach(row => { + while (row.length < maxLength) { + row.push(''); + } + }); + } + + calculateColumnWidths() { + this.columnWidths = this.rows[0].map((_column, columnIndex) => + PasteMarkdownTable.maxColumnWidth(this.rows, columnIndex), + ); + } + + formatColumn(column, index) { + const spaces = Array(this.columnWidths[index] - column.length + 1).join(' '); + return column + spaces; + } + + generateHeaderBreak() { + // Add 3 dashes to line things up: there is additional spacing for the pipe characters + const dashes = this.columnWidths.map((width, index) => + Array(this.columnWidths[index] + 3).join('-'), + ); + return `|${dashes.join('|')}|`; + } +} diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 62b390a46d7..79739072abb 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Dropzone from 'dropzone'; import _ from 'underscore'; import './behaviors/preview_markdown'; +import PasteMarkdownTable from './behaviors/markdown/paste_markdown_table'; import csrf from './lib/utils/csrf'; import axios from './lib/utils/axios_utils'; import { n__, __ } from '~/locale'; @@ -173,14 +174,25 @@ export default function dropzoneInput(form) { // eslint-disable-next-line consistent-return handlePaste = event => { const pasteEvent = event.originalEvent; - if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { - const image = isImage(pasteEvent); - if (image) { + const { clipboardData } = pasteEvent; + if (clipboardData && clipboardData.items) { + // Apple Numbers copies a table as an image, HTML, and text, so + // we need to check for the presence of a table first. + if (PasteMarkdownTable.isTable(clipboardData)) { event.preventDefault(); - const filename = getFilename(pasteEvent) || 'image.png'; - const text = `{{${filename}}}`; + const converter = new PasteMarkdownTable(clipboardData); + const text = converter.convertToTableMarkdown(); pasteText(text); - return uploadFile(image.getAsFile(), filename); + } else { + const image = isImage(pasteEvent); + + if (image) { + event.preventDefault(); + const filename = getFilename(pasteEvent) || 'image.png'; + const text = `{{${filename}}}`; + pasteText(text); + return uploadFile(image.getAsFile(), filename); + } } } }; |