Skip to content

Commit 3842ae1

Browse files
authored
Merge pull request #13217 from pikinier20/scaladoc/snippet-includes
Add snippet includes feature. Introduce new snippet UX/UI.
2 parents 038cc73 + 36c7c36 commit 3842ae1

21 files changed

+502
-165
lines changed

scaladoc-js/resources/scaladoc-searchbar.css

Lines changed: 201 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -124,34 +124,206 @@
124124
margin-left: auto;
125125
}
126126

127-
.snippet-comment-button {
128-
position: absolute;
127+
/* Snippets */
128+
129+
.snippet {
130+
cursor: default;
131+
}
132+
133+
.snippet .snippet-meta {
134+
border-top: 2px solid var(--inactive-bg);
135+
color: var(--inactive-fg);
136+
margin-top: 10px;
137+
padding-top: 10px;
138+
font-size: 0.75em;
139+
}
140+
141+
.snippet-meta .snippet-label {
142+
font-weight: bold;
143+
}
144+
145+
.snippet .buttons {
146+
--icon-size: 16px;
147+
}
148+
149+
.snippet-showhide {
150+
display: flex;
151+
flex-direction: row;
152+
align-items: center;
153+
--slider-width: 40px;
154+
--slider-height: 16px;
155+
--slider-diameter: calc(var(--slider-height) - 4px);
156+
}
157+
158+
.snippet-showhide p {
159+
margin-left: 4px;
160+
margin-bottom: 0;
161+
margin-top: 0;
162+
color: var(--inactive-fg);
163+
}
164+
165+
.snippet-showhide-button {
129166
display: inline-block;
130-
left: 50%;
131-
width: 24px;
132-
height: 24px;
133-
background:
134-
linear-gradient(#fff, #fff),
135-
linear-gradient(#fff, #fff),
136-
#aaa;
137-
background-position: center;
138-
background-size: 50% 2px, 2px 50%;
139-
background-repeat: no-repeat;
140-
border-radius: 12px;
141-
box-shadow: 0 0 2px var(--black);
142-
}
143-
144-
.snippet-comment-button:hover {
145-
background:
146-
linear-gradient(#444, #444),
147-
linear-gradient(#444, #444),
148-
#ddd;
149-
background-position: center;
150-
background-size: 50% 2px, 2px 50%;
151-
background-repeat: no-repeat;
152-
}
153-
154-
.hide-snippet-comments-button {
155-
-ms-transform: rotate(45deg);
156-
transform: rotate(45deg);
167+
position: relative;
168+
width: var(--slider-width);
169+
height: var(--slider-height);
170+
margin-bottom: 0;
171+
}
172+
173+
.snippet-showhide-button input {
174+
opacity: 0;
175+
width: 0;
176+
height: 0;
177+
}
178+
179+
.snippet-showhide-button .slider {
180+
position: absolute;
181+
cursor: pointer;
182+
top: 0;
183+
left: 0;
184+
right: 0;
185+
bottom: 0;
186+
background-color: var(--inactive-bg);
187+
-webkit-transition: .4s;
188+
transition: .4s;
189+
border-radius: var(--slider-height);
190+
}
191+
192+
.snippet-showhide-button .slider:before {
193+
position: absolute;
194+
content: "";
195+
height: var(--slider-diameter);
196+
width: var(--slider-diameter);
197+
left: 2px;
198+
bottom: 2px;
199+
background-color: var(--inactive-fg);
200+
-webkit-transition: .4s;
201+
transition: .4s;
202+
border-radius: 50%;
203+
}
204+
205+
.snippet-showhide-button .slider:hover::before {
206+
background-color: var(--active-fg);
207+
}
208+
209+
input:checked + .slider {
210+
background-color: var(--active-bg);
211+
}
212+
213+
input:focus + .slider {
214+
box-shadow: 0 0 1px var(--active-bg-shadow);
215+
}
216+
217+
input:checked + .slider:before {
218+
--translation-size: calc(var(--slider-width) - var(--slider-diameter) - 4px);
219+
-webkit-transform: translateX(var(--translation-size));
220+
-ms-transform: translateX(var(--translation-size));
221+
transform: translateX(var(--translation-size));
222+
}
223+
224+
.snippet .buttons .tooltip::after {
225+
top: 32px;
226+
}
227+
228+
.snippet .buttons {
229+
display: flex;
230+
flex-direction: row-reverse;
231+
justify-content: flex-start;
232+
}
233+
234+
.snippet .buttons button {
235+
outline: none;
236+
background: none;
237+
border: none;
238+
font-size: var(--icon-size);
239+
color: var(--inactive-fg);
240+
cursor: pointer;
157241
}
242+
243+
.snippet .buttons button:hover:not(:disabled) {
244+
color: var(--inactive-fg-shadow)
245+
}
246+
247+
.snippet .buttons button:active:not(:disabled) {
248+
transform: translateY(2px);
249+
color: var(--active-fg)
250+
}
251+
252+
.snippet .buttons button:disabled {
253+
color: var(--inactive-bg)
254+
}
255+
256+
257+
.snippet .buttons>:not(:last-child) {
258+
border-left: 2px solid var(--inactive-bg);
259+
}
260+
261+
.snippet .buttons>* {
262+
padding-left: 5px;
263+
padding-right: 5px;
264+
}
265+
266+
.unselectable {
267+
-webkit-user-select: none;
268+
-moz-user-select: none;
269+
-ms-user-select: none;
270+
user-select: none;
271+
}
272+
273+
.included-section {
274+
display: flex;
275+
flex-direction: column;
276+
}
277+
278+
.included-section a {
279+
color: var(--inactive-fg) !important;
280+
font-size: 0.75em;
281+
}
282+
283+
.included-section b {
284+
font-weight: bold;
285+
}
286+
287+
.hideable.hidden {
288+
display: none;
289+
}
290+
291+
@media(max-width: 576px) {
292+
.snippet-showhide {
293+
--slider-width: 32px;
294+
--slider-height: 16px;
295+
}
296+
297+
.snippet .buttons {
298+
--icon-size: 16px;
299+
font-size: 16px;
300+
}
301+
302+
}
303+
304+
@media(max-width: 360px) {
305+
.snippet-showhide {
306+
--slider-width: 32px;
307+
--slider-height: 16px;
308+
}
309+
310+
.snippet .buttons {
311+
--icon-size: 16px;
312+
font-size: 0px;
313+
}
314+
315+
}
316+
317+
@media(max-width: 240px) {
318+
.snippet-showhide {
319+
--slider-width: 24px;
320+
--slider-height: 10px;
321+
}
322+
323+
.snippet .buttons {
324+
--icon-size: 16px;
325+
font-size: 0px;
326+
}
327+
328+
}
329+
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package dotty.tools.scaladoc
2+
3+
import org.scalajs.dom._
4+
import org.scalajs.dom.ext._
5+
6+
class CodeSnippets:
7+
8+
private def getButtonsSection(snippet: html.Element): Option[html.Div] = snippet.querySelector("div.buttons") match {
9+
case div: html.Div => Some(div)
10+
case _ => None
11+
}
12+
13+
def enrichSnippets() = document.querySelectorAll("div.snippet").foreach {
14+
case snippet: html.Element =>
15+
snippet.addEventListener("click", e => e.stopPropagation())
16+
snippetAnchor(snippet)
17+
handleHideableCode(snippet)
18+
handleImportedCode(snippet)
19+
copyRunButtons(snippet)
20+
}
21+
22+
private def handleHideableCode(snippet: html.Element): Unit = {
23+
def toggleHide(e: html.Element | html.Document) = e.querySelectorAll(".hideable").foreach {
24+
case e: html.Element => e.classList.toggle("hidden")
25+
case _ =>
26+
}
27+
def createShowHideButton(toggleRoot: html.Element) = {
28+
val div = document.createElement("div")
29+
div.classList.add("snippet-showhide")
30+
val p = document.createElement("p")
31+
p.textContent = "Show collapsed lines"
32+
val showHideButton = document.createElement("label")
33+
showHideButton.classList.add("snippet-showhide-button")
34+
val checkbox = document.createElement("input").asInstanceOf[html.Input]
35+
checkbox.`type` = "checkbox"
36+
val slider = document.createElement("span")
37+
slider.classList.add("slider")
38+
showHideButton.appendChild(checkbox)
39+
showHideButton.appendChild(slider)
40+
checkbox.addEventListener("change", _ => toggleHide(toggleRoot))
41+
div.appendChild(showHideButton)
42+
div.appendChild(p)
43+
div
44+
}
45+
46+
toggleHide(snippet)
47+
val buttonsSection = getButtonsSection(snippet)
48+
val hideables = snippet.querySelectorAll(".hideable")
49+
if hideables != null && hideables.nonEmpty then {
50+
val showHideButton = createShowHideButton(snippet)
51+
buttonsSection.foreach(_.appendChild(showHideButton))
52+
}
53+
}
54+
55+
private def snippetAnchor(snippet: html.Element): Unit = snippet.querySelector(".snippet-meta .snippet-label") match {
56+
case e: html.Element =>
57+
val name = e.textContent.trim
58+
val anchor = document.createElement("a").asInstanceOf[html.Anchor]
59+
anchor.id = s"snippet-$name"
60+
snippet.insertBefore(anchor, snippet.firstChild)
61+
case _ =>
62+
}
63+
64+
private def handleImportedCode(snippet: html.Element): Unit = {
65+
val included = snippet.querySelectorAll("code span.include")
66+
val pre = snippet.querySelector("pre")
67+
if included != null && included.nonEmpty && pre != null then {
68+
val includesDiv = document.createElement("div")
69+
includesDiv.classList.add("included-section")
70+
includesDiv.classList.add("hideable")
71+
included
72+
.collect { case e: html.Element => e }
73+
.toList
74+
.filter(_.hasAttribute("name"))
75+
.map(_.getAttribute("name"))
76+
.distinct
77+
.map { name =>
78+
val a = document.createElement("a").asInstanceOf[html.Anchor]
79+
a.classList.add("unselectable")
80+
a.href = s"#snippet-$name"
81+
a.innerHTML = s"included <b>$name</b>"
82+
a
83+
}
84+
.foreach(a => includesDiv.appendChild(a))
85+
86+
snippet.insertBefore(includesDiv, pre)
87+
}
88+
}
89+
90+
private def copyRunButtons(snippet: html.Element) = {
91+
def copyButton = {
92+
val div = document.createElement("div")
93+
val button = document.createElement("button")
94+
val icon = document.createElement("i")
95+
icon.classList.add("far")
96+
icon.classList.add("fa-clone")
97+
button.appendChild(icon)
98+
button.classList.add("copy-button")
99+
button.addEventListener("click", _ => {
100+
val code = snippet.querySelectorAll("code>span:not(.hidden)")
101+
.map(_.textContent)
102+
.mkString
103+
window.navigator.clipboard.writeText(code)
104+
})
105+
div.appendChild(button)
106+
div
107+
}
108+
def runButton = {
109+
val div = document.createElement("div")
110+
val button = document.createElement("button").asInstanceOf[html.Button]
111+
val icon = document.createElement("i")
112+
icon.classList.add("fas")
113+
icon.classList.add("fa-play")
114+
button.appendChild(icon)
115+
button.classList.add("run-button")
116+
button.addEventListener("click", _ => {}) // TODO: Run button #13065
117+
button.disabled = true
118+
div.appendChild(button)
119+
div
120+
}
121+
val buttonsSection = getButtonsSection(snippet)
122+
buttonsSection.foreach(s =>
123+
s.appendChild(copyButton)
124+
// Temporarily disabled
125+
// s.appendChild(runButton)
126+
)
127+
}
128+
129+
enrichSnippets()
130+

scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)