8275406: Add copy-to-clipboard feature to snippet UI

Reviewed-by: erikj, jjg
This commit is contained in:
Hannes Wallnöfer 2021-11-02 12:10:47 +00:00
parent 9971a2cab3
commit 8630f55ed7
14 changed files with 180 additions and 14 deletions

View File

@ -75,7 +75,7 @@ define SetupInterimModule
EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \
Standard.java, \
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java, \
COPY := .gif .png .xml .css .js .js.template .txt javax.tools.JavaCompilerTool, \
COPY := .gif .png .xml .css .svg .js .js.template .txt javax.tools.JavaCompilerTool, \
BIN := $(BUILDTOOLS_OUTPUTDIR)/interim_langtools_modules/$1.interim, \
DISABLED_WARNINGS := module options, \
JAVAC_FLAGS := \

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@ -23,4 +23,4 @@
# questions.
#
COPY += .xml .css .js .js.template .png .txt
COPY += .xml .css .svg .js .js.template .png .txt

View File

@ -290,6 +290,8 @@ public class HtmlDoclet extends AbstractDoclet {
}
f = DocFile.createFileForOutput(configuration, DocPaths.JAVASCRIPT);
f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.JAVASCRIPT), true, true);
f = DocFile.createFileForOutput(configuration, DocPaths.CLIPBOARD_SVG);
f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.CLIPBOARD_SVG), true, true);
if (options.createIndex()) {
f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_JS);
f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_JS_TEMPLATE), configuration.docResources);

View File

