diff --git a/src/lib/components/ZettelEditor.svelte b/src/lib/components/ZettelEditor.svelte index 684865c..00b6add 100644 --- a/src/lib/components/ZettelEditor.svelte +++ b/src/lib/components/ZettelEditor.svelte @@ -155,6 +155,13 @@ keys: Object.keys(publicationResult), }); + console.log("Event structure details:", JSON.stringify(publicationResult.metadata.eventStructure, null, 2)); + console.log("Content events details:", publicationResult.contentEvents?.map(e => ({ + dTag: e.tags?.find(t => t[0] === 'd')?.[1], + title: e.tags?.find(t => t[0] === 'title')?.[1], + content: e.content?.substring(0, 100) + '...' + }))); + // Helper to get d-tag from event (works with both NDK events and serialized events) const getEventDTag = (event: any) => { if (event?.tagValue) { @@ -216,43 +223,25 @@ const titleTag = event?.tags.find((t: string[]) => t[0] === "title"); const eventTitle = titleTag ? titleTag[1] : node.title; - // Debug logging for Chapter 1 event finding - if (node.title === "Chapter 1") { - console.log("[DEBUG] Chapter 1 preview processing:"); - console.log(" node.title:", node.title); - console.log(" node.dTag:", node.dTag); - console.log(" node.eventType:", node.eventType); - console.log(" node.eventKind:", node.eventKind); - console.log(" found event:", !!event); - console.log(" event?.content:", JSON.stringify(event?.content)); - if (event) { - console.log(" event d-tag:", getEventDTag(event)); - console.log(" event tags:", event.tags); - } - console.log(" contentEvents available:", publicationResult.contentEvents?.map(e => ({ - dTag: getEventDTag(e), - content: e.content?.substring(0, 50) + "..." - }))); - } // For content events, remove the first heading from content since we'll use the title tag let processedContent = event?.content || ""; if (event && node.eventType === "content") { - // Remove the first heading line (which should match the title) + // Remove the heading line that matches this section's title and level (if present) + // This is important because content events should not include their own title heading + // since the title is displayed separately from the "title" tag const lines = processedContent.split("\n"); - const firstHeadingIndex = lines.findIndex((line: string) => - line.match(/^=+\s+/), + const expectedHeading = `${"=".repeat(node.level)} ${node.title}`; + const titleHeadingIndex = lines.findIndex((line: string) => + line.trim() === expectedHeading.trim(), ); - if (firstHeadingIndex !== -1) { - // Remove the heading line and join back - lines.splice(firstHeadingIndex, 1); + if (titleHeadingIndex !== -1) { + // Remove only the specific title heading line + lines.splice(titleHeadingIndex, 1); processedContent = lines.join("\n").trim(); } } - if (node.title === "Chapter 1") { - console.log(" final processedContent:", JSON.stringify(processedContent)); - } return { title: eventTitle, @@ -1067,13 +1056,54 @@
- {@html asciidoctor.convert(section.content, { - standalone: false, - attributes: { - showtitle: false, - sectids: false, - }, - })} + {@html (() => { + // Check if content contains nested headers + const hasNestedHeaders = section.content.includes('\n===') || section.content.includes('\n===='); + + if (hasNestedHeaders) { + // For proper nested header parsing, we need full document context + // Create a complete AsciiDoc document structure + // Important: Ensure proper level sequence for nested headers + const fullDoc = `= Temporary Document\n\n${"=".repeat(section.level)} ${section.title}\n\n${section.content}`; + + + const rendered = asciidoctor.convert(fullDoc, { + standalone: false, + attributes: { + showtitle: false, + sectids: false, + }, + }); + + + // Extract just the content we want (remove the temporary structure) + // Find the section we care about + const sectionStart = rendered.indexOf(``, sectionStart); + if (nextSectionStart !== -1) { + // Get everything after our section header + const afterHeader = rendered.substring(nextSectionStart + ``.length); + // Find where the section ends (at the closing div) + const sectionEnd = afterHeader.lastIndexOf('
'); + if (sectionEnd !== -1) { + const extracted = afterHeader.substring(0, sectionEnd); + return extracted; + } + } + } + return rendered; + } else { + // Simple content without nested headers + return asciidoctor.convert(section.content, { + standalone: false, + attributes: { + showtitle: false, + sectids: false, + }, + }); + } + })()} {/if} diff --git a/src/lib/utils/publication_tree_processor.ts b/src/lib/utils/publication_tree_processor.ts index 8f16b66..7922bf8 100644 --- a/src/lib/utils/publication_tree_processor.ts +++ b/src/lib/utils/publication_tree_processor.ts @@ -221,8 +221,8 @@ function extractSegmentsAtLevel( } /** - * Recursively collect sections at or above the specified level - * NKBIP-01: Level N parsing includes sections from level 2 through level N + * Recursively collect sections for hierarchical parsing + * NKBIP-01: Level N parsing needs Level 2 through Level N sections for proper structure */ function collectSectionsAtLevel( hierarchy: SectionNode[], @@ -232,7 +232,7 @@ function collectSectionsAtLevel( function traverse(nodes: SectionNode[]) { for (const node of nodes) { - // Include sections from level 2 up to target level + // Include sections from level 2 up to target level for hierarchical structure if (node.level >= 2 && node.level <= targetLevel) { collected.push(node); } @@ -333,32 +333,36 @@ function parseSegmentContent( // Extract content (everything after attributes, but stop at child sections) const contentLines = sectionLines.slice(contentStartIdx); - // Find where child sections start (deeper level headers) + // Find where to stop content extraction based on parse level let contentEndIdx = contentLines.length; const currentSectionLevel = sectionLines[0].match(/^(=+)/)?.[1].length || 2; for (let i = 0; i < contentLines.length; i++) { const line = contentLines[i]; const headerMatch = line.match(/^(=+)\s+/); - if (headerMatch && headerMatch[1].length > currentSectionLevel) { - // Found a child section header - stop content extraction here - contentEndIdx = i; - break; + if (headerMatch) { + // At all parse levels: Include child headers, stop only at sibling/parent headers + // This ensures that content events include their nested content + if (headerMatch[1].length <= currentSectionLevel) { + contentEndIdx = i; + break; + } } } const content = contentLines.slice(0, contentEndIdx).join("\n").trim(); - // Debug logging for content extraction - if (sectionLines[0].includes("Chapter 1")) { - console.log("[DEBUG] Chapter 1 content extraction in parseSegmentContent:"); - console.log(" sectionLines:", sectionLines); - console.log(" contentStartIdx:", contentStartIdx); - console.log(" contentLines:", contentLines); - console.log(" contentEndIdx:", contentEndIdx); - console.log(" extracted content:", JSON.stringify(content)); + // Debug logging for Level 3+ content extraction + if (parseLevel === 3 && sectionLines[0].includes("subheader")) { + console.log(`[DEBUG] Level 3 content extraction for subheader:`); + console.log(` parseLevel: ${parseLevel}`); + console.log(` sectionLines:`, JSON.stringify(sectionLines)); + console.log(` currentSectionLevel: ${currentSectionLevel}`); + console.log(` contentEndIdx: ${contentEndIdx}`); + console.log(` extracted content:`, JSON.stringify(content)); } + return { attributes, content }; } @@ -648,14 +652,6 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent { event.tags = tags; event.content = segment.content; - // Debug logging for Chapter 1 content events - if (segment.title === "Chapter 1") { - console.log("[DEBUG] Creating content event for Chapter 1:"); - console.log(" segment.title:", segment.title); - console.log(" segment.content:", JSON.stringify(segment.content)); - console.log(" segment.level:", segment.level); - console.log(" event.content:", JSON.stringify(event.content)); - } return event; } @@ -834,6 +830,7 @@ function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] { combinedContent += `\n\n${"=".repeat(nested.level)} ${nested.title}\n${nested.content}`; } + level2Groups.push({ ...segment, content: combinedContent,