summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-31 18:09:10 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-31 18:09:10 +0000
commit62ddd3a00522d62ab23c7804e24dbe1c941bc0a7 (patch)
tree71f2cf18cdc83a3fce5221d05df3a792a562dd4e /app/assets/javascripts
parent1e6a9268646e7346519610492fc2a02d6655a663 (diff)
downloadgitlab-ce-62ddd3a00522d62ab23c7804e24dbe1c941bc0a7.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/behaviors/markdown/paste_markdown_table.js88
-rw-r--r--app/assets/javascripts/dropzone_input.js24
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);
+ }
}
}
};