From 1f3837737db48cd769bd543d84643b5c1ace1b70 Mon Sep 17 00:00:00 2001 From: Hackall <36754621+hackall360@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:48:54 -0700 Subject: [PATCH] feat: centralize highlight.js utilities --- docs/README.md | 2 +- index.html | 3 ++- js/chat/chat-init.js | 16 +++++--------- js/chat/chat-storage.js | 41 ++++++++++++++-------------------- js/ui/highlight.js | 49 +++++++++++++++++++++++++++++++++++++++++ js/ui/ui.js | 43 ++---------------------------------- 6 files changed, 76 insertions(+), 78 deletions(-) create mode 100644 js/ui/highlight.js diff --git a/docs/README.md b/docs/README.md index 1a1bcc7..091ecf0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,7 @@ Your web application, titled **"Unity Chat U1 6.6"**, provides an interactive ch - **Speech Recognition:** Users can dictate messages through voice input, which captures speech and translates it into textual inputs in real-time. - **Message Handling:** - - **Markdown Support:** AI-generated responses utilize Markdown, enhanced with syntax highlighting (via highlight.js) for clarity in code snippets. + - **Markdown Support:** AI-generated responses utilize Markdown with highlight.js, offering syntax highlighting for over 100 languages and automatically adapting to the active site theme. - **Image Embedding:** Automatically embeds images generated by Pollinations based on AI conversation content. - **Editing and Regeneration:** Users can edit their messages or regenerate AI responses conveniently from within the chat interface. diff --git a/index.html b/index.html index b33a062..59219ef 100644 --- a/index.html +++ b/index.html @@ -468,7 +468,8 @@ - + + diff --git a/js/chat/chat-init.js b/js/chat/chat-init.js index d82bcfb..f2882d2 100644 --- a/js/chat/chat-init.js +++ b/js/chat/chat-init.js @@ -13,10 +13,6 @@ document.addEventListener("DOMContentLoaded", () => { if (newTitle && newTitle !== currentSession.name) Storage.renameSession(currentSession.id, newTitle); } }; - const highlightAllCodeBlocks = () => { - if (!window.hljs) return; - chatBox.querySelectorAll("pre code").forEach(block => hljs.highlightElement(block)); - }; const appendMessage = ({ role, content, index, imageUrls = [], audioUrls = [] }) => { const container = document.createElement("div"); container.classList.add("message"); @@ -116,8 +112,8 @@ document.addEventListener("DOMContentLoaded", () => { block.parentNode.insertAdjacentElement("afterend", buttonContainer); }); chatBox.appendChild(container); - chatBox.scrollTop = chatBox.scrollHeight; - highlightAllCodeBlocks(); + chatBox.scrollTop = chatBox.scrollHeight; + window.highlightUtils?.highlightAllCodeBlocks(chatBox); }; const downloadCodeAsTxt = (codeContent, language) => { const blob = new Blob([codeContent], { type: "text/plain" }); @@ -394,7 +390,7 @@ document.addEventListener("DOMContentLoaded", () => { } } }); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); }; window.addNewMessage = ({ role, content, imageUrls = [], audioUrls = [] }) => { const currentSession = Storage.getCurrentSession(); @@ -439,14 +435,14 @@ document.addEventListener("DOMContentLoaded", () => { chatBox.scrollTop = chatBox.scrollHeight; sendToPolliLib(() => { loadingDiv.remove(); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); }, newContent); showToast("User message updated and new response generated"); } else { currentSession.messages[msgIndex].content = newContent; Storage.updateSessionMessages(currentSession.id, currentSession.messages); renderStoredMessages(currentSession.messages); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); showToast("AI message updated"); } }; @@ -484,7 +480,7 @@ document.addEventListener("DOMContentLoaded", () => { console.log(`Sending re-generate request for user message: ${userMessage} (with unique suffix: ${uniqueUserMessage})`); window.sendToPolliLib(() => { loadingDiv.remove(); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); checkAndUpdateSessionTitle(); showToast("Response regenerated successfully"); }, uniqueUserMessage); diff --git a/js/chat/chat-storage.js b/js/chat/chat-storage.js index d72f5ec..afa67cf 100644 --- a/js/chat/chat-storage.js +++ b/js/chat/chat-storage.js @@ -25,15 +25,6 @@ document.addEventListener("DOMContentLoaded", () => { } } } - function highlightAllCodeBlocks() { - if (!window.hljs) { - return; - } - const codeBlocks = chatBox.querySelectorAll("pre code"); - codeBlocks.forEach((block) => { - hljs.highlightElement(block); - }); - } function appendMessage({ role, content, index, imageUrls = [], audioUrls = [] }) { const container = document.createElement("div"); container.classList.add("message"); @@ -147,9 +138,9 @@ document.addEventListener("DOMContentLoaded", () => { buttonContainer.appendChild(downloadCodeBtn); block.parentNode.insertAdjacentElement("afterend", buttonContainer); }); - chatBox.appendChild(container); - chatBox.scrollTop = chatBox.scrollHeight; - highlightAllCodeBlocks(); + chatBox.appendChild(container); + chatBox.scrollTop = chatBox.scrollHeight; + window.highlightUtils?.highlightAllCodeBlocks(chatBox); } function downloadCodeAsTxt(codeContent, language) { const blob = new Blob([codeContent], { type: "text/plain" }); @@ -400,7 +391,7 @@ document.addEventListener("DOMContentLoaded", () => { audioUrls: storedAudio }); }); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); chatInput.disabled = false; chatInput.focus(); } @@ -450,18 +441,18 @@ document.addEventListener("DOMContentLoaded", () => { chatBox.scrollTop = chatBox.scrollHeight; window.sendToPolliLib(() => { loadingDiv.remove(); - highlightAllCodeBlocks(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); }, newContent); showToast("User message updated and new response generated"); } else { currentSession.messages[msgIndex].content = newContent; Storage.updateSessionMessages(currentSession.id, currentSession.messages); renderStoredMessages(currentSession.messages); - highlightAllCodeBlocks(); - showToast("AI message updated"); - } - } - function reGenerateAIResponse(aiIndex) { + window.highlightUtils?.highlightAllCodeBlocks(chatBox); + showToast("AI message updated"); + } + } + function reGenerateAIResponse(aiIndex) { console.log(`Re-generating AI response for index: ${aiIndex}`); const currentSession = Storage.getCurrentSession(); if (aiIndex < 0 || aiIndex >= currentSession.messages.length || currentSession.messages[aiIndex].role !== "ai") { @@ -496,12 +487,12 @@ document.addEventListener("DOMContentLoaded", () => { chatBox.scrollTop = chatBox.scrollHeight; const uniqueUserMessage = `${userMessage} [regen-${Date.now()}-${Math.random().toString(36).substring(2)}]`; console.log(`Sending re-generate request for user message: ${userMessage} (with unique suffix: ${uniqueUserMessage})`); - window.sendToPolliLib(() => { - loadingDiv.remove(); - highlightAllCodeBlocks(); - showToast("Response regenerated successfully"); - }, uniqueUserMessage); - } + window.sendToPolliLib(() => { + loadingDiv.remove(); + window.highlightUtils?.highlightAllCodeBlocks(chatBox); + showToast("Response regenerated successfully"); + }, uniqueUserMessage); + } if (voiceToggleBtn) { voiceToggleBtn.addEventListener("click", window._chatInternals.toggleAutoSpeak); diff --git a/js/ui/highlight.js b/js/ui/highlight.js new file mode 100644 index 0000000..86ad11f --- /dev/null +++ b/js/ui/highlight.js @@ -0,0 +1,49 @@ +(function() { + const HLJS_BASE = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/"; + + const hljsThemeMap = { + light: "github", + dark: "github-dark", + hacker: "a11y-dark", + oled: "atom-one-dark", + "subtle-light": "atom-one-light", + burple: "atom-one-dark", + "pretty-pink": "github", + nord: "nord", + "solarized-light": "solarized-light", + "solarized-dark": "solarized-dark", + "gruvbox-light": "gruvbox-light", + "gruvbox-dark": "gruvbox-dark", + cyberpunk: "atom-one-dark", + dracula: "dracula", + monokai: "monokai", + "material-dark": "atom-one-dark", + "material-light": "atom-one-light", + "pastel-dream": "github", + "ocean-breeze": "github", + "vintage-paper": "github", + honeycomb: "github", + "rainbow-throwup": "github", + serenity: "atom-one-light" + }; + + let hljsThemeLink = document.getElementById("hljs-theme-link"); + if (!hljsThemeLink) { + hljsThemeLink = document.createElement("link"); + hljsThemeLink.id = "hljs-theme-link"; + hljsThemeLink.rel = "stylesheet"; + document.head.appendChild(hljsThemeLink); + } + + function updateHighlightTheme(themeValue) { + const hlTheme = hljsThemeMap[themeValue] || "github-dark"; + hljsThemeLink.href = `${HLJS_BASE}${hlTheme}.min.css`; + } + + function highlightAllCodeBlocks(container = document) { + if (!window.hljs) return; + container.querySelectorAll("pre code").forEach(block => hljs.highlightElement(block)); + } + + window.highlightUtils = { updateHighlightTheme, highlightAllCodeBlocks }; +})(); diff --git a/js/ui/ui.js b/js/ui/ui.js index 846d68c..7e9522f 100644 --- a/js/ui/ui.js +++ b/js/ui/ui.js @@ -38,13 +38,6 @@ document.addEventListener("DOMContentLoaded", () => { themeLinkElement.rel = "stylesheet"; document.head.appendChild(themeLinkElement); } - let hljsThemeLink = document.getElementById("hljs-theme-link"); - if (!hljsThemeLink) { - hljsThemeLink = document.createElement("link"); - hljsThemeLink.id = "hljs-theme-link"; - hljsThemeLink.rel = "stylesheet"; - document.head.appendChild(hljsThemeLink); - } const allThemes = [ { value: "light", label: "Light", file: "themes/light.css" }, @@ -72,38 +65,6 @@ document.addEventListener("DOMContentLoaded", () => { { value: "serenity", label: "Serenity", file: "themes/serenity.css" } ]; - const hljsThemeMap = { - light: "github", - dark: "github-dark", - hacker: "a11y-dark", - oled: "atom-one-dark", - "subtle-light": "atom-one-light", - burple: "atom-one-dark", - "pretty-pink": "github", - nord: "nord", - "solarized-light": "solarized-light", - "solarized-dark": "solarized-dark", - "gruvbox-light": "gruvbox-light", - "gruvbox-dark": "gruvbox-dark", - cyberpunk: "atom-one-dark", - dracula: "dracula", - monokai: "monokai", - "material-dark": "atom-one-dark", - "material-light": "atom-one-light", - "pastel-dream": "github", - "ocean-breeze": "github", - "vintage-paper": "github", - honeycomb: "github", - "rainbow-throwup": "github", - serenity: "atom-one-light" - }; - - const HLJS_BASE = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/"; - - function updateHighlightTheme(themeValue) { - const hlTheme = hljsThemeMap[themeValue] || "github-dark"; - hljsThemeLink.href = `${HLJS_BASE}${hlTheme}.min.css`; - } function populateThemeDropdowns() { themeSelect.innerHTML = ""; @@ -130,7 +91,7 @@ document.addEventListener("DOMContentLoaded", () => { themeSelectSettings.value = savedTheme; const found = allThemes.find(t => t.value === savedTheme); themeLinkElement.href = found ? found.file : "themes/dark.css"; - updateHighlightTheme(savedTheme); + window.highlightUtils?.updateHighlightTheme(savedTheme); } loadUserTheme(); @@ -140,7 +101,7 @@ document.addEventListener("DOMContentLoaded", () => { themeSelectSettings.value = newThemeValue; const found = allThemes.find(t => t.value === newThemeValue); themeLinkElement.href = found ? found.file : ""; - updateHighlightTheme(newThemeValue); + window.highlightUtils?.updateHighlightTheme(newThemeValue); } themeSelect.addEventListener("change", () => {