@ -53,6 +53,7 @@ import com.sun.source.doctree.SystemPropertyTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.util.DocTreePath;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlId;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
@ -381,12 +382,23 @@ public class TagletWriterImpl extends TagletWriter {
@Override
protected Content snippetTagOutput(Element element, SnippetTree tag, StyledText content) {
HtmlTree result = new HtmlTree(TagName.PRE).setStyle(HtmlStyle.snippet);
result.add(Text.of(utils.normalizeNewlines("\n")));
String copyText = resources.getText("doclet.Copy_snippet_to_clipboard");
String copiedText = resources.getText("doclet.Copied_snippet_to_clipboard");
HtmlTree copy = HtmlTree.DIV(HtmlStyle.snippetContainer,
HtmlTree.A("#", new HtmlTree(TagName.IMG)
.put(HtmlAttr.SRC, htmlWriter.pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath())
.put(HtmlAttr.ALT, copyText))
.addStyle(HtmlStyle.snippetCopy)
.put(HtmlAttr.ONCLICK, "copySnippet(this)")
.put(HtmlAttr.ARIA_LABEL, copyText)
.put(HtmlAttr.DATA_COPIED, copiedText));
HtmlTree pre = new HtmlTree(TagName.PRE)
.setStyle(HtmlStyle.snippet);
pre.add(Text.of(utils.normalizeNewlines("\n")));
content.consumeBy((styles, sequence) -> {
CharSequence text = utils.normalizeNewlines(sequence);
if (styles.isEmpty()) {
result.add(text);
pre.add(text);
} else {
Element e = null;
String t = null;
@ -431,10 +443,10 @@ public class TagletWriterImpl extends TagletWriter {
c = HtmlTree.SPAN(Text.of(sequence));
classes.forEach(((HtmlTree) c)::addStyle);
}
result.add(c);
pre.add(c);
}
});
return result;
return copy.add(pre);
}
/*

View File

@ -47,6 +47,7 @@ public enum HtmlAttr {
CLEAR,
COLS,
CONTENT,
DATA_COPIED("data-copied"), // custom HTML5 data attribute
DISABLED,
FOR,
HREF,

View File

@ -52,6 +52,16 @@ import java.util.regex.Pattern;
*/
public enum HtmlStyle {
/**
* The class of the {@code div} element containing a snippet element.
*/
snippetContainer,
/**
* The class of the {@code a} element to copy snippet content to the clipboard.
*/
snippetCopy,
//<editor-fold desc="navigation bar">
//
// The following constants are used for the main navigation bar that appears in the

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
This code is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 only, as
published by the Free Software Foundation. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the LICENSE file that accompanied this code.
This code is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
version 2 for more details (a copy is included in the LICENSE file that
accompanied this code).
You should have received a copy of the GNU General Public License version
2 along with this work; if not, write to the Free Software Foundation,
Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
or visit www.oracle.com if you need additional information or have any
questions.
-->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 460" fill="#505050">
<path
d="M 346,8 H 108 C 90,8 75,23 75,41 v 316 c 0,18 15,33 33,33 h 238 c 18,0 33,-15 33,-33 V 41 C 379,23 364,8 346,8 Z m -8,344 H 116 c -2,0 -3,-1 -3,-3 V 49 c 0,-2 1,-3 3,-3 h 222 c 2,0 3,1 3,3 v 300 h 10e-4 c 0,2 -1,3 -3,3 z"/>
<path
d="m 290,389 v 26 h 10e-4 c 0,2 -1,3 -3,3 H 49 c -2,0 -3,-1 -3,-3 V 99 c 0,-2 1,-3 3,-3 h 27 v 0 l -5e-4,-38 H 41 C 23,58 8,73 8,91 v 332 c 10e-4,18 15,33 33,33 h 254 c 18,0 33,-15 33,-33 v -34"/>
</svg>

View File

@ -54,6 +54,8 @@ for duplicates. Include error messages and the following diagnostic in your repo
doclet.File_not_found=File not found: {0}
doclet.Copy_Overwrite_warning=File {0} not copied to {1} due to existing file with same name...
doclet.Copy_Ignored_warning=File {0} not copied: invalid name
doclet.Copy_snippet_to_clipboard=Copy
doclet.Copied_snippet_to_clipboard=Copied!
doclet.Copying_File_0_To_Dir_1=Copying file {0} to directory {1}...
doclet.Copying_File_0_To_File_1=Copying file {0} to file {1}...
doclet.No_Public_Classes_To_Document=No public or protected classes found to document.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -106,6 +106,21 @@ function indexFilesLoaded() {
&& tagSearchIndex;
}
function copySnippet(link) {
var textarea = document.createElement("textarea");
textarea.style.height = 0;
document.body.appendChild(textarea);
textarea.value = link.nextElementSibling.innerText;
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
link.classList.add("copied");
var parent = link.parentElement;
parent.onmouseleave = parent.ontouchend = function() {
link.classList.remove("copied");
};
}
// Workaround for scroll position not being included in browser history (8249133)
document.addEventListener("DOMContentLoaded", function(e) {
var contentDiv = document.querySelector("div.flex-content");

View File

@ -933,7 +933,67 @@ pre.snippet {
overflow: auto;
white-space: pre;
}
div.snippet-container {
position: relative;
}
a.snippet-copy {
position: absolute;
top: 8px;
right: 8px;
}
a.snippet-copy img {
width: 18px;
height: 18px;
padding: 0.05em 0;
opacity: 50%;
transition: opacity 0.2s;
}
div.snippet-container:hover a.snippet-copy img {
opacity: 80%;
}
div.snippet-container a.snippet-copy:hover img {
opacity: 100%;
}
a.snippet-copy:active img {
background: #d3d3d3;
opacity: 100%;
}
a.snippet-copy::before {
color: #3d3d3d;
content: attr(aria-label);
font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;
font-size: 85%;
line-height: 1.2em;
padding: 0.2em;
position: absolute;
opacity: 80%;
transition: opacity 0.2s;
white-space: nowrap;
top: -0.01em;
right: 1.5em;
display: none;
}
div.snippet-container:hover a.snippet-copy::before {
display: inherit;
}
div.snippet-container a.snippet-copy:hover::before {
opacity: 100%;
}
a.snippet-copy.copied::before {
content: attr(data-copied);
}
a.snippet-copy:active::before {
background-color: #dadada;
}
@media screen and (max-width: 800px) {
pre.snippet {
padding-top: 26px;
}
a.snippet-copy {
top: 6px;
right: 6px;
}
}
pre.snippet .italic {
font-style: italic;
}

View File

@ -97,6 +97,9 @@ public class DocPaths {
/** The name of the default javascript file. */
public static final DocPath JAVASCRIPT = DocPath.create("script.js");
/** The name of the copy-to-clipboard icon file. */
public static final DocPath CLIPBOARD_SVG = DocPath.create("copy.svg");
/** The name of the stylesheet file overriding jQuery UI stylesheet. */
public static final DocPath JQUERY_OVERRIDES_CSS = DocPath.create("jquery-ui.overrides.css");

View File

@ -211,6 +211,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
Hello, Snippet!
</pre>
@ -852,6 +855,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
%s</pre>
</div>""".formatted(id, t.expectedOutput()));
@ -946,6 +952,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
%s</pre>
</div>""".formatted(index, expectedOutput));
@ -1505,6 +1514,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
%s</pre>
</div>""".formatted(index, t.expectedOutput()));
@ -1620,6 +1632,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case0</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="copySni\
ppet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.svg" alt="\
Copy"></a>
<pre class="snippet">
</pre>
</div>""");
@ -1627,6 +1642,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case1</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="copySni\
ppet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.svg" alt="\
Copy"></a>
<pre class="snippet">
</pre>
</div>""");
@ -1726,6 +1744,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
2</pre>
</div>
@ -1808,6 +1829,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
%s</pre>
</div>""".formatted(index, t.expectedOutput()));
@ -2138,6 +2162,9 @@ public class TestSnippetTag extends JavadocTester {
"""
<span class="element-name">case%s</span>()</div>
<div class="block">
<div class="snippet-container"><a href="#" class="snippet-copy" onclick="cop\
ySnippet(this)" aria-label="Copy" data-copied="Copied!"><img src="../copy.sv\
g" alt="Copy"></a>
<pre class="snippet">
%s</pre>
</div>""".formatted(index, t.expectedOutput()));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -351,7 +351,7 @@ public class LinkChecker extends HtmlChecker {
void addReference(String name, Path from, int line) {
if (checked) {
if (name != null) {
if (name != null && !name.isEmpty()) {
ID id = map.get(name);
if (id == null || !id.declared) {
error(from, line, "id not found: " + this.name + "#" + name);
@ -368,7 +368,7 @@ public class LinkChecker extends HtmlChecker {
void check() {
map.forEach((name, id) -> {
if (name != null && !id.declared) {
if (name != null && !name.isEmpty() && !id.declared) {
//log.error(currFile, 0, "id not declared: " + name);
for (Position ref : id.references) {
error(ref.path, ref.line, "id not found: " + this.name + "#" + name);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -196,6 +196,7 @@ class APITest {
"allclasses-index.html",
"allpackages-index.html",
"constant-values.html",
"copy.svg",
"deprecated-list.html",
"help-doc.html",
"index-all.html",