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
1
Merge Requests
1
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
nexedi
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
Hide 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';
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
);
}
}
}
};
...
...
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.
## Set maximum pages size
The maximum size of the unpacked archive per project can be configured
in the
Admin area
under the Application settings in
the
**Maximum size of pages (MB)**
.
You can configure the maximum size of the unpacked archive per project
in the
Admin area
. Under Application settings, edit
the
**Maximum size of pages (MB)**
.
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
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
remove_repository
(
project
)
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
render_issue
(
create
(
:issue
,
project:
project
))
end
...
...
spec/javascripts/dropzone_input_spec.js
View file @
62ddd3a0
import
$
from
'
jquery
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
dropzoneInput
from
'
~/dropzone_input
'
;
import
PasteMarkdownTable
from
'
~/behaviors/markdown/paste_markdown_table
'
;
const
TEST_FILE
=
new
File
([],
'
somefile.jpg
'
);
TEST_FILE
.
upload
=
{};
...
...
@@ -25,6 +26,34 @@ describe('dropzone_input', () => {
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
'
,
()
=>
{
let
form
;
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