@brillout/docpress 0.7.9 → 0.7.10
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/dist/navigation/Navigation.d.ts +6 -0
- package/dist/navigation/Navigation.js +5 -4
- package/navigation/Navigation-items.css +0 -3
- package/navigation/Navigation-layout.css +12 -10
- package/navigation/Navigation.tsx +15 -12
- package/navigation/initPressKit.ts +1 -1
- package/navigation/navigation-fullscreen/initNavigationFullscreen.ts +0 -87
- package/package.json +1 -1
- package/renderer/getCSSForResponsiveFullcreenNavItems.ts +127 -0
- package/renderer/onRenderHtml.tsx +7 -0
|
@@ -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,6 +11,8 @@ 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';
|
|
@@ -37,10 +39,9 @@ function NavigationMask() {
|
|
|
37
39
|
function NavigationContent(props) {
|
|
38
40
|
var navItemsWithComputed = addComputedProps(props.navItems, props.currentUrl);
|
|
39
41
|
var navItemsGrouped = groupByLevelMin(navItemsWithComputed);
|
|
40
|
-
return (React.createElement("div", { id: props.id, className: "navigation-content" },
|
|
41
|
-
React.createElement(
|
|
42
|
-
|
|
43
|
-
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 })); }))); })));
|
|
44
45
|
}
|
|
45
46
|
function NavItemComponent(_a) {
|
|
46
47
|
var _b;
|
|
@@ -110,18 +110,20 @@ html.navigation-fullscreen #navigation-content-detached,
|
|
|
110
110
|
html.navigation-fullscreen #detached-note {
|
|
111
111
|
display: none !important;
|
|
112
112
|
}
|
|
113
|
-
html.navigation-fullscreen
|
|
113
|
+
html.navigation-fullscreen {
|
|
114
|
+
/* disable scroll of main view */
|
|
115
|
+
overflow: hidden !important;
|
|
116
|
+
}
|
|
117
|
+
html.navigation-fullscreen #navigation-body {
|
|
114
118
|
display: flex;
|
|
115
|
-
|
|
119
|
+
justify-content: center;
|
|
116
120
|
}
|
|
117
|
-
html.navigation-fullscreen
|
|
121
|
+
html.navigation-fullscreen #navigation-content-main {
|
|
118
122
|
flex-grow: 1;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
html.navigation-fullscreen .nav-column > .nav-items-group:first-child > .nav-item-level-1:first-child {
|
|
122
|
-
margin-top: 0px;
|
|
123
|
+
margin-top: -25px;
|
|
124
|
+
column-gap: 20px;
|
|
123
125
|
}
|
|
124
|
-
html.navigation-fullscreen {
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
html.navigation-fullscreen .nav-items-group {
|
|
127
|
+
break-inside: avoid;
|
|
128
|
+
width: 100%;
|
|
127
129
|
}
|
|
@@ -3,6 +3,10 @@ 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'
|
|
@@ -75,16 +79,14 @@ function NavigationContent(props: {
|
|
|
75
79
|
|
|
76
80
|
return (
|
|
77
81
|
<div id={props.id} className="navigation-content">
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
))}
|
|
87
|
-
</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
|
+
))}
|
|
88
90
|
</div>
|
|
89
91
|
)
|
|
90
92
|
}
|
|
@@ -133,8 +135,9 @@ function NavItemComponent({
|
|
|
133
135
|
)
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
type NavItemGrouped = ReturnType<typeof groupByLevelMin>[number]
|
|
139
|
+
function groupByLevelMin<T extends NavItem>(navItems: T[]) {
|
|
140
|
+
const navItemsGrouped: (T & { navItemChilds: T[] })[] = []
|
|
138
141
|
const levelMin: number = Math.min(...navItems.map((h) => h.level))
|
|
139
142
|
navItems.forEach((navItem) => {
|
|
140
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
|
}
|
|
@@ -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,127 @@
|
|
|
1
|
+
export { getCSSForResponsiveFullcreenNavItems }
|
|
2
|
+
|
|
3
|
+
// There doens't seem to be as simpler way to have a column layout that uses the whole width real estate.
|
|
4
|
+
// - https://stackoverflow.com/questions/9683425/css-column-count-not-respected
|
|
5
|
+
// - https://stackoverflow.com/questions/25446921/get-flexbox-column-wrap-to-use-full-width-and-minimize-height
|
|
6
|
+
// - https://stackoverflow.com/questions/74873283/how-to-create-a-css-grid-with-3-columns-having-column-flow
|
|
7
|
+
// - https://stackoverflow.com/questions/50693793/3-columns-grid-top-to-bottom-using-grid-css
|
|
8
|
+
// - https://stackoverflow.com/questions/9119347/html-css-vertical-flow-layout-columnar-style-how-to-implement
|
|
9
|
+
// - https://stackoverflow.com/questions/27119691/how-to-start-a-new-column-in-flex-column-wrap-layout
|
|
10
|
+
// - https://stackoverflow.com/questions/45264354/is-it-possible-to-place-more-than-one-element-into-a-css-grid-cell-without-overl/49047281#49047281
|
|
11
|
+
|
|
12
|
+
import assert from 'assert'
|
|
13
|
+
import { type NavItemGrouped } from '../navigation/Navigation'
|
|
14
|
+
|
|
15
|
+
const columnWidthMin = 300
|
|
16
|
+
const columnWidthMax = 350
|
|
17
|
+
|
|
18
|
+
function getCSSForResponsiveFullcreenNavItems(navItemsGrouped: NavItemGrouped[]) {
|
|
19
|
+
let CSS = '\n'
|
|
20
|
+
for (let numberOfColumns = navItemsGrouped.length; numberOfColumns >= 1; numberOfColumns--) {
|
|
21
|
+
let CSS_block: string[] = []
|
|
22
|
+
CSS_block.push(
|
|
23
|
+
...[
|
|
24
|
+
` html.navigation-fullscreen #navigation-content-main {`,
|
|
25
|
+
` column-count: ${numberOfColumns};`,
|
|
26
|
+
` max-width: min(100%, ${columnWidthMax * numberOfColumns}px);`,
|
|
27
|
+
` }`,
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
const columnsUnmerged = navItemsGrouped.map((navItem) => navItem.navItemChilds.length)
|
|
31
|
+
const columnsIdMap = determineColumns(columnsUnmerged, numberOfColumns)
|
|
32
|
+
const columnBreakPoints = determineColumnBreakPoints(columnsIdMap)
|
|
33
|
+
columnBreakPoints.forEach((columnBreakPoint, columnUngroupedId) => {
|
|
34
|
+
CSS_block.push(
|
|
35
|
+
...[
|
|
36
|
+
` .nav-items-group:nth-child(${columnUngroupedId + 1}) {`,
|
|
37
|
+
` break-before: ${columnBreakPoint ? 'column' : 'avoid'};`,
|
|
38
|
+
` }`,
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
const noMediaQuery = numberOfColumns === navItemsGrouped.length
|
|
43
|
+
if (!noMediaQuery) {
|
|
44
|
+
const maxWidth = (numberOfColumns + 1) * columnWidthMin - 1
|
|
45
|
+
CSS_block = [
|
|
46
|
+
//
|
|
47
|
+
`@media screen and (max-width: ${maxWidth}px) {`,
|
|
48
|
+
...CSS_block,
|
|
49
|
+
`}`,
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
CSS += CSS_block.join('\n') + '\n'
|
|
53
|
+
}
|
|
54
|
+
return CSS
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function determineColumnBreakPoints(columnsIdMap: number[]): boolean[] {
|
|
58
|
+
assert(columnsIdMap[0] === 0)
|
|
59
|
+
let columnGroupedIdBefore = -1
|
|
60
|
+
const columnBreakPoints = columnsIdMap.map((columnGroupedId) => {
|
|
61
|
+
assert(
|
|
62
|
+
[
|
|
63
|
+
//
|
|
64
|
+
columnGroupedIdBefore,
|
|
65
|
+
columnGroupedIdBefore + 1,
|
|
66
|
+
].includes(columnGroupedId),
|
|
67
|
+
)
|
|
68
|
+
const val = columnGroupedId !== columnGroupedIdBefore
|
|
69
|
+
columnGroupedIdBefore = columnGroupedId
|
|
70
|
+
return val
|
|
71
|
+
})
|
|
72
|
+
return columnBreakPoints
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function determineColumns(columnsUnmerged: number[], numberOfColumns: number): number[] {
|
|
76
|
+
assert(numberOfColumns <= columnsUnmerged.length)
|
|
77
|
+
const columnsMergingInit: ColumnMerging[] = columnsUnmerged.map((columnHeight, i) => ({
|
|
78
|
+
columnIdsMerged: [i],
|
|
79
|
+
heightTotal: columnHeight,
|
|
80
|
+
}))
|
|
81
|
+
const columnsMerged = mergeColumns(columnsMergingInit, numberOfColumns)
|
|
82
|
+
const columnsIdMap: number[] = new Array(columnsUnmerged.length)
|
|
83
|
+
assert(columnsMerged.length === numberOfColumns)
|
|
84
|
+
columnsMerged.forEach((columnMerged, columnMergedId) => {
|
|
85
|
+
columnMerged.columnIdsMerged.forEach((columnId) => {
|
|
86
|
+
columnsIdMap[columnId] = columnMergedId
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
assert(columnsIdMap.length === columnsUnmerged.length)
|
|
90
|
+
|
|
91
|
+
return columnsIdMap
|
|
92
|
+
}
|
|
93
|
+
type ColumnMerging = { columnIdsMerged: number[]; heightTotal: number }
|
|
94
|
+
function mergeColumns(columnsMerging: ColumnMerging[], numberOfColumns: number): ColumnMerging[] {
|
|
95
|
+
if (columnsMerging.length <= numberOfColumns) return columnsMerging
|
|
96
|
+
|
|
97
|
+
let mergeCandidate: null | (ColumnMerging & { i: number }) = null
|
|
98
|
+
for (let i = 0; i <= columnsMerging.length - 2; i++) {
|
|
99
|
+
const column1 = columnsMerging[i + 0]
|
|
100
|
+
const column2 = columnsMerging[i + 1]
|
|
101
|
+
const heightTotal = column1.heightTotal + column2.heightTotal
|
|
102
|
+
if (!mergeCandidate || mergeCandidate.heightTotal > heightTotal) {
|
|
103
|
+
mergeCandidate = {
|
|
104
|
+
i,
|
|
105
|
+
columnIdsMerged: [
|
|
106
|
+
//
|
|
107
|
+
...column1.columnIdsMerged,
|
|
108
|
+
...column2.columnIdsMerged,
|
|
109
|
+
],
|
|
110
|
+
heightTotal,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
assert(mergeCandidate)
|
|
115
|
+
|
|
116
|
+
const { i } = mergeCandidate
|
|
117
|
+
assert(-1 < i && i < columnsMerging.length - 1)
|
|
118
|
+
const columnsMergingMod = [
|
|
119
|
+
//
|
|
120
|
+
...columnsMerging.slice(0, i),
|
|
121
|
+
mergeCandidate,
|
|
122
|
+
...columnsMerging.slice(i + 2),
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
assert(columnsMergingMod.length === columnsMerging.length - 1)
|
|
126
|
+
return mergeColumns(columnsMergingMod, numberOfColumns)
|
|
127
|
+
}
|
|
@@ -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,10 @@ 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
|
+
|
|
18
24
|
const descriptionTag = pageContextResolved.isLandingPage
|
|
19
25
|
? dangerouslySkipEscape(`<meta name="description" content="${pageContextResolved.meta.tagline}" />`)
|
|
20
26
|
: ''
|
|
@@ -30,6 +36,7 @@ Promise<Awaited<ReturnType<OnRenderHtmlAsync>>> => {
|
|
|
30
36
|
${descriptionTag}
|
|
31
37
|
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
|
32
38
|
${getOpenGraphTags(pageContext.urlPathname, pageContextResolved.documentTitle, pageContextResolved.meta)}
|
|
39
|
+
<style>${dangerouslySkipEscape(CSSResponsiveNavItems)}</style>
|
|
33
40
|
</head>
|
|
34
41
|
<body>
|
|
35
42
|
<div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
|