orga-build 0.2.7 → 0.3.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.org +220 -0
- package/lib/content.d.ts +51 -0
- package/lib/files.d.ts +9 -6
- package/lib/files.d.ts.map +1 -1
- package/lib/files.js +117 -2
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +52 -0
- package/package.json +7 -3
package/README.org
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#+TITLE: orga-build
|
|
2
|
+
|
|
3
|
+
A simple tool that builds org-mode files into a website.
|
|
4
|
+
|
|
5
|
+
* Installation
|
|
6
|
+
|
|
7
|
+
#+begin_src bash
|
|
8
|
+
npm install orga-build
|
|
9
|
+
#+end_src
|
|
10
|
+
|
|
11
|
+
* Content Query API
|
|
12
|
+
|
|
13
|
+
orga-build provides an Astro-inspired content query API via the =orga-build:content= virtual module. This allows you to safely query content entries from any page or layout without circular imports.
|
|
14
|
+
|
|
15
|
+
** Importing
|
|
16
|
+
|
|
17
|
+
#+begin_src typescript
|
|
18
|
+
import { getPages, getPage } from 'orga-build:content'
|
|
19
|
+
#+end_src
|
|
20
|
+
|
|
21
|
+
** API Reference
|
|
22
|
+
|
|
23
|
+
*** =getPages(path?, filter?)=
|
|
24
|
+
|
|
25
|
+
Get all content entries matching a path pattern.
|
|
26
|
+
|
|
27
|
+
*Parameters:*
|
|
28
|
+
- =path= (optional): Path prefix to filter by (e.g., ='writing'=, ='content/writing/2025'=)
|
|
29
|
+
- =filter= (optional): Filter function to further refine results
|
|
30
|
+
|
|
31
|
+
*Returns:* Array of =ContentEntry= objects
|
|
32
|
+
|
|
33
|
+
*Examples:*
|
|
34
|
+
|
|
35
|
+
#+begin_src typescript
|
|
36
|
+
// Get all entries
|
|
37
|
+
const all = getPages()
|
|
38
|
+
|
|
39
|
+
// Get all entries in the 'writing' path
|
|
40
|
+
const writing = getPages('writing')
|
|
41
|
+
|
|
42
|
+
// Get entries in a nested path
|
|
43
|
+
const posts2025 = getPages('content/writing/2025')
|
|
44
|
+
|
|
45
|
+
// Filter out drafts
|
|
46
|
+
const published = getPages('writing', (entry) => {
|
|
47
|
+
return entry.data['draft'] !== 'true'
|
|
48
|
+
})
|
|
49
|
+
#+end_src
|
|
50
|
+
|
|
51
|
+
*** =getPage(idOrSlug, path?)=
|
|
52
|
+
|
|
53
|
+
Get a single content entry by id or slug.
|
|
54
|
+
|
|
55
|
+
*Parameters:*
|
|
56
|
+
- =idOrSlug=: The id or slug of the entry to find
|
|
57
|
+
- =path= (optional): Path prefix to search within
|
|
58
|
+
|
|
59
|
+
*Returns:* =ContentEntry | undefined=
|
|
60
|
+
|
|
61
|
+
*Examples:*
|
|
62
|
+
|
|
63
|
+
#+begin_src typescript
|
|
64
|
+
// Get by slug
|
|
65
|
+
const post = getPage('/writing/the-birth-of-emacsclient')
|
|
66
|
+
|
|
67
|
+
// Get by id within a path
|
|
68
|
+
const post = getPage('the-birth-of-emacsclient', 'writing')
|
|
69
|
+
#+end_src
|
|
70
|
+
|
|
71
|
+
*** =getEntries(refs)=
|
|
72
|
+
|
|
73
|
+
Get multiple content entries by reference.
|
|
74
|
+
|
|
75
|
+
*Parameters:*
|
|
76
|
+
- =refs=: Array of references with =id= and optional =path=
|
|
77
|
+
|
|
78
|
+
*Returns:* Array of =ContentEntry | undefined=
|
|
79
|
+
|
|
80
|
+
*Examples:*
|
|
81
|
+
|
|
82
|
+
#+begin_src typescript
|
|
83
|
+
const entries = getEntries([
|
|
84
|
+
{ id: 'post-1', path: 'writing' },
|
|
85
|
+
{ id: 'post-2', path: 'writing' }
|
|
86
|
+
])
|
|
87
|
+
#+end_src
|
|
88
|
+
|
|
89
|
+
*** Aliases
|
|
90
|
+
|
|
91
|
+
For Astro familiarity:
|
|
92
|
+
- =getCollection= - Alias for =getPages=
|
|
93
|
+
- =getEntry= - Alias for =getPage=
|
|
94
|
+
|
|
95
|
+
** ContentEntry Type
|
|
96
|
+
|
|
97
|
+
Each entry has the following structure:
|
|
98
|
+
|
|
99
|
+
#+begin_src typescript
|
|
100
|
+
interface ContentEntry {
|
|
101
|
+
id: string // e.g., 'post-name' or 'index'
|
|
102
|
+
slug: string // e.g., '/writing/post-name'
|
|
103
|
+
path: string // e.g., 'writing' or 'content/writing/2025'
|
|
104
|
+
filePath: string // absolute source file path
|
|
105
|
+
ext: 'org' | 'tsx' | 'jsx' // file extension
|
|
106
|
+
data: Record<string, unknown> // metadata from org headers
|
|
107
|
+
}
|
|
108
|
+
#+end_src
|
|
109
|
+
|
|
110
|
+
** Metadata Extraction
|
|
111
|
+
|
|
112
|
+
For =.org= files, orga-build automatically extracts metadata from org-mode headers:
|
|
113
|
+
|
|
114
|
+
#+begin_example
|
|
115
|
+
#+title: My Post
|
|
116
|
+
#+date: 2025-01-15
|
|
117
|
+
#+draft: false
|
|
118
|
+
#+end_example
|
|
119
|
+
|
|
120
|
+
This becomes:
|
|
121
|
+
|
|
122
|
+
#+begin_src typescript
|
|
123
|
+
{
|
|
124
|
+
data: {
|
|
125
|
+
title: 'My Post',
|
|
126
|
+
date: '2025-01-15',
|
|
127
|
+
draft: 'false'
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
#+end_src
|
|
131
|
+
|
|
132
|
+
For =.tsx= and =.jsx= files, the =data= field is currently empty in v1.
|
|
133
|
+
|
|
134
|
+
** Path Matching Behavior
|
|
135
|
+
|
|
136
|
+
Path matching works hierarchically:
|
|
137
|
+
|
|
138
|
+
- =getPages()= - Returns all entries
|
|
139
|
+
- =getPages('writing')= - Returns entries where:
|
|
140
|
+
- =path = 'writing'=, or
|
|
141
|
+
- =path= starts with ='writing/'=
|
|
142
|
+
- =getPages('content/writing/2025')= - Returns entries under that specific subtree
|
|
143
|
+
|
|
144
|
+
*Path Derivation Examples:*
|
|
145
|
+
|
|
146
|
+
| File Path | Slug | Path | ID |
|
|
147
|
+
|-----------+------+------+----|
|
|
148
|
+
| =pages/writing/foo.org= | =/writing/foo= | =writing= | =foo= |
|
|
149
|
+
| =pages/content/writing/2025/post.org= | =/content/writing/2025/post= | =content/writing/2025= | =post= |
|
|
150
|
+
| =pages/index.org= | =/= | =''= (empty) | =index= |
|
|
151
|
+
| =pages/about.org= | =/about= | =''= (empty) | =about= |
|
|
152
|
+
|
|
153
|
+
** Example: Blog Index Page
|
|
154
|
+
|
|
155
|
+
#+begin_src tsx
|
|
156
|
+
import { getPages } from 'orga-build:content'
|
|
157
|
+
|
|
158
|
+
export default function BlogIndex() {
|
|
159
|
+
const posts = getPages('writing', (entry) => {
|
|
160
|
+
return entry.data['draft'] !== 'true'
|
|
161
|
+
}).sort((a, b) => {
|
|
162
|
+
// Sort by date descending
|
|
163
|
+
return String(b.data['date']).localeCompare(String(a.data['date']))
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div>
|
|
168
|
+
<h1>Blog Posts</h1>
|
|
169
|
+
<ul>
|
|
170
|
+
{posts.map((post) => (
|
|
171
|
+
<li key={post.id}>
|
|
172
|
+
<a href={post.slug}>{String(post.data['title'])}</a>
|
|
173
|
+
<span>{String(post.data['date'])}</span>
|
|
174
|
+
</li>
|
|
175
|
+
))}
|
|
176
|
+
</ul>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
#+end_src
|
|
181
|
+
|
|
182
|
+
** Example: Related Posts
|
|
183
|
+
|
|
184
|
+
#+begin_src tsx
|
|
185
|
+
import { getPages } from 'orga-build:content'
|
|
186
|
+
|
|
187
|
+
export default function Post({ slug }: { slug: string }) {
|
|
188
|
+
// Get current post
|
|
189
|
+
const currentPost = getPages().find((p) => p.slug === slug)
|
|
190
|
+
|
|
191
|
+
// Get related posts from same path
|
|
192
|
+
const related = getPages(currentPost?.path).filter(
|
|
193
|
+
(p) => p.slug !== slug
|
|
194
|
+
).slice(0, 3)
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
<h2>Related Posts</h2>
|
|
199
|
+
<ul>
|
|
200
|
+
{related.map((post) => (
|
|
201
|
+
<li key={post.id}>
|
|
202
|
+
<a href={post.slug}>{String(post.data['title'])}</a>
|
|
203
|
+
</li>
|
|
204
|
+
))}
|
|
205
|
+
</ul>
|
|
206
|
+
</div>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
#+end_src
|
|
210
|
+
|
|
211
|
+
* Development
|
|
212
|
+
|
|
213
|
+
** TODO Items
|
|
214
|
+
|
|
215
|
+
- resolve relative path in links and images
|
|
216
|
+
- monitor file changes and cache properly
|
|
217
|
+
|
|
218
|
+
* License
|
|
219
|
+
|
|
220
|
+
MIT
|
package/lib/content.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
declare module 'orga-build:content' {
|
|
2
|
+
export interface ContentEntry {
|
|
3
|
+
id: string
|
|
4
|
+
slug: string
|
|
5
|
+
path: string
|
|
6
|
+
filePath: string
|
|
7
|
+
ext: 'org' | 'tsx' | 'jsx'
|
|
8
|
+
data: Record<string, unknown>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get all content entries matching a path pattern
|
|
13
|
+
* @param path - Optional path prefix to filter by (e.g., 'writing', 'content/writing/2025')
|
|
14
|
+
* @param filter - Optional filter function to further refine results
|
|
15
|
+
* @returns Array of matching content entries
|
|
16
|
+
*/
|
|
17
|
+
export function getPages(
|
|
18
|
+
path?: string,
|
|
19
|
+
filter?: (entry: ContentEntry) => boolean
|
|
20
|
+
): ContentEntry[]
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get a single content entry by id or slug
|
|
24
|
+
* @param idOrSlug - The id or slug of the entry to find
|
|
25
|
+
* @param path - Optional path prefix to search within
|
|
26
|
+
* @returns The matching content entry or undefined
|
|
27
|
+
*/
|
|
28
|
+
export function getPage(
|
|
29
|
+
idOrSlug: string,
|
|
30
|
+
path?: string
|
|
31
|
+
): ContentEntry | undefined
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get multiple content entries by reference
|
|
35
|
+
* @param refs - Array of references with id and optional path
|
|
36
|
+
* @returns Array of matching content entries (may include undefined)
|
|
37
|
+
*/
|
|
38
|
+
export function getEntries(
|
|
39
|
+
refs: Array<{ path?: string; id: string }>
|
|
40
|
+
): Array<ContentEntry | undefined>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Alias for getPages
|
|
44
|
+
*/
|
|
45
|
+
export const getCollection: typeof getPages
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Alias for getPage
|
|
49
|
+
*/
|
|
50
|
+
export const getEntry: typeof getPage
|
|
51
|
+
}
|
package/lib/files.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} Page
|
|
3
|
-
* @property {string} dataPath
|
|
4
|
-
* @property {string} [title]
|
|
5
|
-
* Path to the page data file
|
|
6
|
-
*/
|
|
7
1
|
/**
|
|
8
2
|
* @param {string} dir
|
|
9
3
|
*/
|
|
@@ -12,6 +6,7 @@ export function setup(dir: string): {
|
|
|
12
6
|
page: (id: string) => Promise<Page>;
|
|
13
7
|
components: () => Promise<string | null>;
|
|
14
8
|
layouts: () => Promise<Record<string, string>>;
|
|
9
|
+
contentEntries: () => Promise<ContentEntry[]>;
|
|
15
10
|
};
|
|
16
11
|
export type Page = {
|
|
17
12
|
dataPath: string;
|
|
@@ -20,4 +15,12 @@ export type Page = {
|
|
|
20
15
|
*/
|
|
21
16
|
title?: string;
|
|
22
17
|
};
|
|
18
|
+
export type ContentEntry = {
|
|
19
|
+
id: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
path: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
ext: "org" | "tsx" | "jsx";
|
|
24
|
+
data: Record<string, unknown>;
|
|
25
|
+
};
|
|
23
26
|
//# sourceMappingURL=files.d.ts.map
|
package/lib/files.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"AA8EA;;GAEG;AACH,2BAFW,MAAM;;eA2HJ,MAAM;;;;EAKlB;;cAxMa,MAAM;;;;YACN,MAAM;;;QAMN,MAAM;UACN,MAAM;UACN,MAAM;cACN,MAAM;SACN,KAAK,GAAG,KAAK,GAAG,KAAK;UACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
|
package/lib/files.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { globby } from 'globby'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { getSettings } from 'orga'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @typedef {Object} Page
|
|
@@ -8,6 +10,72 @@ import path from 'node:path'
|
|
|
8
10
|
* Path to the page data file
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ContentEntry
|
|
15
|
+
* @property {string} id
|
|
16
|
+
* @property {string} slug
|
|
17
|
+
* @property {string} path
|
|
18
|
+
* @property {string} filePath
|
|
19
|
+
* @property {'org' | 'tsx' | 'jsx'} ext
|
|
20
|
+
* @property {Record<string, unknown>} data
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extract file extension from file path
|
|
25
|
+
* @param {string} filePath
|
|
26
|
+
* @returns {'org' | 'tsx' | 'jsx'}
|
|
27
|
+
*/
|
|
28
|
+
function getFileExtension(filePath) {
|
|
29
|
+
const match = filePath.match(/\.(org|tsx|jsx)$/)
|
|
30
|
+
return /** @type {'org' | 'tsx' | 'jsx'} */ (match ? match[1] : 'org')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Derive content path from slug
|
|
35
|
+
* @param {string} slug - e.g. '/writing/foo', '/content/writing/2025/post', '/'
|
|
36
|
+
* @returns {string} - e.g. 'writing', 'content/writing/2025', ''
|
|
37
|
+
*/
|
|
38
|
+
function getContentPath(slug) {
|
|
39
|
+
// Remove leading slash
|
|
40
|
+
let normalized = slug.replace(/^\/+/, '')
|
|
41
|
+
// Remove trailing slash
|
|
42
|
+
normalized = normalized.replace(/\/+$/, '')
|
|
43
|
+
|
|
44
|
+
// If root page, return empty string
|
|
45
|
+
if (!normalized) {
|
|
46
|
+
return ''
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Get all segments except the last one (the file name)
|
|
50
|
+
const segments = normalized.split('/')
|
|
51
|
+
if (segments.length === 1) {
|
|
52
|
+
// Single segment like '/about' -> path is empty (root level)
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Multiple segments like '/writing/foo' -> path is 'writing'
|
|
57
|
+
return segments.slice(0, -1).join('/')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Derive content id from slug
|
|
62
|
+
* @param {string} slug - e.g. '/writing/foo', '/writing', '/'
|
|
63
|
+
* @returns {string} - e.g. 'foo', 'writing', 'index'
|
|
64
|
+
*/
|
|
65
|
+
function getContentId(slug) {
|
|
66
|
+
// Remove leading and trailing slashes
|
|
67
|
+
let normalized = slug.replace(/^\/+/, '').replace(/\/+$/, '')
|
|
68
|
+
|
|
69
|
+
// If root page, return 'index'
|
|
70
|
+
if (!normalized) {
|
|
71
|
+
return 'index'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get last segment
|
|
75
|
+
const segments = normalized.split('/')
|
|
76
|
+
return segments[segments.length - 1] || 'index'
|
|
77
|
+
}
|
|
78
|
+
|
|
11
79
|
/**
|
|
12
80
|
* @param {string} dir
|
|
13
81
|
*/
|
|
@@ -78,13 +146,60 @@ export function setup(dir) {
|
|
|
78
146
|
return files[0] ? path.join(dir, files[0]) : null
|
|
79
147
|
})
|
|
80
148
|
|
|
81
|
-
|
|
149
|
+
const contentEntries = cache(async function () {
|
|
150
|
+
const allPages = await pages()
|
|
151
|
+
/** @type {ContentEntry[]} */
|
|
152
|
+
const entries = []
|
|
153
|
+
|
|
154
|
+
for (const [slug, pageData] of Object.entries(allPages)) {
|
|
155
|
+
const filePath = pageData.dataPath
|
|
156
|
+
const ext = getFileExtension(filePath)
|
|
157
|
+
|
|
158
|
+
// Derive path from directory structure
|
|
159
|
+
const derivedPath = getContentPath(slug)
|
|
160
|
+
|
|
161
|
+
// Derive id from the slug (last segment or 'index')
|
|
162
|
+
const id = getContentId(slug)
|
|
163
|
+
|
|
164
|
+
/** @type {Record<string, unknown>} */
|
|
165
|
+
let data = {}
|
|
166
|
+
|
|
167
|
+
// Extract metadata from .org files
|
|
168
|
+
if (ext === 'org') {
|
|
169
|
+
try {
|
|
170
|
+
const content = await readFile(filePath, 'utf-8')
|
|
171
|
+
data = getSettings(content)
|
|
172
|
+
} catch (/** @type {any} */ error) {
|
|
173
|
+
console.warn(
|
|
174
|
+
`Failed to read metadata from ${filePath}:`,
|
|
175
|
+
error?.message || error
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
entries.push({
|
|
181
|
+
id,
|
|
182
|
+
slug,
|
|
183
|
+
path: derivedPath,
|
|
184
|
+
filePath,
|
|
185
|
+
ext,
|
|
186
|
+
data
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return entries
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const files = {
|
|
82
194
|
pages,
|
|
83
195
|
page,
|
|
84
196
|
components,
|
|
85
|
-
layouts
|
|
197
|
+
layouts,
|
|
198
|
+
contentEntries
|
|
86
199
|
}
|
|
87
200
|
|
|
201
|
+
return files
|
|
202
|
+
|
|
88
203
|
/** @param {string} id */
|
|
89
204
|
async function page(id) {
|
|
90
205
|
const all = await pages()
|
package/lib/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["vite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["vite.js"],"names":[],"mappings":"AASA;;;;GAIG;AACH,uCAHG;IAAwB,GAAG,EAAnB,MAAM;CACd,GAAU,OAAO,MAAM,EAAE,MAAM,CA6JjC"}
|
package/lib/vite.js
CHANGED
|
@@ -4,6 +4,8 @@ import path from 'node:path'
|
|
|
4
4
|
const magicModulePrefix = '/@orga-build/'
|
|
5
5
|
const pagesModuleId = magicModulePrefix + 'pages'
|
|
6
6
|
const appEntryId = `${magicModulePrefix}main.js`
|
|
7
|
+
const contentModuleId = 'orga-build:content'
|
|
8
|
+
const contentModuleIdResolved = '\0' + contentModuleId
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* @param {Object} options
|
|
@@ -39,6 +41,11 @@ export function pluginFactory({ dir }) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
reloadVirtualModule('/')
|
|
44
|
+
|
|
45
|
+
// Invalidate content module on file changes
|
|
46
|
+
watcher.on('change', () => {
|
|
47
|
+
reloadVirtualModule(contentModuleIdResolved)
|
|
48
|
+
})
|
|
42
49
|
},
|
|
43
50
|
|
|
44
51
|
buildStart() {},
|
|
@@ -47,6 +54,9 @@ export function pluginFactory({ dir }) {
|
|
|
47
54
|
if (id === appEntryId) {
|
|
48
55
|
return appEntryId
|
|
49
56
|
}
|
|
57
|
+
if (id === contentModuleId) {
|
|
58
|
+
return contentModuleIdResolved
|
|
59
|
+
}
|
|
50
60
|
if (id.startsWith(magicModulePrefix)) {
|
|
51
61
|
return id
|
|
52
62
|
}
|
|
@@ -55,6 +65,9 @@ export function pluginFactory({ dir }) {
|
|
|
55
65
|
if (id === appEntryId) {
|
|
56
66
|
return `import "orga-build/csr";`
|
|
57
67
|
}
|
|
68
|
+
if (id === contentModuleIdResolved) {
|
|
69
|
+
return await renderContentModule()
|
|
70
|
+
}
|
|
58
71
|
if (id === pagesModuleId) {
|
|
59
72
|
return await renderPageList()
|
|
60
73
|
}
|
|
@@ -115,4 +128,43 @@ export default pages;
|
|
|
115
128
|
}
|
|
116
129
|
return ''
|
|
117
130
|
}
|
|
131
|
+
|
|
132
|
+
async function renderContentModule() {
|
|
133
|
+
const entries = await files.contentEntries()
|
|
134
|
+
const manifest = JSON.stringify(entries, null, 2)
|
|
135
|
+
|
|
136
|
+
return `
|
|
137
|
+
const __entries = ${manifest}
|
|
138
|
+
|
|
139
|
+
function normalizePath(path = '') {
|
|
140
|
+
return String(path).replace(/^\\/+|\\/+$/g, '')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function pathMatches(entryPath, queryPath) {
|
|
144
|
+
const e = normalizePath(entryPath)
|
|
145
|
+
const q = normalizePath(queryPath)
|
|
146
|
+
if (!q) return true
|
|
147
|
+
return e === q || e.startsWith(q + '/')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function getPages(path = '', filter) {
|
|
151
|
+
const list = __entries.filter((e) => pathMatches(e.path, path))
|
|
152
|
+
return typeof filter === 'function' ? list.filter(filter) : list
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getPage(idOrSlug, path = '') {
|
|
156
|
+
return __entries.find((e) => {
|
|
157
|
+
if (!pathMatches(e.path, path)) return false
|
|
158
|
+
return e.id === idOrSlug || e.slug === idOrSlug
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getEntries(refs) {
|
|
163
|
+
return refs.map((r) => getPage(r.id, r.path || ''))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const getCollection = getPages
|
|
167
|
+
export const getEntry = getPage
|
|
168
|
+
`
|
|
169
|
+
}
|
|
118
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orga-build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A simple tool that builds org-mode files into a website",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"cli.js",
|
|
12
12
|
"index.js",
|
|
13
13
|
"index.d.ts",
|
|
14
|
-
"index.d.ts.map"
|
|
14
|
+
"index.d.ts.map",
|
|
15
|
+
"lib/content.d.ts"
|
|
15
16
|
],
|
|
16
17
|
"exports": {
|
|
17
18
|
".": {
|
|
@@ -19,7 +20,10 @@
|
|
|
19
20
|
"import": "./index.js"
|
|
20
21
|
},
|
|
21
22
|
"./csr": "./lib/csr.jsx",
|
|
22
|
-
"./components": "./lib/components.js"
|
|
23
|
+
"./components": "./lib/components.js",
|
|
24
|
+
"./content": {
|
|
25
|
+
"types": "./lib/content.d.ts"
|
|
26
|
+
}
|
|
23
27
|
},
|
|
24
28
|
"keywords": [
|
|
25
29
|
"orgajs",
|