Skip to content

Commit bd5a3d9

Browse files
committed
FIX indent list: Verbesserung der Listenverarbeitung im Markdown-Parser zur Unterstützung von hierarchischen Einrückungen und korrektem HTML-Ausgabeformat. Anpassungen an der CSS-Datei für präzisere Listenstile und Abstände.
1 parent a4ee7b9 commit bd5a3d9

2 files changed

Lines changed: 195 additions & 42 deletions

File tree

css/markdown-styles.css

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,29 +185,42 @@ code {
185185
/* Lists mit hierarchischer Einrückung und rundem Block-Stil */
186186
.markdown-content ul,
187187
.markdown-content ol {
188-
padding: var(--spacing-md);
188+
/* padding: var(--spacing-md); */
189+
/* Entfernt für präzisere padding-left Steuerung */
190+
padding-left: 2em;
191+
/* Basiseinrückung für Listen, Platz für Marker + Text */
189192
margin: 0;
190193
background-color: rgba(255, 255, 255, 0.7);
191194
border-radius: var(--border-radius-md);
192195
border: 1px solid var(--border-color);
193196
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
194197
overflow: hidden;
198+
/* Beibehalten, falls Inhalt doch mal überläuft */
199+
list-style-position: outside;
200+
/* Sicherstellen, dass Marker außerhalb des Textflusses sind */
195201
}
196202

197203
/* Einrückung für Listen basierend auf vorhergehender Überschrift */
204+
/* Einrückung für Listen basierend auf vorhergehender Überschrift */
205+
/* Ziel: Liste (Außenkante) beginnt bündig mit der Überschrift (Außenkante) */
206+
.markdown-content h1+ul,
207+
.markdown-content h1+ol,
198208
.markdown-content h2+ul,
199-
.markdown-content h2+ol {
200-
margin-left: 20px;
201-
}
202-
209+
.markdown-content h2+ol,
203210
.markdown-content h3+ul,
204-
.markdown-content h3+ol {
205-
margin-left: 40px;
206-
}
207-
211+
.markdown-content h3+ol,
208212
.markdown-content h4+ul,
209-
.markdown-content h4+ol {
210-
margin-left: 60px;
213+
.markdown-content h4+ol,
214+
.markdown-content h5+ul,
215+
.markdown-content h5+ol,
216+
.markdown-content h6+ul,
217+
.markdown-content h6+ol {
218+
margin-left: 0;
219+
/* Liste bündig mit dem Überschriftenblock */
220+
margin-top: var(--spacing-sm);
221+
/* Etwas Abstand zur Überschrift oben */
222+
margin-bottom: var(--spacing-sm);
223+
/* Etwas Abstand zum nächsten Element unten */
211224
}
212225

213226
/* Verschachtelte Listen sollten zusätzliche Einrückung haben */
@@ -216,6 +229,9 @@ code {
216229
.markdown-content ul ol,
217230
.markdown-content ol ul {
218231
margin-left: 20px;
232+
/* Einrückung für verschachtelte Listen */
233+
padding-left: 0;
234+
/* Reset padding-left, da margin-left die Einrückung steuert und die Elternliste bereits padding hat */
219235
border-left: 2px solid var(--primary-color-light);
220236
border-radius: var(--border-radius-sm);
221237
margin-top: var(--spacing-sm);
@@ -227,6 +243,9 @@ code {
227243
.markdown-content ol li {
228244
margin-bottom: var(--spacing-sm);
229245
position: relative;
246+
/* Wichtig für die absolute Positionierung von :before */
247+
padding-left: 0;
248+
/* Standard-Padding für li zurücksetzen, Einrückung kommt von ul/ol und spezifischem ul li */
230249
}
231250

232251
.markdown-content ul li:last-child,
@@ -237,21 +256,39 @@ code {
237256
/* Spezielle Stile für ungeordnete Listen */
238257
.markdown-content ul {
239258
list-style-type: none;
259+
/* Benutzerdefinierte Marker über :before */
260+
}
261+
262+
.markdown-content ul li {
263+
padding-left: 0;
264+
/* Text beginnt bündig mit ol li Text (nach ul Padding) */
265+
/* Platz für das :before Element und Textabstand wird durch ::before Positionierung gehandhabt */
240266
}
241267

242268
.markdown-content ul li:before {
243269
content: "•";
244270
color: var(--primary-color);
245271
font-weight: bold;
246-
display: inline-block;
247-
width: 1em;
248-
margin-left: -1em;
272+
position: absolute;
273+
left: -1.2em;
274+
/* Zieht den Marker links vom Textanfang in den ul-Padding-Bereich */
275+
/* Positioniert den Marker am Anfang des li (vor dem padding-left des li-Textes) */
276+
top: 0.05em;
277+
/* Feinanpassung für vertikale Ausrichtung, abhängig von Schriftart/line-height */
278+
/* width: 1em; */
279+
/* Breite kann oft weggelassen werden, wenn Marker ein einzelnes Zeichen ist */
249280
}
250281

251-
.markdown-content li {
252-
margin-bottom: 0.2rem;
282+
/* Für ol Listen wird die Standardnummerierung verwendet, `list-style-position: outside` und `padding-left` auf `ol` kümmern sich darum. */
283+
.markdown-content ol {
284+
list-style-type: decimal;
285+
/* Oder andere gewünschte Nummerierung */
253286
}
254287

288+
/* Die generische .markdown-content li Regel (Zeile 253 im Original) wird entfernt,
289+
da sie mit der spezifischeren .markdown-content ul li, .markdown-content ol li Regel für margin-bottom kollidiert.
290+
Die spezifischeren Regeln mit var(--spacing-sm) sind konsistenter. */
291+
255292
/* Blockquotes */
256293
.markdown-content blockquote {
257294
border-left: 4px solid var(--primary-color);

js/markdown-parser.js

Lines changed: 142 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function parseMarkdown(markdown) {
8787
// 3. Parse Tables
8888
const tableRegex = /^\s*\|(.+)\|\s*$\n^\s*\|(?:\s*[-:]+[-\s|:]*)+\|\s*$\n((?:^\s*\|.+\|\s*$\n?)+)/gm;
8989
processedMarkdown = processedMarkdown.replace(tableRegex, (match) => {
90-
try { // Keep existing table parsing logic
90+
try { // Keep existing table parsing logic
9191
const lines = match.trim().split('\n');
9292
if (lines.length < 3) return match;
9393
const headerLine = lines[0].trim();
@@ -153,53 +153,169 @@ function parseMarkdown(markdown) {
153153
}
154154
return `<a href="${url}" target="_blank">${text}</a>`;
155155
});
156-
// Lists
157-
processedMarkdown = processedMarkdown.replace(/^\s*-\s+(.*$)/gm, '<li class="ul-item">$1</li>'); // Added space after -
158-
processedMarkdown = processedMarkdown.replace(/^\s*\d+\.\s+(.*$)/gm, '<li class="ol-item">$1</li>'); // Added space after .
159-
// Wrap consecutive list items (handle potential empty lines between items)
160-
processedMarkdown = processedMarkdown.replace(/(?:<li class="ul-item">.*?<\/li>\s*)+/g, (match) => `<ul>${match.replace(/\s*$/, '')}</ul>`);
161-
processedMarkdown = processedMarkdown.replace(/(?:<li class="ol-item">.*?<\/li>\s*)+/g, (match) => `<ol>${match.replace(/\s*$/, '')}</ol>`);
162-
processedMarkdown = processedMarkdown.replace(/class="ul-item"/g, '');
163-
processedMarkdown = processedMarkdown.replace(/class="ol-item"/g, '');
164-
// Emphasis
156+
// Emphasis (moved before list processing for simplicity, can be refined if needed)
165157
processedMarkdown = processedMarkdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
166158
processedMarkdown = processedMarkdown.replace(/\*(.*?)\*/g, '<em>$1</em>');
167159

168-
// 5. Replace Code Block Placeholders
160+
// 5. Advanced List Processing
161+
function processListSegment(segment) {
162+
const lines = segment.split('\n');
163+
let html = '';
164+
const listStack = []; // { type: 'ul' | 'ol', indent: number }
165+
166+
for (const line of lines) {
167+
const trimmedLine = line.trim();
168+
if (trimmedLine === '') { // Preserve intentional empty lines within list processing logic
169+
if (listStack.length > 0) html += '\n'; // Could be between items or part of multi-line item
170+
continue;
171+
}
172+
173+
const itemRegex = /^(\s*)([-*+]|\d+\.)\s+(.*)/;
174+
const match = line.match(itemRegex); // Match on original line to get correct indentation
175+
176+
if (match) {
177+
const indentLength = match[1].length;
178+
const bulletOrOrder = match[2];
179+
let itemContent = match[3]; // This is the rest of the line
180+
181+
const listType = isNaN(parseInt(bulletOrOrder, 10)) ? 'ul' : 'ol';
182+
183+
// Close nested lists if current indent is less
184+
while (listStack.length > 0 && indentLength < listStack[listStack.length - 1].indent) {
185+
html += `</${listStack.pop().type}>\n</li>\n`;
186+
}
187+
188+
// Start a new list or a nested list
189+
if (listStack.length === 0 || listType !== listStack[listStack.length - 1].type || indentLength > listStack[listStack.length - 1].indent) {
190+
if (listStack.length > 0 && listType === listStack[listStack.length - 1].type && indentLength < listStack[listStack.length - 1].indent) {
191+
//This case should be handled by the while loop above, but as a safeguard:
192+
html += `</${listStack.pop().type}>\n</li>\n`;
193+
}
194+
if (listStack.length > 0 && indentLength === listStack[listStack.length - 1].indent && listType !== listStack[listStack.length - 1].type) {
195+
// Different list type at same indent level, close previous
196+
html += `</${listStack.pop().type}>\n</li>\n`;
197+
}
198+
199+
html += (listStack.length > 0 ? '' : '') + `<${listType}>\n`;
200+
listStack.push({ type: listType, indent: indentLength });
201+
} else if (indentLength < listStack[listStack.length - 1].indent) {
202+
// This case should be handled by the while loop above.
203+
// It implies de-indenting to a previous list level of the same type.
204+
}
205+
206+
207+
html += `<li>${itemContent}`;
208+
// Subsequent lines that are more indented are part of this list item
209+
// This simple parser doesn't handle complex multi-line items elegantly without further lookahead or state.
210+
// For now, we assume a list item ends at a newline unless the next line is also a list item.
211+
// The paragraph logic later will handle non-list text.
212+
html += `</li>\n`; // Close li immediately for this simplified model
213+
214+
} else {
215+
// Not a list item, close all open lists
216+
while (listStack.length > 0) {
217+
html += `</${listStack.pop().type}>\n` + (listStack.length > 0 ? '</li>\n' : '');
218+
}
219+
// This line will be handled by paragraph logic if it's not empty
220+
// We need to ensure this non-list content is preserved.
221+
// The current logic processes the *whole* markdown for lists at once.
222+
// It might be better to split by non-list blocks first.
223+
// For now, we output the non-list line to be handled later.
224+
html += line + '\n'; // Add non-list line back
225+
}
226+
}
227+
228+
// Close any remaining open lists
229+
while (listStack.length > 0) {
230+
html += `</${listStack.pop().type}>\n` + (listStack.length > 0 ? '</li>\n' : '');
231+
}
232+
return html.trim();
233+
}
234+
235+
236+
// We need to split the markdown by sections that are definitively NOT lists,
237+
// like headings, code blocks, tables, hr, etc., and then process potential list segments.
238+
// This is a more complex tokenization problem.
239+
// A simpler, yet potentially flawed approach for now:
240+
// Let the paragraph logic run, and then try to fix lists. This is what was failing.
241+
242+
// New Strategy: Process lists on lines that look like list items,
243+
// and then ensure paragraph logic doesn't mess them up.
244+
245+
let listLines = [];
246+
let nonListAccumulator = [];
247+
const lines = processedMarkdown.split('\n');
248+
let finalProcessedLines = [];
249+
250+
for (let i = 0; i < lines.length; i++) {
251+
const line = lines[i];
252+
if (/^\s*([-*+]|\d+\.)\s+/.test(line)) {
253+
if (nonListAccumulator.length > 0) {
254+
finalProcessedLines.push(nonListAccumulator.join('\n'));
255+
nonListAccumulator = [];
256+
}
257+
listLines.push(line);
258+
} else {
259+
if (listLines.length > 0) {
260+
finalProcessedLines.push(processListSegment(listLines.join('\n')));
261+
listLines = [];
262+
}
263+
nonListAccumulator.push(line);
264+
}
265+
}
266+
if (listLines.length > 0) {
267+
finalProcessedLines.push(processListSegment(listLines.join('\n')));
268+
}
269+
if (nonListAccumulator.length > 0) {
270+
finalProcessedLines.push(nonListAccumulator.join('\n'));
271+
}
272+
processedMarkdown = finalProcessedLines.join('\n');
273+
274+
275+
// 6. Replace Code Block Placeholders
169276
codePlaceholders.forEach((placeholder, index) => {
170277
const placeholderString = `__CODE_BLOCK_${index}__`;
171278
if (placeholder.type === 'code') { // Interactive Python
172279
const id = `code-block-${window.codeBlocks.length}`; // Use global counter
173-
const language = placeholder.language; // Should be 'python'
280+
const language = placeholder.language;
174281
console.log(`Creating interactive code block ID ${id} for slide ${slideIndex + 1}`);
175282
window.codeBlocks.push({ id, code: placeholder.code, language: language });
176-
// Ensure the placeholder is replaced even if surrounded by <p> tags initially
177283
processedMarkdown = processedMarkdown.replace(new RegExp(`<p>\\s*${placeholderString}\\s*<\\/p>|${placeholderString}`),
178-
`<div id="${id}" class="code-editor-container" data-language="${language}"></div>`);
284+
`<div id="${id}" class="code-editor-container" data-language="${language}"></div>`);
179285
} else { // Non-interactive pre/code
180286
const language = placeholder.language;
181287
const languageClass = language ? ` class="language-${language}"` : '';
182-
const escapedCode = placeholder.code.replace(/</g, '<').replace(/>/g, '>');
183-
// Ensure the placeholder is replaced even if surrounded by <p> tags initially
288+
const escapedCode = placeholder.code.replace(/</g, '<').replace(/>/g, '>'); // Corrected escaping
184289
processedMarkdown = processedMarkdown.replace(new RegExp(`<p>\\s*${placeholderString}\\s*<\\/p>|${placeholderString}`),
185-
`<pre><code${languageClass}>${escapedCode}</code></pre>`);
290+
`<pre><code${languageClass}>${escapedCode}</code></pre>`);
186291
}
187292
});
188293

189-
// 6. Paragraphs (apply last, carefully)
294+
// 7. Paragraphs (apply last, carefully)
190295
processedMarkdown = processedMarkdown.split('\n').map(line => {
191296
const trimmedLine = line.trim();
192-
// Wrap line in <p> only if it's not empty, not already a tag, and not just whitespace
193-
if (trimmedLine && !trimmedLine.startsWith('<') && !trimmedLine.endsWith('>')) {
194-
return `<p>${trimmedLine}</p>`;
297+
if (trimmedLine === '') {
298+
return ''; // Preserve or remove empty lines based on desired behavior.
195299
}
196-
return line; // Keep lines that are empty or already tags
300+
// Check if the line is already part of a known block structure or is a block tag itself
301+
const isBlockTag = /^\s*<(ul|ol|li|h[1-6]|table|thead|tbody|tr|th|td|pre|div|figure|figcaption|blockquote|hr|p)(\s|>)/i.test(trimmedLine);
302+
const isClosingBlockTag = /^\s*<\/(ul|ol|li|h[1-6]|table|thead|tbody|tr|th|td|pre|div|figure|figcaption|blockquote|hr|p)>/i.test(trimmedLine);
303+
304+
if (trimmedLine && !isBlockTag && !isClosingBlockTag) {
305+
// Further check: if it's inside a list item, don't wrap. This is hard without full context.
306+
// The list processing should ideally handle multi-line list items internally.
307+
return `<p>${trimmedLine}</p>`;
308+
}
309+
return line; // Keep lines that are empty, already tags, or part of block elements
197310
}).join('\n');
198-
// Clean up paragraphs around block elements more aggressively
199-
processedMarkdown = processedMarkdown.replace(/<p>\s*(<(?:ul|ol|h[1-6]|div|pre|table)[^>]*>[\s\S]*?<\/(?:ul|ol|h[1-6]|div|pre|table)>)\s*<\/p>/g, '$1');
200-
processedMarkdown = processedMarkdown.replace(/<p>\s*<\/p>/g, ''); // Remove empty paragraphs
201311

202-
// 7. Wrap slide content with the necessary wrapper and add to final HTML
312+
// Cleanup: Remove <p> tags that might have been wrongly inserted around block elements or are empty.
313+
processedMarkdown = processedMarkdown.replace(/<p>\s*(<(ul|ol|h[1-6]|div|pre|table|blockquote|hr)(?:[^>]*>[\s\S]*?<\/\2>)?)\s*<\/p>/gi, '$1');
314+
// Remove <p> tags around list items if the list processing didn't prevent it.
315+
processedMarkdown = processedMarkdown.replace(/<p>\s*(<li(?:[^>]*)?>[\s\S]*?<\/li>)\s*<\/p>/gi, '$1');
316+
processedMarkdown = processedMarkdown.replace(/<p>\s*<\/p>/gi, ''); // Remove empty p tags
317+
318+
// 8. Wrap slide content
203319
// Add data-index for potential JS targeting
204320
const slideContent = processedMarkdown.trim();
205321
finalHtml += `<div class="board-slide" data-index="${slideIndex}">

0 commit comments

Comments
 (0)