diff --git a/package-lock.json b/package-lock.json
index 2a2a194..255a41d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@types/prismjs": "^1.26.5",
"dompurify": "^3.2.6",
+ "highlight.js": "^11.11.1",
"loglevel": "^1.9.2",
"marked": "^16.2.0",
"mermaid": "^11.10.0",
@@ -3421,6 +3422,15 @@
"node": ">=8"
}
},
+ "node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
diff --git a/package.json b/package.json
index ce2e6e2..2a9d14b 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@types/prismjs": "^1.26.5",
"dompurify": "^3.2.6",
+ "highlight.js": "^11.11.1",
"loglevel": "^1.9.2",
"marked": "^16.2.0",
"mermaid": "^11.10.0",
diff --git a/public/themes-manifest.json b/public/themes-manifest.json
index 9535be5..c9b9ae4 100644
--- a/public/themes-manifest.json
+++ b/public/themes-manifest.json
@@ -14,5 +14,5 @@
"hasMasterSlide": true
}
},
- "generated": "2025-08-22T01:40:33.956Z"
+ "generated": "2025-08-22T02:15:00.761Z"
}
\ No newline at end of file
diff --git a/public/themes/default/layouts/code-slide.html b/public/themes/default/layouts/code-slide.html
index d4945d9..fb71d0f 100644
--- a/public/themes/default/layouts/code-slide.html
+++ b/public/themes/default/layouts/code-slide.html
@@ -1,8 +1,24 @@
-
-
- {{title}}
-
-
-
-
+
+
+ {{title}}
+
+
+
{{code}}
+
+
+ {{notes}}
+
\ No newline at end of file
diff --git a/public/themes/default/style.css b/public/themes/default/style.css
index 3f017e9..48a3619 100644
--- a/public/themes/default/style.css
+++ b/public/themes/default/style.css
@@ -439,25 +439,302 @@
background: rgba(149, 116, 235, 0.1);
}
+/* JavaScript syntax highlighting with Prism.js */
+.language-javascript,
+.code-block,
+.markdown-content pre,
+.markdown-content code {
+ background: #1e1e1e;
+ border: 1px solid var(--theme-secondary);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin: 1.5em 0;
+ overflow-x: auto;
+ font-family: var(--theme-font-code);
+ font-size: clamp(0.75rem, 1.5vw, 0.9rem);
+ line-height: 1.4;
+ position: relative;
+ white-space: pre-wrap;
+ display: block;
+}
+
+.language-javascript code,
+.code-block code {
+ background: none;
+ padding: 0;
+ color: #e6e6e6;
+ font-family: inherit;
+}
+
+/* JavaScript-specific Prism tokens */
+.language-javascript .token.comment,
+.language-javascript .token.prolog,
+.language-javascript .token.doctype,
+.language-javascript .token.cdata,
+.markdown-content .token.comment,
+.markdown-content .token.prolog,
+.markdown-content .token.doctype,
+.markdown-content .token.cdata {
+ color: #6a9955;
+ font-style: italic;
+}
+
+.language-javascript .token.punctuation {
+ color: #d4d4d4;
+}
+
+.language-javascript .token.property,
+.language-javascript .token.tag,
+.language-javascript .token.boolean,
+.language-javascript .token.number,
+.language-javascript .token.constant,
+.language-javascript .token.symbol,
+.language-javascript .token.deleted {
+ color: #b5cea8;
+}
+
+.language-javascript .token.selector,
+.language-javascript .token.attr-name,
+.language-javascript .token.string,
+.language-javascript .token.char,
+.language-javascript .token.builtin,
+.language-javascript .token.inserted {
+ color: #ce9178;
+}
+
+.language-javascript .token.operator,
+.language-javascript .token.entity,
+.language-javascript .token.url {
+ color: #d4d4d4;
+}
+
+.language-javascript .token.atrule,
+.language-javascript .token.attr-value,
+.language-javascript .token.keyword {
+ color: #569cd6;
+}
+
+.language-javascript .token.function,
+.language-javascript .token.class-name {
+ color: #dcdcaa;
+}
+
+.language-javascript .token.regex,
+.language-javascript .token.important,
+.language-javascript .token.variable {
+ color: #d16969;
+}
+
+.language-javascript .token.important,
+.language-javascript .token.bold {
+ font-weight: bold;
+}
+
+.language-javascript .token.italic {
+ font-style: italic;
+}
+
+/* Universal token styles for markdown content */
+.markdown-content .token.punctuation { color: #d4d4d4; }
+
+.markdown-content .token.property,
+.markdown-content .token.tag,
+.markdown-content .token.boolean,
+.markdown-content .token.number,
+.markdown-content .token.constant,
+.markdown-content .token.symbol,
+.markdown-content .token.deleted { color: #b5cea8; }
+
+.markdown-content .token.selector,
+.markdown-content .token.attr-name,
+.markdown-content .token.string,
+.markdown-content .token.char,
+.markdown-content .token.builtin,
+.markdown-content .token.inserted { color: #ce9178; }
+
+.markdown-content .token.operator,
+.markdown-content .token.entity,
+.markdown-content .token.url { color: #d4d4d4; }
+
+.markdown-content .token.atrule,
+.markdown-content .token.attr-value,
+.markdown-content .token.keyword { color: #569cd6; }
+
+.markdown-content .token.function,
+.markdown-content .token.class-name { color: #dcdcaa; }
+
+.markdown-content .token.regex,
+.markdown-content .token.important,
+.markdown-content .token.variable { color: #d16969; }
+
+.markdown-content .token.parameter { color: #9cdcfe; }
+
+.markdown-content .token.important,
+.markdown-content .token.bold { font-weight: bold; }
+
+.markdown-content .token.italic { font-style: italic; }
+
+/* Language label */
+.language-javascript::before {
+ content: 'JavaScript';
+ position: absolute;
+ top: 0.5rem;
+ right: 1rem;
+ font-size: 0.75rem;
+ color: var(--theme-text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ opacity: 0.7;
+}
+
+/* Code slide layout */
+.layout-code-slide,
+.slide-container .layout-code-slide {
+ justify-content: flex-start;
+ align-items: stretch;
+ text-align: left;
+ padding: 2rem;
+}
+
+.layout-code-slide .slot[data-slot="title"] {
+ font-size: clamp(1.5rem, 4vw, 2rem);
+ margin-bottom: 1.5rem;
+ text-align: center;
+ color: var(--theme-text);
+}
+
+.layout-code-slide .slot[data-slot="code"] {
+ flex: 1;
+ margin-bottom: 1rem;
+}
+
+.layout-code-slide .slot[data-slot="notes"] {
+ font-size: clamp(0.9rem, 2vw, 1rem);
+ color: var(--theme-text-secondary);
+ line-height: 1.5;
+ text-align: left;
+}
+
+/* Code slot styling - now targets the pre element */
+pre.slot[data-type="code"],
+pre.code-content {
+ background: #1e1e1e !important;
+ border: 1px solid var(--theme-secondary);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin: 0;
+ overflow-x: auto;
+ font-family: var(--theme-font-code);
+ font-size: clamp(0.75rem, 1.5vw, 0.9rem);
+ line-height: 1.4;
+ color: #e6e6e6;
+ position: relative;
+ white-space: pre !important;
+ display: block !important;
+ text-align: left !important;
+}
+
+/* Override any inherited slot styles for code elements */
+pre.slot[data-type="code"] * {
+ white-space: inherit;
+ display: inline;
+}
+
+/* Ensure spans from highlighting don't break lines */
+pre.slot[data-type="code"] .hljs-keyword,
+pre.slot[data-type="code"] .hljs-string,
+pre.slot[data-type="code"] .hljs-number,
+pre.slot[data-type="code"] .hljs-comment,
+pre.slot[data-type="code"] .hljs-function,
+pre.slot[data-type="code"] .hljs-variable,
+pre.slot[data-type="code"] .hljs-punctuation,
+pre.slot[data-type="code"] .hljs-operator,
+pre.slot[data-type="code"] span {
+ display: inline !important;
+ white-space: inherit !important;
+}
+
+pre.slot[data-type="code"]::before,
+pre.code-content::before {
+ content: 'JavaScript';
+ position: absolute;
+ top: 0.5rem;
+ right: 1rem;
+ font-size: 0.75rem;
+ color: var(--theme-text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ opacity: 0.7;
+ font-family: var(--theme-font-body);
+}
+
+/* Highlight.js token colors (VS Code Dark theme) */
+.hljs-keyword,
+.hljs-built_in,
+.hljs-type,
+.hljs-literal { color: #569cd6; }
+
+.hljs-string,
+.hljs-regexp { color: #ce9178; }
+
+.hljs-number { color: #b5cea8; }
+
+.hljs-comment {
+ color: #6a9955;
+ font-style: italic;
+}
+
+.hljs-function .hljs-title,
+.hljs-title.function_ { color: #dcdcaa; }
+
+.hljs-variable,
+.hljs-params { color: #9cdcfe; }
+
+.hljs-punctuation,
+.hljs-operator { color: #d4d4d4; }
+
+.hljs-attr,
+.hljs-property { color: #92c5f7; }
+
+.hljs-tag,
+.hljs-name { color: #569cd6; }
+
+.hljs-attribute { color: #9cdcfe; }
+
+/* Code editor textarea styling */
+.field-textarea.code-field {
+ font-family: var(--theme-font-code);
+ font-size: 0.9rem;
+ line-height: 1.4;
+ background: #1a1a1a;
+ color: #e6e6e6;
+ border: 1px solid var(--theme-secondary);
+ border-radius: 4px;
+ padding: 0.75rem;
+ white-space: pre;
+ tab-size: 2;
+}
+
+.field-textarea.code-field::placeholder {
+ color: #6a6a6a;
+ font-style: italic;
+}
+
/* Responsive adjustments for enhanced features */
@media (max-width: 768px) {
- .slide-code {
+ .layout-code-slide {
+ padding: 1rem;
+ }
+
+ .hljs-code,
+ .hljs-code pre,
+ .hljs-code code {
padding: 1rem;
font-size: 0.8rem;
}
- .slide-code::before {
+ .hljs-code::before {
font-size: 0.7rem;
}
-
- .mermaid-container {
- padding: 0.5rem;
- margin: 1rem 0;
- }
-
- .slide-callout {
- padding: 0.75rem 1rem;
- margin: 1rem 0;
- }
}
diff --git a/src/components/presentations/PresentationMode.tsx b/src/components/presentations/PresentationMode.tsx
index 4abfd96..fcf7d7a 100644
--- a/src/components/presentations/PresentationMode.tsx
+++ b/src/components/presentations/PresentationMode.tsx
@@ -4,6 +4,7 @@ import { useParams, useNavigate } from 'react-router-dom';
import { renderTemplateWithSampleData } from '../../utils/templateRenderer.ts';
import { sanitizeSlideTemplate } from '../../utils/htmlSanitizer.ts';
import { renderSlideMarkdown, isMarkdownContent } from '../../utils/markdownProcessor.ts';
+import { highlightCode } from '../../utils/codeHighlighter.ts';
import { loggers } from '../../utils/logger.ts';
import './PresentationMode.css';
import {usePresentationLoader} from "./hooks/usePresentationLoader.ts";
@@ -66,13 +67,20 @@ export const PresentationMode: React.FC = () => {
Object.entries(slide.content).forEach(([slotId, content]) => {
const regex = new RegExp(`\\{\\{${slotId}\\}\\}`, 'g');
- // Find the corresponding slot to determine if it should be processed as markdown
+ // Find the corresponding slot to determine processing type
const slot = layout.slots.find(s => s.id === slotId);
- const shouldProcessAsMarkdown = slot?.type === 'markdown' ||
- (slot?.type === 'text' && isMarkdownContent(content));
- const processedContent = shouldProcessAsMarkdown ?
- renderSlideMarkdown(content, slot?.type) : content;
+ let processedContent = content;
+
+ // Process based on slot type
+ if (slot?.type === 'code') {
+ // Handle code highlighting
+ const language = slot.attributes?.['data-language'] || 'javascript';
+ processedContent = highlightCode(content, language);
+ } else if (slot?.type === 'markdown' || (slot?.type === 'text' && isMarkdownContent(content))) {
+ // Handle markdown processing
+ processedContent = renderSlideMarkdown(content, slot?.type);
+ }
renderedTemplate = renderedTemplate.replace(regex, processedContent);
});
diff --git a/src/components/slide-editor/ContentEditor.tsx b/src/components/slide-editor/ContentEditor.tsx
index dc26602..7ac6eb6 100644
--- a/src/components/slide-editor/ContentEditor.tsx
+++ b/src/components/slide-editor/ContentEditor.tsx
@@ -45,17 +45,32 @@ export const ContentEditor: React.FC
= ({
);
}
- if (slot.type === 'markdown' || (slot.type === 'text' && slot.id.includes('content'))) {
+ if (slot.type === 'code' || slot.type === 'markdown' || (slot.type === 'text' && slot.id.includes('content'))) {
+ const getPlaceholder = () => {
+ if (slot.type === 'code') {
+ return slot.placeholder || 'Enter your JavaScript code here...';
+ }
+ if (slot.type === 'markdown') {
+ return slot.placeholder || 'Enter markdown content (e.g., **bold**, *italic*, - list items)';
+ }
+ return slot.placeholder || `Enter ${slot.id}`;
+ };
+
+ const getRows = () => {
+ if (slot.type === 'code') return 8;
+ if (slot.type === 'markdown') return 6;
+ return 4;
+ };
+
return (