(node, indent = 0, inListItem = false, trimText = false)
| 229 | |
| 230 | // Process all child nodes |
| 231 | const processNode = (node, indent = 0, inListItem = false, trimText = false) => { |
| 232 | // Skip citation pills and other metadata elements |
| 233 | if (node.nodeType === Node.ELEMENT_NODE) { |
| 234 | if (node.hasAttribute('data-testid') && node.getAttribute('data-testid') === 'webpage-citation-pill') { |
| 235 | return; |
| 236 | } |
| 237 | // Skip other common ChatGPT UI elements |
| 238 | if (node.classList && ( |
| 239 | node.classList.contains('citation-pill') || |
| 240 | node.classList.contains('browse-link') |
| 241 | )) { |
| 242 | return; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | // Handle display formulas (block level) |
| 247 | if (node.classList && node.classList.contains('katex-display')) { |
| 248 | const mathML = node.querySelector('annotation[encoding="application/x-tex"]'); |
| 249 | if (mathML) { |
| 250 | result += '\n$$\n' + mathML.textContent.trim() + '\n$$\n'; |
| 251 | } |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | // Handle inline formulas |
| 256 | if (node.classList && node.classList.contains('katex') && !node.closest('.katex-display')) { |
| 257 | const mathML = node.querySelector('annotation[encoding="application/x-tex"]'); |
| 258 | if (mathML) { |
| 259 | result += '$' + mathML.textContent.trim() + '$'; |
| 260 | } |
| 261 | return; |
| 262 | } |
| 263 | |
| 264 | // Handle headings |
| 265 | if (node.tagName && /^H[1-6]$/.test(node.tagName)) { |
| 266 | const level = node.tagName[1]; |
| 267 | const headingMark = '#'.repeat(parseInt(level)); |
| 268 | result += '\n' + headingMark + ' '; |
| 269 | node.childNodes.forEach(child => processNode(child, indent, false)); |
| 270 | result += '\n\n'; |
| 271 | return; |
| 272 | } |
| 273 | |
| 274 | // Handle paragraphs |
| 275 | if (node.tagName === 'P') { |
| 276 | // In list items, trim leading/trailing whitespace from text nodes |
| 277 | if (inListItem) { |
| 278 | const childNodes = Array.from(node.childNodes); |
| 279 | |
| 280 | // Find first and last non-whitespace-only nodes |
| 281 | let firstIdx = -1; |
| 282 | let lastIdx = -1; |
| 283 | for (let i = 0; i < childNodes.length; i++) { |
| 284 | const child = childNodes[i]; |
| 285 | if (child.nodeType !== Node.TEXT_NODE || child.textContent.trim()) { |
| 286 | if (firstIdx === -1) firstIdx = i; |
| 287 | lastIdx = i; |
| 288 | } |
no test coverage detected