@brillout/docpress 0.7.7 → 0.7.9-commit-ea6d0e2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Layout.tsx +1 -1
- package/autoScrollNav.ts +20 -20
- package/components/Contributors.tsx +1 -0
- package/dist/autoScrollNav.d.ts +3 -0
- package/dist/autoScrollNav.js +36 -0
- package/dist/components/Contributors.js +1 -0
- package/dist/navigation/Navigation.d.ts +6 -0
- package/dist/navigation/Navigation.js +8 -5
- package/dist/navigation/navigation-fullscreen/NavigationFullscreenButton.js +2 -2
- package/navigation/Navigation-items.css +0 -3
- package/navigation/Navigation-layout.css +11 -7
- package/navigation/Navigation.tsx +18 -12
- package/navigation/initPressKit.ts +1 -1
- package/navigation/navigation-fullscreen/NavigationFullscreenButton.tsx +2 -0
- package/navigation/navigation-fullscreen/initNavigationFullscreen.ts +1 -88
- package/package.json +1 -1
- package/renderer/getCSSForResponsiveFullcreenNavItems.ts +99 -0
- package/renderer/onRenderClient.tsx +7 -22
- package/renderer/onRenderHtml.tsx +9 -0
package/Layout.tsx
CHANGED
|
@@ -42,7 +42,7 @@ function Layout({ children }: { children: React.ReactNode }) {
|
|
|
42
42
|
</a>
|
|
43
43
|
)}
|
|
44
44
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
45
|
-
<TopNavigationLink id="doclink" aria-label={hotkeyLabel} data-balloon-pos="left">
|
|
45
|
+
<TopNavigationLink id="doclink" aria-label={hotkeyLabel} data-balloon-pos="left" data-balloon-blunt>
|
|
46
46
|
Documentation
|
|
47
47
|
</TopNavigationLink>
|
|
48
48
|
{topNavigationList.map(({ title, url }) => (
|
package/autoScrollNav.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
export { autoScrollNav }
|
|
2
|
+
export const autoScrollNav_SSR = `autoScrollNav();${autoScrollNav.toString()}`
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// - We cannot use TypeScript syntax because of autoScrollNav_SSR
|
|
5
|
+
// - We have to save & restore `dodocument.documentElement.scrollTop` because scrollIntoView() scrolls the main view. (I don't know why).
|
|
6
|
+
// - Failed alternatives:
|
|
7
|
+
// - scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
|
|
8
|
+
// - Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering).
|
|
9
|
+
// ```js
|
|
10
|
+
// const navigationContainerEl = document.getElementById("navigation-container")
|
|
11
|
+
// const offset = navLink.offsetTop - (window.innerHeight / 2)
|
|
12
|
+
// navigationContainerEl.scrollTop = offset
|
|
13
|
+
// ```
|
|
14
|
+
// - Doesn't work: scrollIntoView() still scrolls the main view
|
|
15
|
+
// ```js
|
|
16
|
+
// const overflowOriginal = document.documentElement.style.overflow
|
|
17
|
+
// document.documentElement.style.overflow = 'hidden'
|
|
18
|
+
// // ...
|
|
19
|
+
// document.documentElement.style.overflow = overflowOriginal
|
|
20
|
+
// ```
|
|
4
21
|
|
|
5
22
|
function autoScrollNav() {
|
|
6
23
|
const navigationEl = document.getElementById('navigation-content-main')
|
|
7
|
-
|
|
24
|
+
if (!navigationEl) return
|
|
8
25
|
const href = window.location.pathname
|
|
9
|
-
const navLinks
|
|
10
|
-
assert(navLinks.length <= 1, { navLinks, href })
|
|
26
|
+
const navLinks = Array.from(navigationEl.querySelectorAll(`a[href="${href}"]`))
|
|
11
27
|
const navLink = navLinks[0]
|
|
12
28
|
if (!navLink) return
|
|
13
29
|
|
|
14
|
-
/* Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering)
|
|
15
|
-
const navigationContainerEl = document.getElementById("navigation-container")
|
|
16
|
-
const offset = navLink.offsetTop - (window.innerHeight / 2)
|
|
17
|
-
navigationContainerEl.scrollTop = offset
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/* Doesn't work: scrollIntoView() still scrolls the main view
|
|
21
|
-
const overflowOriginal = document.documentElement.style.overflow
|
|
22
|
-
document.documentElement.style.overflow = 'hidden'
|
|
23
|
-
...
|
|
24
|
-
document.documentElement.style.overflow = overflowOriginal
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
30
|
const scrollTopOriginal = document.documentElement.scrollTop
|
|
28
31
|
navLink.scrollIntoView({
|
|
29
|
-
// @ts-ignore https://github.com/microsoft/TypeScript/issues/46654
|
|
30
32
|
behavior: 'instant',
|
|
31
33
|
block: 'center',
|
|
32
34
|
inline: 'start',
|
|
33
35
|
})
|
|
34
|
-
// Avoid scrollIntoView() from scrolling the main view.
|
|
35
|
-
// - Alternatively scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
|
|
36
36
|
document.documentElement.scrollTop = scrollTopOriginal
|
|
37
37
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { autoScrollNav };
|
|
2
|
+
export var autoScrollNav_SSR = "autoScrollNav();".concat(autoScrollNav.toString());
|
|
3
|
+
// - We cannot use TypeScript syntax because of autoScrollNav_SSR
|
|
4
|
+
// - We have to save & restore `dodocument.documentElement.scrollTop` because scrollIntoView() scrolls the main view. (I don't know why).
|
|
5
|
+
// - Failed alternatives:
|
|
6
|
+
// - scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
|
|
7
|
+
// - Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering).
|
|
8
|
+
// ```js
|
|
9
|
+
// const navigationContainerEl = document.getElementById("navigation-container")
|
|
10
|
+
// const offset = navLink.offsetTop - (window.innerHeight / 2)
|
|
11
|
+
// navigationContainerEl.scrollTop = offset
|
|
12
|
+
// ```
|
|
13
|
+
// - Doesn't work: scrollIntoView() still scrolls the main view
|
|
14
|
+
// ```js
|
|
15
|
+
// const overflowOriginal = document.documentElement.style.overflow
|
|
16
|
+
// document.documentElement.style.overflow = 'hidden'
|
|
17
|
+
// // ...
|
|
18
|
+
// document.documentElement.style.overflow = overflowOriginal
|
|
19
|
+
// ```
|
|
20
|
+
function autoScrollNav() {
|
|
21
|
+
var navigationEl = document.getElementById('navigation-content-main');
|
|
22
|
+
if (!navigationEl)
|
|
23
|
+
return;
|
|
24
|
+
var href = window.location.pathname;
|
|
25
|
+
var navLinks = Array.from(navigationEl.querySelectorAll("a[href=\"".concat(href, "\"]")));
|
|
26
|
+
var navLink = navLinks[0];
|
|
27
|
+
if (!navLink)
|
|
28
|
+
return;
|
|
29
|
+
var scrollTopOriginal = document.documentElement.scrollTop;
|
|
30
|
+
navLink.scrollIntoView({
|
|
31
|
+
behavior: 'instant',
|
|
32
|
+
block: 'center',
|
|
33
|
+
inline: 'start',
|
|
34
|
+
});
|
|
35
|
+
document.documentElement.scrollTop = scrollTopOriginal;
|
|
36
|
+
}
|
|
@@ -2,6 +2,8 @@ export { Navigation };
|
|
|
2
2
|
export { NavigationMask };
|
|
3
3
|
export type { NavigationData };
|
|
4
4
|
export type { NavItem };
|
|
5
|
+
export { groupByLevelMin };
|
|
6
|
+
export type { NavItemGrouped };
|
|
5
7
|
import React from 'react';
|
|
6
8
|
import './Navigation.css';
|
|
7
9
|
type NavigationData = Parameters<typeof Navigation>[0];
|
|
@@ -18,3 +20,7 @@ type NavItem = {
|
|
|
18
20
|
title: string;
|
|
19
21
|
titleInNav: string;
|
|
20
22
|
};
|
|
23
|
+
type NavItemGrouped = ReturnType<typeof groupByLevelMin>[number];
|
|
24
|
+
declare function groupByLevelMin<T extends NavItem>(navItems: T[]): (T & {
|
|
25
|
+
navItemChilds: T[];
|
|
26
|
+
})[];
|
|
@@ -11,12 +11,15 @@ var __assign = (this && this.__assign) || function () {
|
|
|
11
11
|
};
|
|
12
12
|
export { Navigation };
|
|
13
13
|
export { NavigationMask };
|
|
14
|
+
// TODO/refactor: do this only on the server side?
|
|
15
|
+
export { groupByLevelMin };
|
|
14
16
|
import React from 'react';
|
|
15
17
|
import { NavigationHeader } from './NavigationHeader';
|
|
16
18
|
import { assert, Emoji, assertWarning, jsxToTextContent } from '../utils/server';
|
|
17
19
|
import './Navigation.css';
|
|
18
20
|
import { NavigationFullscreenClose } from './navigation-fullscreen/NavigationFullscreenButton';
|
|
19
21
|
import { parseTitle } from '../parseTitle';
|
|
22
|
+
import { autoScrollNav_SSR } from '../autoScrollNav';
|
|
20
23
|
function Navigation(_a) {
|
|
21
24
|
var navItems = _a.navItems, navItemsAll = _a.navItemsAll, currentUrl = _a.currentUrl, isDetachedPage = _a.isDetachedPage;
|
|
22
25
|
return (React.createElement(React.Fragment, null,
|
|
@@ -27,7 +30,8 @@ function Navigation(_a) {
|
|
|
27
30
|
navItems.length > 1 && (React.createElement(NavigationContent, { id: "navigation-content-detached", navItems: navItems, currentUrl: currentUrl })),
|
|
28
31
|
React.createElement(DetachedPageNote, null))),
|
|
29
32
|
React.createElement(NavigationContent, { id: "navigation-content-main", navItems: navItemsAll, currentUrl: currentUrl }),
|
|
30
|
-
React.createElement(NavigationFullscreenClose, null)))
|
|
33
|
+
React.createElement(NavigationFullscreenClose, null))),
|
|
34
|
+
React.createElement("script", { dangerouslySetInnerHTML: { __html: autoScrollNav_SSR } })));
|
|
31
35
|
}
|
|
32
36
|
function NavigationMask() {
|
|
33
37
|
return React.createElement("div", { id: "mobile-navigation-mask" });
|
|
@@ -35,10 +39,9 @@ function NavigationMask() {
|
|
|
35
39
|
function NavigationContent(props) {
|
|
36
40
|
var navItemsWithComputed = addComputedProps(props.navItems, props.currentUrl);
|
|
37
41
|
var navItemsGrouped = groupByLevelMin(navItemsWithComputed);
|
|
38
|
-
return (React.createElement("div", { id: props.id, className: "navigation-content" },
|
|
39
|
-
React.createElement(
|
|
40
|
-
|
|
41
|
-
navItemGroup.navItemChilds.map(function (navItem, j) { return (React.createElement(NavItemComponent, { navItem: navItem, key: j })); }))); }))));
|
|
42
|
+
return (React.createElement("div", { id: props.id, className: "navigation-content" }, navItemsGrouped.map(function (navItemGroup, i) { return (React.createElement("div", { className: "nav-items-group", key: i },
|
|
43
|
+
React.createElement(NavItemComponent, { navItem: navItemGroup }),
|
|
44
|
+
navItemGroup.navItemChilds.map(function (navItem, j) { return (React.createElement(NavItemComponent, { navItem: navItem, key: j })); }))); })));
|
|
42
45
|
}
|
|
43
46
|
function NavItemComponent(_a) {
|
|
44
47
|
var _b;
|
|
@@ -15,9 +15,9 @@ function NavigationFullscreenButton() {
|
|
|
15
15
|
overflow: 'hidden',
|
|
16
16
|
} },
|
|
17
17
|
React.createElement("div", null)),
|
|
18
|
-
React.createElement("div", { style: { position: 'fixed', height: '100vh', width: 20 }, "aria-label": hotkeyLabel, "data-balloon-pos": "right" }))));
|
|
18
|
+
React.createElement("div", { style: { position: 'fixed', height: '100vh', width: 20 }, "aria-label": hotkeyLabel, "data-balloon-pos": "right", "data-balloon-blunt": true }))));
|
|
19
19
|
}
|
|
20
20
|
function NavigationFullscreenClose() {
|
|
21
|
-
return (React.createElement("a", { id: "navigation-fullscreen-close", style: { position: 'absolute', top: 11, right: 11, zIndex: 10 }, "aria-label": hotkeyLabel, "data-balloon-pos": "left" },
|
|
21
|
+
return (React.createElement("a", { id: "navigation-fullscreen-close", style: { position: 'absolute', top: 11, right: 11, zIndex: 10 }, "aria-label": hotkeyLabel, "data-balloon-pos": "left", "data-balloon-blunt": true },
|
|
22
22
|
React.createElement("img", { src: closeIcon, height: 50, width: 50, style: { display: 'block' } })));
|
|
23
23
|
}
|
|
@@ -114,14 +114,18 @@ html.navigation-fullscreen .navigation-content {
|
|
|
114
114
|
display: flex;
|
|
115
115
|
margin: auto; /* A `max-width` is set by src/navigation/navigation-fullscreen/initNavigationFullscreen.ts */
|
|
116
116
|
}
|
|
117
|
-
html.navigation-fullscreen .navigation-content > .nav-column {
|
|
118
|
-
flex-grow: 1;
|
|
119
|
-
max-width: 350px;
|
|
120
|
-
}
|
|
121
|
-
html.navigation-fullscreen .nav-column > .nav-items-group:first-child > .nav-item-level-1:first-child {
|
|
122
|
-
margin-top: 0px;
|
|
123
|
-
}
|
|
124
117
|
html.navigation-fullscreen {
|
|
125
118
|
/* disable scroll of main view */
|
|
126
119
|
overflow: hidden !important;
|
|
127
120
|
}
|
|
121
|
+
html.navigation-fullscreen #navigation-body {
|
|
122
|
+
display: flex;
|
|
123
|
+
}
|
|
124
|
+
html.navigation-fullscreen #navigation-content-main {
|
|
125
|
+
flex-grow: 1;
|
|
126
|
+
display: grid;
|
|
127
|
+
margin-top: -25px;
|
|
128
|
+
}
|
|
129
|
+
html.navigation-fullscreen .nav-items-group {
|
|
130
|
+
padding: 0 10px;
|
|
131
|
+
}
|
|
@@ -3,12 +3,17 @@ export { NavigationMask }
|
|
|
3
3
|
export type { NavigationData }
|
|
4
4
|
export type { NavItem }
|
|
5
5
|
|
|
6
|
+
// TODO/refactor: do this only on the server side?
|
|
7
|
+
export { groupByLevelMin }
|
|
8
|
+
export type { NavItemGrouped }
|
|
9
|
+
|
|
6
10
|
import React from 'react'
|
|
7
11
|
import { NavigationHeader } from './NavigationHeader'
|
|
8
12
|
import { assert, Emoji, assertWarning, jsxToTextContent } from '../utils/server'
|
|
9
13
|
import './Navigation.css'
|
|
10
14
|
import { NavigationFullscreenClose } from './navigation-fullscreen/NavigationFullscreenButton'
|
|
11
15
|
import { parseTitle } from '../parseTitle'
|
|
16
|
+
import { autoScrollNav_SSR } from '../autoScrollNav'
|
|
12
17
|
|
|
13
18
|
type NavigationData = Parameters<typeof Navigation>[0]
|
|
14
19
|
|
|
@@ -40,6 +45,8 @@ function Navigation({
|
|
|
40
45
|
<NavigationFullscreenClose />
|
|
41
46
|
</div>
|
|
42
47
|
</div>
|
|
48
|
+
{/* Early scrolling, to avoid flashing */}
|
|
49
|
+
<script dangerouslySetInnerHTML={{ __html: autoScrollNav_SSR }}></script>
|
|
43
50
|
</>
|
|
44
51
|
)
|
|
45
52
|
}
|
|
@@ -72,16 +79,14 @@ function NavigationContent(props: {
|
|
|
72
79
|
|
|
73
80
|
return (
|
|
74
81
|
<div id={props.id} className="navigation-content">
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
82
|
+
{navItemsGrouped.map((navItemGroup, i) => (
|
|
83
|
+
<div className="nav-items-group" key={i}>
|
|
84
|
+
<NavItemComponent navItem={navItemGroup} />
|
|
85
|
+
{navItemGroup.navItemChilds.map((navItem, j) => (
|
|
86
|
+
<NavItemComponent navItem={navItem} key={j} />
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
))}
|
|
85
90
|
</div>
|
|
86
91
|
)
|
|
87
92
|
}
|
|
@@ -130,8 +135,9 @@ function NavItemComponent({
|
|
|
130
135
|
)
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
type NavItemGrouped = ReturnType<typeof groupByLevelMin>[number]
|
|
139
|
+
function groupByLevelMin<T extends NavItem>(navItems: T[]) {
|
|
140
|
+
const navItemsGrouped: (T & { navItemChilds: T[] })[] = []
|
|
135
141
|
const levelMin: number = Math.min(...navItems.map((h) => h.level))
|
|
136
142
|
navItems.forEach((navItem) => {
|
|
137
143
|
if (navItem.level === levelMin) {
|
|
@@ -14,6 +14,6 @@ function navigationHeaderRightClickInterceptor() {
|
|
|
14
14
|
const navHeaderImg = document.querySelector('#navigation-header-logo img') as HTMLElement
|
|
15
15
|
navHeaderImg.oncontextmenu = (ev) => {
|
|
16
16
|
ev.preventDefault()
|
|
17
|
-
navigate('/press')
|
|
17
|
+
navigate('/press#logo')
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -25,6 +25,7 @@ function NavigationFullscreenButton() {
|
|
|
25
25
|
style={{ position: 'fixed', height: '100vh', width: 20 }}
|
|
26
26
|
aria-label={hotkeyLabel}
|
|
27
27
|
data-balloon-pos="right"
|
|
28
|
+
data-balloon-blunt
|
|
28
29
|
></div>
|
|
29
30
|
</a>
|
|
30
31
|
</>
|
|
@@ -38,6 +39,7 @@ function NavigationFullscreenClose() {
|
|
|
38
39
|
style={{ position: 'absolute', top: 11, right: 11, zIndex: 10 }}
|
|
39
40
|
aria-label={hotkeyLabel}
|
|
40
41
|
data-balloon-pos="left"
|
|
42
|
+
data-balloon-blunt
|
|
41
43
|
>
|
|
42
44
|
<img src={closeIcon} height={50} width={50} style={{ display: 'block' }} />
|
|
43
45
|
</a>
|
|
@@ -7,7 +7,7 @@ import { assert } from '../../utils/client'
|
|
|
7
7
|
let scrollPositionBeforeToggle: number
|
|
8
8
|
|
|
9
9
|
function initNavigationFullscreenOnce() {
|
|
10
|
-
scrollPositionBeforeToggle = 0
|
|
10
|
+
scrollPositionBeforeToggle = 0 // Initial scroll of fullscreen navigation is 0
|
|
11
11
|
initKeyBindings()
|
|
12
12
|
}
|
|
13
13
|
function initKeyBindings() {
|
|
@@ -20,13 +20,11 @@ function initKeyBindings() {
|
|
|
20
20
|
},
|
|
21
21
|
false,
|
|
22
22
|
)
|
|
23
|
-
window.addEventListener('resize', updateColumnWidth, { passive: true })
|
|
24
23
|
initTopNavigation()
|
|
25
24
|
}
|
|
26
25
|
function initNavigationFullscreen() {
|
|
27
26
|
document.getElementById('navigation-fullscreen-button')!.onclick = toggleNavExpend
|
|
28
27
|
document.getElementById('navigation-fullscreen-close')!.onclick = toggleNavExpend
|
|
29
|
-
updateColumnWidth()
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
function toggleNavExpend() {
|
|
@@ -42,91 +40,6 @@ function hideNavigationFullScreen() {
|
|
|
42
40
|
toggleNavExpend()
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
function updateColumnWidth() {
|
|
46
|
-
const navMinWidth = 299
|
|
47
|
-
const navH1Groups = Array.from(document.querySelectorAll('#navigation-content-main .nav-items-group'))
|
|
48
|
-
const numberOfColumnsMax = navH1Groups.length
|
|
49
|
-
|
|
50
|
-
const widthAvailable = getViewportWidth()
|
|
51
|
-
const numberOfColumns = Math.max(1, Math.min(numberOfColumnsMax, Math.floor(widthAvailable / navMinWidth)))
|
|
52
|
-
|
|
53
|
-
let columns = navH1Groups.map((navH1Group) => {
|
|
54
|
-
const column = [
|
|
55
|
-
{
|
|
56
|
-
element: navH1Group,
|
|
57
|
-
elementHeight: navH1Group.children.length,
|
|
58
|
-
},
|
|
59
|
-
]
|
|
60
|
-
return column
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
mergeColumns(columns, numberOfColumns)
|
|
64
|
-
|
|
65
|
-
const navContent = document.getElementById('navigation-content-main')!
|
|
66
|
-
|
|
67
|
-
Array.from(navContent.children).forEach((child) => {
|
|
68
|
-
assert(child.className === 'nav-column')
|
|
69
|
-
})
|
|
70
|
-
navContent.innerHTML = ''
|
|
71
|
-
|
|
72
|
-
columns.forEach((column) => {
|
|
73
|
-
const columnEl = document.createElement('div')
|
|
74
|
-
columnEl.className = 'nav-column'
|
|
75
|
-
column.forEach(({ element }) => {
|
|
76
|
-
columnEl.appendChild(element)
|
|
77
|
-
})
|
|
78
|
-
navContent.appendChild(columnEl)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const navItemMaxWidth = 350
|
|
82
|
-
navContent.style.maxWidth = `${numberOfColumns * navItemMaxWidth}px`
|
|
83
|
-
}
|
|
84
|
-
function getViewportWidth(): number {
|
|
85
|
-
// `window.innerWidth` inlcudes scrollbar width: https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
|
|
86
|
-
return document.documentElement.clientWidth
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function mergeColumns<T>(columns: { element: T; elementHeight: number }[][], maxNumberOfColumns: number) {
|
|
90
|
-
assert(columns.length > 0)
|
|
91
|
-
assert(maxNumberOfColumns > 0)
|
|
92
|
-
if (columns.length <= maxNumberOfColumns) {
|
|
93
|
-
return columns
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let mergeCandidate = {
|
|
97
|
-
i: -1,
|
|
98
|
-
mergeHeight: Infinity,
|
|
99
|
-
}
|
|
100
|
-
for (let i = 0; i <= columns.length - 2; i++) {
|
|
101
|
-
const column1 = columns[i + 0]
|
|
102
|
-
const column2 = columns[i + 1]
|
|
103
|
-
const column1Height = sum(column1.map((c) => c.elementHeight))
|
|
104
|
-
const column2Height = sum(column2.map((c) => c.elementHeight))
|
|
105
|
-
const mergeHeight = column1Height + column2Height
|
|
106
|
-
if (mergeCandidate.mergeHeight > mergeHeight) {
|
|
107
|
-
mergeCandidate = {
|
|
108
|
-
i,
|
|
109
|
-
mergeHeight,
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
const { i } = mergeCandidate
|
|
116
|
-
assert(-1 < i && i < columns.length - 1, { i, columnsLength: columns.length, maxNumberOfColumns })
|
|
117
|
-
columns[i] = [...columns[i], ...columns[i + 1]]
|
|
118
|
-
columns.splice(i + 1, 1)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
mergeColumns(columns, maxNumberOfColumns)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function sum(arr: number[]): number {
|
|
125
|
-
let total = 0
|
|
126
|
-
arr.forEach((n) => (total += n))
|
|
127
|
-
return total
|
|
128
|
-
}
|
|
129
|
-
|
|
130
43
|
function initTopNavigation() {
|
|
131
44
|
document.addEventListener('click', (ev) => {
|
|
132
45
|
const linkTag = findLinkTag(ev.target as HTMLElement)
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export { getCSSForResponsiveFullcreenNavItems }
|
|
2
|
+
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import { type NavItemGrouped } from '../navigation/Navigation'
|
|
5
|
+
|
|
6
|
+
function getCSSForResponsiveFullcreenNavItems(navItemsGrouped: NavItemGrouped[]) {
|
|
7
|
+
const columnWidthMin = 300
|
|
8
|
+
const columnWidthMax = 350
|
|
9
|
+
const columnsUnmerged = navItemsGrouped.map((navItem) => navItem.navItemChilds.length)
|
|
10
|
+
let CSS = '\n'
|
|
11
|
+
for (let numberOfColumns = navItemsGrouped.length; numberOfColumns >= 1; numberOfColumns--) {
|
|
12
|
+
let CSS_block: string[] = []
|
|
13
|
+
CSS_block.push(
|
|
14
|
+
...[
|
|
15
|
+
//
|
|
16
|
+
` html.navigation-fullscreen #navigation-content-main {`,
|
|
17
|
+
` max-width: ${columnWidthMax * numberOfColumns}px;`,
|
|
18
|
+
` grid-template-columns: repeat(${numberOfColumns}, auto);`,
|
|
19
|
+
` }`,
|
|
20
|
+
],
|
|
21
|
+
)
|
|
22
|
+
const columnsIdMap = determineColumns(columnsUnmerged, numberOfColumns)
|
|
23
|
+
columnsIdMap.forEach((columnGroupedId, columnUngroupedId) => {
|
|
24
|
+
CSS_block.push(
|
|
25
|
+
...[
|
|
26
|
+
//
|
|
27
|
+
` .nav-items-group:nth-child(${columnUngroupedId + 1}) {`,
|
|
28
|
+
` grid-column: ${columnGroupedId + 1};`,
|
|
29
|
+
` }`,
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
const noMediaQuery = numberOfColumns === navItemsGrouped.length
|
|
34
|
+
if (!noMediaQuery) {
|
|
35
|
+
const maxWidth = (numberOfColumns + 1) * columnWidthMin - 1
|
|
36
|
+
CSS_block = [
|
|
37
|
+
//
|
|
38
|
+
`@media screen and (max-width: ${maxWidth}px) {`,
|
|
39
|
+
...CSS_block,
|
|
40
|
+
`}`,
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
CSS += CSS_block.join('\n') + '\n'
|
|
44
|
+
}
|
|
45
|
+
return CSS
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function determineColumns(columnsUnmerged: number[], numberOfColumns: number): number[] {
|
|
49
|
+
assert(numberOfColumns <= columnsUnmerged.length)
|
|
50
|
+
const columnsMergingInit: ColumnMerging[] = columnsUnmerged.map((columnHeight, i) => ({
|
|
51
|
+
columnIdsMerged: [i],
|
|
52
|
+
heightTotal: columnHeight,
|
|
53
|
+
}))
|
|
54
|
+
const columnsMerged = mergeColumns(columnsMergingInit, numberOfColumns)
|
|
55
|
+
const columnsIdMap: number[] = new Array(columnsUnmerged.length)
|
|
56
|
+
assert(columnsMerged.length === numberOfColumns)
|
|
57
|
+
columnsMerged.forEach((columnMerged, columnMergedId) => {
|
|
58
|
+
columnMerged.columnIdsMerged.forEach((columnId) => {
|
|
59
|
+
columnsIdMap[columnId] = columnMergedId
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
assert(columnsIdMap.length === columnsUnmerged.length)
|
|
63
|
+
return columnsIdMap
|
|
64
|
+
}
|
|
65
|
+
type ColumnMerging = { columnIdsMerged: number[]; heightTotal: number }
|
|
66
|
+
function mergeColumns(columnsMerging: ColumnMerging[], maxNumberOfColumns: number): ColumnMerging[] {
|
|
67
|
+
if (columnsMerging.length <= maxNumberOfColumns) return columnsMerging
|
|
68
|
+
|
|
69
|
+
let mergeCandidate: null | (ColumnMerging & { i: number }) = null
|
|
70
|
+
for (let i = 0; i <= columnsMerging.length - 2; i++) {
|
|
71
|
+
const column1 = columnsMerging[i + 0]
|
|
72
|
+
const column2 = columnsMerging[i + 1]
|
|
73
|
+
const heightTotal = column1.heightTotal + column2.heightTotal
|
|
74
|
+
if (!mergeCandidate || mergeCandidate.heightTotal > heightTotal) {
|
|
75
|
+
mergeCandidate = {
|
|
76
|
+
i,
|
|
77
|
+
columnIdsMerged: [
|
|
78
|
+
//
|
|
79
|
+
...column1.columnIdsMerged,
|
|
80
|
+
...column2.columnIdsMerged,
|
|
81
|
+
],
|
|
82
|
+
heightTotal,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
assert(mergeCandidate)
|
|
87
|
+
|
|
88
|
+
const { i } = mergeCandidate
|
|
89
|
+
assert(-1 < i && i < columnsMerging.length - 1)
|
|
90
|
+
const columnsMergingMod = [
|
|
91
|
+
//
|
|
92
|
+
...columnsMerging.slice(0, i),
|
|
93
|
+
mergeCandidate,
|
|
94
|
+
...columnsMerging.slice(i + 2),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
assert(columnsMergingMod.length === columnsMerging.length - 1)
|
|
98
|
+
return mergeColumns(columnsMergingMod, maxNumberOfColumns)
|
|
99
|
+
}
|
|
@@ -18,12 +18,13 @@ import { installSectionUrlHashs } from '../installSectionUrlHashs'
|
|
|
18
18
|
import { addFeatureClickHandlers, addTwitterWidgets } from '../components/FeatureList/FeatureList.client'
|
|
19
19
|
|
|
20
20
|
addEcosystemStamp()
|
|
21
|
-
initOnLinkClick()
|
|
22
21
|
initNavigationFullscreenOnce()
|
|
23
22
|
|
|
24
23
|
let root: ReactDOM.Root
|
|
25
24
|
let renderPromiseResolve: () => void
|
|
26
25
|
async function onRenderClient(pageContext: PageContextClient) {
|
|
26
|
+
onRenderStart()
|
|
27
|
+
|
|
27
28
|
// TODO: stop using any
|
|
28
29
|
const pageContextResolved: PageContextResolved = (pageContext as any).pageContextResolved
|
|
29
30
|
const renderPromise = new Promise<void>((r) => {
|
|
@@ -52,6 +53,11 @@ function applyHead(pageContext: PageContextClient) {
|
|
|
52
53
|
document.title = pageContextResolved.documentTitle
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function onRenderStart() {
|
|
57
|
+
hideMobileNavigation()
|
|
58
|
+
hideNavigationFullScreen()
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
function onRenderDone() {
|
|
56
62
|
autoScrollNav()
|
|
57
63
|
installSectionUrlHashs()
|
|
@@ -69,27 +75,6 @@ function OnRenderDoneHook({ children }: { children: React.ReactNode }) {
|
|
|
69
75
|
return children
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
function initOnLinkClick() {
|
|
73
|
-
document.addEventListener('click', (ev) => {
|
|
74
|
-
const linkTag = findLinkTag(ev.target as HTMLElement)
|
|
75
|
-
if (!linkTag) return
|
|
76
|
-
const url = linkTag.getAttribute('href')
|
|
77
|
-
if (!url) return
|
|
78
|
-
hideMobileNavigation()
|
|
79
|
-
hideNavigationFullScreen()
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
function findLinkTag(target: HTMLElement): null | HTMLElement {
|
|
83
|
-
while (target.tagName !== 'A') {
|
|
84
|
-
const { parentNode } = target
|
|
85
|
-
if (!parentNode) {
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
|
-
target = parentNode as HTMLElement
|
|
89
|
-
}
|
|
90
|
-
return target
|
|
91
|
-
}
|
|
92
|
-
|
|
93
78
|
function setHydrationIsFinished() {
|
|
94
79
|
// Used by:
|
|
95
80
|
// - https://github.com/vikejs/vike/blob/9d67f3dd4bdfb38c835186b8147251e0e3b06657/docs/.testRun.ts#L22
|
|
@@ -6,6 +6,8 @@ import { assert } from '../utils/server'
|
|
|
6
6
|
import type { PageContextResolved } from '../config/resolvePageContext'
|
|
7
7
|
import { getPageElement } from './getPageElement'
|
|
8
8
|
import type { OnRenderHtmlAsync } from 'vike/types'
|
|
9
|
+
import { groupByLevelMin } from '../navigation/Navigation'
|
|
10
|
+
import { getCSSForResponsiveFullcreenNavItems } from './getCSSForResponsiveFullcreenNavItems'
|
|
9
11
|
|
|
10
12
|
const onRenderHtml: OnRenderHtmlAsync = async (
|
|
11
13
|
pageContext,
|
|
@@ -15,6 +17,12 @@ Promise<Awaited<ReturnType<OnRenderHtmlAsync>>> => {
|
|
|
15
17
|
|
|
16
18
|
const page = getPageElement(pageContext, pageContextResolved)
|
|
17
19
|
|
|
20
|
+
const { navItemsAll } = pageContextResolved.navigationData
|
|
21
|
+
const navItemsGrouped = groupByLevelMin(navItemsAll)
|
|
22
|
+
const CSSResponsiveNavItems = getCSSForResponsiveFullcreenNavItems(navItemsGrouped)
|
|
23
|
+
|
|
24
|
+
pageContextResolved.navigationData.navItems
|
|
25
|
+
|
|
18
26
|
const descriptionTag = pageContextResolved.isLandingPage
|
|
19
27
|
? dangerouslySkipEscape(`<meta name="description" content="${pageContextResolved.meta.tagline}" />`)
|
|
20
28
|
: ''
|
|
@@ -30,6 +38,7 @@ Promise<Awaited<ReturnType<OnRenderHtmlAsync>>> => {
|
|
|
30
38
|
${descriptionTag}
|
|
31
39
|
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
|
32
40
|
${getOpenGraphTags(pageContext.urlPathname, pageContextResolved.documentTitle, pageContextResolved.meta)}
|
|
41
|
+
<style>${dangerouslySkipEscape(CSSResponsiveNavItems)}</style>
|
|
33
42
|
</head>
|
|
34
43
|
<body>
|
|
35
44
|
<div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
|