@ -5,12 +5,12 @@
@@ -5,12 +5,12 @@
* 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 {
@@ -20,7 +20,7 @@ export interface PublicationTreeFactoryResult {
metadata : {
title : string ;
totalSections : number ;
contentType : 'article' | 'scattered-notes' | 'none' ;
contentType : "article" | "scattered-notes" | "none" ;
attributes : Record < string , string > ;
} ;
}
@ -32,9 +32,8 @@ export interface PublicationTreeFactoryResult {
@@ -32,9 +32,8 @@ export interface PublicationTreeFactoryResult {
export async function createPublicationTreeFromContent (
content : string ,
ndk : NDK ,
parseLevel : number = 2
parseLevel : number = 2 ,
) : Promise < PublicationTreeFactoryResult > {
// For preview purposes, we can work without authentication
// Authentication is only required for actual publishing
const hasActiveUser = ! ! ndk . activeUser ;
@ -49,7 +48,7 @@ export async function createPublicationTreeFromContent(
@@ -49,7 +48,7 @@ export async function createPublicationTreeFromContent(
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 ) ;
@ -60,8 +59,7 @@ export async function createPublicationTreeFromContent(
@@ -60,8 +59,7 @@ export async function createPublicationTreeFromContent(
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" ) ;
@ -79,7 +77,6 @@ export async function createPublicationTreeFromContent(
@@ -79,7 +77,6 @@ export async function createPublicationTreeFromContent(
await tree . addEvent ( contentEvent , rootEvent ) ;
contentEvents . push ( contentEvent ) ;
}
} else {
throw new Error ( "No valid content found to create publication tree" ) ;
}
@ -87,7 +84,7 @@ export async function createPublicationTreeFromContent(
@@ -87,7 +84,7 @@ export async function createPublicationTreeFromContent(
// Create reactive Svelte wrapper
const svelteTree = new SveltePublicationTree (
indexEvent || contentEvents [ 0 ] ,
ndk
ndk ,
) ;
return {
@ -99,8 +96,8 @@ export async function createPublicationTreeFromContent(
@@ -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 {
@@ -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,23 +135,22 @@ function createIndexEvent(parsed: any, ndk: NDK): NDKEvent {
@@ -143,23 +135,22 @@ 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 ) ;
@ -168,7 +159,7 @@ function createContentEvent(section: any, documentParsed: any, ndk: NDK): NDKEve
@@ -168,7 +159,7 @@ function createContentEvent(section: any, documentParsed: any, ndk: NDK): NDKEve
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
@@ -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 ;
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 } ] / g u , " - " )
. replace ( /-+/g , "-" )
. replace ( /^-|-$/g , "" ) || "untitled" ;
return (
title
. toLowerCase ( )
. replace ( / [ ^ \ p { L } \ p { N } ] / g u , " - " )
. replace ( /-+/g , "-" )
. replace ( /^-|-$/g , "" ) || "untitled"
) ;
}
/ * *
* Add document attributes as Nostr tags
* /
function addDocumentAttributesToTags ( tags : string [ ] [ ] , attributes : Record < string , string > , pubkey : string ) {
function addDocumentAttributesToTags (
tags : string [ ] [ ] ,
attributes : Record < string , string > ,
pubkey : string ,
) {
// Standard metadata
if ( attributes . author ) tags . push ( [ "author" , attributes . author ] ) ;
if ( attributes . version ) tags . push ( [ "version" , attributes . version ] ) ;
@ -219,9 +218,7 @@ function addDocumentAttributesToTags(tags: string[][], attributes: Record<string
@@ -219,9 +218,7 @@ function addDocumentAttributesToTags(tags: string[][], attributes: Record<string
// Tags
if ( attributes . tags ) {
attributes . tags . split ( ',' ) . forEach ( tag = >
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<string
@@ -234,39 +231,90 @@ function addDocumentAttributesToTags(tags: string[][], attributes: Record<string
/ * *
* Add section - specific attributes as tags
* /
function addSectionAttributesToTags ( tags : string [ ] [ ] , attributes : Record < string , string > ) {
function addSectionAttributesToTags (
tags : string [ ] [ ] ,
attributes : Record < string , string > ,
) {
addCustomAttributes ( tags , attributes ) ;
}
/ * *
* Inherit relevant document attributes for content events
* /
function inheritDocumentAttributes ( tags : string [ ] [ ] , documentAttributes : Record < string , string > ) {
function inheritDocumentAttributes (
tags : string [ ] [ ] ,
documentAttributes : Record < string , string > ,
) {
// 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 < string , string > ) {
function addCustomAttributes (
tags : string [ ] [ ] ,
attributes : Record < string , string > ,
) {
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,16 +328,18 @@ function generateIndexContent(parsed: any): string {
@@ -280,16 +328,18 @@ 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
@ -298,14 +348,16 @@ export async function exportEventsFromTree(result: PublicationTreeFactoryResult)
@@ -298,14 +348,16 @@ export async function exportEventsFromTree(result: PublicationTreeFactoryResult)
}
// 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) {
@@ -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" ,
} ;
}