coralite-plugin-aggregation 0.10.0 → 0.11.0

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 CHANGED
@@ -33,10 +33,24 @@ Create a template for individual items (e.g., `templates/coralite-post.html`):
33
33
  ```html
34
34
  <template id="coralite-post">
35
35
  <article class="post">
36
- <h2><a href="{{ $urlPathname }}">{{ meta_title }}</a></h2>
37
- <p>{{ meta_description }}</p>
36
+ <h2><a href="{{ url }}">{{ title }}</a></h2>
37
+ <p>{{ description }}</p>
38
38
  </article>
39
39
  </template>
40
+
41
+ <script type="module">
42
+ import { defineComponent } from 'coralite'
43
+
44
+ export default defineComponent({
45
+ data: ({ page }) => {
46
+ return {
47
+ url: page.url.pathname,
48
+ title: page.meta.title,
49
+ description: page.meta.description
50
+ }
51
+ }
52
+ })
53
+ </script>
40
54
  ```
41
55
 
42
56
  Create a component to list them (e.g., `templates/blog-list.html`):
@@ -50,28 +64,30 @@ Create a component to list them (e.g., `templates/blog-list.html`):
50
64
 
51
65
  <script type="module">
52
66
  import { defineComponent } from 'coralite'
53
- import { aggregation } from 'coralite/plugins'
67
+ import { aggregate } from 'aggregation'
54
68
 
