@alhisan/gac 1.0.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/LICENSE +674 -0
- package/README.md +144 -0
- package/alhisan-gac-1.0.0.tgz +0 -0
- package/bin/gac.js +10 -0
- package/package.json +16 -0
- package/src/cli.js +329 -0
- package/src/config.js +129 -0
- package/src/gpt4all.js +168 -0
- package/src/markdown.js +326 -0
package/src/gpt4all.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import terminalKit from 'terminal-kit';
|
|
2
|
+
import { createMarkdownRenderer } from './markdown.js';
|
|
3
|
+
|
|
4
|
+
const { terminal: term } = terminalKit;
|
|
5
|
+
|
|
6
|
+
function getContentDelta(chunk) {
|
|
7
|
+
if (!chunk || !chunk.choices || !chunk.choices[0]) return '';
|
|
8
|
+
const choice = chunk.choices[0];
|
|
9
|
+
if (choice.delta && choice.delta.content) return choice.delta.content;
|
|
10
|
+
if (choice.message && choice.message.content) return choice.message.content;
|
|
11
|
+
if (choice.text) return choice.text;
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function parseStream(response, onToken, renderer) {
|
|
16
|
+
const reader = response.body.getReader();
|
|
17
|
+
const decoder = new TextDecoder('utf-8');
|
|
18
|
+
let buffer = '';
|
|
19
|
+
let fullText = '';
|
|
20
|
+
let lineBuffer = '';
|
|
21
|
+
|
|
22
|
+
while (true) {
|
|
23
|
+
const { value, done } = await reader.read();
|
|
24
|
+
if (done) break;
|
|
25
|
+
buffer += decoder.decode(value, { stream: true });
|
|
26
|
+
|
|
27
|
+
const lines = buffer.split('\n');
|
|
28
|
+
buffer = lines.pop() || '';
|
|
29
|
+
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
33
|
+
const payload = trimmed.replace(/^data:\s*/, '');
|
|
34
|
+
if (payload === '[DONE]') {
|
|
35
|
+
return fullText;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const json = JSON.parse(payload);
|
|
40
|
+
const delta = getContentDelta(json);
|
|
41
|
+
if (delta) {
|
|
42
|
+
fullText += delta;
|
|
43
|
+
if (!renderer) {
|
|
44
|
+
onToken(delta);
|
|
45
|
+
} else {
|
|
46
|
+
lineBuffer += delta;
|
|
47
|
+
const lines = lineBuffer.split('\n');
|
|
48
|
+
lineBuffer = lines.pop() || '';
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
onToken(`${renderer.renderLine(line)}\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// Ignore non-JSON payloads
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (renderer && lineBuffer) {
|
|
61
|
+
onToken(renderer.renderLine(lineBuffer));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return fullText;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeBaseUrl(baseUrl) {
|
|
68
|
+
const trimmed = baseUrl.replace(/\/$/, '');
|
|
69
|
+
if (trimmed.endsWith('/v1')) return trimmed;
|
|
70
|
+
return `${trimmed}/v1`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function fetchJson(url, payload) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(url, payload);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const text = await response.text();
|
|
78
|
+
throw new Error(`GPT4All error ${response.status}: ${text}`);
|
|
79
|
+
}
|
|
80
|
+
return await response.json();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err.message && err.message.startsWith('GPT4All error')) {
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`Failed to connect to ${url}. Is GPT4All running and reachable? (${err.message})`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function listModels(baseUrl) {
|
|
90
|
+
const url = `${normalizeBaseUrl(baseUrl)}/models`;
|
|
91
|
+
const json = await fetchJson(url, { method: 'GET' });
|
|
92
|
+
if (!json || !Array.isArray(json.data)) return [];
|
|
93
|
+
return json.data.map((model) => model.id).filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function fetchCompletion(url, payload) {
|
|
97
|
+
try {
|
|
98
|
+
return await fetch(url, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
body: JSON.stringify(payload)
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
throw new Error(`Failed to connect to ${url}. Is GPT4All running and reachable? (${err.message})`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function handleError(response) {
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
throw new Error(`GPT4All error ${response.status}: ${text}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function chatCompletion(
|
|
114
|
+
{ baseUrl, model, temperature, maxTokens, stream, renderMarkdown, markdownStyles },
|
|
115
|
+
messages
|
|
116
|
+
) {
|
|
117
|
+
const url = `${normalizeBaseUrl(baseUrl)}/chat/completions`;
|
|
118
|
+
const payload = {
|
|
119
|
+
model,
|
|
120
|
+
messages,
|
|
121
|
+
temperature,
|
|
122
|
+
max_tokens: maxTokens,
|
|
123
|
+
stream: Boolean(stream)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
let response = await fetchCompletion(url, payload);
|
|
127
|
+
|
|
128
|
+
const renderer = renderMarkdown ? createMarkdownRenderer(markdownStyles) : null;
|
|
129
|
+
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const text = await response.text();
|
|
132
|
+
if (stream && response.status === 400 && text.includes('stream') && text.includes('not supported')) {
|
|
133
|
+
const retryPayload = { ...payload, stream: false };
|
|
134
|
+
response = await fetchCompletion(url, retryPayload);
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
await handleError(response);
|
|
137
|
+
}
|
|
138
|
+
const json = await response.json();
|
|
139
|
+
const content = getContentDelta(json);
|
|
140
|
+
if (renderer) {
|
|
141
|
+
term(renderer.renderText(content));
|
|
142
|
+
} else {
|
|
143
|
+
term(content);
|
|
144
|
+
}
|
|
145
|
+
return content;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw new Error(`GPT4All error ${response.status}: ${text}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (stream) {
|
|
152
|
+
const contentType = response.headers.get('content-type') || '';
|
|
153
|
+
if (contentType.includes('text/event-stream')) {
|
|
154
|
+
return parseStream(response, (chunk) => term(chunk), renderer);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const json = await response.json();
|
|
159
|
+
const content = getContentDelta(json);
|
|
160
|
+
if (stream) {
|
|
161
|
+
if (renderer) {
|
|
162
|
+
term(renderer.renderText(content));
|
|
163
|
+
} else {
|
|
164
|
+
term(content);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return content;
|
|
168
|
+
}
|
package/src/markdown.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import terminalKit from 'terminal-kit';
|
|
2
|
+
|
|
3
|
+
const { terminal: term } = terminalKit;
|
|
4
|
+
|
|
5
|
+
const ANSI = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
bold: '\x1b[1m',
|
|
8
|
+
italic: '\x1b[3m',
|
|
9
|
+
dim: '\x1b[2m',
|
|
10
|
+
underline: '\x1b[4m',
|
|
11
|
+
cyan: '\x1b[36m',
|
|
12
|
+
bgBlack: '\x1b[40m',
|
|
13
|
+
brightWhite: '\x1b[97m'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseHexColor(token) {
|
|
17
|
+
if (!token) return null;
|
|
18
|
+
const raw = token.replace(/^#/, '');
|
|
19
|
+
if (!/^[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(raw)) return null;
|
|
20
|
+
const hex = raw.length === 3 ? raw.split('').map((c) => `${c}${c}`).join('') : raw;
|
|
21
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
22
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
23
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
24
|
+
return { r, g, b };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseFgHex(token) {
|
|
28
|
+
if (!token) return null;
|
|
29
|
+
if (!token.startsWith('#')) return null;
|
|
30
|
+
return parseHexColor(token);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseBgHex(token) {
|
|
34
|
+
if (!token) return null;
|
|
35
|
+
if (!token.startsWith('bg')) return null;
|
|
36
|
+
const trimmed = token.replace(/^bg:/, 'bg').replace(/^bg#/, 'bg');
|
|
37
|
+
const match = trimmed.match(/^bg(#.+)$/);
|
|
38
|
+
if (!match) return null;
|
|
39
|
+
return parseHexColor(match[1]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isDefaultFg(token) {
|
|
43
|
+
return token === 'default' || token === 'fg:default' || token === 'fg-default';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isDefaultBg(token) {
|
|
47
|
+
return token === 'bg:default' || token === 'bg-default';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function applyAnsi(styles, text) {
|
|
51
|
+
let codes = '';
|
|
52
|
+
for (const style of styles) {
|
|
53
|
+
if (ANSI[style]) {
|
|
54
|
+
codes += ANSI[style];
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (isDefaultFg(style)) {
|
|
58
|
+
codes += '\x1b[39m';
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (isDefaultBg(style)) {
|
|
62
|
+
codes += '\x1b[49m';
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const fgHex = parseFgHex(style);
|
|
66
|
+
if (fgHex) {
|
|
67
|
+
codes += `\x1b[38;2;${fgHex.r};${fgHex.g};${fgHex.b}m`;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const bgHex = parseBgHex(style);
|
|
71
|
+
if (bgHex) {
|
|
72
|
+
codes += `\x1b[48;2;${bgHex.r};${bgHex.g};${bgHex.b}m`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!codes) return text;
|
|
77
|
+
return `${codes}${text}${ANSI.reset}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function applyStyle(styles, text) {
|
|
81
|
+
if (styles.some((style) => parseFgHex(style) || parseBgHex(style) || isDefaultFg(style) || isDefaultBg(style))) {
|
|
82
|
+
return applyAnsi(styles, text);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let chain = term;
|
|
86
|
+
for (const style of styles) {
|
|
87
|
+
if (chain[style]) {
|
|
88
|
+
chain = chain[style];
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
return applyAnsi(styles, text);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (chain && typeof chain.str === 'function') {
|
|
95
|
+
return chain.str(text);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return applyAnsi(styles, text);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const DEFAULT_STYLES = {
|
|
102
|
+
headerStyles: ['bold'],
|
|
103
|
+
headerStylesByLevel: {
|
|
104
|
+
1: ['bold', 'brightWhite'],
|
|
105
|
+
2: ['bold'],
|
|
106
|
+
3: ['bold'],
|
|
107
|
+
4: ['dim'],
|
|
108
|
+
5: ['dim'],
|
|
109
|
+
6: ['dim']
|
|
110
|
+
},
|
|
111
|
+
headerUnderline: true,
|
|
112
|
+
headerUnderlineLevels: [1],
|
|
113
|
+
headerUnderlineStyle: ['dim'],
|
|
114
|
+
headerUnderlineChar: '─',
|
|
115
|
+
codeStyles: ['cyan'],
|
|
116
|
+
codeBackground: ['bgBlack'],
|
|
117
|
+
codeBorder: true,
|
|
118
|
+
codeBorderStyle: ['dim'],
|
|
119
|
+
codeGutter: '│ ',
|
|
120
|
+
codeBorderChars: {
|
|
121
|
+
topLeft: '┌',
|
|
122
|
+
top: '─',
|
|
123
|
+
topRight: '┐',
|
|
124
|
+
bottomLeft: '└',
|
|
125
|
+
bottom: '─',
|
|
126
|
+
bottomRight: '┘'
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function normalizeStyles(styles) {
|
|
131
|
+
if (!styles) return [];
|
|
132
|
+
return Array.isArray(styles) ? styles : [styles];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function mergeStyles(options) {
|
|
136
|
+
return {
|
|
137
|
+
...DEFAULT_STYLES,
|
|
138
|
+
...options,
|
|
139
|
+
codeBorderChars: {
|
|
140
|
+
...DEFAULT_STYLES.codeBorderChars,
|
|
141
|
+
...(options && options.codeBorderChars ? options.codeBorderChars : {})
|
|
142
|
+
},
|
|
143
|
+
headerStylesByLevel: {
|
|
144
|
+
...DEFAULT_STYLES.headerStylesByLevel,
|
|
145
|
+
...(options && options.headerStylesByLevel ? options.headerStylesByLevel : {})
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function applyInlineMarkdown(text) {
|
|
151
|
+
let output = text;
|
|
152
|
+
|
|
153
|
+
output = output.replace(/`([^`]+)`/g, (match, code) => applyStyle(['dim'], code));
|
|
154
|
+
output = output.replace(/\*\*([^*]+)\*\*/g, (match, bold) => applyStyle(['bold'], bold));
|
|
155
|
+
output = output.replace(/_([^_]+)_/g, (match, italic) => applyStyle(['italic'], italic));
|
|
156
|
+
output = output.replace(
|
|
157
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
158
|
+
(match, label, url) => `${applyStyle(['underline'], label)} (${url})`
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return output;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function matchFence(line) {
|
|
165
|
+
const match = line.match(/^\s*(?:[-*+]\s+|\d+\.\s+)?(```|~~~)\s*([A-Za-z0-9_-]+)?\s*$/);
|
|
166
|
+
if (!match) return null;
|
|
167
|
+
return { fence: match[1], lang: (match[2] || '').toLowerCase() };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function createMarkdownRenderer(options = {}) {
|
|
171
|
+
const styles = mergeStyles(options);
|
|
172
|
+
const state = {
|
|
173
|
+
inCodeBlock: false,
|
|
174
|
+
inIndentedCode: false,
|
|
175
|
+
fenceType: null,
|
|
176
|
+
prevBlank: true,
|
|
177
|
+
markdownWrapper: false,
|
|
178
|
+
wrapperFenceType: null
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function headerStylesForLevel(level) {
|
|
182
|
+
const levelStyles = styles.headerStylesByLevel && styles.headerStylesByLevel[level];
|
|
183
|
+
return normalizeStyles(levelStyles || styles.headerStyles);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function renderHeader(level, text, prefix = '') {
|
|
187
|
+
const styledHeader = applyStyle(headerStylesForLevel(level), text);
|
|
188
|
+
const underlineAllowed =
|
|
189
|
+
styles.headerUnderline &&
|
|
190
|
+
(!Array.isArray(styles.headerUnderlineLevels) || styles.headerUnderlineLevels.includes(level));
|
|
191
|
+
if (!underlineAllowed) {
|
|
192
|
+
return `${state.prevBlank ? '' : '\n'}${prefix}${styledHeader}`;
|
|
193
|
+
}
|
|
194
|
+
const underline = (styles.headerUnderlineChar || '─').repeat(Math.max(text.length, 4));
|
|
195
|
+
const separator = state.prevBlank ? '' : '\n';
|
|
196
|
+
const pad = prefix ? ' '.repeat(prefix.replace(/\t/g, ' ').length) : '';
|
|
197
|
+
return `${separator}${prefix}${styledHeader}\n${pad}${applyStyle(normalizeStyles(styles.headerUnderlineStyle), underline)}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ruleLine(left, fill, right) {
|
|
201
|
+
const width = Math.max(20, Math.min(term.width || 80, 100));
|
|
202
|
+
const inner = Math.max(width - 2, 1);
|
|
203
|
+
return applyStyle(normalizeStyles(styles.codeBorderStyle), `${left}${fill.repeat(inner)}${right}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderCodeLine(line) {
|
|
207
|
+
const prefix = applyStyle(normalizeStyles(styles.codeBorderStyle), styles.codeGutter);
|
|
208
|
+
const codeStyles = [...normalizeStyles(styles.codeBackground), ...normalizeStyles(styles.codeStyles)];
|
|
209
|
+
return `${prefix}${applyStyle(codeStyles, line)}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function renderMarkdownLine(sanitized, trimmed) {
|
|
213
|
+
const headerMatch = trimmed.match(/^(#{1,6})\s+(.+?)(?:\s+#+\s*|#+\s*)?$/);
|
|
214
|
+
if (headerMatch) {
|
|
215
|
+
const level = headerMatch[1].length;
|
|
216
|
+
const headerText = headerMatch[2];
|
|
217
|
+
return renderHeader(level, headerText);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const listHeaderMatch = sanitized.match(
|
|
221
|
+
/^(\s*[-*+]+\s+|\s*\d+[.)]\s+)(#{1,6})\s+(.+?)(?:\s+#+\s*|#+\s*)?$/
|
|
222
|
+
);
|
|
223
|
+
if (listHeaderMatch) {
|
|
224
|
+
const prefix = listHeaderMatch[1];
|
|
225
|
+
const level = listHeaderMatch[2].length;
|
|
226
|
+
const headerText = listHeaderMatch[3];
|
|
227
|
+
return renderHeader(level, headerText, prefix);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (/^(-{3,}|_{3,}|\*{3,})\s*$/.test(trimmed)) {
|
|
231
|
+
const underline = (styles.headerUnderlineChar || '─').repeat(24);
|
|
232
|
+
return applyStyle(normalizeStyles(styles.headerUnderlineStyle), underline);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (trimmed.startsWith('>')) {
|
|
236
|
+
return applyStyle(['dim'], applyInlineMarkdown(sanitized));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return applyInlineMarkdown(sanitized);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderLine(line) {
|
|
243
|
+
const sanitized = line.replace(/\r/g, '');
|
|
244
|
+
const trimmed = sanitized.trim();
|
|
245
|
+
const isBlank = trimmed.length === 0;
|
|
246
|
+
const indentedMatch = sanitized.match(/^(?:\t| {4,})(.*)$/);
|
|
247
|
+
const fenceMatch = matchFence(sanitized);
|
|
248
|
+
if (fenceMatch) {
|
|
249
|
+
const { fence, lang } = fenceMatch;
|
|
250
|
+
if (state.inCodeBlock && fence === state.fenceType && !lang) {
|
|
251
|
+
state.inCodeBlock = false;
|
|
252
|
+
state.fenceType = null;
|
|
253
|
+
if (!styles.codeBorder) return '';
|
|
254
|
+
const chars = styles.codeBorderChars;
|
|
255
|
+
state.prevBlank = false;
|
|
256
|
+
return ruleLine(chars.bottomLeft, chars.bottom, chars.bottomRight);
|
|
257
|
+
}
|
|
258
|
+
if (state.inCodeBlock) {
|
|
259
|
+
state.prevBlank = false;
|
|
260
|
+
return renderCodeLine(sanitized);
|
|
261
|
+
}
|
|
262
|
+
if (state.markdownWrapper && fence === state.wrapperFenceType && !lang) {
|
|
263
|
+
state.markdownWrapper = false;
|
|
264
|
+
state.wrapperFenceType = null;
|
|
265
|
+
state.prevBlank = false;
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
if (!state.markdownWrapper && (lang === 'markdown' || lang === 'md')) {
|
|
269
|
+
state.markdownWrapper = true;
|
|
270
|
+
state.wrapperFenceType = fence;
|
|
271
|
+
state.prevBlank = false;
|
|
272
|
+
return '';
|
|
273
|
+
}
|
|
274
|
+
state.inCodeBlock = true;
|
|
275
|
+
state.fenceType = fence;
|
|
276
|
+
if (!styles.codeBorder) return '';
|
|
277
|
+
const chars = styles.codeBorderChars;
|
|
278
|
+
state.prevBlank = false;
|
|
279
|
+
return ruleLine(chars.topLeft, chars.top, chars.topRight);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (state.inCodeBlock) {
|
|
283
|
+
state.prevBlank = false;
|
|
284
|
+
return renderCodeLine(sanitized);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (state.inIndentedCode) {
|
|
288
|
+
if (indentedMatch) {
|
|
289
|
+
state.prevBlank = false;
|
|
290
|
+
return renderCodeLine(indentedMatch[1]);
|
|
291
|
+
}
|
|
292
|
+
state.inIndentedCode = false;
|
|
293
|
+
if (styles.codeBorder) {
|
|
294
|
+
const chars = styles.codeBorderChars;
|
|
295
|
+
const closing = ruleLine(chars.bottomLeft, chars.bottom, chars.bottomRight);
|
|
296
|
+
state.prevBlank = isBlank;
|
|
297
|
+
return `${closing}\n${renderMarkdownLine(sanitized, trimmed)}`;
|
|
298
|
+
}
|
|
299
|
+
state.prevBlank = isBlank;
|
|
300
|
+
return renderMarkdownLine(sanitized, trimmed);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (indentedMatch && state.prevBlank) {
|
|
304
|
+
state.inIndentedCode = true;
|
|
305
|
+
if (styles.codeBorder) {
|
|
306
|
+
const chars = styles.codeBorderChars;
|
|
307
|
+
const opening = ruleLine(chars.topLeft, chars.top, chars.topRight);
|
|
308
|
+
state.prevBlank = false;
|
|
309
|
+
return `${opening}\n${renderCodeLine(indentedMatch[1])}`;
|
|
310
|
+
}
|
|
311
|
+
state.prevBlank = false;
|
|
312
|
+
return renderCodeLine(indentedMatch[1]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
state.prevBlank = isBlank;
|
|
316
|
+
return renderMarkdownLine(sanitized, trimmed);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function renderText(text) {
|
|
320
|
+
const lines = text.split('\n');
|
|
321
|
+
const rendered = lines.map((line) => renderLine(line));
|
|
322
|
+
return rendered.join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { renderLine, renderText, state };
|
|
326
|
+
}
|