@rettangoli/sites 0.1.0 → 0.2.0-rc1
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/package.json +8 -11
- package/src/cli/build.js +27 -160
- package/src/cli/index.js +2 -2
- package/src/createSiteBuilder.js +261 -0
- package/src/index.js +3 -0
- package/src/rtglMarkdown.js +126 -0
- package/README.md +0 -103
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rettangoli/sites",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-rc1",
|
|
4
4
|
"description": "Generate static sites using Markdown and YAML. Straightforward, zero-complexity. Complete toolkit for landing pages, blogs, documentation, admin dashboards, and more.git remote add origin git@github.com:yuusoft-org/sitic.git",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Luciano Hanyon Wu",
|
|
@@ -17,22 +17,19 @@
|
|
|
17
17
|
"templates"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"
|
|
21
|
-
"html-minifier-terser": "^7.2.0",
|
|
20
|
+
"jempl": "^0.2.0-rc1",
|
|
22
21
|
"js-yaml": "^4.1.0",
|
|
23
|
-
"liquidjs": "^10.21.0",
|
|
24
|
-
"luxon": "^3.6.1",
|
|
25
22
|
"markdown-it": "^14.1.0",
|
|
26
|
-
"
|
|
27
|
-
"shiki": "^3.3.0"
|
|
23
|
+
"yahtml": "^0.0.2-rc1"
|
|
28
24
|
},
|
|
29
25
|
"devDependencies": {
|
|
30
|
-
"
|
|
26
|
+
"memfs": "^4.36.0",
|
|
27
|
+
"puty": "^0.0.4"
|
|
31
28
|
},
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"sitic": "src/cli.js"
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "vitest run --reporter=verbose"
|
|
35
31
|
},
|
|
32
|
+
"type": "module",
|
|
36
33
|
"license": "MIT",
|
|
37
34
|
"keywords": [
|
|
38
35
|
"static",
|
package/src/cli/build.js
CHANGED
|
@@ -1,166 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
// Create the equivalent of __dirname for ES modules
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = dirname(__filename);
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
safeYamlLoad,
|
|
11
|
-
safeReadFile,
|
|
12
|
-
deepMerge,
|
|
13
|
-
createTemplateRenderer,
|
|
14
|
-
createFolderIfNotExists,
|
|
15
|
-
configureMarkdown,
|
|
16
|
-
loadCollections,
|
|
17
|
-
createFileFormatHandlers,
|
|
18
|
-
loadItems,
|
|
19
|
-
copyDirRecursive,
|
|
20
|
-
} from "../common.js";
|
|
21
|
-
import { rm } from "fs/promises";
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createSiteBuilder } from '../createSiteBuilder.js';
|
|
22
4
|
|
|
23
5
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param {Object} options - Options for
|
|
26
|
-
* @param {string} options.
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {string} options.outputPath - Path to output directory
|
|
6
|
+
* Build the static site
|
|
7
|
+
* @param {Object} options - Options for building the site
|
|
8
|
+
* @param {string} options.rootDir - Root directory of the site (defaults to cwd)
|
|
9
|
+
* @param {Object} options.mdRender - Optional markdown renderer
|
|
29
10
|
*/
|
|
30
|
-
export const
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const inputYaml = await safeReadFile(dataPath);
|
|
44
|
-
|
|
45
|
-
const templates = await loadItems({
|
|
46
|
-
path: join(__dirname, "./templates"),
|
|
47
|
-
name: "templates",
|
|
48
|
-
isYaml: false,
|
|
49
|
-
keepExtension: true,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (templatesPath) {
|
|
53
|
-
const customTemplates = await loadItems({
|
|
54
|
-
path: templatesPath,
|
|
55
|
-
name: "templates",
|
|
56
|
-
isYaml: false,
|
|
57
|
-
keepExtension: true,
|
|
58
|
-
});
|
|
59
|
-
Object.assign(templates, customTemplates);
|
|
11
|
+
export const buildSite = async (options = {}) => {
|
|
12
|
+
const { rootDir = process.cwd(), mdRender } = options;
|
|
13
|
+
|
|
14
|
+
// Try to load config file if it exists
|
|
15
|
+
let config = {};
|
|
16
|
+
if (!mdRender) {
|
|
17
|
+
try {
|
|
18
|
+
const configPath = path.join(rootDir, 'sites.config.js');
|
|
19
|
+
const configModule = await import(configPath);
|
|
20
|
+
config = configModule.default || {};
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Config file is optional, continue without it
|
|
23
|
+
}
|
|
60
24
|
}
|
|
61
25
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
isYaml: true,
|
|
26
|
+
const build = createSiteBuilder({
|
|
27
|
+
fs,
|
|
28
|
+
rootDir,
|
|
29
|
+
mdRender: mdRender || config.mdRender
|
|
67
30
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
name: "components",
|
|
72
|
-
isYaml: false,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
if (componentsPath) {
|
|
76
|
-
const customComponents = await loadItems({
|
|
77
|
-
path: componentsPath,
|
|
78
|
-
name: "components",
|
|
79
|
-
isYaml: false,
|
|
80
|
-
});
|
|
81
|
-
Object.assign(components, customComponents);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const collections = await loadCollections(pagesPath);
|
|
85
|
-
|
|
86
|
-
const liquidParse = createTemplateRenderer({
|
|
87
|
-
templates,
|
|
88
|
-
filters: {
|
|
89
|
-
json: (obj) => JSON.stringify(obj),
|
|
90
|
-
"json-escaped": (obj) => {
|
|
91
|
-
if (!obj) {
|
|
92
|
-
return "";
|
|
93
|
-
}
|
|
94
|
-
return encodeURIComponent(JSON.stringify(obj));
|
|
95
|
-
},
|
|
96
|
-
postDate: (dateObj) => {
|
|
97
|
-
if (!dateObj || typeof dateObj !== 'string') {
|
|
98
|
-
return ''; // Return empty string or some default value if dateObj is undefined or not a string
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
return DateTime.fromFormat(dateObj, "yyyy-MM-dd").toLocaleString(
|
|
102
|
-
DateTime.DATE_MED
|
|
103
|
-
);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error(`Error formatting date "${dateObj}":`, error.message);
|
|
106
|
-
return dateObj; // Return the original date string if parsing fails
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
let data;
|
|
113
|
-
try {
|
|
114
|
-
data = safeYamlLoad(liquidParse(inputYaml, { collections }));
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error("Error creating template renderer:", error);
|
|
117
|
-
throw error;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Create global data object for templates
|
|
121
|
-
const globalData = {
|
|
122
|
-
data,
|
|
123
|
-
collections,
|
|
124
|
-
records,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const yamlComponentRenderer = (content) => {
|
|
128
|
-
const renderedContent = liquidParse(content, globalData);
|
|
129
|
-
const yamlContent = safeYamlLoad(renderedContent);
|
|
130
|
-
|
|
131
|
-
return yamlContent
|
|
132
|
-
.map(({ component, data }) => {
|
|
133
|
-
const foundComponent = components[component];
|
|
134
|
-
if (!foundComponent) {
|
|
135
|
-
throw new Error(`Component not found for ${component}`);
|
|
136
|
-
}
|
|
137
|
-
return liquidParse(foundComponent, deepMerge(globalData, data));
|
|
138
|
-
})
|
|
139
|
-
.join("\n");
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const md = configureMarkdown({
|
|
143
|
-
yamlComponentRenderer,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const fileFormatHandlers = createFileFormatHandlers({
|
|
147
|
-
basePath: pagesPath,
|
|
148
|
-
templates,
|
|
149
|
-
liquidParse,
|
|
150
|
-
data: globalData.data,
|
|
151
|
-
collections,
|
|
152
|
-
md,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
await rm(outputPath, { recursive: true, force: true });
|
|
157
|
-
await createFolderIfNotExists(outputPath);
|
|
158
|
-
await copyDirRecursive(pagesPath, outputPath, fileFormatHandlers);
|
|
159
|
-
console.log(`Pages copied from ${pagesPath} to ${outputPath} successfully`);
|
|
160
|
-
} catch (error) {
|
|
161
|
-
console.error(
|
|
162
|
-
`Error copying pages from ${pagesPath} to ${outputPath}:`,
|
|
163
|
-
error
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
31
|
+
|
|
32
|
+
build();
|
|
33
|
+
};
|
package/src/cli/index.js
CHANGED
|
@@ -0,0 +1,261 @@
|
|
|
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 }) {
|
|
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 process a single page file
|
|
74
|
+
function processPage(pagePath, outputRelativePath, isMarkdown = false) {
|
|
75
|
+
console.log(`Processing ${pagePath}...`);
|
|
76
|
+
|
|
77
|
+
// Read page content
|
|
78
|
+
const pageFileContent = fs.readFileSync(pagePath, 'utf8');
|
|
79
|
+
|
|
80
|
+
// Extract frontmatter and content
|
|
81
|
+
const lines = pageFileContent.split('\n');
|
|
82
|
+
let frontmatterStart = -1;
|
|
83
|
+
let frontmatterEnd = -1;
|
|
84
|
+
let frontmatterCount = 0;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
if (lines[i].trim() === '---') {
|
|
88
|
+
frontmatterCount++;
|
|
89
|
+
if (frontmatterCount === 1) {
|
|
90
|
+
frontmatterStart = i + 1;
|
|
91
|
+
} else if (frontmatterCount === 2) {
|
|
92
|
+
frontmatterEnd = i;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Store frontmatter
|
|
99
|
+
let frontmatter = {};
|
|
100
|
+
if (frontmatterStart > 0 && frontmatterEnd > frontmatterStart) {
|
|
101
|
+
const frontmatterContent = lines.slice(frontmatterStart, frontmatterEnd).join('\n');
|
|
102
|
+
frontmatter = yaml.load(frontmatterContent, { schema: yaml.JSON_SCHEMA }) || {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get content after frontmatter
|
|
106
|
+
const contentStart = frontmatterEnd + 1;
|
|
107
|
+
const rawContent = lines.slice(contentStart).join('\n').trim();
|
|
108
|
+
|
|
109
|
+
// Merge global data with frontmatter for the page context
|
|
110
|
+
const pageData = { ...globalData, ...frontmatter };
|
|
111
|
+
|
|
112
|
+
let processedPageContent;
|
|
113
|
+
|
|
114
|
+
if (isMarkdown) {
|
|
115
|
+
// Process markdown content with MarkdownIt
|
|
116
|
+
const htmlContent = md.render(rawContent);
|
|
117
|
+
// For markdown, store as raw HTML that will be inserted directly
|
|
118
|
+
processedPageContent = { __html: htmlContent };
|
|
119
|
+
} else {
|
|
120
|
+
// Convert YAML content to JSON
|
|
121
|
+
const pageContent = yaml.load(rawContent, { schema: yaml.JSON_SCHEMA });
|
|
122
|
+
// Process the page content to resolve any $partial references with page data
|
|
123
|
+
processedPageContent = parseAndRender(pageContent, pageData, { partials });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Find the template specified in frontmatter
|
|
127
|
+
let templateToUse = null;
|
|
128
|
+
if (frontmatter.template) {
|
|
129
|
+
// Look up template by exact path
|
|
130
|
+
templateToUse = templates[frontmatter.template];
|
|
131
|
+
if (!templateToUse) {
|
|
132
|
+
throw new Error(`Template "${frontmatter.template}" not found in ${pagePath}. Available templates: ${Object.keys(templates).join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Use the template with jempl to render the processed page content
|
|
137
|
+
let htmlString;
|
|
138
|
+
|
|
139
|
+
if (isMarkdown) {
|
|
140
|
+
if (templateToUse) {
|
|
141
|
+
// For markdown with template, use a placeholder and replace after
|
|
142
|
+
const placeholder = '___MARKDOWN_CONTENT_PLACEHOLDER___';
|
|
143
|
+
const templateData = { ...pageData, content: placeholder };
|
|
144
|
+
const templateResult = parseAndRender(templateToUse, templateData, { partials });
|
|
145
|
+
htmlString = convertToHtml(templateResult);
|
|
146
|
+
// Replace the placeholder with actual HTML content
|
|
147
|
+
htmlString = htmlString.replace(placeholder, processedPageContent.__html);
|
|
148
|
+
} else {
|
|
149
|
+
// Markdown without template - use HTML directly
|
|
150
|
+
htmlString = processedPageContent.__html;
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
// YAML content
|
|
154
|
+
const templateData = { ...pageData, content: processedPageContent };
|
|
155
|
+
const result = templateToUse
|
|
156
|
+
? parseAndRender(templateToUse, templateData, { partials })
|
|
157
|
+
: processedPageContent;
|
|
158
|
+
htmlString = convertToHtml(result);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create output directory if it doesn't exist
|
|
162
|
+
const outputPath = path.join(rootDir, '_site', outputRelativePath);
|
|
163
|
+
const outputDir = path.dirname(outputPath);
|
|
164
|
+
if (!fs.existsSync(outputDir)) {
|
|
165
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Write HTML to output file
|
|
169
|
+
fs.writeFileSync(outputPath, htmlString);
|
|
170
|
+
console.log(` -> Written to ${outputPath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Process all YAML and Markdown files in pages directory recursively
|
|
174
|
+
function processAllPages(dir, basePath = '') {
|
|
175
|
+
const pagesDir = path.join(rootDir, 'pages');
|
|
176
|
+
const fullDir = path.join(pagesDir, basePath);
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(fullDir)) return;
|
|
179
|
+
|
|
180
|
+
const items = fs.readdirSync(fullDir, { withFileTypes: true });
|
|
181
|
+
|
|
182
|
+
for (const item of items) {
|
|
183
|
+
const itemPath = path.join(fullDir, item.name);
|
|
184
|
+
const relativePath = basePath ? path.join(basePath, item.name) : item.name;
|
|
185
|
+
|
|
186
|
+
if (item.isDirectory()) {
|
|
187
|
+
// Recursively process subdirectories
|
|
188
|
+
processAllPages(dir, relativePath);
|
|
189
|
+
} else if (item.isFile()) {
|
|
190
|
+
if (item.name.endsWith('.yaml')) {
|
|
191
|
+
// Process YAML file
|
|
192
|
+
const outputFileName = item.name.replace('.yaml', '.html');
|
|
193
|
+
const outputRelativePath = basePath ? path.join(basePath, outputFileName) : outputFileName;
|
|
194
|
+
processPage(itemPath, outputRelativePath, false);
|
|
195
|
+
} else if (item.name.endsWith('.md')) {
|
|
196
|
+
// Process Markdown file
|
|
197
|
+
const outputFileName = item.name.replace('.md', '.html');
|
|
198
|
+
const outputRelativePath = basePath ? path.join(basePath, outputFileName) : outputFileName;
|
|
199
|
+
processPage(itemPath, outputRelativePath, true);
|
|
200
|
+
}
|
|
201
|
+
// Ignore other file types
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Function to copy static files recursively
|
|
207
|
+
function copyStaticFiles() {
|
|
208
|
+
const staticDir = path.join(rootDir, 'static');
|
|
209
|
+
const outputDir = path.join(rootDir, '_site');
|
|
210
|
+
|
|
211
|
+
if (!fs.existsSync(staticDir)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Ensure output directory exists
|
|
216
|
+
if (!fs.existsSync(outputDir)) {
|
|
217
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function copyRecursive(src, dest) {
|
|
221
|
+
const stats = fs.statSync(src);
|
|
222
|
+
|
|
223
|
+
if (stats.isDirectory()) {
|
|
224
|
+
// Create directory if it doesn't exist
|
|
225
|
+
if (!fs.existsSync(dest)) {
|
|
226
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Copy all items in directory
|
|
230
|
+
const items = fs.readdirSync(src);
|
|
231
|
+
items.forEach(item => {
|
|
232
|
+
copyRecursive(path.join(src, item), path.join(dest, item));
|
|
233
|
+
});
|
|
234
|
+
} else if (stats.isFile()) {
|
|
235
|
+
// Copy file
|
|
236
|
+
fs.copyFileSync(src, dest);
|
|
237
|
+
console.log(` -> Copied ${src} to ${dest}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log('Copying static files...');
|
|
242
|
+
const items = fs.readdirSync(staticDir);
|
|
243
|
+
items.forEach(item => {
|
|
244
|
+
const srcPath = path.join(staticDir, item);
|
|
245
|
+
const destPath = path.join(outputDir, item);
|
|
246
|
+
copyRecursive(srcPath, destPath);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Start build process
|
|
251
|
+
console.log('Starting build process...');
|
|
252
|
+
|
|
253
|
+
// Copy static files first (they can be overwritten by pages)
|
|
254
|
+
copyStaticFiles();
|
|
255
|
+
|
|
256
|
+
// Process all pages (can overwrite static files)
|
|
257
|
+
processAllPages('');
|
|
258
|
+
|
|
259
|
+
console.log('Build complete!');
|
|
260
|
+
};
|
|
261
|
+
}
|
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();
|