@rettangoli/sites 0.1.1 → 0.2.0-rc2
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 +258 -66
- package/package.json +8 -11
- package/src/cli/build.js +28 -160
- package/src/cli/index.js +2 -2
- package/src/createSiteBuilder.js +350 -0
- package/src/index.js +3 -0
- package/src/rtglMarkdown.js +126 -0
- package/src/cli/components/core/articlelist.html +0 -19
- package/src/cli/components/core/cta1.html +0 -9
- package/src/cli/components/core/features1.html +0 -24
- package/src/cli/components/core/hero1.html +0 -4
- package/src/cli/components/core/hero2.html +0 -31
- package/src/cli/components/core/sectionlist1.html +0 -15
- package/src/cli/components/core/spacer.html +0 -1
- package/src/cli/components/core/table1.html +0 -2
- package/src/cli/templates/core/admin.html +0 -21
- package/src/cli/templates/core/agreementList.html +0 -102
- package/src/cli/templates/core/article.html +0 -35
- package/src/cli/templates/core/base.html +0 -28
- package/src/cli/templates/core/documentation.html +0 -28
- package/src/cli/templates/core/footer.html +0 -34
- package/src/cli/templates/core/htmlHeader.html +0 -41
- package/src/cli/templates/core/htmlHeaderTable.html +0 -113
- package/src/cli/templates/core/navbar.html +0 -11
- package/src/common.js +0 -803
- package/src/markdownItAsync.js +0 -101
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { convertToHtml } from 'yahtml';
|
|
2
|
+
import { parseAndRender } from 'jempl';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
import MarkdownIt from 'markdown-it';
|
|
7
|
+
|
|
8
|
+
export function createSiteBuilder({ fs, rootDir = '.', mdRender, functions = {} }) {
|
|
9
|
+
return function build() {
|
|
10
|
+
// Use provided mdRender or default to standard markdown-it
|
|
11
|
+
const md = mdRender || MarkdownIt();
|
|
12
|
+
|
|
13
|
+
// Read all partials and create a JSON object
|
|
14
|
+
const partialsDir = path.join(rootDir, 'partials');
|
|
15
|
+
const partials = {};
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(partialsDir)) {
|
|
18
|
+
const files = fs.readdirSync(partialsDir);
|
|
19
|
+
files.forEach(file => {
|
|
20
|
+
const filePath = path.join(partialsDir, file);
|
|
21
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
22
|
+
const nameWithoutExt = path.basename(file, path.extname(file));
|
|
23
|
+
// Convert partial content from YAML string to JSON
|
|
24
|
+
partials[nameWithoutExt] = yaml.load(fileContent, { schema: yaml.JSON_SCHEMA });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Read all data files and create a JSON object
|
|
29
|
+
const dataDir = path.join(rootDir, 'data');
|
|
30
|
+
const globalData = {};
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(dataDir)) {
|
|
33
|
+
const files = fs.readdirSync(dataDir);
|
|
34
|
+
files.forEach(file => {
|
|
35
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
36
|
+
const filePath = path.join(dataDir, file);
|
|
37
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
38
|
+
const nameWithoutExt = path.basename(file, path.extname(file));
|
|
39
|
+
// Load YAML content and store under filename key
|
|
40
|
+
globalData[nameWithoutExt] = yaml.load(fileContent, { schema: yaml.JSON_SCHEMA });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Read all templates and create a JSON object
|
|
46
|
+
const templatesDir = path.join(rootDir, 'templates');
|
|
47
|
+
const templates = {};
|
|
48
|
+
|
|
49
|
+
function readTemplatesRecursively(dir, basePath = '') {
|
|
50
|
+
if (!fs.existsSync(dir)) return;
|
|
51
|
+
|
|
52
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
items.forEach(item => {
|
|
55
|
+
const itemPath = path.join(dir, item.name);
|
|
56
|
+
|
|
57
|
+
if (item.isDirectory()) {
|
|
58
|
+
// Recursively read subdirectories
|
|
59
|
+
const newBasePath = basePath ? `${basePath}/${item.name}` : item.name;
|
|
60
|
+
readTemplatesRecursively(itemPath, newBasePath);
|
|
61
|
+
} else if (item.isFile() && item.name.endsWith('.yaml')) {
|
|
62
|
+
// Read and convert YAML file
|
|
63
|
+
const fileContent = fs.readFileSync(itemPath, 'utf8');
|
|
64
|
+
const nameWithoutExt = path.basename(item.name, '.yaml');
|
|
65
|
+
const templateKey = basePath ? `${basePath}/${nameWithoutExt}` : nameWithoutExt;
|
|
66
|
+
templates[templateKey] = yaml.load(fileContent, { schema: yaml.JSON_SCHEMA });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
readTemplatesRecursively(templatesDir);
|
|
72
|
+
|
|
73
|
+
// Function to extract frontmatter from a page file
|
|
74
|
+
function extractFrontmatter(pagePath) {
|
|
75
|
+
const pageFileContent = fs.readFileSync(pagePath, 'utf8');
|
|
76
|
+
const lines = pageFileContent.split('\n');
|
|
77
|
+
let frontmatterStart = -1;
|
|
78
|
+
let frontmatterEnd = -1;
|
|
79
|
+
let frontmatterCount = 0;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
if (lines[i].trim() === '---') {
|
|
83
|
+
frontmatterCount++;
|
|
84
|
+
if (frontmatterCount === 1) {
|
|
85
|
+
frontmatterStart = i + 1;
|
|
86
|
+
} else if (frontmatterCount === 2) {
|
|
87
|
+
frontmatterEnd = i;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let frontmatter = {};
|
|
94
|
+
if (frontmatterStart > 0 && frontmatterEnd > frontmatterStart) {
|
|
95
|
+
const frontmatterContent = lines.slice(frontmatterStart, frontmatterEnd).join('\n');
|
|
96
|
+
frontmatter = yaml.load(frontmatterContent, { schema: yaml.JSON_SCHEMA }) || {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return frontmatter;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Function to scan all pages and build collections
|
|
103
|
+
function buildCollections() {
|
|
104
|
+
const collections = {};
|
|
105
|
+
const pagesDir = path.join(rootDir, 'pages');
|
|
106
|
+
|
|
107
|
+
function scanPages(dir, basePath = '') {
|
|
108
|
+
const fullDir = path.join(pagesDir, basePath);
|
|
109
|
+
if (!fs.existsSync(fullDir)) return;
|
|
110
|
+
|
|
111
|
+
const items = fs.readdirSync(fullDir, { withFileTypes: true });
|
|
112
|
+
|
|
113
|
+
for (const item of items) {
|
|
114
|
+
const itemPath = path.join(fullDir, item.name);
|
|
115
|
+
const relativePath = basePath ? path.join(basePath, item.name) : item.name;
|
|
116
|
+
|
|
117
|
+
if (item.isDirectory()) {
|
|
118
|
+
// Recursively scan subdirectories
|
|
119
|
+
scanPages(dir, relativePath);
|
|
120
|
+
} else if (item.isFile() && (item.name.endsWith('.yaml') || item.name.endsWith('.md'))) {
|
|
121
|
+
// Extract frontmatter
|
|
122
|
+
const frontmatter = extractFrontmatter(itemPath);
|
|
123
|
+
|
|
124
|
+
// Calculate URL
|
|
125
|
+
const outputFileName = item.name.replace(/\.(yaml|md)$/, '.html');
|
|
126
|
+
const outputRelativePath = basePath ? path.join(basePath, outputFileName) : outputFileName;
|
|
127
|
+
const url = '/' + outputRelativePath.replace(/\\/g, '/');
|
|
128
|
+
|
|
129
|
+
// Process tags
|
|
130
|
+
if (frontmatter.tags) {
|
|
131
|
+
// Normalize tags to array
|
|
132
|
+
const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [frontmatter.tags];
|
|
133
|
+
|
|
134
|
+
// Add to collections
|
|
135
|
+
tags.forEach(tag => {
|
|
136
|
+
if (typeof tag === 'string' && tag.trim()) {
|
|
137
|
+
const trimmedTag = tag.trim();
|
|
138
|
+
if (!collections[trimmedTag]) {
|
|
139
|
+
collections[trimmedTag] = [];
|
|
140
|
+
}
|
|
141
|
+
collections[trimmedTag].push({
|
|
142
|
+
data: frontmatter,
|
|
143
|
+
url: url
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
scanPages('');
|
|
153
|
+
return collections;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Build collections in first pass
|
|
157
|
+
console.log('Building collections...');
|
|
158
|
+
const collections = buildCollections();
|
|
159
|
+
|
|
160
|
+
// Function to process a single page file
|
|
161
|
+
function processPage(pagePath, outputRelativePath, isMarkdown = false) {
|
|
162
|
+
console.log(`Processing ${pagePath}...`);
|
|
163
|
+
|
|
164
|
+
// Read page content
|
|
165
|
+
const pageFileContent = fs.readFileSync(pagePath, 'utf8');
|
|
166
|
+
|
|
167
|
+
// Extract frontmatter and content
|
|
168
|
+
const lines = pageFileContent.split('\n');
|
|
169
|
+
let frontmatterStart = -1;
|
|
170
|
+
let frontmatterEnd = -1;
|
|
171
|
+
let frontmatterCount = 0;
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
if (lines[i].trim() === '---') {
|
|
175
|
+
frontmatterCount++;
|
|
176
|
+
if (frontmatterCount === 1) {
|
|
177
|
+
frontmatterStart = i + 1;
|
|
178
|
+
} else if (frontmatterCount === 2) {
|
|
179
|
+
frontmatterEnd = i;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Store frontmatter
|
|
186
|
+
let frontmatter = {};
|
|
187
|
+
if (frontmatterStart > 0 && frontmatterEnd > frontmatterStart) {
|
|
188
|
+
const frontmatterContent = lines.slice(frontmatterStart, frontmatterEnd).join('\n');
|
|
189
|
+
frontmatter = yaml.load(frontmatterContent, { schema: yaml.JSON_SCHEMA }) || {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Get content after frontmatter
|
|
193
|
+
const contentStart = frontmatterEnd + 1;
|
|
194
|
+
const rawContent = lines.slice(contentStart).join('\n').trim();
|
|
195
|
+
|
|
196
|
+
// Merge global data with frontmatter and collections for the page context
|
|
197
|
+
const pageData = { ...globalData, ...frontmatter, collections };
|
|
198
|
+
|
|
199
|
+
let processedPageContent;
|
|
200
|
+
|
|
201
|
+
if (isMarkdown) {
|
|
202
|
+
// Process markdown content with MarkdownIt
|
|
203
|
+
const htmlContent = md.render(rawContent);
|
|
204
|
+
// For markdown, store as raw HTML that will be inserted directly
|
|
205
|
+
processedPageContent = { __html: htmlContent };
|
|
206
|
+
} else {
|
|
207
|
+
// Convert YAML content to JSON
|
|
208
|
+
const pageContent = yaml.load(rawContent, { schema: yaml.JSON_SCHEMA });
|
|
209
|
+
// Process the page content to resolve any $partial references with page data
|
|
210
|
+
processedPageContent = parseAndRender(pageContent, pageData, { partials, functions });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Find the template specified in frontmatter
|
|
214
|
+
let templateToUse = null;
|
|
215
|
+
if (frontmatter.template) {
|
|
216
|
+
// Look up template by exact path
|
|
217
|
+
templateToUse = templates[frontmatter.template];
|
|
218
|
+
if (!templateToUse) {
|
|
219
|
+
throw new Error(`Template "${frontmatter.template}" not found in ${pagePath}. Available templates: ${Object.keys(templates).join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Use the template with jempl to render the processed page content
|
|
224
|
+
let htmlString;
|
|
225
|
+
|
|
226
|
+
if (isMarkdown) {
|
|
227
|
+
if (templateToUse) {
|
|
228
|
+
// For markdown with template, use a placeholder and replace after
|
|
229
|
+
const placeholder = '___MARKDOWN_CONTENT_PLACEHOLDER___';
|
|
230
|
+
const templateData = { ...pageData, content: placeholder, collections };
|
|
231
|
+
const templateResult = parseAndRender(templateToUse, templateData, { partials, functions });
|
|
232
|
+
htmlString = convertToHtml(templateResult);
|
|
233
|
+
// Replace the placeholder with actual HTML content
|
|
234
|
+
htmlString = htmlString.replace(placeholder, processedPageContent.__html);
|
|
235
|
+
} else {
|
|
236
|
+
// Markdown without template - use HTML directly
|
|
237
|
+
htmlString = processedPageContent.__html;
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
// YAML content
|
|
241
|
+
const templateData = { ...pageData, content: processedPageContent, collections };
|
|
242
|
+
const result = templateToUse
|
|
243
|
+
? parseAndRender(templateToUse, templateData, { partials, functions })
|
|
244
|
+
: processedPageContent;
|
|
245
|
+
// Ensure result is an array for convertToHtml
|
|
246
|
+
const resultArray = Array.isArray(result) ? result : [result];
|
|
247
|
+
htmlString = convertToHtml(resultArray);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Create output directory if it doesn't exist
|
|
251
|
+
const outputPath = path.join(rootDir, '_site', outputRelativePath);
|
|
252
|
+
const outputDir = path.dirname(outputPath);
|
|
253
|
+
if (!fs.existsSync(outputDir)) {
|
|
254
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Write HTML to output file
|
|
258
|
+
fs.writeFileSync(outputPath, htmlString);
|
|
259
|
+
console.log(` -> Written to ${outputPath}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Process all YAML and Markdown files in pages directory recursively
|
|
263
|
+
function processAllPages(dir, basePath = '') {
|
|
264
|
+
const pagesDir = path.join(rootDir, 'pages');
|
|
265
|
+
const fullDir = path.join(pagesDir, basePath);
|
|
266
|
+
|
|
267
|
+
if (!fs.existsSync(fullDir)) return;
|
|
268
|
+
|
|
269
|
+
const items = fs.readdirSync(fullDir, { withFileTypes: true });
|
|
270
|
+
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
const itemPath = path.join(fullDir, item.name);
|
|
273
|
+
const relativePath = basePath ? path.join(basePath, item.name) : item.name;
|
|
274
|
+
|
|
275
|
+
if (item.isDirectory()) {
|
|
276
|
+
// Recursively process subdirectories
|
|
277
|
+
processAllPages(dir, relativePath);
|
|
278
|
+
} else if (item.isFile()) {
|
|
279
|
+
if (item.name.endsWith('.yaml')) {
|
|
280
|
+
// Process YAML file
|
|
281
|
+
const outputFileName = item.name.replace('.yaml', '.html');
|
|
282
|
+
const outputRelativePath = basePath ? path.join(basePath, outputFileName) : outputFileName;
|
|
283
|
+
processPage(itemPath, outputRelativePath, false);
|
|
284
|
+
} else if (item.name.endsWith('.md')) {
|
|
285
|
+
// Process Markdown file
|
|
286
|
+
const outputFileName = item.name.replace('.md', '.html');
|
|
287
|
+
const outputRelativePath = basePath ? path.join(basePath, outputFileName) : outputFileName;
|
|
288
|
+
processPage(itemPath, outputRelativePath, true);
|
|
289
|
+
}
|
|
290
|
+
// Ignore other file types
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Function to copy static files recursively
|
|
296
|
+
function copyStaticFiles() {
|
|
297
|
+
const staticDir = path.join(rootDir, 'static');
|
|
298
|
+
const outputDir = path.join(rootDir, '_site');
|
|
299
|
+
|
|
300
|
+
if (!fs.existsSync(staticDir)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Ensure output directory exists
|
|
305
|
+
if (!fs.existsSync(outputDir)) {
|
|
306
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function copyRecursive(src, dest) {
|
|
310
|
+
const stats = fs.statSync(src);
|
|
311
|
+
|
|
312
|
+
if (stats.isDirectory()) {
|
|
313
|
+
// Create directory if it doesn't exist
|
|
314
|
+
if (!fs.existsSync(dest)) {
|
|
315
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Copy all items in directory
|
|
319
|
+
const items = fs.readdirSync(src);
|
|
320
|
+
items.forEach(item => {
|
|
321
|
+
copyRecursive(path.join(src, item), path.join(dest, item));
|
|
322
|
+
});
|
|
323
|
+
} else if (stats.isFile()) {
|
|
324
|
+
// Copy file
|
|
325
|
+
fs.copyFileSync(src, dest);
|
|
326
|
+
console.log(` -> Copied ${src} to ${dest}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log('Copying static files...');
|
|
331
|
+
const items = fs.readdirSync(staticDir);
|
|
332
|
+
items.forEach(item => {
|
|
333
|
+
const srcPath = path.join(staticDir, item);
|
|
334
|
+
const destPath = path.join(outputDir, item);
|
|
335
|
+
copyRecursive(srcPath, destPath);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Start build process
|
|
340
|
+
console.log('Starting build process...');
|
|
341
|
+
|
|
342
|
+
// Copy static files first (they can be overwritten by pages)
|
|
343
|
+
copyStaticFiles();
|
|
344
|
+
|
|
345
|
+
// Process all pages (can overwrite static files)
|
|
346
|
+
processAllPages('');
|
|
347
|
+
|
|
348
|
+
console.log('Build complete!');
|
|
349
|
+
};
|
|
350
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
|
|
3
|
+
// Simple slug generation function
|
|
4
|
+
function generateSlug(text) {
|
|
5
|
+
return text
|
|
6
|
+
.toLowerCase()
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/[^\w\s-]/g, '')
|
|
9
|
+
.replace(/[\s_-]+/g, '-')
|
|
10
|
+
.replace(/^-+|-+$/g, '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom Markdown renderer configuration for Rettangoli
|
|
15
|
+
* Adds rtgl-specific elements and styling
|
|
16
|
+
*/
|
|
17
|
+
export const createRtglMarkdown = () => {
|
|
18
|
+
const md = MarkdownIt({
|
|
19
|
+
// Additional configuration can be added here
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Header configuration
|
|
23
|
+
md.renderer.rules.heading_open = (tokens, idx, options, env, self) => {
|
|
24
|
+
const token = tokens[idx];
|
|
25
|
+
const level = token.markup.length;
|
|
26
|
+
const inlineToken = tokens[idx + 1];
|
|
27
|
+
const headingText = inlineToken.content;
|
|
28
|
+
const id = generateSlug(headingText);
|
|
29
|
+
|
|
30
|
+
// Map heading levels to size values
|
|
31
|
+
const sizes = { 1: "h1", 2: "h2", 3: "h3", 4: "h4" };
|
|
32
|
+
const size = sizes[level] || "md";
|
|
33
|
+
|
|
34
|
+
return `<rtgl-text id="${id}" mt="lg" s="${size}" mb="md"> <a href="#${id}" style="display: contents;">`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
md.renderer.rules.heading_close = () => "</a></rtgl-text>\n";
|
|
38
|
+
|
|
39
|
+
// Paragraph configuration
|
|
40
|
+
md.renderer.rules.paragraph_open = (tokens, idx, options, env, self) => {
|
|
41
|
+
// Check if we're inside a list item
|
|
42
|
+
let isInListItem = false;
|
|
43
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
44
|
+
if (tokens[i].type === 'list_item_open') {
|
|
45
|
+
isInListItem = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (tokens[i].type === 'list_item_close') {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Don't wrap paragraphs in list items with rtgl-text
|
|
54
|
+
if (isInListItem) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
return `<rtgl-text s="bl" mb="lg">`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
md.renderer.rules.paragraph_close = (tokens, idx, options, env, self) => {
|
|
61
|
+
// Check if we're inside a list item
|
|
62
|
+
let isInListItem = false;
|
|
63
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
64
|
+
if (tokens[i].type === 'list_item_open') {
|
|
65
|
+
isInListItem = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
if (tokens[i].type === 'list_item_close') {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Don't wrap paragraphs in list items with rtgl-text
|
|
74
|
+
if (isInListItem) {
|
|
75
|
+
return '\n';
|
|
76
|
+
}
|
|
77
|
+
return "</rtgl-text>\n";
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Table configuration
|
|
81
|
+
md.renderer.rules.table_open = () => '<rtgl-view w="f">\n<table>';
|
|
82
|
+
md.renderer.rules.table_close = () => "</table>\n</rtgl-view>";
|
|
83
|
+
|
|
84
|
+
// Link configuration - add target="_blank" to all external links
|
|
85
|
+
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
|
86
|
+
const token = tokens[idx];
|
|
87
|
+
const targetIndex = token.attrIndex("target");
|
|
88
|
+
const href =
|
|
89
|
+
(token.attrs && token.attrs.find((attr) => attr[0] === "href")?.[1]) ||
|
|
90
|
+
"";
|
|
91
|
+
const isExternal = href.startsWith("http") || href.startsWith("//");
|
|
92
|
+
|
|
93
|
+
// If this is an external link or already has target="_blank"
|
|
94
|
+
if (isExternal || targetIndex >= 0) {
|
|
95
|
+
if (targetIndex < 0) {
|
|
96
|
+
token.attrPush(["target", "_blank"]);
|
|
97
|
+
}
|
|
98
|
+
token.attrPush(["rel", "noreferrer"]);
|
|
99
|
+
|
|
100
|
+
// Find the next text token to use for the aria-label
|
|
101
|
+
let nextIdx = idx + 1;
|
|
102
|
+
let textContent = "";
|
|
103
|
+
while (nextIdx < tokens.length && tokens[nextIdx].type !== "link_close") {
|
|
104
|
+
if (tokens[nextIdx].type === "text") {
|
|
105
|
+
textContent += tokens[nextIdx].content;
|
|
106
|
+
}
|
|
107
|
+
nextIdx++;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add aria-label for external links
|
|
111
|
+
if (textContent.trim() && token.attrIndex("aria-label") < 0) {
|
|
112
|
+
token.attrPush([
|
|
113
|
+
"aria-label",
|
|
114
|
+
`${textContent.trim()} (opens in new tab)`,
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return self.renderToken(tokens, idx, options);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return md;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Export a default instance for convenience
|
|
126
|
+
export default createRtglMarkdown();
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<rtgl-text><a href="{{ back.href }}" aria-label="{{ back.text }}">{{ back.text }}</a></rtgl-text>
|
|
2
|
-
<rtgl-text s="h2" mt="l">{{ title }}</rtgl-text>
|
|
3
|
-
<rtgl-view mt="l">
|
|
4
|
-
{{ subtitle }}
|
|
5
|
-
</rtgl-view>
|
|
6
|
-
<rtgl-view g="l" mt="xl">
|
|
7
|
-
{%- for item in items -%}
|
|
8
|
-
|
|
9
|
-
<rtgl-view w="f" d="h" av="c">
|
|
10
|
-
{% if item.date %}
|
|
11
|
-
<rtgl-view d="h" w="120">
|
|
12
|
-
<rtgl-text>{{ item.date | postDate }}</rtgl-text>
|
|
13
|
-
</rtgl-view>
|
|
14
|
-
{% endif %}
|
|
15
|
-
<rtgl-text s="lg"><a href="{{ item.url }}">{{ item.title }}</a></rtgl-text>
|
|
16
|
-
</rtgl-view>
|
|
17
|
-
|
|
18
|
-
{%- endfor -%}
|
|
19
|
-
</rtgl-view>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<rtgl-view w="f" ph="xl">
|
|
2
|
-
<rtgl-view w="f" ah="c" bw="s" br="l" p="xl">
|
|
3
|
-
<rtgl-text s="h3" ta="c" mt="l"> {{ cta.title }} </rtgl-text>
|
|
4
|
-
<rtgl-text mt="l" ta="c" c="mu-fg"> {{ cta.description }} </rtgl-text>
|
|
5
|
-
<rtgl-button mt="xl" href="{{ cta.cta.href }}" target="_blank" rel="noreferrer"> {{
|
|
6
|
-
cta.cta.text
|
|
7
|
-
}} </rtgl-button>
|
|
8
|
-
</rtgl-view>
|
|
9
|
-
</rtgl-view>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<rtgl-view d="h" h="64"></rtgl-view>
|
|
2
|
-
<rtgl-view pv="xl" mv="xl" mh="l" g="xl" ah="c">
|
|
3
|
-
<rtgl-text s="h2" at="c"> {{ feature.title }} </rtgl-text>
|
|
4
|
-
<rtgl-view d="h" w="f" g="xl" av="c" mt="l">
|
|
5
|
-
<rtgl-view flex="2" mb="m" g="m" style="min-width: 320px;">
|
|
6
|
-
<rtgl-text s="bl" mt="l"> {{ feature.description }} </rtgl-text>
|
|
7
|
-
<ul>
|
|
8
|
-
<rtgl-view >
|
|
9
|
-
{%- for item in feature.items -%}
|
|
10
|
-
<li>
|
|
11
|
-
<rtgl-view>
|
|
12
|
-
<rtgl-text mt="l"><b> {{ item.label }}:</b> {{ item.description }} </rtgl-text>
|
|
13
|
-
</rtgl-view>
|
|
14
|
-
</li>
|
|
15
|
-
{%- endfor -%}
|
|
16
|
-
</rtgl-view>
|
|
17
|
-
</ul>
|
|
18
|
-
</rtgl-view>
|
|
19
|
-
<rtgl-view flex="3" style="min-width: 320px; overflow: hidden;" bw="xs" br="s">
|
|
20
|
-
<rtgl-image src="{{ feature.picture }}" w="f" alt="{{ feature.imageAlt | default: feature.title }}"></rtgl-image>
|
|
21
|
-
</rtgl-view>
|
|
22
|
-
</rtgl-view>
|
|
23
|
-
</rtgl-view>
|
|
24
|
-
<rtgl-view d="h" h="64"></rtgl-view>
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{%- if hero.video -%}
|
|
2
|
-
<rtgl-view h="8vh"></rtgl-view>
|
|
3
|
-
{%- else -%}
|
|
4
|
-
<rtgl-view h="20vh"></rtgl-view>
|
|
5
|
-
{%- endif -%}
|
|
6
|
-
<rtgl-view w="f" pv="xl" av="c" ah="c" ph="xl">
|
|
7
|
-
<rtgl-text s="h1" ta="c" mb="lg"> {{ hero.title }} </rtgl-text>
|
|
8
|
-
{%- for subtitle in hero.subtitles -%}
|
|
9
|
-
<rtgl-text s="lg" ta="c"> {{ subtitle }} </rtgl-text>
|
|
10
|
-
{%- endfor -%}
|
|
11
|
-
{%- if hero.cta -%}
|
|
12
|
-
<rtgl-button mt="xl" href="{{ hero.cta.href }}" {% if hero.cta.openNewTab %}target="_blank" rel="noreferrer"{% endif %}> {{
|
|
13
|
-
hero.cta.text
|
|
14
|
-
}} </rtgl-button>
|
|
15
|
-
{%- endif -%}
|
|
16
|
-
</rtgl-view>
|
|
17
|
-
|
|
18
|
-
{%- if hero.video-%}
|
|
19
|
-
<rtgl-view w="f" ah="c" mt="xl">
|
|
20
|
-
|
|
21
|
-
<rtgl-view lg-h="calc((100vw - 32px) / 1.777)" lg-w="calc(100vw - 32px)" h="calc(66.66vw / 1.777)" w="66.66vw"
|
|
22
|
-
bw="xs" br="xs" shadow="sm">
|
|
23
|
-
<video playsinline loop controls width="100%" aria-label="{{ hero.video.title | default: 'Video content' }}">
|
|
24
|
-
<source src="{{ hero.video.url }}" type="video/mp4">
|
|
25
|
-
</video>
|
|
26
|
-
</rtgl-view>
|
|
27
|
-
</rtgl-view>
|
|
28
|
-
{%- else -%}
|
|
29
|
-
<rtgl-view h="20vh">
|
|
30
|
-
</rtgl-view>
|
|
31
|
-
{%- endif -%}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<rtgl-view g="m" w="f" mt="xl">
|
|
2
|
-
<rtgl-text s="h3">{{ sectionTitle }}</rtgl-text>
|
|
3
|
-
<rtgl-view g="l" w="f">
|
|
4
|
-
{% for item in items %}
|
|
5
|
-
<a href="{{ item.href }}" style="display: contents; color: inherit;">
|
|
6
|
-
<rtgl-view bgc="mu" bw="s" br="m" p="m" w="f" g="xs">
|
|
7
|
-
<rtgl-view d="h" g="m">
|
|
8
|
-
<rtgl-text s="h4">{{ item.label }}</rtgl-text>
|
|
9
|
-
</rtgl-view>
|
|
10
|
-
<rtgl-text>{{ item.description }}</rtgl-text>
|
|
11
|
-
</rtgl-view>
|
|
12
|
-
</a>
|
|
13
|
-
{% endfor %}
|
|
14
|
-
</rtgl-view>
|
|
15
|
-
</rtgl-view>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<rtgl-view h="{{ height }}"></rtgl-view>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
{% include "core/htmlHeader.html" %}
|
|
6
|
-
{% include "core/htmlHeaderTable.html" %}
|
|
7
|
-
</head>
|
|
8
|
-
|
|
9
|
-
<body class="dark">
|
|
10
|
-
<rtgl-view w="f" ah="c" h="f">
|
|
11
|
-
<rtgl-view d="h" w="f" h="f">
|
|
12
|
-
<rtgl-sidebar title="{{ docs.title | json-escaped }}" items="{{ admin.items | json-escaped }}"></rtgl-sidebar>
|
|
13
|
-
<rtgl-view h="100vh" w="f" sv id="content-container" ph="l">
|
|
14
|
-
{{ content }}
|
|
15
|
-
<rtgl-view h="50vh"></rtgl-view>
|
|
16
|
-
</rtgl-view>
|
|
17
|
-
</rtgl-view>
|
|
18
|
-
</rtgl-view>
|
|
19
|
-
</body>
|
|
20
|
-
|
|
21
|
-
</html>
|