summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/behaviors
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-09 09:07:51 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-09 09:07:51 +0000
commit5afd8575506372dd64c238203bd05b4826f3ae2e (patch)
treee167192fdc7d73fcc1aa5bd33b535b813120ec37 /app/assets/javascripts/behaviors
parent8bda404e2919234c299f088b7d8d04f8449125de (diff)
downloadgitlab-ce-5afd8575506372dd64c238203bd05b4826f3ae2e.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/behaviors')
-rw-r--r--app/assets/javascripts/behaviors/markdown/paste_markdown_table.js94
1 files changed, 64 insertions, 30 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js b/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
index d14799c976b..665a7216424 100644
--- a/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
+++ b/app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
@@ -1,58 +1,81 @@
+const maxColumnWidth = (rows, columnIndex) => Math.max(...rows.map(row => row[columnIndex].length));
+
export default class PasteMarkdownTable {
constructor(clipboardData) {
this.data = clipboardData;
+ this.columnWidths = [];
+ this.rows = [];
+ this.tableFound = this.parseTable();
+ }
+
+ isTable() {
+ return this.tableFound;
}
- static maxColumnWidth(rows, columnIndex) {
- return Math.max.apply(null, rows.map(row => row[columnIndex].length));
+ convertToTableMarkdown() {
+ 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');
}
+ // Private methods below
+
// 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')) {
+ // 3. The number of rows in the "text/plain" data matches that of the "text/html" data
+ // 4. The max number of columns in "text/plain" matches that of the "text/html" data
+ parseTable() {
+ if (!this.data.types.includes('text/html') || !this.data.types.includes('text/plain')) {
return false;
}
- const htmlData = data.getData('text/html');
- const doc = new DOMParser().parseFromString(htmlData, 'text/html');
+ const htmlData = this.data.getData('text/html');
+ this.doc = new DOMParser().parseFromString(htmlData, 'text/html');
+ const tables = this.doc.querySelectorAll('table');
// 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;
+ if (tables.length !== 1) {
+ return false;
}
- 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 splitRows = text.split(/[\n\u0085\u2028\u2029]|\r\n?/g);
- 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(' | ')} |`,
- );
+ // Now check that the number of rows matches between HTML and text
+ if (this.doc.querySelectorAll('tr').length !== splitRows.length) {
+ return false;
+ }
- // Insert a header break (e.g. -----) to the second row
- markdownRows.splice(1, 0, this.generateHeaderBreak());
+ this.rows = splitRows.map(row => row.split('\t'));
+ this.normalizeRows();
- return markdownRows.join('\n');
+ // Check that the max number of columns in the HTML matches the number of
+ // columns in the text. GitHub, for example, copies a line number and the
+ // line itself into the HTML data.
+ if (!this.columnCountsMatch()) {
+ return false;
+ }
+
+ return true;
}
// Ensure each row has the same number of columns
@@ -69,10 +92,21 @@ export default class PasteMarkdownTable {
calculateColumnWidths() {
this.columnWidths = this.rows[0].map((_column, columnIndex) =>
- PasteMarkdownTable.maxColumnWidth(this.rows, columnIndex),
+ maxColumnWidth(this.rows, columnIndex),
);
}
+ columnCountsMatch() {
+ const textColumnCount = this.rows[0].length;
+ let htmlColumnCount = 0;
+
+ this.doc.querySelectorAll('table tr').forEach(row => {
+ htmlColumnCount = Math.max(row.cells.length, htmlColumnCount);
+ });
+
+ return textColumnCount === htmlColumnCount;
+ }
+
formatColumn(column, index) {
const spaces = Array(this.columnWidths[index] - column.length + 1).join(' ');
return column + spaces;