@uniweb/runtime 0.1.2 → 0.2.2
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/README.md +48 -171
- package/package.json +8 -20
- package/src/components/Blocks.jsx +26 -0
- package/src/components/Layout.jsx +100 -0
- package/src/components/PageRenderer.jsx +26 -21
- package/src/components/WebsiteRenderer.jsx +5 -0
- package/src/hooks/useHeadMeta.js +188 -0
- package/src/hooks/useRememberScroll.js +105 -0
- package/src/hooks/useScrollDepth.js +71 -0
- package/src/index.jsx +29 -44
- package/src/components/Link.jsx +0 -28
- package/src/components/SafeHtml.jsx +0 -22
- package/src/core/block.js +0 -311
- package/src/core/input.js +0 -15
- package/src/core/page.js +0 -75
- package/src/core/uniweb.js +0 -103
- package/src/core/website.js +0 -157
- package/src/vite/content-collector.js +0 -269
- package/src/vite/foundation-plugin.js +0 -194
- package/src/vite/index.js +0 -7
- package/src/vite/site-content-plugin.js +0 -135
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @uniweb/runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Minimal runtime for loading Uniweb foundations and orchestrating rendering.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This package provides the runtime
|
|
7
|
+
This package provides the browser-side runtime for Uniweb sites. It loads foundations dynamically and renders content using React Router.
|
|
8
|
+
|
|
9
|
+
For Vite build plugins, see [`@uniweb/build`](https://github.com/uniweb/build).
|
|
8
10
|
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
@@ -12,13 +14,6 @@ This package provides the runtime environment for Uniweb sites—content-driven
|
|
|
12
14
|
npm install @uniweb/runtime
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
## Features
|
|
16
|
-
|
|
17
|
-
- **Foundation Loading** - Load Foundations via dynamic import or import maps
|
|
18
|
-
- **Content Rendering** - Render pages from structured content (JSON/YAML)
|
|
19
|
-
- **Vite Plugins** - Collect site content and serve foundations in development
|
|
20
|
-
- **React Integration** - Built on React with React Router for navigation
|
|
21
|
-
|
|
22
17
|
## Usage
|
|
23
18
|
|
|
24
19
|
### Runtime Loading
|
|
@@ -26,7 +21,7 @@ npm install @uniweb/runtime
|
|
|
26
21
|
Initialize the runtime with a Foundation URL:
|
|
27
22
|
|
|
28
23
|
```jsx
|
|
29
|
-
import
|
|
24
|
+
import initRuntime from '@uniweb/runtime'
|
|
30
25
|
|
|
31
26
|
// Load foundation from URL
|
|
32
27
|
initRuntime('/foundation/foundation.js', {
|
|
@@ -40,194 +35,76 @@ initRuntime({
|
|
|
40
35
|
})
|
|
41
36
|
```
|
|
42
37
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
Use the Vite plugins to collect content and optionally serve a foundation:
|
|
46
|
-
|
|
47
|
-
```js
|
|
48
|
-
// vite.config.js
|
|
49
|
-
import { defineConfig } from 'vite'
|
|
50
|
-
import react from '@vitejs/plugin-react'
|
|
51
|
-
import { siteContentPlugin, foundationPlugin } from '@uniweb/runtime/vite'
|
|
52
|
-
|
|
53
|
-
export default defineConfig({
|
|
54
|
-
plugins: [
|
|
55
|
-
react(),
|
|
56
|
-
|
|
57
|
-
// Collect content from pages/ directory
|
|
58
|
-
siteContentPlugin({
|
|
59
|
-
sitePath: './',
|
|
60
|
-
inject: true, // Inject into HTML
|
|
61
|
-
}),
|
|
62
|
-
|
|
63
|
-
// Optional: Serve foundation in dev mode
|
|
64
|
-
foundationPlugin({
|
|
65
|
-
name: 'my-foundation',
|
|
66
|
-
path: '../my-foundation',
|
|
67
|
-
serve: '/foundation',
|
|
68
|
-
}),
|
|
69
|
-
]
|
|
70
|
-
})
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Site Content Structure
|
|
74
|
-
|
|
75
|
-
Sites use a pages/ directory structure:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
site/
|
|
79
|
-
├── site.yml # Site configuration
|
|
80
|
-
├── theme.yml # Theme configuration
|
|
81
|
-
└── pages/
|
|
82
|
-
├── @header/ # Special: header section
|
|
83
|
-
│ └── 1.md
|
|
84
|
-
├── @footer/ # Special: footer section
|
|
85
|
-
│ └── 1.md
|
|
86
|
-
├── home/ # Home page (route: /)
|
|
87
|
-
│ ├── page.yml # Page metadata
|
|
88
|
-
│ ├── 1.md # First section
|
|
89
|
-
│ └── 2.md # Second section
|
|
90
|
-
└── about/ # About page (route: /about)
|
|
91
|
-
├── page.yml
|
|
92
|
-
└── 1.md
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Section Markdown Format
|
|
96
|
-
|
|
97
|
-
Each `.md` file is a section with YAML frontmatter for configuration and markdown body for content:
|
|
98
|
-
|
|
99
|
-
```markdown
|
|
100
|
-
---
|
|
101
|
-
component: Hero
|
|
102
|
-
preset: default
|
|
103
|
-
theme: dark
|
|
104
|
-
---
|
|
38
|
+
### Static Bundling
|
|
105
39
|
|
|
106
|
-
|
|
40
|
+
Foundation imported directly and bundled with the site:
|
|
107
41
|
|
|
108
|
-
|
|
42
|
+
```jsx
|
|
43
|
+
import * as Foundation from '@my-org/my-foundation'
|
|
44
|
+
import initRuntime from '@uniweb/runtime'
|
|
109
45
|
|
|
110
|
-
|
|
46
|
+
initRuntime(Foundation)
|
|
111
47
|
```
|
|
112
48
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
## API Reference
|
|
116
|
-
|
|
117
|
-
### Runtime Functions
|
|
118
|
-
|
|
119
|
-
| Function | Description |
|
|
120
|
-
|----------|-------------|
|
|
121
|
-
| `initRuntime(source, options)` | Initialize runtime with foundation |
|
|
122
|
-
| `initRTE(source, options)` | Alias for initRuntime |
|
|
123
|
-
|
|
124
|
-
### Exported Components
|
|
125
|
-
|
|
126
|
-
| Component | Description |
|
|
127
|
-
|-----------|-------------|
|
|
128
|
-
| `Link` | Router-aware link component |
|
|
129
|
-
| `SafeHtml` | Safe HTML rendering |
|
|
130
|
-
| `ChildBlocks` | Render child sections |
|
|
131
|
-
| `ErrorBoundary` | Error boundary wrapper |
|
|
132
|
-
|
|
133
|
-
### Vite Plugins
|
|
49
|
+
## API
|
|
134
50
|
|
|
135
|
-
|
|
136
|
-
|--------|-------------|
|
|
137
|
-
| `siteContentPlugin(options)` | Collect and inject site content |
|
|
138
|
-
| `foundationPlugin(options)` | Build and serve foundation in dev |
|
|
139
|
-
| `collectSiteContent(sitePath)` | Programmatic content collection |
|
|
51
|
+
### initRuntime(source, options)
|
|
140
52
|
|
|
141
|
-
|
|
53
|
+
Initialize the runtime with a foundation.
|
|
142
54
|
|
|
143
|
-
**
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
pagesDir: 'pages', // Pages subdirectory
|
|
148
|
-
inject: true, // Inject into HTML
|
|
149
|
-
filename: 'site-content.json', // Output filename
|
|
150
|
-
watch: true // Watch for changes (dev)
|
|
151
|
-
}
|
|
152
|
-
```
|
|
55
|
+
**source** - One of:
|
|
56
|
+
- URL string to foundation module
|
|
57
|
+
- Object with `{ url, cssUrl }`
|
|
58
|
+
- Foundation module object (for static bundling)
|
|
153
59
|
|
|
154
|
-
**
|
|
60
|
+
**options:**
|
|
155
61
|
```js
|
|
156
62
|
{
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
watch: true, // Watch for changes
|
|
161
|
-
buildOnStart: true // Build when dev server starts
|
|
63
|
+
development: false, // Enable dev mode (StrictMode, verbose errors)
|
|
64
|
+
configData: null, // Site content (or reads from DOM)
|
|
65
|
+
basename: undefined // React Router basename
|
|
162
66
|
}
|
|
163
67
|
```
|
|
164
68
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
### Uniweb
|
|
168
|
-
|
|
169
|
-
The main runtime instance (available as `globalThis.uniweb`):
|
|
170
|
-
|
|
171
|
-
```js
|
|
172
|
-
uniweb.getComponent(name) // Get component from foundation
|
|
173
|
-
uniweb.listComponents() // List available components
|
|
174
|
-
uniweb.activeWebsite // Current website instance
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Website
|
|
69
|
+
For core classes (`Uniweb`, `Website`, `Block`, etc.), import from [`@uniweb/core`](https://github.com/uniweb/core).
|
|
178
70
|
|
|
179
|
-
|
|
71
|
+
## Architecture
|
|
180
72
|
|
|
181
|
-
```js
|
|
182
|
-
website.getPage(route) // Get page by route
|
|
183
|
-
website.setActivePage(route) // Navigate to page
|
|
184
|
-
website.localize(value) // Localize a value
|
|
185
|
-
website.getLanguage() // Get current language
|
|
186
73
|
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```js
|
|
193
|
-
block.initComponent() // Initialize foundation component
|
|
194
|
-
block.getBlockContent() // Get structured content
|
|
195
|
-
block.getBlockProperties() // Get configuration properties
|
|
196
|
-
block.getBlockLinks() // Get links from content
|
|
74
|
+
@uniweb/runtime (browser)
|
|
75
|
+
│
|
|
76
|
+
├── Loads foundation dynamically
|
|
77
|
+
├── Creates Uniweb instance via @uniweb/core
|
|
78
|
+
└── Orchestrates React/Router rendering
|
|
197
79
|
```
|
|
198
80
|
|
|
199
|
-
|
|
81
|
+
Foundations should:
|
|
82
|
+
- Import components from `@uniweb/kit` (bundled)
|
|
83
|
+
- Mark `@uniweb/core` as external (provided by runtime)
|
|
200
84
|
|
|
201
|
-
|
|
85
|
+
## Build Plugins
|
|
202
86
|
|
|
203
|
-
|
|
87
|
+
Vite plugins have moved to `@uniweb/build`:
|
|
204
88
|
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
{
|
|
208
|
-
|
|
209
|
-
"react": "https://esm.sh/react@18",
|
|
210
|
-
"react-dom": "https://esm.sh/react-dom@18"
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
</script>
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Static Bundling
|
|
217
|
-
|
|
218
|
-
Foundation imported directly and bundled with the site.
|
|
219
|
-
|
|
220
|
-
```jsx
|
|
221
|
-
import * as Foundation from '@my-org/my-foundation'
|
|
222
|
-
import { initRuntime } from '@uniweb/runtime'
|
|
89
|
+
```js
|
|
90
|
+
// vite.config.js
|
|
91
|
+
import { siteContentPlugin } from '@uniweb/build/site'
|
|
92
|
+
import { foundationDevPlugin } from '@uniweb/build/dev'
|
|
223
93
|
|
|
224
|
-
|
|
94
|
+
export default defineConfig({
|
|
95
|
+
plugins: [
|
|
96
|
+
siteContentPlugin({ sitePath: './', inject: true }),
|
|
97
|
+
foundationDevPlugin({ path: '../foundation', serve: '/foundation' }),
|
|
98
|
+
]
|
|
99
|
+
})
|
|
225
100
|
```
|
|
226
101
|
|
|
227
102
|
## Related Packages
|
|
228
103
|
|
|
229
|
-
- [
|
|
230
|
-
- [`@uniweb/
|
|
104
|
+
- [`@uniweb/core`](https://github.com/uniweb/core) - Core classes (Uniweb, Website, Block)
|
|
105
|
+
- [`@uniweb/kit`](https://github.com/uniweb/kit) - Component library for foundations
|
|
106
|
+
- [`@uniweb/build`](https://github.com/uniweb/build) - Vite build plugins
|
|
107
|
+
- [`uniweb`](https://github.com/uniweb/cli) - CLI for creating projects
|
|
231
108
|
|
|
232
109
|
## License
|
|
233
110
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./src/index.jsx"
|
|
8
|
-
"./vite": "./src/vite/index.js"
|
|
7
|
+
".": "./src/index.jsx"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"src"
|
|
@@ -13,9 +12,8 @@
|
|
|
13
12
|
"keywords": [
|
|
14
13
|
"uniweb",
|
|
15
14
|
"runtime",
|
|
16
|
-
"vite",
|
|
17
15
|
"react",
|
|
18
|
-
"
|
|
16
|
+
"foundation"
|
|
19
17
|
],
|
|
20
18
|
"author": "Proximify",
|
|
21
19
|
"license": "Apache-2.0",
|
|
@@ -31,21 +29,11 @@
|
|
|
31
29
|
"node": ">=20.19"
|
|
32
30
|
},
|
|
33
31
|
"dependencies": {
|
|
34
|
-
"
|
|
35
|
-
"@uniweb/semantic-parser": "1.0.1"
|
|
32
|
+
"@uniweb/core": "0.1.5"
|
|
36
33
|
},
|
|
37
34
|
"peerDependencies": {
|
|
38
|
-
"react": "^18.0.0",
|
|
39
|
-
"react-dom": "^18.0.0",
|
|
40
|
-
"react-router-dom": "^6.0.0 || ^7.0.0"
|
|
41
|
-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
42
|
-
},
|
|
43
|
-
"peerDependenciesMeta": {
|
|
44
|
-
"vite": {
|
|
45
|
-
"optional": true
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
"optionalDependencies": {
|
|
49
|
-
"@uniweb/content-reader": "1.0.1"
|
|
35
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
36
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
37
|
+
"react-router-dom": "^6.0.0 || ^7.0.0"
|
|
50
38
|
}
|
|
51
39
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocks
|
|
3
|
+
*
|
|
4
|
+
* Renders an array of blocks for a layout area (header, body, footer, panels).
|
|
5
|
+
* Used by the Layout component to pre-render each area.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import BlockRenderer from './BlockRenderer.jsx'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render a list of blocks
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} props
|
|
15
|
+
* @param {Block[]} props.blocks - Array of Block instances to render
|
|
16
|
+
* @param {Object} [props.extra] - Extra props to pass to each block
|
|
17
|
+
*/
|
|
18
|
+
export default function Blocks({ blocks, extra = {} }) {
|
|
19
|
+
if (!blocks || blocks.length === 0) return null
|
|
20
|
+
|
|
21
|
+
return blocks.map((block, index) => (
|
|
22
|
+
<React.Fragment key={block.id || index}>
|
|
23
|
+
<BlockRenderer block={block} extra={extra} />
|
|
24
|
+
</React.Fragment>
|
|
25
|
+
))
|
|
26
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates page rendering by assembling layout areas (header, body, footer, panels).
|
|
5
|
+
* Supports foundation-provided custom Layout components via website.getRemoteLayout().
|
|
6
|
+
*
|
|
7
|
+
* Layout Areas:
|
|
8
|
+
* - header: Top navigation, branding (from @header page)
|
|
9
|
+
* - body: Main page content (from page sections)
|
|
10
|
+
* - footer: Bottom navigation, copyright (from @footer page)
|
|
11
|
+
* - left: Left sidebar/panel (from @left page)
|
|
12
|
+
* - right: Right sidebar/panel (from @right page)
|
|
13
|
+
*
|
|
14
|
+
* Custom Layouts:
|
|
15
|
+
* Foundations can export a Layout component that receives pre-rendered areas as props:
|
|
16
|
+
*
|
|
17
|
+
* ```jsx
|
|
18
|
+
* export const site = {
|
|
19
|
+
* Layout: ({ page, website, header, body, footer, left, right }) => (
|
|
20
|
+
* <div className="my-layout">
|
|
21
|
+
* <header>{header}</header>
|
|
22
|
+
* <aside>{left}</aside>
|
|
23
|
+
* <main>{body}</main>
|
|
24
|
+
* <aside>{right}</aside>
|
|
25
|
+
* <footer>{footer}</footer>
|
|
26
|
+
* </div>
|
|
27
|
+
* )
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import React from 'react'
|
|
33
|
+
import Blocks from './Blocks.jsx'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default layout - renders header, body, footer in sequence
|
|
37
|
+
* (no panels in default layout)
|
|
38
|
+
*/
|
|
39
|
+
function DefaultLayout({ header, body, footer }) {
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{header}
|
|
43
|
+
{body}
|
|
44
|
+
{footer}
|
|
45
|
+
</>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Layout component
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} props
|
|
53
|
+
* @param {Page} props.page - Current page instance
|
|
54
|
+
* @param {Website} props.website - Website instance
|
|
55
|
+
*/
|
|
56
|
+
export default function Layout({ page, website }) {
|
|
57
|
+
// Check if foundation provides a custom Layout
|
|
58
|
+
const RemoteLayout = website.getRemoteLayout()
|
|
59
|
+
|
|
60
|
+
// Get block groups from page (respects layout preferences)
|
|
61
|
+
const headerBlocks = page.getHeaderBlocks()
|
|
62
|
+
const bodyBlocks = page.getBodyBlocks()
|
|
63
|
+
const footerBlocks = page.getFooterBlocks()
|
|
64
|
+
const leftBlocks = page.getLeftBlocks()
|
|
65
|
+
const rightBlocks = page.getRightBlocks()
|
|
66
|
+
|
|
67
|
+
// Pre-render each area as React elements
|
|
68
|
+
const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null
|
|
69
|
+
const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null
|
|
70
|
+
const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null
|
|
71
|
+
const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null
|
|
72
|
+
const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null
|
|
73
|
+
|
|
74
|
+
// Use foundation's custom Layout if provided
|
|
75
|
+
if (RemoteLayout) {
|
|
76
|
+
return (
|
|
77
|
+
<RemoteLayout
|
|
78
|
+
page={page}
|
|
79
|
+
website={website}
|
|
80
|
+
header={headerElement}
|
|
81
|
+
body={bodyElement}
|
|
82
|
+
footer={footerElement}
|
|
83
|
+
left={leftElement}
|
|
84
|
+
right={rightElement}
|
|
85
|
+
// Aliases for backwards compatibility
|
|
86
|
+
leftPanel={leftElement}
|
|
87
|
+
rightPanel={rightElement}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Default layout
|
|
93
|
+
return (
|
|
94
|
+
<DefaultLayout
|
|
95
|
+
header={headerElement}
|
|
96
|
+
body={bodyElement}
|
|
97
|
+
footer={footerElement}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PageRenderer
|
|
3
3
|
*
|
|
4
|
-
* Renders a page
|
|
4
|
+
* Renders a page using the Layout component for proper orchestration
|
|
5
|
+
* of header, body, footer, and panel areas.
|
|
6
|
+
* Manages head meta tags for SEO and social sharing.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
import React
|
|
9
|
+
import React from 'react'
|
|
8
10
|
import BlockRenderer from './BlockRenderer.jsx'
|
|
11
|
+
import Layout from './Layout.jsx'
|
|
12
|
+
import { useHeadMeta } from '../hooks/useHeadMeta.js'
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* ChildBlocks - renders child blocks of a block
|
|
@@ -23,17 +27,27 @@ export function ChildBlocks({ block, childBlocks, pure = false, extra = {} }) {
|
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* PageRenderer component
|
|
30
|
+
*
|
|
31
|
+
* Renders the current page using the Layout system which supports:
|
|
32
|
+
* - Header, body, footer areas
|
|
33
|
+
* - Left and right panels
|
|
34
|
+
* - Foundation-provided custom layouts
|
|
35
|
+
* - Per-page layout preferences
|
|
26
36
|
*/
|
|
27
37
|
export default function PageRenderer() {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
38
|
+
const uniweb = globalThis.uniweb
|
|
39
|
+
const website = uniweb?.activeWebsite
|
|
40
|
+
const page = website?.activePage
|
|
41
|
+
const siteName = website?.name || ''
|
|
42
|
+
|
|
43
|
+
// Get head metadata from page (uses Page.getHeadMeta() if available)
|
|
44
|
+
const headMeta = page?.getHeadMeta?.() || {
|
|
45
|
+
title: page?.title || 'Website',
|
|
46
|
+
description: page?.description || ''
|
|
47
|
+
}
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return () => {
|
|
34
|
-
document.title = 'Website'
|
|
35
|
-
}
|
|
36
|
-
}, [pageTitle])
|
|
49
|
+
// Manage head meta tags
|
|
50
|
+
useHeadMeta(headMeta, { siteName })
|
|
37
51
|
|
|
38
52
|
if (!page) {
|
|
39
53
|
return (
|
|
@@ -43,15 +57,6 @@ export default function PageRenderer() {
|
|
|
43
57
|
)
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<>
|
|
50
|
-
{blocks.map((block, index) => (
|
|
51
|
-
<React.Fragment key={block.id || index}>
|
|
52
|
-
<BlockRenderer block={block} />
|
|
53
|
-
</React.Fragment>
|
|
54
|
-
))}
|
|
55
|
-
</>
|
|
56
|
-
)
|
|
60
|
+
// Use Layout component for proper orchestration
|
|
61
|
+
return <Layout page={page} website={website} />
|
|
57
62
|
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* WebsiteRenderer
|
|
3
3
|
*
|
|
4
4
|
* Top-level renderer that sets up theme styles and renders pages.
|
|
5
|
+
* Manages scroll memory for navigation and optional analytics.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import React from 'react'
|
|
8
9
|
import PageRenderer from './PageRenderer.jsx'
|
|
10
|
+
import { useRememberScroll } from '../hooks/useRememberScroll.js'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Build CSS custom properties from theme data
|
|
@@ -57,6 +59,9 @@ function Fonts({ fontsData }) {
|
|
|
57
59
|
export default function WebsiteRenderer() {
|
|
58
60
|
const website = globalThis.uniweb?.activeWebsite
|
|
59
61
|
|
|
62
|
+
// Enable scroll memory for navigation
|
|
63
|
+
useRememberScroll({ enabled: true })
|
|
64
|
+
|
|
60
65
|
if (!website) {
|
|
61
66
|
return (
|
|
62
67
|
<div className="website-loading" style={{ padding: '2rem', textAlign: 'center', color: '#64748b' }}>
|