Skip to content

Commit 54382a4

Browse files
authored
[TASK]: Add all-documentations-menu WebComponent (#742)
Closes #566 For presentation purposes, all data is mocked as a static JS object
1 parent 411fb1e commit 54382a4

File tree

53 files changed

+1703
-1056
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1703
-1056
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
2+
class AllDocumentationMenu extends HTMLElement {
3+
MAINMENU_JSON_URL = 'https://docs.typo3.org/h/typo3/docs-homepage/main/en-us/mainmenu.json';
4+
5+
constructor() {
6+
super();
7+
this.mainButton = this.createMainButton('All documentations');
8+
this.appendChild(this.mainButton);
9+
10+
this.initializeDocumentationsData()
11+
.then(() => {
12+
this.setupComponent()
13+
});
14+
}
15+
16+
async initializeDocumentationsData() {
17+
const url = this.getAttribute('data-override-url') || this.MAINMENU_JSON_URL;
18+
const response = await fetch(url)
19+
if (!response.ok) {
20+
this.data = [];
21+
return
22+
}
23+
24+
const json = await response.json();
25+
this.data = json || [];
26+
}
27+
28+
setupComponent() {
29+
this.classList.add('all-documentations-menu')
30+
31+
this.tooltip = this.createTooltip();
32+
this.appendChild(this.tooltip);
33+
34+
this.popperInstance = null;
35+
36+
this.mainButton.addEventListener('click', (event) => {
37+
event.stopPropagation();
38+
this.toggleTooltip();
39+
});
40+
41+
// hide popup on outside click
42+
document.addEventListener('click', (event) => {
43+
if (!this.tooltip.hasAttribute('data-show')) {
44+
return;
45+
}
46+
47+
if (event.target?.closest('.all-documentations-menu-tooltip')) {
48+
return
49+
}
50+
51+
this.hideTooltip();
52+
})
53+
}
54+
55+
createClassName(name) {
56+
return `all-documentations-menu-${name}`;
57+
}
58+
59+
/** Button */
60+
61+
createMainButton(text) {
62+
const element = document.createElement('button')
63+
element.classList.add(
64+
'btn', 'btn-light',
65+
this.createClassName('button'),
66+
);
67+
element.innerHTML = text;
68+
69+
const icon = document.createElement('i');
70+
icon.classList.add('fa-solid', 'fa-bars');
71+
element.prepend(icon);
72+
73+
return element;
74+
}
75+
76+
/** Documentations popup */
77+
78+
createDocumentationCategoryHeader(text, href) {
79+
let headerElement;
80+
if (href) {
81+
headerElement = document.createElement('a');
82+
headerElement.setAttribute('href', href)
83+
} else {
84+
headerElement = document.createElement('div')
85+
}
86+
87+
headerElement.classList.add(this.createClassName('category-header'))
88+
headerElement.innerHTML = text;
89+
90+
return headerElement;
91+
}
92+
93+
createDocumentationVersionBadge(version) {
94+
const element = document.createElement('a');
95+
element.setAttribute('href', version.href)
96+
element.innerHTML = version.name;
97+
return element;
98+
}
99+
100+
createDocumentationLink(documentation) {
101+
const listItemElement = document.createElement('li');
102+
const anchorElement = document.createElement('a');
103+
anchorElement.setAttribute('href', documentation.href);
104+
anchorElement.innerHTML = documentation.name;
105+
106+
listItemElement.appendChild(anchorElement);
107+
108+
if (!documentation.versions || !documentation.versions.length) {
109+
return listItemElement;
110+
}
111+
112+
const versionsElement = document.createElement('div');
113+
versionsElement.classList.add(this.createClassName('versions'));
114+
115+
for (const version of documentation.versions) {
116+
versionsElement.appendChild(this.createDocumentationVersionBadge(version))
117+
}
118+
119+
listItemElement.appendChild(versionsElement);
120+
121+
return listItemElement;
122+
}
123+
124+
createDocumentationCategory(category) {
125+
const section = document.createElement('div');
126+
section.classList.add('category');
127+
128+
const header = this.createDocumentationCategoryHeader(category.name, category.href);
129+
section.appendChild(header);
130+
131+
if (!category.children || !category.children.length) {
132+
return section;
133+
}
134+
135+
const docsListElement = document.createElement('ul');
136+
docsListElement.classList.add(this.createClassName('documentations'))
137+
138+
for (const child of category.children) {
139+
docsListElement.appendChild(this.createDocumentationLink(child));
140+
}
141+
142+
section.appendChild(docsListElement);
143+
144+
return section;
145+
}
146+
147+
createTooltip() {
148+
const element = document.createElement('div');
149+
element.classList.add(this.createClassName('tooltip'));
150+
element.setAttribute('role', 'topoltip');
151+
152+
const arrowElement = document.createElement('div');
153+
arrowElement.classList.add(this.createClassName('tooltip-arrow'));
154+
arrowElement.setAttribute('data-popper-arrow', '')
155+
element.appendChild(arrowElement);
156+
157+
const categoriesElement = document.createElement('div');
158+
categoriesElement.classList.add(this.createClassName('categories'));
159+
160+
for (const category of this.data) {
161+
categoriesElement.appendChild(this.createDocumentationCategory(category));
162+
}
163+
164+
element.appendChild(categoriesElement);
165+
166+
return element;
167+
}
168+
169+
toggleTooltip() {
170+
if (this.tooltip.hasAttribute('data-show')) {
171+
this.hideTooltip();
172+
} else {
173+
this.showTooltip();
174+
}
175+
}
176+
177+
showTooltip() {
178+
this.tooltip.setAttribute('data-show', '');
179+
180+
this.popperInstance = Popper.createPopper(this.mainButton, this.tooltip, {
181+
placement: 'bottom',
182+
modifiers: [
183+
{ name: 'arrow' },
184+
{ name: 'offset', options: { offset: [0, 10] } }
185+
],
186+
});
187+
}
188+
189+
hideTooltip() {
190+
this.tooltip.removeAttribute('data-show');
191+
if (this.popperInstance) {
192+
this.popperInstance.destroy();
193+
this.popperInstance = null;
194+
}
195+
}
196+
}
197+
198+
customElements.define('all-documentations-menu', AllDocumentationMenu)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
# This is a Proxy file to request a URL from docs.typo.org
4+
# and pass the result along to the local development.
5+
# It is used for debugging the "all documentations menu"
6+
# endpoint.
7+
# Since no active PHP files are part of the local DDEV instance by default,
8+
# you need to manually put that file into the document root:
9+
# $> ln -s ../packages/typo3-docs-theme/assets/js/menu-proxy.php Documentation-GENERATED-temp/menu-proxy.php
10+
11+
$proxyUrl = 'https://docs.typo3.org/h/typo3/docs-homepage/main/en-us/mainmenu.json';
12+
13+
$ch = curl_init();
14+
curl_setopt($ch, CURLOPT_URL, $proxyUrl);
15+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
16+
curl_setopt($ch, CURLOPT_HEADER, true);
17+
$response = curl_exec($ch);
18+
19+
if (curl_errno($ch) || $response === false) {
20+
header('404 Not Found');
21+
echo 'cURL error: ' . curl_error($ch);
22+
curl_close($ch);
23+
exit();
24+
}
25+
$response = (string)$response;
26+
27+
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
28+
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
29+
$headers = substr($response, 0, $header_size);
30+
$body = substr($response, $header_size);
31+
curl_close($ch);
32+
http_response_code($http_code);
33+
$headers_array = explode("\r\n", $headers);
34+
35+
foreach ($headers_array as $header) {
36+
if (!empty($header) && !preg_match('/^Transfer-Encoding:/i', $header) && !preg_match('/^Content-Length:/i', $header)) {
37+
header($header);
38+
}
39+
}
40+
41+
echo $body;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
.all-documentations-menu {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
white-space: nowrap;
6+
7+
&-button {
8+
display: flex;
9+
align-items: center;
10+
gap: 0.4em;
11+
}
12+
13+
&-tooltip {
14+
display: none;
15+
border-radius: 0.6em;
16+
background: white;
17+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
18+
padding: 1.5em 2.5em;
19+
border: 1px solid #eaeaea;
20+
z-index: 50;
21+
22+
&[data-show] {
23+
display: block;
24+
}
25+
26+
&-arrow {
27+
width: 1.2em;
28+
height: 1.2em;
29+
top: -0.2em;
30+
31+
&:before {
32+
display: block;
33+
content: '';
34+
width: 100%;
35+
height: 100%;
36+
transform: rotateZ(45deg);
37+
background-color: white;
38+
}
39+
}
40+
41+
@media screen and (max-width: 768px) {
42+
padding: 1.5em;
43+
}
44+
}
45+
46+
&-categories {
47+
display: grid;
48+
grid-template-columns: repeat(3, 1fr);
49+
gap: 1em 2em;
50+
51+
@media screen and (max-width: 1200px) {
52+
grid-template-columns: repeat(2, 1fr)
53+
}
54+
55+
@media screen and (max-width: 768px) {
56+
grid-template-columns: 1fr;
57+
}
58+
}
59+
60+
&-category {
61+
&-header {
62+
font-size: 1.1em;
63+
font-weight: 700;
64+
color: #333;
65+
position: relative;
66+
text-decoration: none;
67+
68+
&:before {
69+
display: block;
70+
content: '';
71+
width: 0.2em;
72+
height: 100%;
73+
background: $primary;
74+
position: absolute;
75+
top: 0;
76+
left: -0.6em;
77+
}
78+
}
79+
}
80+
81+
a.all-documentations-menu-category-header {
82+
&:hover, &:focus {
83+
text-decoration: .1em underline;
84+
}
85+
}
86+
87+
&-documentations {
88+
display: flex;
89+
flex-direction: column;
90+
gap: 0.2em;
91+
list-style-type: none;
92+
padding: 0.4em 0 0 0;
93+
94+
li {
95+
display: flex;
96+
flex-direction: row;
97+
flex-wrap: wrap;
98+
gap: 0.4em;
99+
100+
a:not(:hover):not(:focus) {
101+
text-decoration: none;
102+
}
103+
}
104+
}
105+
106+
&-versions {
107+
display: flex;
108+
flex-direction: row;
109+
align-items: center;
110+
gap: 0.3em;
111+
112+
a {
113+
background-color: $light;
114+
padding: 0 5px;
115+
font-size: 0.8em;
116+
border-radius: 0.4em;
117+
display: flex;
118+
align-items: center;
119+
120+
&:first-child {
121+
background-color: lighten($primary, 40%);
122+
}
123+
}
124+
}
125+
}

packages/typo3-docs-theme/assets/sass/layout/_structure.scss

+14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@
2626
margin: 0 auto;
2727
padding: 0 calc(#{$grid-gutter-width} / 2);
2828
}
29+
.menu-search-wrapper {
30+
display: flex;
31+
align-items: center;
32+
gap: 0.4em;
33+
34+
search {
35+
flex-grow: 1;
36+
}
37+
38+
@media screen and (max-width: 576px) {
39+
flex-direction: column;
40+
align-items: start;
41+
}
42+
}
2943

3044
/**
3145
* Main

packages/typo3-docs-theme/assets/sass/theme.scss

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
@import 'components/tabs';
4242
@import 'components/textroles';
4343
@import 'components/uml';
44+
@import 'components/allDocumentationMenu';
4445

4546
// Additional classes that can be applied by .. rst-class::
4647
@import 'components/customClasses/bignums';

0 commit comments

Comments
 (0)