bitwrench 1.2.16 → 2.0.7
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 +160 -158
- package/bin/bitwrench.js +3 -0
- package/dist/bitwrench-code-edit.cjs.js +639 -0
- package/dist/bitwrench-code-edit.es5.js +875 -0
- package/dist/bitwrench-code-edit.es5.min.js +15 -0
- package/dist/bitwrench-code-edit.esm.js +628 -0
- package/dist/bitwrench-code-edit.esm.min.js +15 -0
- package/dist/bitwrench-code-edit.umd.js +645 -0
- package/dist/bitwrench-code-edit.umd.min.js +15 -0
- package/dist/bitwrench.cjs.js +6983 -0
- package/dist/bitwrench.cjs.min.js +62 -0
- package/dist/bitwrench.css +5100 -0
- package/dist/bitwrench.es5.js +8446 -0
- package/dist/bitwrench.es5.min.js +31 -0
- package/dist/bitwrench.esm.js +6981 -0
- package/dist/bitwrench.esm.min.js +62 -0
- package/dist/bitwrench.umd.js +6989 -0
- package/dist/bitwrench.umd.min.js +62 -0
- package/dist/builds.json +127 -0
- package/dist/sri.json +18 -0
- package/package.json +86 -24
- package/readme.html +288 -0
- package/src/bitwrench-code-edit.js +627 -0
- package/src/bitwrench-color-utils.js +311 -0
- package/src/bitwrench-component-base.js +736 -0
- package/src/bitwrench-components-inline.js +374 -0
- package/src/bitwrench-components-v2.js +1879 -0
- package/src/bitwrench-components.js +610 -0
- package/src/bitwrench-styles.js +3240 -0
- package/src/bitwrench.js +3367 -0
- package/src/cli/convert.js +205 -0
- package/src/cli/index.js +122 -0
- package/src/cli/inject.js +55 -0
- package/src/cli/layout-default.js +142 -0
- package/src/generate-css.js +381 -0
- package/src/vendor/quikdown.js +654 -0
- package/src/version.js +16 -0
- package/.eslintrc.json +0 -27
- package/.github/workflows/codeql-analysis.yml +0 -72
- package/.travis.yml +0 -34
- package/bitwrench.css +0 -92
- package/bitwrench.js +0 -3348
- package/bitwrench.js_sri.txt +0 -1
- package/bitwrench.min.js +0 -1
- package/bitwrench.min.js_sri.txt +0 -1
- package/bitwrench_ESM.js +0 -3207
- package/bitwrench_ESM.js_sri.txt +0 -1
- package/bitwrench_ESM.min.js +0 -1
- package/bitwrench_ESM.min.js_sri.txt +0 -1
- package/dev/bitwrench-todo.md +0 -215
- package/dev/css-arrows.md +0 -23
- package/dev/docStringDev.js +0 -124
- package/dev/docStringParseDev.js +0 -171
- package/dev/example11-load-mjs-page.html +0 -17
- package/dev/figures.html +0 -37
- package/dev/html_gen.js +0 -349
- package/dev/htmld.md +0 -250
- package/dev/htmldev.html +0 -45
- package/dev/index-old.html +0 -87
- package/dev/misc-notes.md +0 -21
- package/dev/norm.css +0 -30
- package/dev/notes.md +0 -2
- package/dev/pageData.mjs +0 -69
- package/dev/sizes.html +0 -49
- package/dev/universal-js-module.js +0 -37
- package/examples/example1.html +0 -78
- package/examples/example10.html +0 -84
- package/examples/example11.html +0 -17
- package/examples/example12.html +0 -18
- package/examples/example2.html +0 -44
- package/examples/example3.html +0 -50
- package/examples/example4.html +0 -22
- package/examples/example5.html +0 -82
- package/examples/example6.html +0 -128
- package/examples/example7.html +0 -91
- package/examples/example8.html +0 -27
- package/examples/example9.html +0 -102
- package/examples/examplePageData12.mjs +0 -73
- package/examples/pageData.mjs +0 -69
- package/examples/pico.min.css +0 -5
- package/icon/bitwrench-dark-tall.png +0 -0
- package/icon/bitwrench-dark.png +0 -0
- package/icon/bitwrench-icon-lt-grey.png +0 -0
- package/icon/bitwrench-icon.vsd +0 -0
- package/icon/bitwrench-logo-dark.png +0 -0
- package/icon/bitwrench-logo-full.png +0 -0
- package/icon/bitwrench-logo-green.png +0 -0
- package/icon/bitwrench-logo-grey.png +0 -0
- package/icon/bitwrench-logo-white.png +0 -0
- package/icon/bitwrench-logos-colors.png +0 -0
- package/icon/bitwrench-thick-logo.png +0 -0
- package/icon/bitwrench-thick-teal/android-chrome-192x192.png +0 -0
- package/icon/bitwrench-thick-teal/android-chrome-512x512.png +0 -0
- package/icon/bitwrench-thick-teal/apple-touch-icon.png +0 -0
- package/icon/bitwrench-thick-teal/browserconfig.xml +0 -9
- package/icon/bitwrench-thick-teal/favicon-16x16.png +0 -0
- package/icon/bitwrench-thick-teal/favicon-32x32.png +0 -0
- package/icon/bitwrench-thick-teal/favicon.ico +0 -0
- package/icon/bitwrench-thick-teal/mstile-144x144.png +0 -0
- package/icon/bitwrench-thick-teal/mstile-150x150.png +0 -0
- package/icon/bitwrench-thick-teal/mstile-310x150.png +0 -0
- package/icon/bitwrench-thick-teal/mstile-310x310.png +0 -0
- package/icon/bitwrench-thick-teal/mstile-70x70.png +0 -0
- package/icon/bitwrench-thick-teal/site.webmanifest +0 -19
- package/icon/bitwrench-thick-teal.ico +0 -0
- package/icon/bitwrench-thick-teal.svg +0 -44
- package/icon/bitwrench-thick-teal.zip +0 -0
- package/icon/favicon-test.html +0 -20
- package/icon/logos-test.PNG +0 -0
- package/images/bitwrench-512x512.png +0 -0
- package/images/bitwrench-logo-med.png +0 -0
- package/images/bitwrench-thick-logo.png +0 -0
- package/images/bitwrench-thick-logo.svg +0 -64
- package/images/bitwrench-thick-teal.ico +0 -0
- package/images/favicon.ico +0 -0
- package/index.html +0 -282
- package/instr_tmp/bitwrench.js +0 -1350
- package/karma.conf.js +0 -140
- package/makefile +0 -21
- package/quick-docs.html +0 -206
- package/test/bitwrench_test.js +0 -1255
- package/test/karma-test.js +0 -1081
- package/tools/bw_deprecatedNames.js +0 -19
- package/tools/bwconsole.js +0 -20
- package/tools/createSimpleHTMLPage.js +0 -41
- package/tools/emitreadme.sh +0 -4
- package/tools/export-bw-default-css.js +0 -41
- package/tools/umd2ModuleHack.js +0 -32
- package/tools/update-bw-package.js +0 -36
- package/tools/updatereadme.js +0 -34
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitwrench CLI - Single-file conversion pipeline
|
|
3
|
+
* Read → detect type → process → wrap in layout → write
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { resolve, extname, basename, dirname } from 'node:path';
|
|
8
|
+
import quikdown from '../vendor/quikdown.js';
|
|
9
|
+
import bw from '../bitwrench.js';
|
|
10
|
+
import { getAllStyles, THEME_PRESETS } from '../bitwrench-styles.js';
|
|
11
|
+
import { getInjectionHead, getInjectionBodyEnd } from './inject.js';
|
|
12
|
+
import { makePageLayout } from './layout-default.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract title from the first # heading in markdown
|
|
16
|
+
* @param {string} md - Markdown source
|
|
17
|
+
* @returns {string|null} Title text or null
|
|
18
|
+
*/
|
|
19
|
+
function extractMarkdownTitle(md) {
|
|
20
|
+
const match = md.match(/^#\s+(.+?)(?:\s*#*)$/m);
|
|
21
|
+
return match ? match[1].trim() : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract <title> from an HTML document
|
|
26
|
+
* @param {string} html - HTML source
|
|
27
|
+
* @returns {string|null}
|
|
28
|
+
*/
|
|
29
|
+
function extractHtmlTitle(html) {
|
|
30
|
+
const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
31
|
+
return match ? match[1].trim() : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract <body> content from a full HTML document
|
|
36
|
+
* @param {string} html - HTML source
|
|
37
|
+
* @returns {string} Body content
|
|
38
|
+
*/
|
|
39
|
+
function extractHtmlBody(html) {
|
|
40
|
+
const match = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
41
|
+
return match ? match[1].trim() : html;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if HTML string is a full document (has doctype or html tag)
|
|
46
|
+
* @param {string} html
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
function isFullHtmlDoc(html) {
|
|
50
|
+
const trimmed = html.trimStart().toLowerCase();
|
|
51
|
+
return trimmed.startsWith('<!doctype') || trimmed.startsWith('<html');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the injection mode from flags
|
|
56
|
+
* @param {Object} flags
|
|
57
|
+
* @returns {'standalone'|'cdn'|'none'}
|
|
58
|
+
*/
|
|
59
|
+
function resolveInjectionMode(flags) {
|
|
60
|
+
if (flags.standalone) return 'standalone';
|
|
61
|
+
if (flags.cdn) return 'cdn';
|
|
62
|
+
if (flags.noBw) return 'none';
|
|
63
|
+
// Default for single-file: none
|
|
64
|
+
return 'none';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve theme config from --theme flag value
|
|
69
|
+
* @param {string} themeValue - Preset name or hex colors ("primary,secondary" or "primary,secondary,tertiary")
|
|
70
|
+
* @returns {Object} Config for bw.generateTheme
|
|
71
|
+
*/
|
|
72
|
+
function resolveTheme(themeValue) {
|
|
73
|
+
if (!themeValue) return null;
|
|
74
|
+
|
|
75
|
+
// Check preset names
|
|
76
|
+
const preset = THEME_PRESETS[themeValue.toLowerCase()];
|
|
77
|
+
if (preset) return preset;
|
|
78
|
+
|
|
79
|
+
// Parse hex colors: "#336699,#cc6633" or "#336699,#cc6633,#993366"
|
|
80
|
+
const parts = themeValue.split(',').map(s => s.trim());
|
|
81
|
+
if (parts.length >= 2 && parts[0].startsWith('#') && parts[1].startsWith('#')) {
|
|
82
|
+
const config = { primary: parts[0], secondary: parts[1] };
|
|
83
|
+
if (parts[2] && parts[2].startsWith('#')) {
|
|
84
|
+
config.tertiary = parts[2];
|
|
85
|
+
}
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(`Unknown theme: "${themeValue}". Use a preset name (${Object.keys(THEME_PRESETS).join(', ')}) or hex colors ("#primary,#secondary").`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Derive output path from input path (replace extension with .html)
|
|
94
|
+
* @param {string} inputPath
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function deriveOutputPath(inputPath) {
|
|
98
|
+
const ext = extname(inputPath);
|
|
99
|
+
return inputPath.slice(0, -ext.length) + '.html';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert a single file to a styled HTML page
|
|
104
|
+
* @param {string} inputPath - Path to input file
|
|
105
|
+
* @param {Object} flags - CLI flags
|
|
106
|
+
* @returns {string} Output file path
|
|
107
|
+
*/
|
|
108
|
+
export function convertFile(inputPath, flags = {}) {
|
|
109
|
+
const absInput = resolve(inputPath);
|
|
110
|
+
const raw = readFileSync(absInput, 'utf8');
|
|
111
|
+
const ext = extname(absInput).toLowerCase();
|
|
112
|
+
|
|
113
|
+
let bodyHTML = '';
|
|
114
|
+
let autoTitle = null;
|
|
115
|
+
|
|
116
|
+
// Process based on file extension
|
|
117
|
+
switch (ext) {
|
|
118
|
+
case '.md':
|
|
119
|
+
case '.markdown': {
|
|
120
|
+
autoTitle = extractMarkdownTitle(raw);
|
|
121
|
+
bodyHTML = quikdown(raw, { inline_styles: false });
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case '.html':
|
|
125
|
+
case '.htm': {
|
|
126
|
+
if (isFullHtmlDoc(raw)) {
|
|
127
|
+
autoTitle = extractHtmlTitle(raw);
|
|
128
|
+
bodyHTML = extractHtmlBody(raw);
|
|
129
|
+
} else {
|
|
130
|
+
bodyHTML = raw;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case '.json': {
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
if (parsed && typeof parsed === 'object' && parsed.t) {
|
|
137
|
+
// TACO object
|
|
138
|
+
bodyHTML = bw.html(parsed, { raw: true });
|
|
139
|
+
autoTitle = parsed.t === 'html' ? 'TACO Page' : null;
|
|
140
|
+
} else {
|
|
141
|
+
// Plain JSON — pretty-print as code block
|
|
142
|
+
bodyHTML = `<pre><code>${bw.escapeHTML(JSON.stringify(parsed, null, 2))}</code></pre>`;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
throw new Error(`Unsupported file type: ${ext}. Supported: .md, .html, .htm, .json`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Resolve title
|
|
151
|
+
const title = flags.title || autoTitle || basename(absInput, ext);
|
|
152
|
+
|
|
153
|
+
// Resolve injection mode
|
|
154
|
+
const injectionMode = resolveInjectionMode(flags);
|
|
155
|
+
|
|
156
|
+
// Assemble CSS
|
|
157
|
+
let css = '';
|
|
158
|
+
|
|
159
|
+
// 1. Quikdown styles (for markdown files)
|
|
160
|
+
if (ext === '.md' || ext === '.markdown') {
|
|
161
|
+
css += quikdown.emitStyles('quikdown-', 'light');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 2. Theme CSS
|
|
165
|
+
if (flags.theme) {
|
|
166
|
+
const themeConfig = resolveTheme(flags.theme);
|
|
167
|
+
if (themeConfig) {
|
|
168
|
+
const result = bw.generateTheme('', {
|
|
169
|
+
...themeConfig,
|
|
170
|
+
inject: false
|
|
171
|
+
});
|
|
172
|
+
css += '\n' + result.css;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. User CSS file
|
|
177
|
+
if (flags.css) {
|
|
178
|
+
const cssPath = resolve(flags.css);
|
|
179
|
+
css += '\n' + readFileSync(cssPath, 'utf8');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Build the page
|
|
183
|
+
const headInjection = getInjectionHead(injectionMode);
|
|
184
|
+
const bodyEndInjection = getInjectionBodyEnd(injectionMode);
|
|
185
|
+
|
|
186
|
+
const html = makePageLayout({
|
|
187
|
+
title,
|
|
188
|
+
bodyHTML,
|
|
189
|
+
css,
|
|
190
|
+
headInjection,
|
|
191
|
+
bodyEndInjection,
|
|
192
|
+
favicon: flags.favicon || '',
|
|
193
|
+
highlight: !!flags.highlight
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Write output
|
|
197
|
+
const outputPath = flags.output ? resolve(flags.output) : deriveOutputPath(absInput);
|
|
198
|
+
const outputDir = dirname(outputPath);
|
|
199
|
+
mkdirSync(outputDir, { recursive: true });
|
|
200
|
+
writeFileSync(outputPath, html, 'utf8');
|
|
201
|
+
|
|
202
|
+
return outputPath;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export { THEME_PRESETS, resolveTheme, deriveOutputPath, extractMarkdownTitle, extractHtmlTitle };
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitwrench CLI - Main entry point
|
|
3
|
+
* Arg parsing with util.parseArgs(), help, version, dispatch
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
import { VERSION } from '../version.js';
|
|
8
|
+
import { convertFile } from './convert.js';
|
|
9
|
+
|
|
10
|
+
const USAGE = `
|
|
11
|
+
bitwrench v${VERSION} — Document converter & static site generator
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
bitwrench <file> [options] Convert a file to styled HTML
|
|
15
|
+
bitwrench --version Print version
|
|
16
|
+
bitwrench --help Print this help
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
-o, --output <file> Output file path (default: input with .html extension)
|
|
20
|
+
-c, --css <file> Include external CSS file
|
|
21
|
+
-t, --theme <name> Theme preset (ocean, sunset, forest, slate) or hex colors ("#pri,#sec")
|
|
22
|
+
-s, --standalone Embed bitwrench inline (works offline)
|
|
23
|
+
--cdn Link bitwrench via CDN (jsdelivr)
|
|
24
|
+
--no-bw Don't inject bitwrench (plain HTML output)
|
|
25
|
+
--title <text> Page title (default: auto-detect from content)
|
|
26
|
+
-f, --favicon <path> Favicon path or URL
|
|
27
|
+
--highlight Include highlight.js for syntax highlighting
|
|
28
|
+
-v, --verbose Verbose output
|
|
29
|
+
-h, --help Print this help
|
|
30
|
+
--version Print version
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
bitwrench README.md Convert README.md to README.html
|
|
34
|
+
bitwrench README.md -o index.html Specify output file
|
|
35
|
+
bitwrench README.md -o out.html --theme ocean Apply ocean theme
|
|
36
|
+
bitwrench README.md -o out.html --standalone Self-contained offline HTML
|
|
37
|
+
bitwrench README.md -o out.html --highlight With syntax highlighting
|
|
38
|
+
bitwrench doc.md --theme "#336699,#cc6633" Custom theme colors
|
|
39
|
+
`.trim();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse CLI arguments and dispatch
|
|
43
|
+
* @param {string[]} argv - process.argv.slice(2)
|
|
44
|
+
*/
|
|
45
|
+
export function run(argv) {
|
|
46
|
+
let values, positionals;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = parseArgs({
|
|
50
|
+
args: argv,
|
|
51
|
+
strict: true,
|
|
52
|
+
allowPositionals: true,
|
|
53
|
+
options: {
|
|
54
|
+
output: { type: 'string', short: 'o' },
|
|
55
|
+
css: { type: 'string', short: 'c' },
|
|
56
|
+
theme: { type: 'string', short: 't' },
|
|
57
|
+
standalone: { type: 'boolean', short: 's' },
|
|
58
|
+
cdn: { type: 'boolean' },
|
|
59
|
+
'no-bw': { type: 'boolean' },
|
|
60
|
+
title: { type: 'string' },
|
|
61
|
+
favicon: { type: 'string', short: 'f' },
|
|
62
|
+
highlight: { type: 'boolean' },
|
|
63
|
+
verbose: { type: 'boolean', short: 'v' },
|
|
64
|
+
version: { type: 'boolean' },
|
|
65
|
+
help: { type: 'boolean', short: 'h' }
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
values = result.values;
|
|
69
|
+
positionals = result.positionals;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(`Error: ${err.message}`);
|
|
72
|
+
console.error('Run "bitwrench --help" for usage.');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --version
|
|
77
|
+
if (values.version) {
|
|
78
|
+
console.log(`bitwrench v${VERSION}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --help
|
|
83
|
+
if (values.help) {
|
|
84
|
+
console.log(USAGE);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// No positional args → error
|
|
89
|
+
if (positionals.length === 0) {
|
|
90
|
+
console.error('Error: No input file specified.');
|
|
91
|
+
console.error('Run "bitwrench --help" for usage.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Single-file conversion
|
|
96
|
+
const inputFile = positionals[0];
|
|
97
|
+
const flags = {
|
|
98
|
+
output: values.output || null,
|
|
99
|
+
css: values.css || null,
|
|
100
|
+
theme: values.theme || null,
|
|
101
|
+
standalone: !!values.standalone,
|
|
102
|
+
cdn: !!values.cdn,
|
|
103
|
+
noBw: !!values['no-bw'],
|
|
104
|
+
title: values.title || null,
|
|
105
|
+
favicon: values.favicon || null,
|
|
106
|
+
highlight: !!values.highlight,
|
|
107
|
+
verbose: !!values.verbose
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const outputPath = convertFile(inputFile, flags);
|
|
112
|
+
if (flags.verbose) {
|
|
113
|
+
console.log(`Converted: ${inputFile} → ${outputPath}`);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error(`Error: ${err.message}`);
|
|
117
|
+
if (flags.verbose) {
|
|
118
|
+
console.error(err.stack);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitwrench CLI - Injection modes
|
|
3
|
+
* Handles embedding bitwrench into generated HTML pages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { resolve, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const distDir = resolve(__dirname, '../../dist');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get HTML to inject into <head> for the given injection mode
|
|
17
|
+
* @param {'standalone'|'cdn'|'none'} mode
|
|
18
|
+
* @returns {string} HTML string for <head>
|
|
19
|
+
*/
|
|
20
|
+
export function getInjectionHead(mode) {
|
|
21
|
+
if (mode === 'standalone') {
|
|
22
|
+
const umdPath = resolve(distDir, 'bitwrench.umd.min.js');
|
|
23
|
+
const umdSource = readFileSync(umdPath, 'utf8');
|
|
24
|
+
return `<script>${umdSource}</script>`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (mode === 'cdn') {
|
|
28
|
+
let integrity = '';
|
|
29
|
+
try {
|
|
30
|
+
const sriPath = resolve(distDir, 'sri.json');
|
|
31
|
+
const sri = JSON.parse(readFileSync(sriPath, 'utf8'));
|
|
32
|
+
integrity = sri.files['bitwrench.umd.min.js'] || '';
|
|
33
|
+
} catch {
|
|
34
|
+
// SRI file not available — proceed without integrity
|
|
35
|
+
}
|
|
36
|
+
const integrityAttr = integrity ? ` integrity="${integrity}" crossorigin="anonymous"` : '';
|
|
37
|
+
return `<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"${integrityAttr}></script>`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// mode === 'none'
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get HTML to inject before </body> for the given injection mode
|
|
46
|
+
* @param {'standalone'|'cdn'|'none'} mode
|
|
47
|
+
* @returns {string} HTML string before </body>
|
|
48
|
+
*/
|
|
49
|
+
export function getInjectionBodyEnd(mode) {
|
|
50
|
+
if (mode === 'standalone' || mode === 'cdn') {
|
|
51
|
+
return `<script>if(typeof bw!=='undefined'){bw.loadDefaultStyles();}</script>`;
|
|
52
|
+
}
|
|
53
|
+
// mode === 'none'
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitwrench CLI - Default page layout
|
|
3
|
+
* Wraps converted content in a complete HTML document
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import bw from '../bitwrench.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base page CSS for the CLI-generated pages
|
|
10
|
+
*/
|
|
11
|
+
const BASE_PAGE_CSS = `
|
|
12
|
+
.bw-cli-page {
|
|
13
|
+
max-width: 48rem;
|
|
14
|
+
margin: 0 auto;
|
|
15
|
+
padding: 2rem 1.5rem;
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
17
|
+
font-size: 1rem;
|
|
18
|
+
line-height: 1.6;
|
|
19
|
+
color: #333;
|
|
20
|
+
}
|
|
21
|
+
.bw-cli-page pre {
|
|
22
|
+
overflow-x: auto;
|
|
23
|
+
padding: 1em;
|
|
24
|
+
background: #f5f5f5;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
font-size: 0.875em;
|
|
27
|
+
}
|
|
28
|
+
.bw-cli-page code {
|
|
29
|
+
font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, Courier, monospace;
|
|
30
|
+
}
|
|
31
|
+
.bw-cli-page p code {
|
|
32
|
+
background: #f0f0f0;
|
|
33
|
+
padding: 0.15em 0.3em;
|
|
34
|
+
border-radius: 3px;
|
|
35
|
+
font-size: 0.875em;
|
|
36
|
+
}
|
|
37
|
+
.bw-cli-page table {
|
|
38
|
+
border-collapse: collapse;
|
|
39
|
+
width: 100%;
|
|
40
|
+
margin: 1em 0;
|
|
41
|
+
overflow-x: auto;
|
|
42
|
+
display: block;
|
|
43
|
+
}
|
|
44
|
+
.bw-cli-page th, .bw-cli-page td {
|
|
45
|
+
border: 1px solid #ddd;
|
|
46
|
+
padding: 0.5em 0.75em;
|
|
47
|
+
text-align: left;
|
|
48
|
+
}
|
|
49
|
+
.bw-cli-page th {
|
|
50
|
+
background: #f5f5f5;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
}
|
|
53
|
+
.bw-cli-page blockquote {
|
|
54
|
+
border-left: 4px solid #ddd;
|
|
55
|
+
margin-left: 0;
|
|
56
|
+
padding-left: 1em;
|
|
57
|
+
color: #666;
|
|
58
|
+
}
|
|
59
|
+
.bw-cli-page img {
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
height: auto;
|
|
62
|
+
}
|
|
63
|
+
.bw-cli-page h1, .bw-cli-page h2, .bw-cli-page h3,
|
|
64
|
+
.bw-cli-page h4, .bw-cli-page h5, .bw-cli-page h6 {
|
|
65
|
+
margin-top: 1.5em;
|
|
66
|
+
margin-bottom: 0.5em;
|
|
67
|
+
line-height: 1.25;
|
|
68
|
+
}
|
|
69
|
+
.bw-cli-page h1 { font-size: 2em; }
|
|
70
|
+
.bw-cli-page h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
|
71
|
+
.bw-cli-page a { color: #0366d6; text-decoration: none; }
|
|
72
|
+
.bw-cli-page a:hover { text-decoration: underline; }
|
|
73
|
+
.bw-cli-page hr { border: none; border-top: 1px solid #eee; margin: 2em 0; }
|
|
74
|
+
@media (max-width: 600px) {
|
|
75
|
+
.bw-cli-page { padding: 1rem; }
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build a complete HTML page from content and options
|
|
81
|
+
* @param {Object} opts
|
|
82
|
+
* @param {string} opts.title - Page title
|
|
83
|
+
* @param {string} opts.bodyHTML - Rendered HTML content for the body
|
|
84
|
+
* @param {string} [opts.css=''] - Additional CSS to include
|
|
85
|
+
* @param {string} [opts.headInjection=''] - HTML to inject into <head> (bitwrench script)
|
|
86
|
+
* @param {string} [opts.bodyEndInjection=''] - HTML to inject before </body>
|
|
87
|
+
* @param {string} [opts.favicon=''] - Favicon path or URL
|
|
88
|
+
* @param {boolean} [opts.highlight=false] - Include highlight.js CDN
|
|
89
|
+
* @returns {string} Complete HTML document
|
|
90
|
+
*/
|
|
91
|
+
export function makePageLayout(opts) {
|
|
92
|
+
const {
|
|
93
|
+
title = 'Untitled',
|
|
94
|
+
bodyHTML = '',
|
|
95
|
+
css = '',
|
|
96
|
+
headInjection = '',
|
|
97
|
+
bodyEndInjection = '',
|
|
98
|
+
favicon = '',
|
|
99
|
+
highlight = false
|
|
100
|
+
} = opts;
|
|
101
|
+
|
|
102
|
+
const safeTitle = bw.escapeHTML(title);
|
|
103
|
+
const version = bw.version;
|
|
104
|
+
|
|
105
|
+
let faviconTag = '';
|
|
106
|
+
if (favicon) {
|
|
107
|
+
// Only escape quotes and angle brackets for attribute safety, not slashes
|
|
108
|
+
const safeFavicon = favicon.replace(/[&<>"']/g, c => ({
|
|
109
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
110
|
+
})[c]);
|
|
111
|
+
faviconTag = `<link rel="icon" href="${safeFavicon}">`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let highlightHead = '';
|
|
115
|
+
let highlightBodyEnd = '';
|
|
116
|
+
if (highlight) {
|
|
117
|
+
highlightHead = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github.min.css">';
|
|
118
|
+
highlightBodyEnd = '<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>\n<script>hljs.highlightAll();</script>';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const allCSS = BASE_PAGE_CSS + (css ? '\n' + css : '');
|
|
122
|
+
|
|
123
|
+
return `<!DOCTYPE html>
|
|
124
|
+
<html lang="en">
|
|
125
|
+
<head>
|
|
126
|
+
<meta charset="UTF-8">
|
|
127
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
128
|
+
<meta name="generator" content="bitwrench v${version}">
|
|
129
|
+
<title>${safeTitle}</title>
|
|
130
|
+
${faviconTag}${headInjection}${highlightHead}
|
|
131
|
+
<style>${allCSS}</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div class="bw-cli-page">
|
|
135
|
+
${bodyHTML}
|
|
136
|
+
</div>
|
|
137
|
+
${bodyEndInjection}${highlightBodyEnd}
|
|
138
|
+
</body>
|
|
139
|
+
</html>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { BASE_PAGE_CSS };
|