55
69
  export default defineComponent({
56
- tokens: {
57
- posts: async () => {
58
- return await aggregation({
59
- // Path to aggregate pages from (relative to pages directory)
60
- path: ['blog'],
61
- // Template ID to render for each item
62
- template: 'coralite-post',
63
- // Sort by date descending (assuming meta_date exists)
64
- sort: (a, b) => new Date(b.meta_date) - new Date(a.meta_date),
65
- // Limit items per page
66
- limit: 10,
67
- // Enable pagination
68
- pagination: {
69
- segment: 'page', // URL segment: /blog/page/1
70
- maxVisible: 5, // Max pagination links to show
71
- ariaLabel: 'Blog Pagination',
72
- ellipsis: '...'
73
- }
74
- })
70
+ data: async () => {
71
+ const posts = await aggregate({
72
+ // Path to aggregate pages from (relative to pages directory)
73
+ path: ['blog'],
74
+ // Template ID to render for each item
75
+ template: 'coralite-post',
76
+ // Sort by date descending (assuming meta.date exists)
77
+ sort: (a, b) => new Date(b.meta.date) - new Date(a.meta.date),
78
+ // Limit items per page
79
+ limit: 10,
80
+ // Enable pagination
81
+ pagination: {
82
+ segment: 'page', // URL segment: /blog/page/1
83
+ maxVisible: 5, // Max pagination links to show
84
+ ariaLabel: 'Blog Pagination',
85
+ ellipsis: '...'
86
+ }
87
+ })
88
+
89
+ return {
90
+ posts
75
91
  }
76
92
  }
77
93
  })
@@ -114,4 +130,4 @@ When `pagination` is enabled and `limit` is set:
114
130
 
115
131
  ## License
116
132
 
117
- AGPL-3.0-or-later
133
+ MPL-2.0
@@ -7,7 +7,7 @@
7
7
  </template>
8
8
 
9
9
  <script type="module">
10
- import { defineComponent } from 'coralite/plugins'
10
+ import { defineComponent } from 'coralite'
11
11
 
12
12
  export default defineComponent({
13
13
  attributes: {
package/lib/index.js CHANGED
@@ -3,237 +3,237 @@ import path from 'node:path'
3
3
 
4
4
  export const aggregation = definePlugin({
5
5
  name: 'aggregation',
6
-
7
- async exports(options) {
8
- const {
9
- path: paths = [],
10
- component,
11
- pagination,
12
- filter,
13
- sort,
14
- limit,
15
- offset = 0,
16
- recursive = false,
17
- transformState
18
- } = options
19
- const state = this.state || {}
20
- const app = this.app
21
- const pagesRoot = app.options.pages
22
-
23
- // Collect pages
24
- let allPages = []
25
- const uniquePaths = new Set()
26
-
27
- for (const relativePath of paths) {
28
- const targetPath = path.join(pagesRoot, relativePath)
29
-
30
- if (!recursive) {
31
- const pagesInDir = app.pages.getListByPath(targetPath)
32
-
33
- if (pagesInDir) {
34
- for (const page of pagesInDir) {
35
- const pagePath = page.path.pathname
36
-
37
- if (!uniquePaths.has(pagePath)) {
38
- uniquePaths.add(pagePath)
39
- allPages.push(page)
6
+ exports: {
7
+ aggregate: (context) => async (options) => {
8
+ const { state = {}, page: currentPageContext, app, renderContext: currentRenderContext } = context;
9
+ const {
10
+ path: paths = [],
11
+ template: component,
12
+ pagination,
13
+ filter,
14
+ sort,
15
+ limit,
16
+ offset = 0,
17
+ recursive = false,
18
+ transformState
19
+ } = options
20
+ const pagesRoot = app.options.pages
21
+
22
+ // Collect pages
23
+ let allPages = []
24
+ const uniquePaths = new Set()
25
+
26
+ for (const relativePath of paths) {
27
+ const targetPath = path.join(pagesRoot, relativePath)
28
+
29
+ if (!recursive) {
30
+ const pagesInDir = app.pages.getListByPath(targetPath)
31
+
32
+ if (pagesInDir) {
33
+ for (const item of pagesInDir) {
34
+ const itemPath = item.path.pathname
35
+
36
+ if (!uniquePaths.has(itemPath)) {
37
+ uniquePaths.add(itemPath)
38
+ allPages.push(item)
39
+ }
40
40
  }
41
41
  }
42
- }
43
- } else {
44
- // Recursive search
45
- for (const page of app.pages.list) {
46
- const dirname = page.path.dirname
42
+ } else {
43
+ // Recursive search
44
+ for (const item of app.pages.list) {
45
+ const dirname = item.path.dirname
47
46
 
48
- if (dirname === targetPath || dirname.startsWith(targetPath + path.sep)) {
49
- const pagePath = page.path.pathname
47
+ if (dirname === targetPath || dirname.startsWith(targetPath + path.sep)) {
48
+ const itemPath = item.path.pathname
50
49
 
51
- if (!uniquePaths.has(pagePath)) {
52
- uniquePaths.add(pagePath)
53
- allPages.push(page)
50
+ if (!uniquePaths.has(itemPath)) {
51
+ uniquePaths.add(itemPath)
52
+ allPages.push(item)
53
+ }
54
54
  }
55
55
  }
56
56
  }
57
57
  }
58
- }
59
-
60
- // Filter
61
- if (typeof filter === 'function') {
62
- allPages = allPages.filter(page => {
63
- return filter(page.result.state)
64
- })
65
- }
66
58
 
67
- // Sort
68
- if (typeof sort === 'function') {
69
- allPages.sort((a, b) => {
70
- const propsA = (a.result && a.result.state) ? a.result.state : a.state
71
- const propsB = (b.result && b.result.state) ? b.result.state : b.state
72
- return sort(propsA, propsB)
73
- })
74
- }
75
-
76
- // Pagination
77
- let startIndex = offset
78
- let endIndex = allPages.length
79
-
80
- let currentPage = 1
81
- let totalPages = 1
82
-
83
- const currentRenderContext = this.renderContext
84
- const buildId = currentRenderContext && currentRenderContext.buildId
59
+ // Filter
60
+ if (typeof filter === 'function') {
61
+ allPages = allPages.filter(item => {
62
+ const itemState = (item.result && item.result.state) ? item.result.state : item.state
63
+ return filter(itemState)
64
+ })
65
+ }
85
66
 
86
- if (limit) {
87
- if (pagination) {
88
- const segment = pagination.segment || 'page'
89
- const urlPathname = this.page.url.pathname
67
+ // Sort
68
+ if (typeof sort === 'function') {
69
+ allPages.sort((a, b) => {
70
+ const propsA = (a.result && a.result.state) ? a.result.state : a.state
71
+ const propsB = (b.result && b.result.state) ? b.result.state : b.state
72
+ return sort(propsA, propsB)
73
+ })
74
+ }
90
75
 
91
- const escapedSegment = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
92
- const segmentRegex = new RegExp(`/${escapedSegment}/(\\d+)`)
93
- const match = urlPathname.match(segmentRegex)
76
+ // Pagination
77
+ let startIndex = offset
78
+ let endIndex = allPages.length
94
79
 
95
- if (match) {
96
- currentPage = parseInt(match[1], 10)
97
- }
80
+ let currentPage = 1
81
+ let totalPages = 1
98
82
 
99
- startIndex = offset + (currentPage - 1) * limit
100
- endIndex = startIndex + limit
101
- totalPages = Math.ceil(allPages.length / limit)
83
+ const buildId = currentRenderContext && currentRenderContext.buildId
102
84
 
103
- if (!match && currentPage === 1 && totalPages > 1 && buildId) {
104
- const currentPathname = this.page.file.pathname
105
- const currentFilename = this.page.file.filename
106
- const currentDirname = this.page.file.dirname
85
+ if (limit) {
86
+ if (pagination) {
87
+ const segment = pagination.segment || 'page'
88
+ const urlPathname = currentPageContext.url.pathname
107
89
 
108
- let targetDir = currentDirname
109
- let urlPrefixBase = ''
90
+ const escapedSegment = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
91
+ const segmentRegex = new RegExp(`/${escapedSegment}/(\\d+)`)
92
+ const match = urlPathname.match(segmentRegex)
110
93
 
111
- if (currentFilename === 'index.html') {
112
- targetDir = currentDirname
113
- urlPrefixBase = path.dirname(urlPathname)
114
- } else {
115
- const basename = path.basename(currentFilename, path.extname(currentFilename))
116
- targetDir = path.join(currentDirname, basename)
117
- urlPrefixBase = urlPathname.replace(path.extname(currentFilename), '')
94
+ if (match) {
95
+ currentPage = parseInt(match[1], 10)
118
96
  }
119
97
 
120
- if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
98
+ startIndex = offset + (currentPage - 1) * limit
99
+ endIndex = startIndex + limit
100
+ totalPages = Math.ceil(allPages.length / limit)
101
+
102
+ if (!match && currentPage === 1 && totalPages > 1 && buildId) {
103
+ const currentPathname = currentPageContext.file.pathname
104
+ const currentFilename = currentPageContext.file.filename
105
+ const currentDirname = currentPageContext.file.dirname
106
+
107
+ let targetDir = currentDirname
108
+ let urlPrefixBase = ''
109
+
110
+ if (currentFilename === 'index.html') {
111
+ targetDir = currentDirname
112
+ urlPrefixBase = path.dirname(urlPathname)
113
+ } else {
114
+ const basename = path.basename(currentFilename, path.extname(currentFilename))
115
+ targetDir = path.join(currentDirname, basename)
116
+ urlPrefixBase = urlPathname.replace(path.extname(currentFilename), '')
117
+ }
121
118
 
122
- if (currentFilename === 'index.html') {
123
- urlPrefixBase = path.dirname(urlPathname)
124
119
  if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
125
- }
126
120
 
127
- const currentItem = app.pages.getItem(currentPathname)
128
-
129
- for (let i = 2; i <= totalPages; i++) {
130
- const newPathname = path.join(targetDir, segment, `${i}.html`)
131
-
132
- const virtualItem = {
133
- content: currentItem ? currentItem.content : '',
134
- path: {
135
- pathname: newPathname,
136
- dirname: path.dirname(newPathname),
137
- filename: path.basename(newPathname)
138
- },
139
- state: {
140
- paginationBaseUrl: urlPathname,
141
- paginationUrlPrefix: urlPrefixBase
142
- },
143
- type: 'page'
121
+ if (currentFilename === 'index.html') {
122
+ urlPrefixBase = path.dirname(urlPathname)
123
+ if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
144
124
  }
145
125
 
146
- await app.addRenderQueue(virtualItem, buildId)
126
+ const currentItem = app.pages.getItem(currentPathname)
127
+
128
+ for (let i = 2; i <= totalPages; i++) {
129
+ const newPathname = path.join(targetDir, segment, `${i}.html`)
130
+
131
+ const virtualItem = {
132
+ content: currentItem ? currentItem.content : '',
133
+ path: {
134
+ pathname: newPathname,
135
+ dirname: path.dirname(newPathname),
136
+ filename: path.basename(newPathname)
137
+ },
138
+ state: {
139
+ paginationBaseUrl: urlPathname,
140
+ paginationUrlPrefix: urlPrefixBase
141
+ },
142
+ type: 'page'
143
+ }
144
+
145
+ await app.addRenderQueue(virtualItem, buildId)
146
+ }
147
147
  }
148
+ } else {
149
+ endIndex = Math.min(startIndex + limit, allPages.length)
148
150
  }
149
- } else {
150
- endIndex = Math.min(startIndex + limit, allPages.length)
151
151
  }
152
- }
153
152
 
154
- const paginatedPages = allPages.slice(startIndex, endIndex)
155
- const resultNodes = []
156
-
157
- for (const page of paginatedPages) {
158
- const pageProps = (page.result && page.result.state) ? page.result.state : page.state
159
- let itemProps = { ...pageProps }
160
-
161
- // Apply properties transformations
162
- if (transformState && typeof transformState === 'object') {
163
- for (const key in transformState) {
164
- if (Object.prototype.hasOwnProperty.call(transformState, key)) {
165
- const transform = transformState[key]
166
- if (typeof transform === 'string') {
167
- itemProps[key] = pageProps[transform]
168
- } else if (typeof transform === 'function') {
169
- itemProps[key] = transform(pageProps)
153
+ const paginatedPages = allPages.slice(startIndex, endIndex)
154
+ const resultNodes = []
155
+
156
+ for (const item of paginatedPages) {
157
+ const itemState = (item.result && item.result.state) ? item.result.state : item.state
158
+ let itemProps = { ...itemState }
159
+
160
+ // Apply properties transformations
161
+ if (transformState && typeof transformState === 'object') {
162
+ for (const key in transformState) {
163
+ if (Object.prototype.hasOwnProperty.call(transformState, key)) {
164
+ const transform = transformState[key]
165
+ if (typeof transform === 'string') {
166
+ itemProps[key] = itemState[transform]
167
+ } else if (typeof transform === 'function') {
168
+ itemProps[key] = transform(itemState)
169
+ }
170
170
  }
171
171
  }
172
172
  }
173
- }
174
173
 
175
- if (component) {
176
- const componentElement = await app.createComponentElement({
177
- id: component,
178
- state: itemProps,
179
- page: this.page,
180
- renderContext: currentRenderContext
181
- })
174
+ if (component) {
175
+ const componentElement = await app.createComponentElement({
176
+ id: component,
177
+ state: itemProps,
178
+ page: item.result?.page || item.state?.page,
179
+ renderContext: currentRenderContext
180
+ })
182
181
 
183
- if (componentElement && componentElement.children) {
184
- resultNodes.push(...componentElement.children)
182
+ if (componentElement && componentElement.children) {
183
+ resultNodes.push(...componentElement.children)
184
+ }
185
185
  }
186
186
  }
187
- }
188
187
 
189
- if (pagination) {
190
- const paginationComponentId = pagination.component || 'coralite-pagination'
191
- const urlPathname = this.page.url.pathname
188
+ if (pagination) {
189
+ const paginationComponentId = pagination.component || 'coralite-pagination'
190
+ const urlPathname = currentPageContext.url.pathname
192
191
 
193
- let baseUrl = urlPathname
194
- let urlPrefix = ''
192
+ let baseUrl = urlPathname
193
+ let urlPrefix = ''
195
194
 
196
- if (state.paginationBaseUrl) {
197
- baseUrl = state.paginationBaseUrl
198
- }
195
+ if (state.paginationBaseUrl) {
196
+ baseUrl = state.paginationBaseUrl
197
+ }
199
198
 
200
- if (state.paginationUrlPrefix) {
201
- urlPrefix = state.paginationUrlPrefix
202
- } else {
203
- if (baseUrl.endsWith('/index.html') || baseUrl.endsWith('/')) {
204
- urlPrefix = path.dirname(baseUrl)
199
+ if (state.paginationUrlPrefix) {
200
+ urlPrefix = state.paginationUrlPrefix
205
201
  } else {
206
- const basename = path.basename(baseUrl, '.html')
207
- urlPrefix = path.join(path.dirname(baseUrl), basename)
202
+ if (baseUrl.endsWith('/index.html') || baseUrl.endsWith('/')) {
203
+ urlPrefix = path.dirname(baseUrl)
204
+ } else {
205
+ const basename = path.basename(baseUrl, '.html')
206
+ urlPrefix = path.join(path.dirname(baseUrl), basename)
207
+ }
208
208
  }
209
- }
210
209
 
211
- if (!urlPrefix.endsWith('/')) urlPrefix += '/'
212
-
213
- const paginationProps = {
214
- 'current-page': String(currentPage),
215
- 'total-pages': String(totalPages),
216
- 'base-url': baseUrl,
217
- 'url-prefix': urlPrefix,
218
- segment: pagination.segment || 'page',
219
- 'max-visible': String(pagination.maxVisible || 5),
220
- 'aria-label': pagination.ariaLabel || 'Pagination',
221
- ellipsis: pagination.ellipsis || '...'
222
- }
210
+ if (!urlPrefix.endsWith('/')) urlPrefix += '/'
211
+
212
+ const paginationProps = {
213
+ 'current-page': String(currentPage),
214
+ 'total-pages': String(totalPages),
215
+ 'base-url': baseUrl,
216
+ 'url-prefix': urlPrefix,
217
+ segment: pagination.segment || 'page',
218
+ 'max-visible': String(pagination.maxVisible || 5),
219
+ 'aria-label': pagination.ariaLabel || 'Pagination',
220
+ ellipsis: pagination.ellipsis || '...'
221
+ }
223
222
 
224
- const componentElement = await app.createComponentElement({
225
- id: paginationComponentId,
226
- state: paginationProps,
227
- page: this.page,
228
- renderContext: currentRenderContext
229
- })
223
+ const componentElement = await app.createComponentElement({
224
+ id: paginationComponentId,
225
+ state: paginationProps,
226
+ page: currentPageContext,
227
+ renderContext: currentRenderContext
228
+ })
230
229
 
231
- if (componentElement && componentElement.children) {
232
- resultNodes.push(...componentElement.children)
230
+ if (componentElement && componentElement.children) {
231
+ resultNodes.push(...componentElement.children)
232
+ }
233
233
  }
234
- }
235
234
 
236
- return resultNodes
235
+ return app.transform(resultNodes)
236
+ }
237
237
  },
238
238
  components: [
239
239
  // @ts-ignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-plugin-aggregation",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "A Coralite plugin for dynamically collecting, filtering, sorting, and displaying content across multiple sources. Build database-free Coralite websites with automated content aggregation.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -43,7 +43,7 @@
43
43
  "devDependencies": {
44
44
  "@stylistic/eslint-plugin-js": "^4.2.0",
45
45
  "@stylistic/eslint-plugin-plus": "^4.2.0",
46
- "coralite": "^0.34.0"
46
+ "coralite": "^0.35.0"
47
47
  },
48
48
  "scripts": {
49
49
  "test": "node --test ./tests/index.spec.js"