Feature #29214 » 0001-Add-copy-button-to-pre-elements.patch
app/assets/images/icons.svg | ||
---|---|---|
131 | 131 |
<path d="M13 17v-1a1 1 0 0 1 1 -1h1m3 0h1a1 1 0 0 1 1 1v1m0 3v1a1 1 0 0 1 -1 1h-1m-3 0h-1a1 1 0 0 1 -1 -1v-1"/> |
132 | 132 |
<path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/> |
133 | 133 |
</symbol> |
134 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--copy-pre-content"> |
|
135 |
<path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2"/> |
|
136 |
<path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/> |
|
137 |
</symbol> |
|
134 | 138 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--custom-fields"> |
135 | 139 |
<path d="M20 13v-4a2 2 0 0 0 -2 -2h-12a2 2 0 0 0 -2 2v5a2 2 0 0 0 2 2h6"/> |
136 | 140 |
<path d="M15 19l2 2l4 -4"/> |
app/assets/javascripts/application.js | ||
---|---|---|
61 | 61 |
iconElement.setAttribute('href', iconPath.replace(/#.*$/g, "#icon--" + icon)) |
62 | 62 |
} |
63 | 63 | |
64 |
function createSVGIcon(icon) { |
|
65 |
const clonedIcon = document.querySelector('#icon-copy-source svg').cloneNode(true); |
|
66 |
updateSVGIcon(clonedIcon, icon); |
|
67 |
return clonedIcon |
|
68 |
} |
|
69 | ||
64 | 70 |
function collapseAllRowGroups(el) { |
65 | 71 |
var tbody = $(el).parents('tbody').first(); |
66 | 72 |
tbody.children('tr').each(function(index) { |
... | ... | |
210 | 216 |
case "list_status": |
211 | 217 |
case "list_subprojects": |
212 | 218 |
const iconType = values.length > 1 ? 'toggle-minus' : 'toggle-plus'; |
213 |
const clonedIcon = document.querySelector('#icon-copy-source svg').cloneNode(true); |
|
214 |
updateSVGIcon(clonedIcon, iconType); |
|
219 |
const iconSvg = createSVGIcon(iconType) |
|
215 | 220 | |
216 | 221 |
tr.find('.values').append( |
217 | 222 |
$('<span>', { style: 'display:none;' }).append( |
... | ... | |
221 | 226 |
name: `v[${field}][]`, |
222 | 227 |
}), |
223 | 228 |
'\n', |
224 |
$('<span>', { class: `toggle-multiselect icon-only icon-${iconType}` }).append(clonedIcon)
|
|
229 |
$('<span>', { class: `toggle-multiselect icon-only icon-${iconType}` }).append(iconSvg)
|
|
225 | 230 |
) |
226 | 231 |
); |
227 | 232 |
select = tr.find('.values select'); |
... | ... | |
642 | 647 |
return false; |
643 | 648 |
} |
644 | 649 | |
650 |
function setupCopyButtonsToPreElements() { |
|
651 |
document.querySelectorAll('pre:not(.pre-wrapper pre)').forEach((pre) => { |
|
652 |
// Wrap the <pre> element with a container and add a copy button |
|
653 |
const wrapper = document.createElement("div"); |
|
654 |
wrapper.classList.add("pre-wrapper"); |
|
655 | ||
656 |
const copyButton = document.createElement("a"); |
|
657 |
copyButton.title = rm.I18n.buttonCopy; |
|
658 |
copyButton.classList.add("copy-pre-content-link", "icon-only"); |
|
659 |
copyButton.append(createSVGIcon("copy-pre-content")); |
|
660 | ||
661 |
wrapper.appendChild(copyButton); |
|
662 |
wrapper.append(pre.cloneNode(true)); |
|
663 |
pre.replaceWith(wrapper); |
|
664 | ||
665 |
// Copy the contents of the pre tag when copyButton is clicked |
|
666 |
copyButton.addEventListener("click", (event) => { |
|
667 |
event.preventDefault(); |
|
668 |
let textToCopy = (pre.querySelector("code") || pre).textContent.replace(/\n$/, ''); |
|
669 |
if (pre.querySelector("code.syntaxhl")) { textToCopy = textToCopy.replace(/ $/, ''); } // Workaround for half-width space issue in Textile's highlighted code |
|
670 |
navigator.clipboard.writeText(textToCopy).then(() => { |
|
671 |
updateSVGIcon(copyButton, "checked"); |
|
672 |
setTimeout(() => updateSVGIcon(copyButton, "copy-pre-content"), 2000); |
|
673 |
}); |
|
674 |
}); |
|
675 |
}); |
|
676 |
} |
|
677 | ||
645 | 678 |
function updateIssueFrom(url, el) { |
646 | 679 |
$('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){ |
647 | 680 |
$(this).data('valuebeforeupdate', $(this).val()); |
... | ... | |
1158 | 1191 |
}); |
1159 | 1192 |
} |
1160 | 1193 | |
1161 |
$(function () {
|
|
1194 |
function setupHoverTooltips() {
|
|
1162 | 1195 |
$("[title]:not(.no-tooltip)").tooltip({ |
1163 | 1196 |
show: { |
1164 | 1197 |
delay: 400 |
... | ... | |
1168 | 1201 |
at: "center top" |
1169 | 1202 |
} |
1170 | 1203 |
}); |
1171 |
}); |
|
1204 |
} |
|
1205 | ||
1206 |
$(function() { setupHoverTooltips(); }); |
|
1172 | 1207 | |
1173 | 1208 |
function inlineAutoComplete(element) { |
1174 | 1209 |
'use strict'; |
... | ... | |
1362 | 1397 |
$(document).on('focus', '[data-auto-complete=true]', function(event) { |
1363 | 1398 |
inlineAutoComplete(event.target); |
1364 | 1399 |
}); |
1400 |
document.addEventListener("DOMContentLoaded", () => { setupCopyButtonsToPreElements(); }); |
app/assets/stylesheets/application.css | ||
---|---|---|
1498 | 1498 |
div.wiki li {line-height: 1.6; margin-bottom: 0.125rem;} |
1499 | 1499 |
div.wiki li>ul, div.wiki li>ol {margin-bottom: 0;} |
1500 | 1500 | |
1501 |
div.wiki div.pre-wrapper { |
|
1502 |
position: relative; |
|
1503 |
padding-right: 10px; |
|
1504 |
} |
|
1505 | ||
1501 | 1506 |
div.wiki pre { |
1502 | 1507 |
margin: 1em 1em 1em 1.6em; |
1503 | 1508 |
padding: 8px; |
... | ... | |
1515 | 1520 |
border-radius: 0.1em; |
1516 | 1521 |
} |
1517 | 1522 | |
1523 |
div.pre-wrapper a.copy-pre-content-link { |
|
1524 |
position: absolute; |
|
1525 |
top: 0px; |
|
1526 |
right: 8px; |
|
1527 |
cursor: pointer; |
|
1528 |
display: none; |
|
1529 |
} |
|
1530 | ||
1531 |
div.pre-wrapper:hover a.copy-pre-content-link { |
|
1532 |
display: block; |
|
1533 |
} |
|
1534 | ||
1518 | 1535 |
div.wiki ul.toc { |
1519 | 1536 |
background-color: #ffffdd; |
1520 | 1537 |
border: 1px solid #e4e4e4; |
app/helpers/application_helper.rb | ||
---|---|---|
1915 | 1915 |
end |
1916 | 1916 |
end |
1917 | 1917 | |
1918 |
def heads_for_i18n |
|
1919 |
javascript_tag( |
|
1920 |
"rm = window.rm || {};" \ |
|
1921 |
"rm.I18n = rm.I18n || {};" \ |
|
1922 |
"rm.I18n = Object.freeze({buttonCopy: '#{l(:button_copy)}'});" |
|
1923 |
) |
|
1924 |
end |
|
1925 | ||
1918 | 1926 |
def heads_for_auto_complete(project) |
1919 | 1927 |
data_sources = autocomplete_data_sources(project) |
1920 | 1928 |
javascript_tag( |
app/views/journals/update.js.erb | ||
---|---|---|
15 | 15 |
journal_header.append('<%= escape_javascript(render_journal_update_info(@journal)) %>'); |
16 | 16 |
} |
17 | 17 |
setupWikiTableSortableHeader(); |
18 |
setupCopyButtonsToPreElements(); |
|
19 |
setupHoverTooltips(); |
|
18 | 20 |
<% end %> |
19 | 21 | |
20 | 22 |
<%= call_hook(:view_journals_update_js_bottom, { :journal => @journal }) %> |
app/views/layouts/base.html.erb | ||
---|---|---|
12 | 12 |
<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> |
13 | 13 |
<%= javascript_heads %> |
14 | 14 |
<%= heads_for_theme %> |
15 |
<%= heads_for_i18n %> |
|
15 | 16 |
<%= heads_for_auto_complete(@project) %> |
16 | 17 |
<%= call_hook :view_layouts_base_html_head %> |
17 | 18 |
<!-- page specific tags --> |
... | ... | |
129 | 130 | |
130 | 131 |
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div> |
131 | 132 |
<div id="ajax-modal" style="display:none;"></div> |
133 |
<div id="icon-copy-source" style="display: none;"><%= sprite_icon('') %></div> |
|
132 | 134 | |
133 | 135 |
</div> |
134 | 136 |
<%= call_hook :view_layouts_base_body_bottom %> |
app/views/queries/_filters.html.erb | ||
---|---|---|
22 | 22 |
<%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %> |
23 | 23 |
</div> |
24 | 24 | |
25 |
<div id="icon-copy-source" style="display: none;"><%= sprite_icon('') %></div> |
|
26 | 25 |
<%= hidden_field_tag 'f[]', '' %> |
27 | 26 |
<% include_calendar_headers_tags %> |
config/icon_source.yml | ||
---|---|---|
209 | 209 |
svg: square-rounded-plus |
210 | 210 |
- name: toggle-minus |
211 | 211 |
svg: square-rounded-minus |
212 |
- name: copy-pre-content |
|
213 |
svg: clipboard |
config/locales/en.yml | ||
---|---|---|
1193 | 1193 |
button_copy: Copy |
1194 | 1194 |
button_copy_and_follow: Copy and follow |
1195 | 1195 |
button_copy_link: Copy link |
1196 |
button_copied: Copied |
|
1196 | 1197 |
button_annotate: Annotate |
1197 | 1198 |
button_fetch_changesets: Fetch commits |
1198 | 1199 |
button_update: Update |