This commit is contained in:
Saifeddine ALOUI 2024-09-05 03:00:22 +02:00
parent 7a0290d6b6
commit cc12ebe0c2
4 changed files with 516 additions and 76 deletions

View File

@ -36,9 +36,13 @@ Include the following in your HTML file:
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-python.min.js"></script>
<!-- needed for math rendering in markdown -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js"></script>
<!-- MarkdownRenderer -->
<script src="/lollms_assets/js/lollms_markdown_renderer"></script>
<link rel="stylesheet" href="/lollms_assets/css/lollms_markdown_renderer">
<link rel="stylesheet" href="/lollms_assets/css/lollms_styles">
</head>
<body>
<div id="markdown-content"></div>

View File

@ -30,6 +30,9 @@
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-java.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-latex.min.js"></script>
// When served with lollms, just use <script src="/lollms_assets/js/lollms_markdown_renderer"></script>
// <!-- Render math -->
// <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css">
// <script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js"></script>
// Don't forget to get the css too <link rel="stylesheet" href="/lollms_assets/css/lollms_markdown_renderer">
// Make sure there is a global variable called mr that instanciate MarkdownRenderer
@ -37,7 +40,7 @@
class MarkdownRenderer {
async renderMermaidDiagrams(text) {
async renderMermaidDiagrams(text) {
const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g;
const matches = text.match(mermaidCodeRegex);
@ -91,7 +94,7 @@ class MarkdownRenderer {
return text;
}
async renderSVG(text) {
async renderSVG(text) {
const svgCodeRegex = /```svg\n([\s\S]*?)```/g;
const matches = text.match(svgCodeRegex);
@ -144,51 +147,61 @@ async renderSVG(text) {
return text;
}
async renderCodeBlocks(text) {
if (typeof Prism === 'undefined') {
throw new Error('Prism is not loaded. Please include Prism.js in your project.');
}
renderCodeBlocks(text) {
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
return text.replace(codeBlockRegex, (match, language, code) => {
language = language || 'plaintext';
let highlightedCode;
try {
highlightedCode = hljs.highlight(code.trim(), { language: language }).value;
} catch (error) {
console.warn(`Language '${language}' is not supported by highlight.js. Falling back to plaintext.`);
highlightedCode = hljs.highlight(code.trim(), { language: 'plaintext' }).value;
}
const lines = highlightedCode.split('\n');
const numberedLines = lines.map((line, index) =>
`<div class="code-line">
<span class="line-number">${(index + 1).toString().padStart(2, '0')}</span>
<span class="line-content">${line}</span>
</div>`
).join('');
return `
<div class="code-block">
<div class="code-header">
<span class="language">${language}</span>
<button onclick="mr.copyCode(this)" class="copy-button">Copy</button>
</div>
<pre class="code-content"><code class="hljs language-${language}">${numberedLines}</code></pre>
</div>
`;
});
}
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
copyCode(button) {
const codeBlock = button.closest('.code-block');
const codeLines = codeBlock.querySelectorAll('.line-content');
const codeText = Array.from(codeLines).map(line => line.textContent).join('\n');
const renderedText = await text.replace(codeBlockRegex, (match, language, code) => {
language = language || 'plaintext';
navigator.clipboard.writeText(codeText).then(() => {
button.textContent = 'Copied!';
setTimeout(() => {
button.textContent = 'Copy';
}, 2000);
}).catch(err => {
console.error('Failed to copy text: ', err);
button.textContent = 'Failed';
setTimeout(() => {
button.textContent = 'Copy';
}, 2000);
});
}
if (!Prism.languages[language]) {
console.warn(`Language '${language}' is not supported by Prism. Falling back to plaintext.`);
language = 'plaintext';
}
const highlightedCode = Prism.highlight(code.trim(), Prism.languages[language], language);
const lines = highlightedCode.split(/\r?\n/);
const numberedLines = lines.map((line, index) =>
`<span class="code-line"><span class="line-number">${index + 1}</span><span class="line-content">${line}</span></span>`
).join('\n');
return `
<div class="code-block-wrapper bg-gray-100 rounded-lg shadow-md overflow-hidden my-4">
<div class="code-block-header bg-gray-200 px-4 py-2 flex justify-between items-center">
<div class="language-label font-semibold text-gray-700">${language}</div>
<button class="copy-button bg-blue-500 hover:bg-blue-600 text-white font-bold py-1 px-3 rounded" onclick="mr.copyCode(this)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
</button>
</div>
<div class="code-content max-h-[500px] overflow-auto">
<pre class="line-numbers text-sm leading-tight"><code class="language-${language}">${numberedLines}</code></pre>
</div>
</div>`;
});
return renderedText;
}
handleInlineCode(text) {
return text.replace(/`([^`]+)`/g, function(match, code) {
return `<b>${code}</b>`;
@ -196,11 +209,29 @@ async renderSVG(text) {
}
handleMathEquations(text) {
return text.replace(/\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^\n]+?)\$/g, function(match, p1, p2, p3) {
const equation = p1 || p2 || p3;
return '<span class="math">' + equation + '</span>';
});
if (typeof katex === 'undefined') {
console.error('KaTeX is not loaded. Make sure to include KaTeX scripts and CSS.');
return text;
}
return text.replace(/\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^\n]+?)\$/g, function(match, p1, p2, p3) {
const equation = p1 || p2 || p3;
const isDisplayMode = match.startsWith('\\[') || match.startsWith('$$');
try {
return katex.renderToString(equation, {
displayMode: isDisplayMode,
throwOnError: false,
output: 'html'
});
} catch (e) {
console.error("KaTeX rendering error:", e);
return `<span class="math-error">${match}</span>`; // Return error-marked original string if rendering fails
}
});
}
async handleTables(text) {
let alignments = [];
@ -298,6 +329,35 @@ async renderSVG(text) {
return text;
}
initMathJax() {
// Configure MathJax
window.MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$'], ['\\[', '\\]']]
},
svg: {
fontCache: 'global'
},
startup: {
ready: () => {
MathJax.startup.defaultReady();
MathJax.startup.promise.then(() => {
console.log('MathJax is loaded and ready');
// You can add any post-initialization logic here
});
}
}
};
// Load MathJax
if (!window.MathJax) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';
script.async = true;
document.head.appendChild(script);
}
}
async renderMarkdown(text) {
// Handle Mermaid graphs first
text = await this.renderMermaidDiagrams(text);
@ -413,35 +473,7 @@ async renderSVG(text) {
svg.style.transform = `scale(${newScale})`;
}
copyCode(button) {
const codeBlock = button.closest('.code-block-wrapper').querySelector('code');
const codeLines = codeBlock.querySelectorAll('.code-line');
let codeText = '';
codeLines.forEach((line) => {
const lineNumber = line.querySelector('.line-number').textContent;
const lineContent = line.querySelector('.line-content').textContent;
codeText += `${lineNumber} ${lineContent}\n`;
});
navigator.clipboard.writeText(codeText.trim()).then(() => {
button.classList.add('copied');
button.querySelector('svg').style.display = 'none';
button.innerHTML = 'Copied!';
setTimeout(() => {
button.classList.remove('copied');
button.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
`;
}, 2000);
}).catch(err => {
console.error('Failed to copy text: ', err);
});
}
async highlightCode(code, language) {
// Make sure the language is supported by your highlighting library
const supportedLanguage = Prism.languages[language] ? language : 'plaintext';

View File

@ -234,3 +234,68 @@ display: inline;
.code-line:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.math-display {
display: block;
margin: 1em 0;
text-align: center;
}
.math-inline {
display: inline-block;
vertical-align: middle;
}
.code-block {
background-color: #f4f4f4;
border: 1px solid #ddd;
border-radius: 4px;
margin: 1em 0;
font-family: monospace;
font-size: 14px;
line-height: 1.4;
}
.code-header {
background-color: #e0e0e0;
padding: 0.5em;
display: flex;
justify-content: space-between;
align-items: center;
}
.copy-button {
background-color: #4CAF50;
border: none;
color: white;
padding: 5px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
cursor: pointer;
border-radius: 3px;
}
.code-content {
padding: 0.5em 0;
overflow-x: auto;
white-space: pre;
}
.code-line {
display: flex;
padding: 0 0.5em;
}
.line-number {
color: #999;
text-align: right;
padding-right: 1em;
user-select: none;
min-width: 2em;
}
.line-content {
flex: 1;
}

View File

@ -0,0 +1,339 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
@apply scroll-smooth;
}
@font-face {
font-family: 'Roboto';
src: url('./fonts/Roboto/Roboto-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'PTSans';
src: url('./fonts/PTSans/PTSans-Regular.ttf') format('truetype');
}
}
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
.display-none {
@apply hidden;
}
h1 {
@apply text-5xl md:text-6xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-blue-700 dark:from-blue-400 dark:to-blue-500;
}
h2 {
@apply text-3xl font-semibold text-gray-800 dark:text-gray-200;
}
h3 {
@apply text-2xl font-semibold text-gray-700 dark:text-gray-300;
}
h4 {
@apply text-xl font-semibold italic text-gray-600 dark:text-gray-400;
}
p {
@apply text-base text-gray-600 dark:text-gray-300 break-words;
}
ul {
@apply list-disc ml-0;
}
li {
@apply list-disc ml-5;
}
ol {
@apply list-decimal ml-5;
}
:root {
--color-primary: #0e8ef0;
--color-primary-light: #3dabff;
--color-secondary: #0fd974;
--color-accent: #f0700e;
--color-light-text-panel: #ffffff;
--color-dark-text-panel: #ffffff;
--color-bg-light-panel: #7cb5ec;
--color-bg-light: #e2edff;
--color-bg-light-tone: #b9d2f7;
--color-bg-light-code-block: #cad7ed;
--color-bg-light-tone-panel: #8fb5ef;
--color-bg-light-discussion: #c5d8f8;
--color-bg-light-discussion-odd: #d6e7ff;
--color-bg-dark: #132e59;
--color-bg-dark-tone: #25477d;
--color-bg-dark-tone-panel: #4367a3;
--color-bg-dark-code-block: #2254a7;
--color-bg-dark-discussion: #435E8A;
--color-bg-dark-discussion-odd: #284471;
}
/* For both textarea and input elements */
textarea, input, select {
@apply bg-gray-100 dark:bg-gray-800;
}
.background-color {
@apply bg-gradient-to-br from-blue-100 to-blue-200 dark:from-blue-900 dark:to-blue-900 min-h-screen;
}
.toolbar-color {
@apply text-gray-700 dark:text-gray-50 bg-gradient-to-r from-blue-200 to-purple-200 dark:from-blue-800 dark:to-purple-800 rounded-full shadow-lg
}
.panels-color {
@apply text-gray-700 dark:text-gray-50 bg-gradient-to-r from-blue-100 to-blue-200 dark:from-blue-800 dark:to-blue-900 rounded shadow-lg;
}
.unicolor-panels-color {
@apply bg-blue-200 dark:bg-blue-800
}
.chatbox-color {
@apply bg-gradient-to-br from-blue-200 to-blue-300 dark:from-blue-800 dark:to-blue-900
}
.message {
@apply relative w-full rounded-lg m-2 shadow-lg border-2 border-transparent
flex flex-col flex-grow flex-wrap overflow-visible p-4 pb-2;
}
.message:hover {
@apply border-primary border-solid;
}
/* Light theme */
.message:nth-child(even) {
@apply bg-gradient-to-br from-blue-200 to-blue-300 dark:from-blue-800 dark:to-blue-800;
}
.message:nth-child(odd) {
@apply bg-gradient-to-br from-blue-300 to-blue-400 dark:from-blue-800 dark:to-blue-900;
}
.discussion{
@apply mr-2 bg-gradient-to-r from-blue-300 to-blue-400 dark:from-blue-800 dark:to-blue-900 hover:from-blue-100 hover:to-purple-100 hover:dark:from-blue-700 hover:dark:to-purple-700
}
.discussion-hilighted{
@apply bg-gradient-to-r from-blue-200 to-purple-300 dark:from-blue-800 dark:to-purple-900 hover:from-blue-100 hover:to-purple-100 hover:dark:from-blue-700 hover:dark:to-purple-700
}
body {
@apply bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900 dark:to-purple-900 min-h-screen;
}
.bg-gradient-welcome {
@apply bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900 dark:to-purple-900;
}
.bg-gradient-progress {
@apply bg-gradient-to-r from-blue-200 to-purple-200 dark:from-blue-800 dark:to-purple-800;
}
.text-gradient-title {
@apply text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600 dark:from-blue-400 dark:to-purple-400;
}
.text-subtitle {
@apply text-gray-600 dark:text-gray-300;
}
.text-author {
@apply text-gray-500 dark:text-gray-400;
}
.text-loading {
@apply text-gray-700 dark:text-gray-300;
}
.text-progress {
@apply text-blue-600 dark:text-blue-400;
}
.btn-primary {
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded;
}
.btn-secondary {
@apply bg-purple-500 hover:bg-purple-600 text-white font-bold py-2 px-4 rounded;
}
.card {
@apply bg-white dark:bg-gray-800 rounded-lg shadow-md p-6;
}
.input {
@apply bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400;
}
.label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1;
}
.link {
@apply text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300;
}
.navbar-container {
@apply text-gray-700 dark:text-gray-50 bg-gradient-to-r from-blue-200 to-blue-300 dark:from-blue-800 dark:to-blue-900 rounded shadow-lg
}
.game-menu {
@apply flex justify-center items-center relative;
}
.text-shadow-custom {
text-shadow: 1px 1px 0px white, -1px -1px 0px white, 1px -1px 0px white, -1px 1px 0px white;
}
.menu-item {
@apply mb-2 px-4 py-2 text-red-600 dark:text-red-300 font-bold text-lg transition-all duration-300 ease-in-out;
@apply hover:text-gray-500 hover:dark:text-gray-50 hover:transform hover:-translate-y-1;
}
.menu-item.active-link {
@apply rounded-t-md border-red-500 text-shadow-custom text-red-600 font-bold text-lg transition-all duration-300 ease-in-out scale-105;
@apply hover:text-red-600 hover:dark:text-gray-50 hover:transform hover:-translate-y-1;
/* Glow effect on text */
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.menu-item.active-link::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 100%;
height: 5px;
/* Lightsaber colors */
z-index: 10000;
background: linear-gradient(to right, #00ff00, #00ff00, #00ff00); /* Normal mode */
border-radius: 10px;
animation: lightsaber 2s infinite;
}
.dark .menu-item.active-link::after {
background: linear-gradient(to right, #ff0000, #ff0000, #ff0000); /* Dark mode */
}
@keyframes lightsaber {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.app-card {
@apply transition-all duration-300 ease-in-out bg-gradient-to-br from-blue-200 to-blue-300 dark:from-blue-800 dark:to-blue-900 text-gray-800 dark:text-gray-100 shadow-md hover:shadow-lg;
}
.app-card:hover {
@apply transform -translate-y-1;
}
button {
@apply transition-all duration-300 ease-in-out;
}
button:hover {
@apply transform -translate-y-0.5;
}
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: theme('colors.blue.300') theme('colors.blue.100');
}
.dark .scrollbar-thin {
scrollbar-color: theme('colors.blue.700') theme('colors.blue.900');
}
.scrollbar-thin::-webkit-scrollbar {
@apply w-2;
}
.scrollbar-thin::-webkit-scrollbar-track {
@apply bg-blue-100 dark:bg-blue-900 rounded-full;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
@apply bg-blue-300 dark:bg-blue-700 rounded-full;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
@apply bg-blue-400 dark:bg-blue-600;
}
.btn {
@apply font-semibold py-2 px-4 rounded-lg transition-all duration-300 ease-in-out shadow-md flex items-center;
}
.btn-primary {
@apply bg-blue-500 text-white hover:bg-blue-600 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800;
}
.btn-secondary {
@apply bg-blue-200 text-gray-700 hover:bg-blue-300 focus:ring-4 focus:ring-blue-200 dark:bg-blue-700 dark:text-gray-200 dark:hover:bg-blue-600 dark:focus:ring-blue-600;
}
.search-input {
@apply w-full border-b-2 border-blue-200 dark:border-blue-700 py-2 px-4 pl-10 transition-colors duration-300 ease-in-out focus:outline-none focus:border-blue-500 dark:focus:border-blue-400 bg-transparent text-gray-800 dark:text-gray-100;
}
.scrollbar {
@apply scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary
}
.card-title {
@apply text-xl font-bold text-gray-900 dark:text-white mb-2;
}
.card-content {
@apply text-gray-700 dark:text-gray-300;
}
.card-footer {
@apply mt-4 flex justify-between items-center;
}
.card-footer-button {
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded;
}
/* Subcard styles */
.subcard {
@apply bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md p-4;
}
.subcard-title {
@apply text-lg font-bold text-gray-900 dark:text-white mb-2;
}
.subcard-content {
@apply text-gray-700 dark:text-gray-300;
}
.subcard-footer {
@apply mt-4 flex justify-between items-center;
}
.subcard-footer-button {
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded;
}