Compare commits
2 Commits
655e324c88
...
69be84e5ab
Author | SHA1 | Date | |
---|---|---|---|
69be84e5ab | |||
84b2d233a7 |
10
package-lock.json
generated
10
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -14,5 +14,5 @@
|
||||
"hasMasterSlide": true
|
||||
}
|
||||
},
|
||||
"generated": "2025-08-22T01:40:33.956Z"
|
||||
"generated": "2025-08-22T02:19:52.970Z"
|
||||
}
|
@ -1,8 +1,24 @@
|
||||
<div class="fade-in layout-content-slide">
|
||||
<h1 class="slot title-slot" data-slot="title" data-placeholder="Slide Title" data-required>
|
||||
{{title}}
|
||||
</h1>
|
||||
<div id="code" class="slot content-area" data-slot="content" data-placeholder="Your content here..." data-multiline="true">
|
||||
|
||||
</div>
|
||||
<div class="slide layout-code-slide">
|
||||
<h1 class="slot title-slot"
|
||||
data-slot="title"
|
||||
data-type="title"
|
||||
data-placeholder="Code Example Title"
|
||||
data-required>
|
||||
{{title}}
|
||||
</h1>
|
||||
|
||||
<pre class="slot code-content"
|
||||
data-slot="code"
|
||||
data-type="code"
|
||||
data-language="javascript"
|
||||
data-placeholder="Enter your JavaScript code here..."
|
||||
data-multiline="true">{{code}}</pre>
|
||||
|
||||
<div class="slot notes-content"
|
||||
data-slot="notes"
|
||||
data-type="text"
|
||||
data-placeholder="Optional code explanation..."
|
||||
data-multiline="true">
|
||||
{{notes}}
|
||||
</div>
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -45,17 +45,32 @@ export const ContentEditor: React.FC<ContentEditorProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<textarea
|
||||
id={slot.id}
|
||||
value={slideContent[slot.id] || ''}
|
||||
onChange={(e) => onSlotContentChange(slot.id, e.target.value)}
|
||||
placeholder={slot.type === 'markdown'
|
||||
? slot.placeholder || 'Enter markdown content (e.g., **bold**, *italic*, - list items)'
|
||||
: slot.placeholder || `Enter ${slot.id}`}
|
||||
className={`field-textarea ${slot.type === 'markdown' ? 'markdown-field' : ''}`}
|
||||
rows={slot.type === 'markdown' ? 6 : 4}
|
||||
placeholder={getPlaceholder()}
|
||||
className={`field-textarea ${slot.type === 'code' ? 'code-field' : slot.type === 'markdown' ? 'markdown-field' : ''}`}
|
||||
rows={getRows()}
|
||||
style={slot.type === 'code' ? { fontFamily: 'var(--theme-font-code)' } : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { SlideLayout } from '../../types/theme.ts';
|
||||
import { renderSlideMarkdown, isMarkdownContent } from '../../utils/markdownProcessor.ts';
|
||||
import { highlightCode } from '../../utils/codeHighlighter.ts';
|
||||
|
||||
// Helper function to render template with actual content
|
||||
export const renderTemplateWithContent = (layout: SlideLayout, content: Record<string, string>): string => {
|
||||
@ -9,13 +10,20 @@ export const renderTemplateWithContent = (layout: SlideLayout, content: Record<s
|
||||
Object.entries(content).forEach(([slotId, value]) => {
|
||||
const placeholder = 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(value || ''));
|
||||
|
||||
const processedValue = shouldProcessAsMarkdown ?
|
||||
renderSlideMarkdown(value || '', slot?.type) : (value || '');
|
||||
let processedValue = value || '';
|
||||
|
||||
// Process based on slot type
|
||||
if (slot?.type === 'code') {
|
||||
// Handle code highlighting
|
||||
const language = slot.attributes?.['data-language'] || 'javascript';
|
||||
processedValue = highlightCode(value || '', language);
|
||||
} else if (slot?.type === 'markdown' || (slot?.type === 'text' && isMarkdownContent(value || ''))) {
|
||||
// Handle markdown processing
|
||||
processedValue = renderSlideMarkdown(value || '', slot?.type);
|
||||
}
|
||||
|
||||
rendered = rendered.replace(placeholder, processedValue);
|
||||
});
|
||||
|
64
src/utils/codeHighlighter.ts
Normal file
64
src/utils/codeHighlighter.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
|
||||
/**
|
||||
* Code syntax highlighter for slide content
|
||||
* Uses Highlight.js for reliable browser-based highlighting
|
||||
*/
|
||||
|
||||
// Register supported languages
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
|
||||
// Track which languages are available
|
||||
const SUPPORTED_LANGUAGES = ['javascript', 'js'];
|
||||
|
||||
/**
|
||||
* Highlights code with syntax highlighting
|
||||
* @param code - The code string to highlight
|
||||
* @param language - The programming language (optional)
|
||||
* @returns HTML string with syntax highlighting
|
||||
*/
|
||||
export const highlightCode = (code: string, language?: string): string => {
|
||||
if (!code || typeof code !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Normalize language names
|
||||
const normalizedLang = language?.toLowerCase();
|
||||
const targetLang = normalizedLang === 'js' ? 'javascript' : normalizedLang;
|
||||
|
||||
// Try to highlight if language is supported
|
||||
if (targetLang && SUPPORTED_LANGUAGES.includes(targetLang)) {
|
||||
try {
|
||||
const result = hljs.highlight(code, { language: targetLang });
|
||||
return result.value;
|
||||
} catch (error) {
|
||||
console.warn(`Syntax highlighting failed for ${targetLang}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to escaped plain text
|
||||
const escapedCode = code
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
return escapedCode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets list of supported languages
|
||||
*/
|
||||
export const getSupportedLanguages = (): string[] => {
|
||||
return [...SUPPORTED_LANGUAGES];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a language is supported for highlighting
|
||||
*/
|
||||
export const isLanguageSupported = (language?: string): boolean => {
|
||||
if (!language) return false;
|
||||
const normalizedLang = language.toLowerCase();
|
||||
const targetLang = normalizedLang === 'js' ? 'javascript' : normalizedLang;
|
||||
return SUPPORTED_LANGUAGES.includes(targetLang);
|
||||
};
|
@ -47,7 +47,9 @@ const DEFAULT_SLIDE_CONFIG: Required<SanitizeConfig> = {
|
||||
// Headings
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
// Quotes
|
||||
'blockquote', 'cite'
|
||||
'blockquote', 'cite',
|
||||
// Code elements
|
||||
'pre', 'code'
|
||||
],
|
||||
allowedAttributes: [
|
||||
'class', 'id', 'style', 'data-*'
|
||||
|
@ -8,12 +8,8 @@ import DOMPurify from 'dompurify';
|
||||
|
||||
// Configure marked for slide-safe markdown
|
||||
marked.setOptions({
|
||||
// Disable HTML rendering for security
|
||||
sanitize: false, // We'll handle sanitization with DOMPurify
|
||||
gfm: true, // GitHub Flavored Markdown
|
||||
breaks: true, // Convert line breaks to <br>
|
||||
headerIds: false, // Don't generate header IDs
|
||||
mangle: false, // Don't mangle email addresses
|
||||
});
|
||||
|
||||
/**
|
||||
@ -44,7 +40,9 @@ const PURIFY_CONFIG = {
|
||||
// Table attributes
|
||||
'colspan', 'rowspan',
|
||||
// Link attributes (but we'll filter URLs)
|
||||
'target', 'rel'
|
||||
'target', 'rel',
|
||||
// Code highlighting attributes
|
||||
'style' // Allow style attribute for syntax highlighting
|
||||
],
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'applet', 'form', 'input', 'button'],
|
||||
|
@ -64,9 +64,9 @@ const SAMPLE_CONTENT = {
|
||||
markdown: [
|
||||
SAMPLE_MARKDOWN_CONTENT.content,
|
||||
SAMPLE_MARKDOWN_CONTENT.list,
|
||||
SAMPLE_MARKDOWN_CONTENT.codeExample,
|
||||
SAMPLE_MARKDOWN_CONTENT.diagramExample,
|
||||
'## Key Points\n\n- **Important**: Focus on *customer needs*\n- Use `data-driven` decisions\n- > Success comes from teamwork'
|
||||
'## Key Points\n\n- **Important**: Focus on *customer needs*\n- Use `data-driven` decisions\n- > Success comes from teamwork',
|
||||
'### Implementation Steps\n\n1. **Analysis** - Review current metrics\n2. **Strategy** - Define clear objectives\n3. **Execution** - Deploy with *precision*\n\n> Remember: `quality` over quantity',
|
||||
'## Technical Overview\n\n- Modern **JavaScript** frameworks\n- *Responsive* design principles\n- `API-first` architecture\n\n**Next Steps**: Begin development phase'
|
||||
]
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user