Skip to content

Commit 6c7a1dc

Browse files
pikinier20tanishiking
authored andcommitted
Add snippet includes feature. Introduce new snippet UX/UI.
1 parent 78d47f9 commit 6c7a1dc

File tree

10 files changed

+441
-114
lines changed

10 files changed

+441
-114
lines changed

scaladoc-js/resources/scaladoc-searchbar.css

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

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

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

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

0 commit comments

Comments
 (0)