Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
62ddd3a0
Commit
62ddd3a0
authored
Dec 31, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/gitlab@master
parent
1e6a9268
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
262 additions
and
8 deletions
+262
-8
app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
...ts/javascripts/behaviors/markdown/paste_markdown_table.js
+88
-0
app/assets/javascripts/dropzone_input.js
app/assets/javascripts/dropzone_input.js
+18
-6
changelogs/unreleased/sh-cut-and-paste-spreadsheets-markdown.yml
...ogs/unreleased/sh-cut-and-paste-spreadsheets-markdown.yml
+5
-0
doc/administration/pages/index.md
doc/administration/pages/index.md
+18
-2
spec/frontend/behaviors/markdown/paste_markdown_table_spec.js
.../frontend/behaviors/markdown/paste_markdown_table_spec.js
+95
-0
spec/frontend/fixtures/issues.rb
spec/frontend/fixtures/issues.rb
+9
-0
spec/javascripts/dropzone_input_spec.js
spec/javascripts/dropzone_input_spec.js
+29
-0
No files found.
app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
0 → 100644
View file @
62ddd3a0
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\u
0085
\u
2028
\u
2029
]
|
\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
(
'
|
'
)}
|`
;
}
}
app/assets/javascripts/dropzone_input.js
View file @
62ddd3a0
...
@@ -2,6 +2,7 @@ import $ from 'jquery';
...
@@ -2,6 +2,7 @@ import $ from 'jquery';
import
Dropzone
from
'
dropzone
'
;
import
Dropzone
from
'
dropzone
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
'
./behaviors/preview_markdown
'
;
import
'
./behaviors/preview_markdown
'
;
import
PasteMarkdownTable
from
'
./behaviors/markdown/paste_markdown_table
'
;
import
csrf
from
'
./lib/utils/csrf
'
;
import
csrf
from
'
./lib/utils/csrf
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
{
n__
,
__
}
from
'
~/locale
'
;
import
{
n__
,
__
}
from
'
~/locale
'
;
...
@@ -173,8 +174,18 @@ export default function dropzoneInput(form) {
...
@@ -173,8 +174,18 @@ export default function dropzoneInput(form) {
// eslint-disable-next-line consistent-return
// eslint-disable-next-line consistent-return
handlePaste
=
event
=>
{
handlePaste
=
event
=>
{
const
pasteEvent
=
event
.
originalEvent
;
const
pasteEvent
=
event
.
originalEvent
;
if
(
pasteEvent
.
clipboardData
&&
pasteEvent
.
clipboardData
.
items
)
{
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
converter
=
new
PasteMarkdownTable
(
clipboardData
);
const
text
=
converter
.
convertToTableMarkdown
();
pasteText
(
text
);
}
else
{
const
image
=
isImage
(
pasteEvent
);
const
image
=
isImage
(
pasteEvent
);
if
(
image
)
{
if
(
image
)
{
event
.
preventDefault
();
event
.
preventDefault
();
const
filename
=
getFilename
(
pasteEvent
)
||
'
image.png
'
;
const
filename
=
getFilename
(
pasteEvent
)
||
'
image.png
'
;
...
@@ -183,6 +194,7 @@ export default function dropzoneInput(form) {
...
@@ -183,6 +194,7 @@ export default function dropzoneInput(form) {
return
uploadFile
(
image
.
getAsFile
(),
filename
);
return
uploadFile
(
image
.
getAsFile
(),
filename
);
}
}
}
}
}
};
};
isImage
=
data
=>
{
isImage
=
data
=>
{
...
...
changelogs/unreleased/sh-cut-and-paste-spreadsheets-markdown.yml
0 → 100644
View file @
62ddd3a0
---
title
:
Cut and paste Markdown table from a spreadsheet
merge_request
:
22290
author
:
type
:
added
doc/administration/pages/index.md
View file @
62ddd3a0
...
@@ -395,10 +395,26 @@ Omnibus GitLab 11.1.
...
@@ -395,10 +395,26 @@ Omnibus GitLab 11.1.
## Set maximum pages size
## Set maximum pages size
The maximum size of the unpacked archive per project can be configured
in the
You can configure the maximum size of the unpacked archive per project
in the
Admin area
under the Application settings in
the
**Maximum size of pages (MB)**
.
Admin area
. Under Application settings, edit
the
**Maximum size of pages (MB)**
.
The default is 100MB.
The default is 100MB.
### Override maximum pages size per project or group **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/16610) in GitLab 12.7.
To override the global maximum pages size for a specific project:
1.
Navigate to your project's
**Settings > Pages**
page.
1.
Edit the
**Maximum size of pages**
.
1.
Click
**Save changes**
.
To override the global maximum pages size for a specific group:
1.
Navigate to your group's
**Settings > General**
page and expand
**Pages**
.
1.
Edit the
**Maximum size of pages**
.
1.
Click
**Save changes**
.
## Running GitLab Pages on a separate server
## Running GitLab Pages on a separate server
You can run the GitLab Pages daemon on a separate server in order to decrease the load on your main application server.
You can run the GitLab Pages daemon on a separate server in order to decrease the load on your main application server.
...
...
spec/frontend/behaviors/markdown/paste_markdown_table_spec.js
0 → 100644
View file @
62ddd3a0
import
PasteMarkdownTable
from
'
~/behaviors/markdown/paste_markdown_table
'
;
describe
(
'
PasteMarkdownTable
'
,
()
=>
{
let
data
;
beforeEach
(()
=>
{
const
event
=
new
window
.
Event
(
'
paste
'
);
Object
.
defineProperty
(
event
,
'
dataTransfer
'
,
{
value
:
{
getData
:
jest
.
fn
().
mockImplementation
(
type
=>
{
if
(
type
===
'
text/html
'
)
{
return
'
<table><tr><td></td></tr></table>
'
;
}
return
'
hello world
'
;
}),
},
});
data
=
event
.
dataTransfer
;
});
describe
(
'
isTable
'
,
()
=>
{
it
(
'
return false when no HTML data is provided
'
,
()
=>
{
data
.
types
=
[
'
text/plain
'
];
expect
(
PasteMarkdownTable
.
isTable
(
data
)).
toBe
(
false
);
});
it
(
'
returns false when no text data is provided
'
,
()
=>
{
data
.
types
=
[
'
text/html
'
];
expect
(
PasteMarkdownTable
.
isTable
(
data
)).
toBe
(
false
);
});
it
(
'
returns true when a table is provided in both text and HTML
'
,
()
=>
{
data
.
types
=
[
'
text/html
'
,
'
text/plain
'
];
expect
(
PasteMarkdownTable
.
isTable
(
data
)).
toBe
(
true
);
});
it
(
'
returns false when no HTML table is included
'
,
()
=>
{
data
.
types
=
[
'
text/html
'
,
'
text/plain
'
];
data
.
getData
=
jest
.
fn
().
mockImplementation
(()
=>
'
nothing
'
);
expect
(
PasteMarkdownTable
.
isTable
(
data
)).
toBe
(
false
);
});
});
describe
(
'
convertToTableMarkdown
'
,
()
=>
{
let
converter
;
beforeEach
(()
=>
{
converter
=
new
PasteMarkdownTable
(
data
);
});
it
(
'
returns a Markdown table
'
,
()
=>
{
data
.
getData
=
jest
.
fn
().
mockImplementation
(
type
=>
{
if
(
type
===
'
text/plain
'
)
{
return
'
First
\t
Last
\n
John
\t
Doe
\n
Jane
\t
Doe
'
;
}
return
''
;
});
const
expected
=
[
'
| First | Last |
'
,
'
|-------|------|
'
,
'
| John | Doe |
'
,
'
| Jane | Doe |
'
,
].
join
(
'
\n
'
);
expect
(
converter
.
convertToTableMarkdown
()).
toBe
(
expected
);
});
it
(
'
returns a Markdown table with rows normalized
'
,
()
=>
{
data
.
getData
=
jest
.
fn
().
mockImplementation
(
type
=>
{
if
(
type
===
'
text/plain
'
)
{
return
'
First
\t
Last
\n
John
\t
Doe
\n
Jane
'
;
}
return
''
;
});
const
expected
=
[
'
| First | Last |
'
,
'
|-------|------|
'
,
'
| John | Doe |
'
,
'
| Jane | |
'
,
].
join
(
'
\n
'
);
expect
(
converter
.
convertToTableMarkdown
()).
toBe
(
expected
);
});
});
});
spec/frontend/fixtures/issues.rb
View file @
62ddd3a0
...
@@ -23,6 +23,15 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
...
@@ -23,6 +23,15 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
remove_repository
(
project
)
remove_repository
(
project
)
end
end
it
'issues/new-issue.html'
do
get
:new
,
params:
{
namespace_id:
project
.
namespace
.
to_param
,
project_id:
project
}
expect
(
response
).
to
be_successful
end
it
'issues/open-issue.html'
do
it
'issues/open-issue.html'
do
render_issue
(
create
(
:issue
,
project:
project
))
render_issue
(
create
(
:issue
,
project:
project
))
end
end
...
...
spec/javascripts/dropzone_input_spec.js
View file @
62ddd3a0
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
dropzoneInput
from
'
~/dropzone_input
'
;
import
dropzoneInput
from
'
~/dropzone_input
'
;
import
PasteMarkdownTable
from
'
~/behaviors/markdown/paste_markdown_table
'
;
const
TEST_FILE
=
new
File
([],
'
somefile.jpg
'
);
const
TEST_FILE
=
new
File
([],
'
somefile.jpg
'
);
TEST_FILE
.
upload
=
{};
TEST_FILE
.
upload
=
{};
...
@@ -25,6 +26,34 @@ describe('dropzone_input', () => {
...
@@ -25,6 +26,34 @@ describe('dropzone_input', () => {
expect
(
dropzone
.
version
).
toBeTruthy
();
expect
(
dropzone
.
version
).
toBeTruthy
();
});
});
describe
(
'
handlePaste
'
,
()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
'
issues/new-issue.html
'
);
const
form
=
$
(
'
#new_issue
'
);
form
.
data
(
'
uploads-path
'
,
TEST_UPLOAD_PATH
);
dropzoneInput
(
form
);
});
it
(
'
pastes Markdown tables
'
,
()
=>
{
const
event
=
$
.
Event
(
'
paste
'
);
const
origEvent
=
new
Event
(
'
paste
'
);
const
pasteData
=
new
DataTransfer
();
pasteData
.
setData
(
'
text/plain
'
,
'
hello world
'
);
pasteData
.
setData
(
'
text/html
'
,
'
<table></table>
'
);
origEvent
.
clipboardData
=
pasteData
;
event
.
originalEvent
=
origEvent
;
spyOn
(
PasteMarkdownTable
,
'
isTable
'
).
and
.
callThrough
();
spyOn
(
PasteMarkdownTable
.
prototype
,
'
convertToTableMarkdown
'
).
and
.
callThrough
();
$
(
'
.js-gfm-input
'
).
trigger
(
event
);
expect
(
PasteMarkdownTable
.
isTable
).
toHaveBeenCalled
();
expect
(
PasteMarkdownTable
.
prototype
.
convertToTableMarkdown
).
toHaveBeenCalled
();
});
});
describe
(
'
shows error message
'
,
()
=>
{
describe
(
'
shows error message
'
,
()
=>
{
let
form
;
let
form
;
let
dropzone
;
let
dropzone
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment