Copying a rendered issue/comment will paste into GFM textareas as actual GFM
This commit is contained in:
parent
142be72a2a
commit
dbfa58e2da
|
@ -0,0 +1,319 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
(() => {
|
||||
const gfmRules = {
|
||||
// Should have an entry for every filter in lib/banzai/pipeline/gfm_pipeline.rb,
|
||||
// in reverse order.
|
||||
// Should have test coverage in spec/features/copy_as_gfm_spec.rb.
|
||||
"InlineDiffFilter": {
|
||||
"span.idiff.addition": function(el, text) {
|
||||
return "{+" + text + "+}";
|
||||
},
|
||||
"span.idiff.deletion": function(el, text) {
|
||||
return "{-" + text + "-}";
|
||||
},
|
||||
},
|
||||
"TaskListFilter": {
|
||||
"input[type=checkbox].task-list-item-checkbox": function(el, text) {
|
||||
return '[' + (el.checked ? 'x' : ' ') + ']';
|
||||
}
|
||||
},
|
||||
"ReferenceFilter": {
|
||||
"a.gfm:not([data-link=true])": function(el, text) {
|
||||
return el.getAttribute('data-original') || text;
|
||||
},
|
||||
},
|
||||
"AutolinkFilter": {
|
||||
"a": function(el, text) {
|
||||
if (text != el.getAttribute("href")) {
|
||||
// Fall back to handler for MarkdownFilter
|
||||
return false;
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
},
|
||||
"TableOfContentsFilter": {
|
||||
"ul.section-nav": function(el, text) {
|
||||
return "[[_TOC_]]";
|
||||
},
|
||||
},
|
||||
"EmojiFilter": {
|
||||
"img.emoji": function(el, text) {
|
||||
return el.getAttribute("alt");
|
||||
},
|
||||
},
|
||||
"ImageLinkFilter": {
|
||||
"a.no-attachment-icon": function(el, text) {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
"VideoLinkFilter": {
|
||||
".video-container": function(el, text) {
|
||||
var videoEl = el.querySelector('video');
|
||||
if (!videoEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CopyAsGFM.nodeToGFM(videoEl);
|
||||
},
|
||||
"video": function(el, text) {
|
||||
return "![" + el.getAttribute('data-title') + "](" + el.getAttribute("src") + ")";
|
||||
},
|
||||
},
|
||||
"MathFilter": {
|
||||
"pre.code.math[data-math-style='display']": function(el, text) {
|
||||
return "```math\n" + text.trim() + "\n```";
|
||||
},
|
||||
"code.code.math[data-math-style='inline']": function(el, text) {
|
||||
return "$`" + text + "`$";
|
||||
},
|
||||
"span.katex-display span.katex-mathml": function(el, text) {
|
||||
var mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
|
||||
if (!mathAnnotation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return "```math\n" + CopyAsGFM.nodeToGFM(mathAnnotation) + "\n```";
|
||||
},
|
||||
"span.katex-mathml": function(el, text) {
|
||||
var mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
|
||||
if (!mathAnnotation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return "$`" + CopyAsGFM.nodeToGFM(mathAnnotation) + "`$";
|
||||
},
|
||||
"span.katex-html": function(el, text) {
|
||||
return "";
|
||||
},
|
||||
'annotation[encoding="application/x-tex"]': function(el, text) {
|
||||
return text.trim();
|
||||
}
|
||||
},
|
||||
"SyntaxHighlightFilter": {
|
||||
"pre.code.highlight": function(el, text) {
|
||||
var lang = el.getAttribute("lang");
|
||||
if (lang == "text") {
|
||||
lang = "";
|
||||
}
|
||||
return "```" + lang + "\n" + text.trim() + "\n```";
|
||||
},
|
||||
"pre > code": function(el, text) {
|
||||
// Don't wrap code blocks in ``
|
||||
return text;
|
||||
},
|
||||
},
|
||||
"MarkdownFilter": {
|
||||
"code": function(el, text) {
|
||||
var backtickCount = 1;
|
||||
var backtickMatch = text.match(/`+/);
|
||||
if (backtickMatch) {
|
||||
backtickCount = backtickMatch[0].length + 1;
|
||||
}
|
||||
|
||||
var backticks = new Array(backtickCount + 1).join('`');
|
||||
var spaceOrNoSpace = backtickCount > 1 ? " " : "";
|
||||
|
||||
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks;
|
||||
},
|
||||
"blockquote": function(el, text) {
|
||||
return text.trim().split('\n').map(function(s) { return ('> ' + s).trim(); }).join('\n');
|
||||
},
|
||||
"img": function(el, text) {
|
||||
return "![" + el.getAttribute("alt") + "](" + el.getAttribute("src") + ")";
|
||||
},
|
||||
"a.anchor": function(el, text) {
|
||||
return text;
|
||||
},
|
||||
"a": function(el, text) {
|
||||
return "[" + text + "](" + el.getAttribute("href") + ")";
|
||||
},
|
||||
"li": function(el, text) {
|
||||
var lines = text.trim().split('\n');
|
||||
var firstLine = '- ' + lines.shift();
|
||||
var nextLines = lines.map(function(s) { return (' ' + s).replace(/\s+$/, ''); });
|
||||
|
||||
return firstLine + '\n' + nextLines.join('\n');
|
||||
},
|
||||
"ul": function(el, text) {
|
||||
return text;
|
||||
},
|
||||
"ol": function(el, text) {
|
||||
return text.replace(/^- /mg, '1. ');
|
||||
},
|
||||
"h1": function(el, text) {
|
||||
return '# ' + text.trim();
|
||||
},
|
||||
"h2": function(el, text) {
|
||||
return '## ' + text.trim();
|
||||
},
|
||||
"h3": function(el, text) {
|
||||
return '### ' + text.trim();
|
||||
},
|
||||
"h4": function(el, text) {
|
||||
return '#### ' + text.trim();
|
||||
},
|
||||
"h5": function(el, text) {
|
||||
return '##### ' + text.trim();
|
||||
},
|
||||
"h6": function(el, text) {
|
||||
return '###### ' + text.trim();
|
||||
},
|
||||
"strong": function(el, text) {
|
||||
return '**' + text + '**';
|
||||
},
|
||||
"em": function(el, text) {
|
||||
return '_' + text + '_';
|
||||
},
|
||||
"del": function(el, text) {
|
||||
return '~~' + text + '~~';
|
||||
},
|
||||
"sup": function(el, text) {
|
||||
return '^' + text;
|
||||
},
|
||||
"hr": function(el, text) {
|
||||
return '-----';
|
||||
},
|
||||
"table": function(el, text) {
|
||||
var theadText = CopyAsGFM.nodeToGFM(el.querySelector('thead'));
|
||||
var tbodyText = CopyAsGFM.nodeToGFM(el.querySelector('tbody'));
|
||||
|
||||
return theadText + tbodyText;
|
||||
},
|
||||
"thead": function(el, text) {
|
||||
var cells = _.map(el.querySelectorAll('th'), function(cell) {
|
||||
var chars = CopyAsGFM.nodeToGFM(cell).trim().length;
|
||||
|
||||
var before = '';
|
||||
var after = '';
|
||||
switch (cell.style.textAlign) {
|
||||
case 'center':
|
||||
before = ':';
|
||||
after = ':';
|
||||
chars -= 2;
|
||||
break;
|
||||
case 'right':
|
||||
after = ':';
|
||||
chars -= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
chars = Math.max(chars, 0);
|
||||
|
||||
var middle = new Array(chars + 1).join('-');
|
||||
|
||||
return before + middle + after;
|
||||
});
|
||||
return text + '| ' + cells.join(' | ') + ' |';
|
||||
},
|
||||
"tr": function(el, text) {
|
||||
var cells = _.map(el.querySelectorAll('td, th'), function(cell) {
|
||||
return CopyAsGFM.nodeToGFM(cell).trim();
|
||||
});
|
||||
return '| ' + cells.join(' | ') + ' |';
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
class CopyAsGFM {
|
||||
constructor() {
|
||||
$(document).on('copy', '.md, .wiki', this.handleCopy.bind(this));
|
||||
$(document).on('paste', '.js-gfm-input', this.handlePaste.bind(this));
|
||||
}
|
||||
|
||||
handleCopy(e) {
|
||||
var clipboardData = e.originalEvent.clipboardData;
|
||||
if (!clipboardData) return;
|
||||
|
||||
if (!window.getSelection) return;
|
||||
|
||||
var selection = window.getSelection();
|
||||
if (!selection.rangeCount || selection.rangeCount === 0) return;
|
||||
|
||||
var selectedDocument = selection.getRangeAt(0).cloneContents();
|
||||
if (!selectedDocument) return;
|
||||
|
||||
e.preventDefault();
|
||||
clipboardData.setData('text/plain', selectedDocument.textContent);
|
||||
|
||||
var gfm = CopyAsGFM.nodeToGFM(selectedDocument);
|
||||
clipboardData.setData('text/x-gfm', gfm);
|
||||
}
|
||||
|
||||
handlePaste(e) {
|
||||
var clipboardData = e.originalEvent.clipboardData;
|
||||
if (!clipboardData) return;
|
||||
|
||||
var gfm = clipboardData.getData('text/x-gfm');
|
||||
if (!gfm) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
this.insertText(e.target, gfm);
|
||||
}
|
||||
|
||||
insertText(target, text) {
|
||||
// Firefox doesn't support `document.execCommand('insertText', false, text);` on textareas
|
||||
var selectionStart = target.selectionStart;
|
||||
var selectionEnd = target.selectionEnd;
|
||||
var value = target.value;
|
||||
var textBefore = value.substring(0, selectionStart);
|
||||
var textAfter = value.substring(selectionEnd, value.length);
|
||||
var newText = textBefore + text + textAfter;
|
||||
target.value = newText;
|
||||
target.selectionStart = target.selectionEnd = selectionStart + text.length;
|
||||
}
|
||||
|
||||
static nodeToGFM(node) {
|
||||
if (node.nodeType == Node.TEXT_NODE) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
var text = this.innerGFM(node);
|
||||
|
||||
if (node.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
return text;
|
||||
}
|
||||
|
||||
for (var filter in gfmRules) {
|
||||
var rules = gfmRules[filter];
|
||||
|
||||
for (var selector in rules) {
|
||||
var func = rules[selector];
|
||||
|
||||
if (!node.matches(selector)) continue;
|
||||
|
||||
var result = func(node, text);
|
||||
if (result === false) continue;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static innerGFM(parentNode) {
|
||||
var nodes = parentNode.childNodes;
|
||||
|
||||
var clonedParentNode = parentNode.cloneNode(true);
|
||||
var clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
var clonedNode = clonedNodes[i];
|
||||
|
||||
var text = this.nodeToGFM(node);
|
||||
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
|
||||
}
|
||||
|
||||
return clonedParentNode.innerText || clonedParentNode.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.CopyAsGFM = CopyAsGFM;
|
||||
|
||||
new CopyAsGFM();
|
||||
})();
|
|
@ -39,29 +39,33 @@
|
|||
}
|
||||
|
||||
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
|
||||
var quote, replyField, selected, separator;
|
||||
if (window.getSelection) {
|
||||
selected = window.getSelection().toString();
|
||||
replyField = $('.js-main-target-form #note_note');
|
||||
if (selected.trim() === "") {
|
||||
return;
|
||||
}
|
||||
// Put a '>' character before each non-empty line in the selection
|
||||
quote = _.map(selected.split("\n"), function(val) {
|
||||
if (val.trim() !== '') {
|
||||
return "> " + val + "\n";
|
||||
}
|
||||
});
|
||||
// If replyField already has some content, add a newline before our quote
|
||||
separator = replyField.val().trim() !== "" && "\n" || '';
|
||||
replyField.val(function(_, current) {
|
||||
return current + separator + quote.join('') + "\n";
|
||||
});
|
||||
// Trigger autosave for the added text
|
||||
replyField.trigger('input');
|
||||
// Focus the input field
|
||||
return replyField.focus();
|
||||
var quote, replyField, selectedDocument, selected, selection, separator;
|
||||
if (!window.getSelection) return;
|
||||
|
||||
selection = window.getSelection();
|
||||
if (!selection.rangeCount || selection.rangeCount === 0) return;
|
||||
|
||||
selectedDocument = selection.getRangeAt(0).cloneContents();
|
||||
if (!selectedDocument) return;
|
||||
|
||||
selected = window.gl.CopyAsGFM.nodeToGFM(selectedDocument);
|
||||
|
||||
replyField = $('.js-main-target-form #note_note');
|
||||
if (selected.trim() === "") {
|
||||
return;
|
||||
}
|
||||
quote = _.map(selected.split("\n"), function(val) {
|
||||
return "> " + val + "\n";
|
||||
});
|
||||
// If replyField already has some content, add a newline before our quote
|
||||
separator = replyField.val().trim() !== "" && "\n" || '';
|
||||
replyField.val(function(_, current) {
|
||||
return current + separator + quote.join('') + "\n";
|
||||
});
|
||||
// Trigger autosave for the added text
|
||||
replyField.trigger('input');
|
||||
// Focus the input field
|
||||
return replyField.focus();
|
||||
};
|
||||
|
||||
ShortcutsIssuable.prototype.editIssue = function() {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Copying a rendered issue/comment will paste into GFM textareas as actual GFM
|
||||
merge_request:
|
||||
author:
|
|
@ -153,7 +153,7 @@ module Banzai
|
|||
title = object_link_title(object)
|
||||
klass = reference_class(object_sym)
|
||||
|
||||
data = data_attributes_for(link_content || match, project, object)
|
||||
data = data_attributes_for(link_content || match, project, object, link: !!link_content)
|
||||
|
||||
if matches.names.include?("url") && matches[:url]
|
||||
url = matches[:url]
|
||||
|
@ -172,9 +172,10 @@ module Banzai
|
|||
end
|
||||
end
|
||||
|
||||
def data_attributes_for(text, project, object)
|
||||
def data_attributes_for(text, project, object, link: false)
|
||||
data_attribute(
|
||||
original: text,
|
||||
link: link,
|
||||
project: project.id,
|
||||
object_sym => object.id
|
||||
)
|
||||
|
|
|
@ -62,7 +62,7 @@ module Banzai
|
|||
end
|
||||
end
|
||||
|
||||
def data_attributes_for(text, project, object)
|
||||
def data_attributes_for(text, project, object, link: false)
|
||||
if object.is_a?(ExternalIssue)
|
||||
data_attribute(
|
||||
project: project.id,
|
||||
|
|
|
@ -20,17 +20,18 @@ module Banzai
|
|||
code = node.text
|
||||
css_classes = "code highlight"
|
||||
lexer = lexer_for(language)
|
||||
lang = lexer.tag
|
||||
|
||||
begin
|
||||
code = format(lex(lexer, code))
|
||||
|
||||
css_classes << " js-syntax-highlight #{lexer.tag}"
|
||||
css_classes << " js-syntax-highlight #{lang}"
|
||||
rescue
|
||||
# Gracefully handle syntax highlighter bugs/errors to ensure
|
||||
# users can still access an issue/comment/etc.
|
||||
end
|
||||
|
||||
highlighted = %(<pre class="#{css_classes}" v-pre="true"><code>#{code}</code></pre>)
|
||||
highlighted = %(<pre class="#{css_classes}" lang="#{lang}" v-pre="true"><code>#{code}</code></pre>)
|
||||
|
||||
# Extracted to a method to measure it
|
||||
replace_parent_pre_element(node, highlighted)
|
||||
|
|
|
@ -35,7 +35,8 @@ module Banzai
|
|||
src: element['src'],
|
||||
width: '400',
|
||||
controls: true,
|
||||
'data-setup' => '{}')
|
||||
'data-setup' => '{}',
|
||||
'data-title' => element['title'] || element['alt'])
|
||||
|
||||
link = doc.document.create_element(
|
||||
'a',
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
module Banzai
|
||||
module Pipeline
|
||||
class GfmPipeline < BasePipeline
|
||||
# Every filter should have an entry in app/assets/javascripts/copy_as_gfm.js.es6,
|
||||
# in reverse order.
|
||||
# Should have test coverage in spec/features/copy_as_gfm_spec.rb.
|
||||
def self.filters
|
||||
@filters ||= FilterArray[
|
||||
Filter::SyntaxHighlightFilter,
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Copy as GFM', feature: true, js: true do
|
||||
include GitlabMarkdownHelper
|
||||
include ActionView::Helpers::JavaScriptHelper
|
||||
|
||||
before do
|
||||
@feat = MarkdownFeature.new
|
||||
|
||||
# `markdown` helper expects a `@project` variable
|
||||
@project = @feat.project
|
||||
|
||||
visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
|
||||
end
|
||||
|
||||
# Should have an entry for every filter in lib/banzai/pipeline/gfm_pipeline.rb
|
||||
# and app/assets/javascripts/copy_as_gfm.js.es6
|
||||
filters = {
|
||||
'any filter' => [
|
||||
[
|
||||
'crazy nesting',
|
||||
'> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
|
||||
],
|
||||
[
|
||||
'real world example from the gitlab-ce README',
|
||||
<<-GFM.strip_heredoc
|
||||
# GitLab
|
||||
|
||||
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
|
||||
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
|
||||
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
|
||||
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
|
||||
|
||||
## Canonical source
|
||||
|
||||
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
|
||||
|
||||
## Open source software to collaborate on code
|
||||
|
||||
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
|
||||
|
||||
|
||||
- Manage Git repositories with fine grained access controls that keep your code secure
|
||||
|
||||
- Perform code reviews and enhance collaboration with merge requests
|
||||
|
||||
- Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
|
||||
|
||||
- Each project can also have an issue tracker, issue board, and a wiki
|
||||
|
||||
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
|
||||
|
||||
- Completely free and open source (MIT Expat license)
|
||||
GFM
|
||||
]
|
||||
],
|
||||
'InlineDiffFilter' => [
|
||||
'{-Deleted text-}',
|
||||
'{+Added text+}'
|
||||
],
|
||||
'TaskListFilter' => [
|
||||
'- [ ] Unchecked task',
|
||||
'- [x] Checked task',
|
||||
'1. [ ] Unchecked numbered task',
|
||||
'1. [x] Checked numbered task'
|
||||
],
|
||||
'ReferenceFilter' => [
|
||||
['issue reference', -> { @feat.issue.to_reference }],
|
||||
['full issue reference', -> { @feat.issue.to_reference(full: true) }],
|
||||
['issue URL', -> { namespace_project_issue_url(@project.namespace, @project, @feat.issue) }],
|
||||
['issue URL with note anchor', -> { namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123') }],
|
||||
['issue link', -> { "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})" }],
|
||||
['issue link with note anchor', -> { "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})" }],
|
||||
],
|
||||
'AutolinkFilter' => [
|
||||
'https://example.com'
|
||||
],
|
||||
'TableOfContentsFilter' => [
|
||||
'[[_TOC_]]'
|
||||
],
|
||||
'EmojiFilter' => [
|
||||
':thumbsup:'
|
||||
],
|
||||
'ImageLinkFilter' => [
|
||||
'![Image](https://example.com/image.png)'
|
||||
],
|
||||
'VideoLinkFilter' => [
|
||||
'![Video](https://example.com/video.mp4)'
|
||||
],
|
||||
'MathFilter' => [
|
||||
'$`c = \pm\sqrt{a^2 + b^2}`$',
|
||||
[
|
||||
'math block',
|
||||
<<-GFM.strip_heredoc
|
||||
```math
|
||||
c = \pm\sqrt{a^2 + b^2}
|
||||
```
|
||||
GFM
|
||||
]
|
||||
],
|
||||
'SyntaxHighlightFilter' => [
|
||||
[
|
||||
'code block',
|
||||
<<-GFM.strip_heredoc
|
||||
```ruby
|
||||
def foo
|
||||
bar
|
||||
end
|
||||
```
|
||||
GFM
|
||||
]
|
||||
],
|
||||
'MarkdownFilter' => [
|
||||
'`code`',
|
||||
'`` code with ` ticks ``',
|
||||
|
||||
'> Quote',
|
||||
[
|
||||
'multiline quote',
|
||||
<<-GFM.strip_heredoc,
|
||||
> Multiline
|
||||
> Quote
|
||||
>
|
||||
> With multiple paragraphs
|
||||
GFM
|
||||
],
|
||||
|
||||
'![Image](https://example.com/image.png)',
|
||||
|
||||
'# Heading with no anchor link',
|
||||
|
||||
'[Link](https://example.com)',
|
||||
|
||||
'- List item',
|
||||
[
|
||||
'multiline list item',
|
||||
<<-GFM.strip_heredoc,
|
||||
- Multiline
|
||||
List item
|
||||
GFM
|
||||
],
|
||||
[
|
||||
'nested lists',
|
||||
<<-GFM.strip_heredoc,
|
||||
- Nested
|
||||
|
||||
|
||||
- Lists
|
||||
GFM
|
||||
],
|
||||
'1. Numbered list item',
|
||||
[
|
||||
'multiline numbered list item',
|
||||
<<-GFM.strip_heredoc,
|
||||
1. Multiline
|
||||
Numbered list item
|
||||
GFM
|
||||
],
|
||||
[
|
||||
'nested numbered list',
|
||||
<<-GFM.strip_heredoc,
|
||||
1. Nested
|
||||
|
||||
|
||||
1. Numbered lists
|
||||
GFM
|
||||
],
|
||||
|
||||
'# Heading',
|
||||
'## Heading',
|
||||
'### Heading',
|
||||
'#### Heading',
|
||||
'##### Heading',
|
||||
'###### Heading',
|
||||
|
||||
'**Bold**',
|
||||
|
||||
'_Italics_',
|
||||
|
||||
'~~Strikethrough~~',
|
||||
|
||||
'2^2',
|
||||
|
||||
'-----',
|
||||
|
||||
[
|
||||
'table',
|
||||
<<-GFM.strip_heredoc,
|
||||
| Centered | Right | Left |
|
||||
| :------: | ----: | ---- |
|
||||
| Foo | Bar | **Baz** |
|
||||
| Foo | Bar | **Baz** |
|
||||
GFM
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
filters.each do |filter, examples|
|
||||
context filter do
|
||||
examples.each do |ex|
|
||||
if ex.is_a?(String)
|
||||
desc = "'#{ex}'"
|
||||
gfm = ex
|
||||
else
|
||||
desc, gfm = ex
|
||||
end
|
||||
|
||||
it "transforms #{desc} to HTML and back to GFM" do
|
||||
gfm = instance_exec(&gfm) if gfm.is_a?(Proc)
|
||||
|
||||
html = markdown(gfm)
|
||||
gfm2 = html_to_gfm(html)
|
||||
expect(gfm2.strip).to eq(gfm.strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def html_to_gfm(html)
|
||||
js = <<-JS.strip_heredoc
|
||||
(function(html) {
|
||||
var node = document.createElement('div');
|
||||
node.innerHTML = html;
|
||||
return window.gl.CopyAsGFM.nodeToGFM(node);
|
||||
})("#{escape_javascript(html)}")
|
||||
JS
|
||||
page.evaluate_script(js)
|
||||
end
|
||||
|
||||
# Fake a `current_user` helper
|
||||
def current_user
|
||||
@feat.user
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue