@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.
@@ -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("div", { className: "nav-column", style: { position: 'relative' } }, navItemsGrouped.map(function (navItemGroup, i) { return (React.createElement("div", { className: "nav-items-group", key: i },
42
- React.createElement(NavItemComponent, { navItem: navItemGroup }),
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;
@@ -22,9 +22,6 @@
22
22
  padding-right: 4px;
23
23
  text-decoration: none;
24
24
  }
25
- .nav-column:first-of-type > .nav-items-group:first-of-type > .nav-item-level-1:first-of-type {
26
- margin-top: 20px;
27
- }
28
25
  .nav-item-level-1 {
29
26
  margin-top: 30px;
30
27
  font-size: 15.4px;
@@ -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 .navigation-content {
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
- margin: auto; /* A `max-width` is set by src/navigation/navigation-fullscreen/initNavigationFullscreen.ts */
119
+ justify-content: center;
116
120
  }
117
- html.navigation-fullscreen .navigation-content > .nav-column {
121
+ html.navigation-fullscreen #navigation-content-main {
118
122
  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
+ margin-top: -25px;
124
+ column-gap: 20px;
123
125
  }
124
- html.navigation-fullscreen {
125
- /* disable scroll of main view */
126
- overflow: hidden !important;
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
- <div className="nav-column" style={{ position: 'relative' }}>
79
- {navItemsGrouped.map((navItemGroup, i) => (
80
- <div className="nav-items-group" key={i}>
81
- <NavItemComponent navItem={navItemGroup} />
82
- {navItemGroup.navItemChilds.map((navItem, j) => (
83
- <NavItemComponent navItem={navItem} key={j} />
84
- ))}
85
- </div>
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
- function groupByLevelMin(navItems: NavItemComputed[]) {
137
- const navItemsGrouped: (NavItemComputed & { navItemChilds: NavItemComputed[] })[] = []
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.7.9",
3
+ "version": "0.7.10",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -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>