answer_markdown.js 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839
  1. /* Markdown rendering shared by streamed and completed assistant replies.
  2. * Marked 17.0.5 is vendored with its license. Only allowlisted DOM is copied. */
  3. (function () {
  4. const tags = new Set('P BR H1 H2 H3 H4 H5 H6 UL OL LI STRONG EM DEL BLOCKQUOTE PRE CODE HR TABLE THEAD TBODY TR TH TD A'.split(' '));
  5. const drop = new Set('SCRIPT STYLE IFRAME OBJECT EMBED SVG MATH TEMPLATE FORM INPUT IMG VIDEO AUDIO SOURCE LINK META'.split(' '));
  6. function safeCopy(node) {
  7. if (node.nodeType === Node.TEXT_NODE) return document.createTextNode(node.textContent);
  8. const fragment = document.createDocumentFragment();
  9. if (node.nodeType !== Node.ELEMENT_NODE || drop.has(node.tagName)) return fragment;
  10. const out = tags.has(node.tagName) ? document.createElement(node.tagName.toLowerCase()) : fragment;
  11. if (node.tagName === 'A') {
  12. const href = node.getAttribute('href') || '';
  13. try {
  14. const url = new URL(href, location.href);
  15. if (['http:', 'https:', 'mailto:'].includes(url.protocol)) {
  16. out.setAttribute('href', url.href);
  17. out.setAttribute('target', '_blank');
  18. out.setAttribute('rel', 'noopener noreferrer');
  19. }
  20. } catch (_) {}
  21. }
  22. if (node.tagName === 'OL' && /^\d+$/.test(node.getAttribute('start') || '')) out.setAttribute('start', node.getAttribute('start'));
  23. for (const child of node.childNodes) out.appendChild(safeCopy(child));
  24. return out;
  25. }
  26. window.renderAnswer = function (element, text) {
  27. element.classList.add('markdown-body');
  28. const raw = String(text || '');
  29. try {
  30. const template = document.createElement('template');
  31. template.innerHTML = marked.parse(raw, {gfm: true, breaks: true, async: false});
  32. const clean = document.createDocumentFragment();
  33. for (const node of template.content.childNodes) clean.appendChild(safeCopy(node));
  34. element.replaceChildren(clean);
  35. } catch (_) {
  36. element.textContent = raw;
  37. }
  38. };
  39. })();