Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Wrap code
Browse files- app/scripts/export-txt.mjs +89 -1
app/scripts/export-txt.mjs
CHANGED
|
@@ -64,6 +64,15 @@ function parseArgs(argv) {
|
|
| 64 |
return out;
|
| 65 |
}
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
function slugify(text) {
|
| 68 |
return String(text || '')
|
| 69 |
.normalize('NFKD')
|
|
@@ -106,6 +115,82 @@ function headingToMarkdown(level, text) {
|
|
| 106 |
return `${hashes} ${text}`;
|
| 107 |
}
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
/**
|
| 110 |
* Extract and convert article content to TXT format
|
| 111 |
*/
|
|
@@ -503,10 +588,13 @@ async function main() {
|
|
| 503 |
|
| 504 |
console.log('> Extracting article content…');
|
| 505 |
const txtContent = await extractArticleContent(page);
|
|
|
|
|
|
|
|
|
|
| 506 |
|
| 507 |
// Write output
|
| 508 |
const outPath = resolve(cwd, 'dist', `${outFileBase}.txt`);
|
| 509 |
-
await fs.writeFile(outPath,
|
| 510 |
console.log(`✅ TXT exported: ${outPath}`);
|
| 511 |
|
| 512 |
// Copy to public folder
|
|
|
|
| 64 |
return out;
|
| 65 |
}
|
| 66 |
|
| 67 |
+
function parseBoolean(value, defaultValue) {
|
| 68 |
+
if (value === undefined) return defaultValue;
|
| 69 |
+
if (value === true) return true;
|
| 70 |
+
const v = String(value).trim().toLowerCase();
|
| 71 |
+
if (['1', 'true', 'yes', 'y', 'on'].includes(v)) return true;
|
| 72 |
+
if (['0', 'false', 'no', 'n', 'off'].includes(v)) return false;
|
| 73 |
+
return defaultValue;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
function slugify(text) {
|
| 77 |
return String(text || '')
|
| 78 |
.normalize('NFKD')
|
|
|
|
| 115 |
return `${hashes} ${text}`;
|
| 116 |
}
|
| 117 |
|
| 118 |
+
function wrapLineWithIndent(line, maxWidth) {
|
| 119 |
+
if (line.length <= maxWidth) return [line];
|
| 120 |
+
|
| 121 |
+
const indentMatch = line.match(/^\s*/);
|
| 122 |
+
const indent = indentMatch ? indentMatch[0] : '';
|
| 123 |
+
const indentLen = indent.length;
|
| 124 |
+
const available = Math.max(10, maxWidth - indentLen);
|
| 125 |
+
|
| 126 |
+
let rest = line.slice(indentLen);
|
| 127 |
+
const out = [];
|
| 128 |
+
|
| 129 |
+
while (rest.length > available) {
|
| 130 |
+
let breakPos = -1;
|
| 131 |
+
for (let i = available; i >= 1; i--) {
|
| 132 |
+
if (/\s/.test(rest[i - 1])) {
|
| 133 |
+
breakPos = i;
|
| 134 |
+
break;
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
if (breakPos === -1) breakPos = available;
|
| 138 |
+
|
| 139 |
+
const chunk = rest.slice(0, breakPos).replace(/\s+$/g, '');
|
| 140 |
+
out.push(indent + chunk);
|
| 141 |
+
rest = rest.slice(breakPos).replace(/^\s+/g, '');
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
out.push(indent + rest);
|
| 145 |
+
return out;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
function wrapCodeTextAccountingForTags(codeText, maxWidth) {
|
| 149 |
+
const width = Number(maxWidth);
|
| 150 |
+
if (!Number.isFinite(width) || width <= 0) return String(codeText || '');
|
| 151 |
+
|
| 152 |
+
const baseLines = String(codeText || '').split('\n');
|
| 153 |
+
const wrappedLines = [];
|
| 154 |
+
for (const line of baseLines) wrappedLines.push(...wrapLineWithIndent(line, width));
|
| 155 |
+
|
| 156 |
+
if (wrappedLines.length === 0) return '';
|
| 157 |
+
|
| 158 |
+
// If the whole block is a single line, account for both "<c>" and "</c>" on that line.
|
| 159 |
+
if (wrappedLines.length === 1) {
|
| 160 |
+
const maxInner = width - '<c>'.length - '</c>'.length;
|
| 161 |
+
if (wrappedLines[0].length > maxInner && maxInner > 0) {
|
| 162 |
+
return wrapLineWithIndent(wrappedLines[0], maxInner).join('\n');
|
| 163 |
+
}
|
| 164 |
+
return wrappedLines[0];
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Otherwise, "<c>" applies to the first line and "</c>" to the last line only.
|
| 168 |
+
const firstMaxInner = width - '<c>'.length;
|
| 169 |
+
if (firstMaxInner > 0 && wrappedLines[0].length > firstMaxInner) {
|
| 170 |
+
const rewrappedFirst = wrapLineWithIndent(wrappedLines[0], firstMaxInner);
|
| 171 |
+
wrappedLines.splice(0, 1, ...rewrappedFirst);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
const lastMaxInner = width - '</c>'.length;
|
| 175 |
+
const lastIdx = wrappedLines.length - 1;
|
| 176 |
+
if (lastMaxInner > 0 && wrappedLines[lastIdx].length > lastMaxInner) {
|
| 177 |
+
const rewrappedLast = wrapLineWithIndent(wrappedLines[lastIdx], lastMaxInner);
|
| 178 |
+
wrappedLines.splice(lastIdx, 1, ...rewrappedLast);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
return wrappedLines.join('\n');
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
function wrapCodeBlocksInTxt(txt, maxWidth = 80) {
|
| 185 |
+
const width = Number(maxWidth);
|
| 186 |
+
if (!Number.isFinite(width) || width <= 0) return txt;
|
| 187 |
+
|
| 188 |
+
return String(txt || '').replace(/<c>([\s\S]*?)<\/c>/g, (_m, inner) => {
|
| 189 |
+
const wrappedInner = wrapCodeTextAccountingForTags(inner, width);
|
| 190 |
+
return `<c>${wrappedInner}</c>`;
|
| 191 |
+
});
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
/**
|
| 195 |
* Extract and convert article content to TXT format
|
| 196 |
*/
|
|
|
|
| 588 |
|
| 589 |
console.log('> Extracting article content…');
|
| 590 |
const txtContent = await extractArticleContent(page);
|
| 591 |
+
const shouldWrapCode = parseBoolean(args['wrap-code'], true);
|
| 592 |
+
const codeWidth = Number(args['code-width'] ?? 80);
|
| 593 |
+
const finalTxtContent = shouldWrapCode ? wrapCodeBlocksInTxt(txtContent, codeWidth) : txtContent;
|
| 594 |
|
| 595 |
// Write output
|
| 596 |
const outPath = resolve(cwd, 'dist', `${outFileBase}.txt`);
|
| 597 |
+
await fs.writeFile(outPath, finalTxtContent, 'utf-8');
|
| 598 |
console.log(`✅ TXT exported: ${outPath}`);
|
| 599 |
|
| 600 |
// Copy to public folder
|