diff --git a/src/lib/components/ZettelEditor.svelte b/src/lib/components/ZettelEditor.svelte index 4484a91..11d9e2b 100644 --- a/src/lib/components/ZettelEditor.svelte +++ b/src/lib/components/ZettelEditor.svelte @@ -3,10 +3,6 @@ import { EyeOutline, QuestionCircleOutline } from "flowbite-svelte-icons"; import { extractSmartMetadata, - parseAsciiDocWithMetadata, - parseAsciiDocIterative, - generateNostrEvents, - detectContentType, type AsciiDocMetadata, metadataToTags, parseSimpleAttributes, diff --git a/src/lib/services/publisher.ts b/src/lib/services/publisher.ts index d2cb7eb..d5b6706 100644 --- a/src/lib/services/publisher.ts +++ b/src/lib/services/publisher.ts @@ -1,8 +1,10 @@ import { getMimeTags } from "../utils/mime.ts"; import { metadataToTags, - parseAsciiDocWithMetadata, } from "../utils/asciidoc_metadata.ts"; +import { + parseAsciiDocWithMetadata, +} from "../utils/asciidoc_parser.ts"; import NDK, { NDKEvent, NDKRelaySet } from "@nostr-dev-kit/ndk"; import { nip19 } from "nostr-tools"; diff --git a/src/lib/utils/asciidoc_metadata.ts b/src/lib/utils/asciidoc_metadata.ts index db1bfd5..b6eedd7 100644 --- a/src/lib/utils/asciidoc_metadata.ts +++ b/src/lib/utils/asciidoc_metadata.ts @@ -29,16 +29,6 @@ export interface AsciiDocMetadata { export type SectionMetadata = AsciiDocMetadata; -export interface ParsedAsciiDoc { - metadata: AsciiDocMetadata; - content: string; - title: string; - sections: Array<{ - metadata: SectionMetadata; - content: string; - title: string; - }>; -} // Shared attribute mapping based on Asciidoctor standard attributes const ATTRIBUTE_MAP: Record = { @@ -554,53 +544,6 @@ export function extractSectionMetadata(inputSectionContent: string): { return { metadata, content, title }; } -/** - * Parses AsciiDoc content into sections with metadata - */ -export function parseAsciiDocWithMetadata(content: string): ParsedAsciiDoc { - const asciidoctor = createProcessor(); - const document = asciidoctor.load(content, { standalone: false }) as Document; - const { metadata: docMetadata } = extractDocumentMetadata(content); - - // Parse the original content to find section attributes - const lines = content.split(/\r?\n/); - const sectionsWithMetadata: Array<{ - metadata: SectionMetadata; - content: string; - title: string; - }> = []; - let currentSection: string | null = null; - let currentSectionContent: string[] = []; - - for (const line of lines) { - if (line.match(/^==\s+/)) { - // Save previous section if exists - if (currentSection) { - const sectionContent = currentSectionContent.join("\n"); - sectionsWithMetadata.push(extractSectionMetadata(sectionContent)); - } - - // Start new section - currentSection = line; - currentSectionContent = [line]; - } else if (currentSection) { - currentSectionContent.push(line); - } - } - - // Save the last section - if (currentSection) { - const sectionContent = currentSectionContent.join("\n"); - sectionsWithMetadata.push(extractSectionMetadata(sectionContent)); - } - - return { - metadata: docMetadata, - content: document.getSource(), - title: docMetadata.title || '', - sections: sectionsWithMetadata - }; -} /** * Converts metadata to Nostr event tags @@ -701,437 +644,10 @@ export function extractMetadataFromSectionsOnly(content: string): { return { metadata, content }; } -/** - * Iterative AsciiDoc parsing based on specified level - * Level 2: Only == sections become content events (containing all subsections) - * Level 3: == sections become indices + content events, === sections become content events - * Level 4: === sections become indices + content events, ==== sections become content events, etc. - */ -export function parseAsciiDocIterative(content: string, parseLevel: number = 2): ParsedAsciiDoc { - const asciidoctor = createProcessor(); - const document = asciidoctor.load(content, { standalone: false }) as Document; - const { metadata: docMetadata } = extractDocumentMetadata(content); - - const lines = content.split(/\r?\n/); - const sections: Array<{ - metadata: SectionMetadata; - content: string; - title: string; - }> = []; - - if (parseLevel === 2) { - // Level 2: Only == sections become events - const level2Pattern = /^==\s+/; - let currentSection: string | null = null; - let currentSectionContent: string[] = []; - let documentContent: string[] = []; - let inDocumentHeader = true; - - for (const line of lines) { - if (line.match(level2Pattern)) { - inDocumentHeader = false; - - // Save previous section if exists - if (currentSection) { - const sectionContent = currentSectionContent.join('\n'); - const sectionMeta = extractSectionMetadata(sectionContent); - // For level 2, preserve the full content including the header - sections.push({ - ...sectionMeta, - content: sectionContent // Use full content, not stripped - }); - } - - // Start new section - currentSection = line; - currentSectionContent = [line]; - } else if (currentSection) { - currentSectionContent.push(line); - } else if (inDocumentHeader) { - documentContent.push(line); - } - } - - // Save the last section - if (currentSection) { - const sectionContent = currentSectionContent.join('\n'); - const sectionMeta = extractSectionMetadata(sectionContent); - // For level 2, preserve the full content including the header - sections.push({ - ...sectionMeta, - content: sectionContent // Use full content, not stripped - }); - } - - const docContent = documentContent.join('\n'); - return { - metadata: docMetadata, - content: docContent, - title: docMetadata.title || '', - sections: sections - }; - } - - // Level 3+: Parse hierarchically - // All levels from 2 to parseLevel-1 are indices (title only) - // Level parseLevel are content sections (full content) - - // First, collect all sections at the content level (parseLevel) - const contentLevelPattern = new RegExp(`^${'='.repeat(parseLevel)}\\s+`); - let currentSection: string | null = null; - let currentSectionContent: string[] = []; - let documentContent: string[] = []; - let inDocumentHeader = true; - - for (const line of lines) { - if (line.match(contentLevelPattern)) { - inDocumentHeader = false; - - // Save previous section if exists - if (currentSection) { - const sectionContent = currentSectionContent.join('\n'); - const sectionMeta = extractSectionMetadata(sectionContent); - sections.push({ - ...sectionMeta, - content: sectionContent // Full content including headers - }); - } - - // Start new content section - currentSection = line; - currentSectionContent = [line]; - } else if (currentSection) { - // Continue collecting content for current section - currentSectionContent.push(line); - } else if (inDocumentHeader) { - documentContent.push(line); - } - } - - // Save the last section - if (currentSection) { - const sectionContent = currentSectionContent.join('\n'); - const sectionMeta = extractSectionMetadata(sectionContent); - sections.push({ - ...sectionMeta, - content: sectionContent // Full content including headers - }); - } - - // Now collect index sections (all levels from 2 to parseLevel-1) - // These should be shown as navigation/structure but not full content - const indexSections: Array<{ - metadata: SectionMetadata; - content: string; - title: string; - level: number; - }> = []; - - for (let level = 2; level < parseLevel; level++) { - const levelPattern = new RegExp(`^${'='.repeat(level)}\\s+(.+)$`, 'gm'); - const matches = content.matchAll(levelPattern); - - for (const match of matches) { - const title = match[1].trim(); - indexSections.push({ - metadata: { title }, - content: `${'='.repeat(level)} ${title}`, // Just the header line for index sections - title, - level - }); - } - } - - // Add actual level to content sections based on their content - const contentSectionsWithLevel = sections.map(s => ({ - ...s, - level: getSectionLevel(s.content) - })); - - // Combine index sections and content sections - // Sort by position in original content to maintain order - const allSections = [...indexSections, ...contentSectionsWithLevel]; - - // Sort sections by their appearance in the original content - allSections.sort((a, b) => { - const posA = content.indexOf(a.content.split('\n')[0]); - const posB = content.indexOf(b.content.split('\n')[0]); - return posA - posB; - }); - - const docContent = documentContent.join('\n'); - return { - metadata: docMetadata, - content: docContent, - title: docMetadata.title || '', - sections: allSections - }; -} -/** - * Helper function to determine the header level of a section - */ -function getSectionLevel(sectionContent: string): number { - const lines = sectionContent.split(/\r?\n/); - for (const line of lines) { - const match = line.match(/^(=+)\s+/); - if (match) { - return match[1].length; - } - } - return 0; -} -/** - * Helper function to extract just the intro content (before first subsection) - */ -function extractIntroContent(sectionContent: string, currentLevel: number): string { - const lines = sectionContent.split(/\r?\n/); - const introLines: string[] = []; - let foundHeader = false; - - for (const line of lines) { - const headerMatch = line.match(/^(=+)\s+/); - if (headerMatch) { - const level = headerMatch[1].length; - if (level === currentLevel && !foundHeader) { - // This is the section header itself - foundHeader = true; - continue; // Skip the header line itself for intro content - } else if (level > currentLevel) { - // This is a subsection, stop collecting intro content - break; - } - } else if (foundHeader) { - // This is intro content after the header - introLines.push(line); - } - } - - return introLines.join('\n').trim(); -} -/** - * Generates Nostr events from parsed AsciiDoc with proper hierarchical structure - * Based on docreference.md specifications - */ -export function generateNostrEvents(parsed: ParsedAsciiDoc, parseLevel: number = 2, pubkey?: string, maxDepth: number = 6): { - indexEvent?: any; - contentEvents: any[]; -} { - const allEvents: any[] = []; - const actualPubkey = pubkey || 'pubkey'; - - // Helper function to generate section ID - const generateSectionId = (title: string): string => { - return title - .toLowerCase() - .replace(/[^\p{L}\p{N}]/gu, "-") - .replace(/-+/g, "-") - .replace(/^-|-$/g, ""); - }; - - // Build hierarchical tree structure - interface TreeNode { - section: { - metadata: any; - content: string; - title: string; - }; - level: number; - sectionId: string; - tags: [string, string][]; - children: TreeNode[]; - parent?: TreeNode; - } - - // Convert flat sections to tree structure - const buildTree = (): TreeNode[] => { - const roots: TreeNode[] = []; - const stack: TreeNode[] = []; - - for (const section of parsed.sections) { - const level = getSectionLevel(section.content); - const sectionId = generateSectionId(section.title); - const tags = parseSimpleAttributes(section.content); - - const node: TreeNode = { - section, - level, - sectionId, - tags, - children: [], - }; - - // Find the correct parent based on header hierarchy - while (stack.length > 0 && stack[stack.length - 1].level >= level) { - stack.pop(); - } - - if (stack.length === 0) { - // This is a root level section - roots.push(node); - } else { - // This is a child of the last item in stack - const parent = stack[stack.length - 1]; - parent.children.push(node); - node.parent = parent; - } - - stack.push(node); - } - - return roots; - }; - - const tree = buildTree(); - - // Recursively create events from tree - const createEventsFromNode = (node: TreeNode): void => { - const { section, level, sectionId, tags, children } = node; - - // Determine if this node should become an index - const hasChildrenAtTargetLevel = children.some(child => child.level === parseLevel); - const shouldBeIndex = level < parseLevel && (hasChildrenAtTargetLevel || children.some(child => child.level <= parseLevel)); - - if (shouldBeIndex) { - // Create content event for intro text (30041) - const introContent = extractIntroContent(section.content, level); - if (introContent.trim()) { - const contentEvent = { - id: '', - pubkey: '', - created_at: Math.floor(Date.now() / 1000), - kind: 30041, - tags: [ - ['d', `${sectionId}-content`], - ['title', section.title], - ...tags - ], - content: introContent, - sig: '' - }; - allEvents.push(contentEvent); - } - - // Create index event (30040) - const childATags: string[][] = []; - - // Add a-tag for intro content if it exists - if (introContent.trim()) { - childATags.push(['a', `30041:${actualPubkey}:${sectionId}-content`, '', '']); - } - - // Add a-tags for direct children - for (const child of children) { - const childHasSubChildren = child.children.some(grandchild => grandchild.level <= parseLevel); - const childShouldBeIndex = child.level < parseLevel && childHasSubChildren; - const childKind = childShouldBeIndex ? 30040 : 30041; - childATags.push(['a', `${childKind}:${actualPubkey}:${child.sectionId}`, '', '']); - } - - const indexEvent = { - id: '', - pubkey: '', - created_at: Math.floor(Date.now() / 1000), - kind: 30040, - tags: [ - ['d', sectionId], - ['title', section.title], - ...tags, - ...childATags - ], - content: '', - sig: '' - }; - allEvents.push(indexEvent); - } else { - // Create regular content event (30041) - const contentEvent = { - id: '', - pubkey: '', - created_at: Math.floor(Date.now() / 1000), - kind: 30041, - tags: [ - ['d', sectionId], - ['title', section.title], - ...tags - ], - content: section.content, - sig: '' - }; - allEvents.push(contentEvent); - } - - // Recursively process children - for (const child of children) { - createEventsFromNode(child); - } - }; - - // Process all root level sections - for (const rootNode of tree) { - createEventsFromNode(rootNode); - } - - // Create main document index if we have a document title (article format) - if (parsed.title && parsed.title.trim() !== '') { - const documentId = generateSectionId(parsed.title); - const documentTags = parseSimpleAttributes(parsed.content); - - // Create a-tags for all root level sections (level 2) - const mainIndexATags = tree.map(rootNode => { - const hasSubChildren = rootNode.children.some(child => child.level <= parseLevel); - const shouldBeIndex = rootNode.level < parseLevel && hasSubChildren; - const kind = shouldBeIndex ? 30040 : 30041; - return ['a', `${kind}:${actualPubkey}:${rootNode.sectionId}`, '', '']; - }); - - console.log('Debug: Root sections found:', tree.length); - console.log('Debug: Main index a-tags:', mainIndexATags); - - const mainIndexEvent = { - id: '', - pubkey: '', - created_at: Math.floor(Date.now() / 1000), - kind: 30040, - tags: [ - ['d', documentId], - ['title', parsed.title], - ...documentTags, - ...mainIndexATags - ], - content: '', - sig: '' - }; - - return { - indexEvent: mainIndexEvent, - contentEvents: allEvents - }; - } - - // For scattered notes, return only content events - return { - contentEvents: allEvents - }; -} -/** - * Detects content type for smart publishing - */ -export function detectContentType(content: string): 'article' | 'scattered-notes' | 'none' { - const hasDocTitle = content.trim().startsWith('=') && !content.trim().startsWith('=='); - const hasSections = content.includes('=='); - - if (hasDocTitle) { - return 'article'; - } else if (hasSections) { - return 'scattered-notes'; - } else { - return 'none'; - } -} /** * Smart metadata extraction that handles both document headers and section-only content diff --git a/src/lib/utils/event_input_utils.ts b/src/lib/utils/event_input_utils.ts index cdeb501..e2d4f36 100644 --- a/src/lib/utils/event_input_utils.ts +++ b/src/lib/utils/event_input_utils.ts @@ -4,8 +4,10 @@ import { EVENT_KINDS } from "./search_constants"; import { extractDocumentMetadata, metadataToTags, - parseAsciiDocWithMetadata, } from "./asciidoc_metadata.ts"; +import { + parseAsciiDocWithMetadata, +} from "./asciidoc_parser.ts"; // ========================= // Validation diff --git a/src/lib/utils/publication_tree_factory.ts b/src/lib/utils/publication_tree_factory.ts index 73ba483..ba86c23 100644 --- a/src/lib/utils/publication_tree_factory.ts +++ b/src/lib/utils/publication_tree_factory.ts @@ -1,16 +1,16 @@ /** * Factory for creating PublicationTree instances from AsciiDoc content - * + * * This integrates the AST parser with Michael's PublicationTree architecture, * providing a clean bridge between AsciiDoc parsing and Nostr event publishing. */ -import { PublicationTree } from "../data_structures/publication_tree.ts"; -import { SveltePublicationTree } from "../components/publications/svelte_publication_tree.svelte.ts"; -import { parseAsciiDocAST } from "./asciidoc_ast_parser.ts"; +import { PublicationTree } from "$lib/data_structures/publication_tree.ts"; +import { SveltePublicationTree } from "$lib/components/publications/svelte_publication_tree.svelte.ts"; +import { parseAsciiDocAST } from "asciidoc_ast_parser.ts"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import type NDK from "@nostr-dev-kit/ndk"; -import { getMimeTags } from "./mime.ts"; +import { getMimeTags } from "mime.ts"; export interface PublicationTreeFactoryResult { tree: PublicationTree; @@ -20,7 +20,7 @@ export interface PublicationTreeFactoryResult { metadata: { title: string; totalSections: number; - contentType: 'article' | 'scattered-notes' | 'none'; + contentType: "article" | "scattered-notes" | "none"; attributes: Record; }; } @@ -32,62 +32,59 @@ export interface PublicationTreeFactoryResult { export async function createPublicationTreeFromContent( content: string, ndk: NDK, - parseLevel: number = 2 + parseLevel: number = 2, ): Promise { - // For preview purposes, we can work without authentication // Authentication is only required for actual publishing const hasActiveUser = !!ndk.activeUser; // Parse content using AST const parsed = parseAsciiDocAST(content, parseLevel); - + // Determine content type const contentType = detectContentType(parsed); - + let tree: PublicationTree; let indexEvent: NDKEvent | null = null; const contentEvents: NDKEvent[] = []; - if (contentType === 'article' && parsed.title) { + if (contentType === "article" && parsed.title) { // Create hierarchical structure: 30040 index + 30041 content events indexEvent = createIndexEvent(parsed, ndk); tree = new PublicationTree(indexEvent, ndk); - + // Add content events to tree for (const section of parsed.sections) { const contentEvent = createContentEvent(section, parsed, ndk); await tree.addEvent(contentEvent, indexEvent); contentEvents.push(contentEvent); } - - } else if (contentType === 'scattered-notes') { + } else if (contentType === "scattered-notes") { // Create flat structure: only 30041 events if (parsed.sections.length === 0) { throw new Error("No sections found for scattered notes"); } - + // Use first section as root for tree structure const firstSection = parsed.sections[0]; const rootEvent = createContentEvent(firstSection, parsed, ndk); tree = new PublicationTree(rootEvent, ndk); contentEvents.push(rootEvent); - + // Add remaining sections for (let i = 1; i < parsed.sections.length; i++) { const contentEvent = createContentEvent(parsed.sections[i], parsed, ndk); await tree.addEvent(contentEvent, rootEvent); contentEvents.push(contentEvent); } - } else { throw new Error("No valid content found to create publication tree"); } // Create reactive Svelte wrapper const svelteTree = new SveltePublicationTree( - indexEvent || contentEvents[0], - ndk + indexEvent || contentEvents[0], + ndk, ); return { @@ -99,8 +96,8 @@ export async function createPublicationTreeFromContent( title: parsed.title, totalSections: parsed.sections.length, contentType, - attributes: parsed.attributes - } + attributes: parsed.attributes, + }, }; } @@ -112,18 +109,13 @@ function createIndexEvent(parsed: any, ndk: NDK): NDKEvent { event.kind = 30040; event.created_at = Math.floor(Date.now() / 1000); // Use placeholder pubkey for preview if no active user - event.pubkey = ndk.activeUser?.pubkey || 'preview-placeholder-pubkey'; + event.pubkey = ndk.activeUser?.pubkey || "preview-placeholder-pubkey"; // Generate d-tag from title const dTag = generateDTag(parsed.title); const [mTag, MTag] = getMimeTags(30040); - const tags: string[][] = [ - ["d", dTag], - mTag, - MTag, - ["title", parsed.title] - ]; + const tags: string[][] = [["d", dTag], mTag, MTag, ["title", parsed.title]]; // Add document attributes as tags addDocumentAttributesToTags(tags, parsed.attributes, event.pubkey); @@ -143,32 +135,31 @@ function createIndexEvent(parsed: any, ndk: NDK): NDKEvent { /** * Create a 30041 content event from parsed section */ -function createContentEvent(section: any, documentParsed: any, ndk: NDK): NDKEvent { +function createContentEvent( + section: any, + documentParsed: any, + ndk: NDK, +): NDKEvent { const event = new NDKEvent(ndk); event.kind = 30041; event.created_at = Math.floor(Date.now() / 1000); - + // Use placeholder pubkey for preview if no active user - event.pubkey = ndk.activeUser?.pubkey || 'preview-placeholder-pubkey'; + event.pubkey = ndk.activeUser?.pubkey || "preview-placeholder-pubkey"; const dTag = generateDTag(section.title); const [mTag, MTag] = getMimeTags(30041); - const tags: string[][] = [ - ["d", dTag], - mTag, - MTag, - ["title", section.title] - ]; + const tags: string[][] = [["d", dTag], mTag, MTag, ["title", section.title]]; // Add section-specific attributes addSectionAttributesToTags(tags, section.attributes); - + // Add document-level attributes that should be inherited inheritDocumentAttributes(tags, documentParsed.attributes); event.tags = tags; - event.content = section.content || ''; + event.content = section.content || ""; return event; } @@ -176,39 +167,47 @@ function createContentEvent(section: any, documentParsed: any, ndk: NDK): NDKEve /** * Detect content type based on parsed structure */ -function detectContentType(parsed: any): 'article' | 'scattered-notes' | 'none' { +function detectContentType( + parsed: any, +): "article" | "scattered-notes" | "none" { const hasDocTitle = !!parsed.title; const hasSections = parsed.sections.length > 0; // Check if the "title" is actually just the first section title - // This happens when AsciiDoc starts with == instead of = - const titleMatchesFirstSection = parsed.sections.length > 0 && - parsed.title === parsed.sections[0].title; + // This happens when AsciiDoc starts with == instead of = + const titleMatchesFirstSection = + parsed.sections.length > 0 && parsed.title === parsed.sections[0].title; if (hasDocTitle && hasSections && !titleMatchesFirstSection) { - return 'article'; + return "article"; } else if (hasSections) { - return 'scattered-notes'; + return "scattered-notes"; } - return 'none'; + return "none"; } /** * Generate deterministic d-tag from title */ function generateDTag(title: string): string { - return title - .toLowerCase() - .replace(/[^\p{L}\p{N}]/gu, "-") - .replace(/-+/g, "-") - .replace(/^-|-$/g, "") || "untitled"; + return ( + title + .toLowerCase() + .replace(/[^\p{L}\p{N}]/gu, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") || "untitled" + ); } /** * Add document attributes as Nostr tags */ -function addDocumentAttributesToTags(tags: string[][], attributes: Record, pubkey: string) { +function addDocumentAttributesToTags( + tags: string[][], + attributes: Record, + pubkey: string, +) { // Standard metadata if (attributes.author) tags.push(["author", attributes.author]); if (attributes.version) tags.push(["version", attributes.version]); @@ -216,12 +215,10 @@ function addDocumentAttributesToTags(tags: string[][], attributes: Record - tags.push(["t", tag.trim()]) - ); + attributes.tags.split(",").forEach((tag) => tags.push(["t", tag.trim()])); } // Add pubkey reference @@ -234,39 +231,90 @@ function addDocumentAttributesToTags(tags: string[][], attributes: Record) { +function addSectionAttributesToTags( + tags: string[][], + attributes: Record, +) { addCustomAttributes(tags, attributes); } /** * Inherit relevant document attributes for content events */ -function inheritDocumentAttributes(tags: string[][], documentAttributes: Record) { +function inheritDocumentAttributes( + tags: string[][], + documentAttributes: Record, +) { // Inherit selected document attributes - if (documentAttributes.language) tags.push(["language", documentAttributes.language]); + if (documentAttributes.language) + tags.push(["language", documentAttributes.language]); if (documentAttributes.type) tags.push(["type", documentAttributes.type]); } /** * Add custom attributes, filtering out system ones */ -function addCustomAttributes(tags: string[][], attributes: Record) { +function addCustomAttributes( + tags: string[][], + attributes: Record, +) { const systemAttributes = [ - 'attribute-undefined', 'attribute-missing', 'appendix-caption', 'appendix-refsig', - 'caution-caption', 'chapter-refsig', 'example-caption', 'figure-caption', - 'important-caption', 'last-update-label', 'manname-title', 'note-caption', - 'part-refsig', 'preface-title', 'section-refsig', 'table-caption', - 'tip-caption', 'toc-title', 'untitled-label', 'version-label', 'warning-caption', - 'asciidoctor', 'asciidoctor-version', 'safe-mode-name', 'backend', 'doctype', - 'basebackend', 'filetype', 'outfilesuffix', 'stylesdir', 'iconsdir', - 'localdate', 'localyear', 'localtime', 'localdatetime', 'docdate', - 'docyear', 'doctime', 'docdatetime', 'doctitle', 'embedded', 'notitle', + "attribute-undefined", + "attribute-missing", + "appendix-caption", + "appendix-refsig", + "caution-caption", + "chapter-refsig", + "example-caption", + "figure-caption", + "important-caption", + "last-update-label", + "manname-title", + "note-caption", + "part-refsig", + "preface-title", + "section-refsig", + "table-caption", + "tip-caption", + "toc-title", + "untitled-label", + "version-label", + "warning-caption", + "asciidoctor", + "asciidoctor-version", + "safe-mode-name", + "backend", + "doctype", + "basebackend", + "filetype", + "outfilesuffix", + "stylesdir", + "iconsdir", + "localdate", + "localyear", + "localtime", + "localdatetime", + "docdate", + "docyear", + "doctime", + "docdatetime", + "doctitle", + "embedded", + "notitle", // Already handled above - 'author', 'version', 'published', 'language', 'image', 'description', 'tags', 'title', 'type' + "author", + "version", + "published", + "language", + "image", + "description", + "tags", + "title", + "type", ]; Object.entries(attributes).forEach(([key, value]) => { - if (!systemAttributes.includes(key) && value && typeof value === 'string') { + if (!systemAttributes.includes(key) && value && typeof value === "string") { tags.push([key, value]); } }); @@ -280,32 +328,36 @@ function generateIndexContent(parsed: any): string { ${parsed.sections.length} sections available: -${parsed.sections.map((section: any, i: number) => - `${i + 1}. ${section.title}` -).join('\n')}`; +${parsed.sections + .map((section: any, i: number) => `${i + 1}. ${section.title}`) + .join("\n")}`; } /** * Export events from PublicationTree for publishing * This provides compatibility with the current publishing workflow */ -export async function exportEventsFromTree(result: PublicationTreeFactoryResult) { +export async function exportEventsFromTree( + result: PublicationTreeFactoryResult, +) { const events: any[] = []; - + // Add index event if it exists if (result.indexEvent) { events.push(eventToPublishableObject(result.indexEvent)); } - + // Add content events - result.contentEvents.forEach(event => { + result.contentEvents.forEach((event) => { events.push(eventToPublishableObject(event)); }); - + return { - indexEvent: result.indexEvent ? eventToPublishableObject(result.indexEvent) : undefined, + indexEvent: result.indexEvent + ? eventToPublishableObject(result.indexEvent) + : undefined, contentEvents: result.contentEvents.map(eventToPublishableObject), - tree: result.tree + tree: result.tree, }; } @@ -320,6 +372,6 @@ function eventToPublishableObject(event: NDKEvent) { created_at: event.created_at, pubkey: event.pubkey, id: event.id, - title: event.tags.find(t => t[0] === 'title')?.[1] || 'Untitled' + title: event.tags.find((t) => t[0] === "title")?.[1] || "Untitled", }; -} \ No newline at end of file +} diff --git a/src/routes/new/compose/+page.svelte b/src/routes/new/compose/+page.svelte index c99121c..61073e7 100644 --- a/src/routes/new/compose/+page.svelte +++ b/src/routes/new/compose/+page.svelte @@ -1,8 +1,6 @@