Commit 0a828fff authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

chore: full codebase sync

parent e78099e7
...@@ -64,3 +64,6 @@ backend/schema_dump.json ...@@ -64,3 +64,6 @@ backend/schema_dump.json
*.db *.db
*.db-shm *.db-shm
*.db-wal *.db-wal
# Node
node_modules/
...@@ -82,8 +82,8 @@ ...@@ -82,8 +82,8 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.2/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.0/mermaid.min.js"></script>
<script src="cookbook.js?v=2"></script> <script src="cookbook.js?v=5"></script>
</body> </body>
</html> </html>
...@@ -105,7 +105,8 @@ document.addEventListener('DOMContentLoaded', async () => { ...@@ -105,7 +105,8 @@ document.addEventListener('DOMContentLoaded', async () => {
markAsRead(id); markAsRead(id);
} catch (err) { } catch (err) {
recipeContent.innerHTML = `<div style="color:#ef4444; padding:20px;">Lỗi: Không tìm thấy data cho recipe <b>${id}</b>.</div>`; console.error('Error loading recipe:', err);
recipeContent.innerHTML = `<div style="color:#ef4444; padding:20px;">Lỗi khi tải recipe <b>${id}</b>: ${err.message}</div>`;
} }
} }
...@@ -162,7 +163,12 @@ document.addEventListener('DOMContentLoaded', async () => { ...@@ -162,7 +163,12 @@ document.addEventListener('DOMContentLoaded', async () => {
// Extract mermaid blocks for rendering // Extract mermaid blocks for rendering
parsedHtml = parsedHtml.replace(/<code class="language-mermaid">([\s\S]*?)<\/code>/g, '<div class="mermaid">$1</div>'); parsedHtml = parsedHtml.replace(/<code class="language-mermaid">([\s\S]*?)<\/code>/g, '<div class="mermaid">$1</div>');
// Prevent wrapping mermaid block in a pre tag from markdown // Prevent wrapping mermaid block in a pre tag from markdown
parsedHtml = parsedHtml.replace(/<pre><div class="mermaid">([\s\S]*?)<\/div><\/pre>/g, '<div class="mermaid">$1</div>'); parsedHtml = parsedHtml.replace(/<pre>\s*<div class="mermaid">([\s\S]*?)<\/div>\s*<\/pre>/g, '<div class="mermaid">$1</div>');
// Unescape HTML entities so Mermaid can parse correctly (e.g. ->> instead of -&gt;&gt;)
parsedHtml = parsedHtml.replace(/<div class="mermaid">([\s\S]*?)<\/div>/g, function(match, p1) {
let unescaped = p1.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'");
return '<div class="mermaid">' + unescaped + '</div>';
});
html += `<div class="text-block" style="font-size: 14px; line-height: 1.6;">${parsedHtml}</div>`; html += `<div class="text-block" style="font-size: 14px; line-height: 1.6;">${parsedHtml}</div>`;
} else { } else {
html += `<pre>${sec.rawContent}</pre>`; html += `<pre>${sec.rawContent}</pre>`;
...@@ -285,12 +291,35 @@ document.addEventListener('DOMContentLoaded', async () => { ...@@ -285,12 +291,35 @@ document.addEventListener('DOMContentLoaded', async () => {
recipeContent.onscroll = () => { recipeContent.onscroll = () => {
backToTop.style.display = recipeContent.scrollTop > 300 ? 'block' : 'none'; backToTop.style.display = recipeContent.scrollTop > 300 ? 'block' : 'none';
const scrollable = recipeContent.scrollHeight - recipeContent.clientHeight;
if (scrollable > 0) {
const scrolled = (recipeContent.scrollTop / scrollable) * 100;
const bar = document.getElementById('readingProgressBar');
if (bar) bar.style.width = `${scrolled}%`;
}
}; };
backToTop.onclick = () => { backToTop.onclick = () => {
recipeContent.scrollTo({ top: 0, behavior: 'smooth' }); recipeContent.scrollTo({ top: 0, behavior: 'smooth' });
}; };
window.copyAnchor = (id) => {
const url = new URL(window.location.href);
url.hash = id;
navigator.clipboard.writeText(url.toString());
const hashEl = document.querySelector(`#${id} .anchor-link`);
if (hashEl) {
const og = hashEl.textContent;
hashEl.textContent = '✓';
setTimeout(() => hashEl.textContent = og, 2000);
}
};
window.loadRecipeFromNav = (id, groupName, title) => {
loadRecipe(id, groupName, title);
};
recipeSearch.oninput = (e) => { recipeSearch.oninput = (e) => {
const term = e.target.value.toLowerCase(); const term = e.target.value.toLowerCase();
document.querySelectorAll('.recipe-item').forEach(el => { document.querySelectorAll('.recipe-item').forEach(el => {
...@@ -333,4 +362,4 @@ document.addEventListener('DOMContentLoaded', async () => { ...@@ -333,4 +362,4 @@ document.addEventListener('DOMContentLoaded', async () => {
const progressEl = document.getElementById('progressPercent'); const progressEl = document.getElementById('progressPercent');
if (progressEl) progressEl.textContent = `${percent}%`; if (progressEl) progressEl.textContent = `${percent}%`;
} }
}); });
{
"name": "chatbot-canifa-feedback",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"marked": "^18.0.3"
}
},
"node_modules/marked": {
"version": "18.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.3.tgz",
"integrity": "sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
}
}
}
{
"dependencies": {
"marked": "^18.0.3"
}
}
const marked = require('marked');
const html = marked.parse('```mermaid\nsequenceDiagram\n A->>B: Hello\n```');
let parsed = html.replace(/<code class="language-mermaid">([\s\S]*?)<\/code>/g, '<div class="mermaid">$1</div>');
parsed = parsed.replace(/<pre><div class="mermaid">([\s\S]*?)<\/div><\/pre>/g, '<div class="mermaid">$1</div>');
console.log('MARKDOWN OUTPUT:', html);
console.log('PARSED OUTPUT:', parsed);
const marked = require('marked');
const html = '<pre>\n<code class="language-mermaid">sequenceDiagram\n A->>B: Hello\n</code>\n</pre>';
let parsed = html.replace(/<code class="language-mermaid">([\s\S]*?)<\/code>/g, '<div class="mermaid">$1</div>');
parsed = parsed.replace(/<pre>\s*<div class="mermaid">([\s\S]*?)<\/div>\s*<\/pre>/g, '<div class="mermaid">$1</div>');
// also need to decode HTML entities!
const he = require('html-entities');
// wait, we can just write a custom decoding logic or decode standard ones
parsed = parsed.replace(/<div class="mermaid">([\s\S]*?)<\/div>/g, function(match, p1) {
let unescaped = p1.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'");
return '<div class="mermaid">' + unescaped + '</div>';
});
console.log('PARSED:', parsed);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment