Commit 68903277 authored by Douwe Maan's avatar Douwe Maan

Copy code as GFM from diffs, blobs and GFM code blocks

parent b7166806
...@@ -118,10 +118,10 @@ const gfmRules = { ...@@ -118,10 +118,10 @@ const gfmRules = {
}, },
SyntaxHighlightFilter: { SyntaxHighlightFilter: {
'pre.code.highlight'(el, t) { 'pre.code.highlight'(el, t) {
const text = t.trim(); const text = t.trimRight();
let lang = el.getAttribute('lang'); let lang = el.getAttribute('lang');
if (lang === 'plaintext') { if (!lang || lang === 'plaintext') {
lang = ''; lang = '';
} }
...@@ -157,7 +157,7 @@ const gfmRules = { ...@@ -157,7 +157,7 @@ const gfmRules = {
const backticks = Array(backtickCount + 1).join('`'); const backticks = Array(backtickCount + 1).join('`');
const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
}, },
'blockquote'(el, text) { 'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
...@@ -273,28 +273,29 @@ const gfmRules = { ...@@ -273,28 +273,29 @@ const gfmRules = {
class CopyAsGFM { class CopyAsGFM {
constructor() { constructor() {
$(document).on('copy', '.md, .wiki', this.handleCopy); $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('paste', '.js-gfm-input', this.handlePaste); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
} }
handleCopy(e) { copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment(); const documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) return;
// If the documentFragment contains more than just Markdown, don't copy as GFM. const el = transformer(documentFragment.cloneNode(true));
if (documentFragment.querySelector('.md, .wiki')) return; if (!el) return;
e.preventDefault(); e.preventDefault();
clipboardData.setData('text/plain', documentFragment.textContent); e.stopPropagation();
const gfm = CopyAsGFM.nodeToGFM(documentFragment); clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', gfm); clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
} }
handlePaste(e) { pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
...@@ -306,7 +307,54 @@ class CopyAsGFM { ...@@ -306,7 +307,54 @@ class CopyAsGFM {
window.gl.utils.insertText(e.target, gfm); window.gl.utils.insertText(e.target, gfm);
} }
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
return documentFragment;
}
static transformCodeSelection(documentFragment) {
const lineEls = documentFragment.querySelectorAll('.line');
let codeEl;
if (lineEls.length > 1) {
codeEl = document.createElement('pre');
codeEl.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang');
if (lang) {
codeEl.setAttribute('lang', lang);
}
} else {
codeEl = document.createElement('code');
}
if (lineEls.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) {
const lineEl = lineEls[i];
codeEl.appendChild(lineEl);
codeEl.appendChild(document.createTextNode('\n'));
}
} else {
codeEl.appendChild(documentFragment);
}
return codeEl;
}
static selectionToGFM(documentFragment, transformer) {
const el = transformer(documentFragment.cloneNode(true));
if (!el) return null;
return CopyAsGFM.nodeToGFM(el);
}
static nodeToGFM(node) { static nodeToGFM(node) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
return node.textContent; return node.textContent;
} }
......
---
title: Copy code as GFM from diffs, blobs and GFM code blocks
merge_request:
author:
...@@ -5,8 +5,6 @@ module Banzai ...@@ -5,8 +5,6 @@ module Banzai
# HTML Filter to highlight fenced code blocks # HTML Filter to highlight fenced code blocks
# #
class SyntaxHighlightFilter < HTML::Pipeline::Filter class SyntaxHighlightFilter < HTML::Pipeline::Filter
include Rouge::Plugins::Redcarpet
def call def call
doc.search('pre > code').each do |node| doc.search('pre > code').each do |node|
highlight_node(node) highlight_node(node)
...@@ -23,7 +21,7 @@ module Banzai ...@@ -23,7 +21,7 @@ module Banzai
lang = lexer.tag lang = lexer.tag
begin begin
code = format(lex(lexer, code)) code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: lang)
css_classes << " js-syntax-highlight #{lang}" css_classes << " js-syntax-highlight #{lang}"
rescue rescue
...@@ -45,10 +43,6 @@ module Banzai ...@@ -45,10 +43,6 @@ module Banzai
lexer.lex(code) lexer.lex(code)
end end
def format(tokens)
rouge_formatter.format(tokens)
end
def lexer_for(language) def lexer_for(language)
(Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new (Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new
end end
...@@ -57,11 +51,6 @@ module Banzai ...@@ -57,11 +51,6 @@ module Banzai
# Replace the parent `pre` element with the entire highlighted block # Replace the parent `pre` element with the entire highlighted block
node.parent.replace(highlighted) node.parent.replace(highlighted)
end end
# Override Rouge::Plugins::Redcarpet#rouge_formatter
def rouge_formatter(lexer = nil)
@rouge_formatter ||= Rouge::Formatters::HTML.new
end
end end
end end
end end
...@@ -14,7 +14,7 @@ module Gitlab ...@@ -14,7 +14,7 @@ module Gitlab
end end
def initialize(blob_name, blob_content, repository: nil) def initialize(blob_name, blob_content, repository: nil)
@formatter = Rouge::Formatters::HTMLGitlab.new @formatter = Rouge::Formatters::HTMLGitlab
@repository = repository @repository = repository
@blob_name = blob_name @blob_name = blob_name
@blob_content = blob_content @blob_content = blob_content
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
hl_lexer = self.lexer hl_lexer = self.lexer
end end
@formatter.format(hl_lexer.lex(text, continue: continue)).html_safe @formatter.format(hl_lexer.lex(text, continue: continue), tag: hl_lexer.tag).html_safe
rescue rescue
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end end
......
...@@ -6,9 +6,10 @@ module Rouge ...@@ -6,9 +6,10 @@ module Rouge
# Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance. # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
# #
# [+linenostart+] The line number for the first line (default: 1). # [+linenostart+] The line number for the first line (default: 1).
def initialize(linenostart: 1) def initialize(linenostart: 1, tag: nil)
@linenostart = linenostart @linenostart = linenostart
@line_number = linenostart @line_number = linenostart
@tag = tag
end end
def stream(tokens, &b) def stream(tokens, &b)
...@@ -17,7 +18,7 @@ module Rouge ...@@ -17,7 +18,7 @@ module Rouge
yield "\n" unless is_first yield "\n" unless is_first
is_first = false is_first = false
yield %(<span id="LC#{@line_number}" class="line">) yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
line.each { |token, value| yield span(token, value.chomp) } line.each { |token, value| yield span(token, value.chomp) }
yield %(</span>) yield %(</span>)
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment