Browse Source

Remove debug logging

- @src/lib/components/ZettelEditor
- @src/lib/services/publisher
- @src/lib/utils/asciidoc_metadata
==
- @src/lib/routes/my-notes/+page.svelte, will move to event structure preview
master
limina1 6 months ago
parent
commit
5843aaa5f7
  1. 24
      src/lib/components/ZettelEditor.svelte
  2. 32
      src/lib/services/publisher.ts
  3. 168
      src/lib/utils/asciidoc_metadata.ts
  4. 271
      src/lib/utils/publication_tree_processor.ts
  5. 15
      src/routes/my-notes/+page.svelte

24
src/lib/components/ZettelEditor.svelte

@ -4,8 +4,7 @@ @@ -4,8 +4,7 @@
import { EditorView, basicSetup } from "codemirror";
import { EditorState, StateField, StateEffect } from "@codemirror/state";
import { markdown } from "@codemirror/lang-markdown";
import { oneDark } from "@codemirror/theme-one-dark";
import { ViewPlugin, Decoration, type DecorationSet } from "@codemirror/view";
import { Decoration, type DecorationSet } from "@codemirror/view";
import { RangeSet } from "@codemirror/state";
import { onMount } from "svelte";
import {
@ -92,27 +91,6 @@ import Asciidoctor from "asciidoctor"; @@ -92,27 +91,6 @@ import Asciidoctor from "asciidoctor";
// Export events for publishing workflow
return exportEventsFromTree(result);
})
.then(events => {
// Debug: Check what we're getting from exportEventsFromTree
console.log("Events from exportEventsFromTree:", events);
console.log("Event keys:", Object.keys(events));
if (events.indexEvent) {
console.log("Index event keys:", Object.keys(events.indexEvent));
}
if (events.contentEvents?.[0]) {
console.log("First content event keys:", Object.keys(events.contentEvents[0]));
}
generatedEvents = events;
console.log("Tree factory result:", {
contentType,
indexEvent: !!events.indexEvent,
contentEvents: events.contentEvents.length,
parseLevel: parseLevel
});
})
.catch(error => {
console.error("Tree factory error:", error);
publicationResult = null;

32
src/lib/services/publisher.ts

@ -191,21 +191,8 @@ export async function publishSingleEvent( @@ -191,21 +191,8 @@ export async function publishSingleEvent(
const publishedToRelays = await ndkEvent.publish(relaySet);
if (publishedToRelays.size > 0) {
// Debug: Log the event structure in a clean, concise format
const dTagEntry = tags.find((t) => t[0] === "d");
const dTag = dTagEntry ? dTagEntry[1] : "";
const titleTag = tags.find((t) => t[0] === "title");
const title = titleTag ? titleTag[1] : "Untitled";
console.log(`Event verified: ${ndkEvent.id}`);
return { success: true, eventId: ndkEvent.id };
} else {
const titleTag = tags.find((t) => t[0] === "title");
const title = titleTag ? titleTag[1] : "Untitled";
console.error(
`Failed to publish event: ${title} (${kind}) - no relays responded`,
);
throw new Error("Failed to publish to any relays");
}
} catch (error) {
@ -290,25 +277,6 @@ export async function publishMultipleZettels( @@ -290,25 +277,6 @@ export async function publishMultipleZettels(
results.push({ success: false, error: errorMessage });
}
}
// Debug: extract and log 'e' and 'a' tags from all published events
publishedEvents.forEach((ev) => {
// Extract d-tag from tags
const dTagEntry = ev.tags.find((t) => t[0] === "d");
const dTag = dTagEntry ? dTagEntry[1] : "";
const aTag = `${ev.kind}:${ev.pubkey}:${dTag}`;
console.log(`Event ${ev.id} tags:`);
console.log(" e:", ev.id);
console.log(" a:", aTag);
// Print nevent and naddr using nip19
const nevent = nip19.neventEncode({ id: ev.id });
const naddr = nip19.naddrEncode({
kind: ev.kind,
pubkey: ev.pubkey,
identifier: dTag,
});
console.log(" nevent:", nevent);
console.log(" naddr:", naddr);
});
return results;
} catch (error) {
const errorMessage =

168
src/lib/utils/asciidoc_metadata.ts

@ -2,7 +2,6 @@ @@ -2,7 +2,6 @@
* AsciiDoc Metadata Extraction Service using Asciidoctor
*
* Thin wrapper around Asciidoctor's built-in metadata extraction capabilities.
* Leverages the existing Pharos parser to avoid duplication.
*/
// @ts-ignore
@ -23,38 +22,37 @@ export interface AsciiDocMetadata { @@ -23,38 +22,37 @@ export interface AsciiDocMetadata {
source?: string;
publishedBy?: string;
type?: string;
autoUpdate?: 'yes' | 'ask' | 'no';
autoUpdate?: "yes" | "ask" | "no";
customAttributes?: Record<string, string>;
}
export type SectionMetadata = AsciiDocMetadata;
// Shared attribute mapping based on Asciidoctor standard attributes
const ATTRIBUTE_MAP: Record<string, keyof AsciiDocMetadata> = {
// Standard Asciidoctor attributes
"author": "authors",
"description": "summary",
"keywords": "tags",
"revnumber": "version",
"revdate": "publicationDate",
"revremark": "edition",
"title": "title",
author: "authors",
description: "summary",
keywords: "tags",
revnumber: "version",
revdate: "publicationDate",
revremark: "edition",
title: "title",
// Custom attributes for Alexandria
"published_by": "publishedBy",
"publisher": "publisher",
"summary": "summary",
"image": "coverImage",
"cover": "coverImage",
"isbn": "isbn",
"source": "source",
"type": "type",
published_by: "publishedBy",
publisher: "publisher",
summary: "summary",
image: "coverImage",
cover: "coverImage",
isbn: "isbn",
source: "source",
type: "type",
"auto-update": "autoUpdate",
"version": "version",
"edition": "edition",
"published_on": "publicationDate",
"date": "publicationDate",
version: "version",
edition: "edition",
published_on: "publicationDate",
date: "publicationDate",
"version-label": "version",
};
@ -70,21 +68,21 @@ function createProcessor() { @@ -70,21 +68,21 @@ function createProcessor() {
*/
function decodeHtmlEntities(text: string): string {
const entities: Record<string, string> = {
'&#8217;': "'",
'&#8216;': "'",
'&#8220;': '"',
'&#8221;': '"',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
'&apos;': "'",
"&#8217;": "'",
"&#8216;": "'",
"&#8220;": '"',
"&#8221;": '"',
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
"&apos;": "'",
};
let result = text;
for (const [entity, char] of Object.entries(entities)) {
result = result.replace(new RegExp(entity, 'g'), char);
result = result.replace(new RegExp(entity, "g"), char);
}
return result;
}
@ -141,7 +139,11 @@ function mapAttributesToMetadata( @@ -141,7 +139,11 @@ function mapAttributesToMetadata(
} else {
(metadata as any)[metadataKey] = value;
}
} else if (value && typeof value === 'string' && !systemAttributes.includes(key)) {
} else if (
value &&
typeof value === "string" &&
!systemAttributes.includes(key)
) {
// Handle unknown/custom attributes - but only if they're not system attributes
if (!metadata.customAttributes) {
metadata.customAttributes = {};
@ -261,11 +263,27 @@ function extractSectionAuthors(sectionContent: string): string[] { @@ -261,11 +263,27 @@ function extractSectionAuthors(sectionContent: string): string[] {
// System attributes to filter out when adding custom attributes as tags
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'
"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",
];
/**
@ -303,9 +321,9 @@ function stripSectionHeader(sectionContent: string): string { @@ -303,9 +321,9 @@ function stripSectionHeader(sectionContent: string): string {
}
// Handle empty lines - don't add more than one consecutive empty line
if (line.trim() === '') {
if (line.trim() === "") {
if (!lastWasEmpty) {
processedLines.push('');
processedLines.push("");
}
lastWasEmpty = true;
} else {
@ -315,7 +333,10 @@ function stripSectionHeader(sectionContent: string): string { @@ -315,7 +333,10 @@ function stripSectionHeader(sectionContent: string): string {
}
// Remove extra blank lines and normalize newlines
return processedLines.join('\n').replace(/\n\s*\n\s*\n/g, '\n\n').trim();
return processedLines
.join("\n")
.replace(/\n\s*\n\s*\n/g, "\n\n")
.trim();
}
/**
@ -355,19 +376,19 @@ function stripDocumentHeader(content: string): string { @@ -355,19 +376,19 @@ function stripDocumentHeader(content: string): string {
const processedLines = [];
for (let i = 0; i < filteredLines.length; i++) {
const line = filteredLines[i];
const prevLine = i > 0 ? filteredLines[i - 1] : '';
const nextLine = i < filteredLines.length - 1 ? filteredLines[i + 1] : '';
const prevLine = i > 0 ? filteredLines[i - 1] : "";
const nextLine = i < filteredLines.length - 1 ? filteredLines[i + 1] : "";
// If this is a deeper header (====+), ensure it has newlines around it
if (line.match(/^====+\s+/)) {
// Add newline before if previous line isn't blank
if (prevLine && prevLine.trim() !== '') {
processedLines.push('');
if (prevLine && prevLine.trim() !== "") {
processedLines.push("");
}
processedLines.push(line);
// Add newline after if next line isn't blank and exists
if (nextLine && nextLine.trim() !== '') {
processedLines.push('');
if (nextLine && nextLine.trim() !== "") {
processedLines.push("");
}
} else {
processedLines.push(line);
@ -375,7 +396,10 @@ function stripDocumentHeader(content: string): string { @@ -375,7 +396,10 @@ function stripDocumentHeader(content: string): string {
}
// Remove extra blank lines and normalize newlines
return processedLines.join('\n').replace(/\n\s*\n\s*\n/g, '\n\n').trim();
return processedLines
.join("\n")
.replace(/\n\s*\n\s*\n/g, "\n\n")
.trim();
}
/**
@ -394,11 +418,14 @@ export function parseSimpleAttributes(content: string): [string, string][] { @@ -394,11 +418,14 @@ export function parseSimpleAttributes(content: string): [string, string][] {
const tagName = key.trim();
const tagValue = value.trim();
if (tagName === 'tags') {
if (tagName === "tags") {
// Special handling for :tags: - split into individual t-tags
const tags_list = tagValue.split(',').map(t => t.trim()).filter(t => t.length > 0);
tags_list.forEach(tag => {
tags.push(['t', tag]);
const tags_list = tagValue
.split(",")
.map((t) => t.trim())
.filter((t) => t.length > 0);
tags_list.forEach((tag) => {
tags.push(["t", tag]);
});
} else {
// Regular attribute -> [tagname, tagvalue]
@ -460,17 +487,29 @@ export function extractDocumentMetadata(inputContent: string): { @@ -460,17 +487,29 @@ export function extractDocumentMetadata(inputContent: string): {
// Extract revision info (only if it looks like valid revision data)
const revisionNumber = document.getRevisionNumber();
if (revisionNumber && revisionNumber !== 'Version' && !revisionNumber.includes('==')) {
if (
revisionNumber &&
revisionNumber !== "Version" &&
!revisionNumber.includes("==")
) {
metadata.version = revisionNumber;
}
const revisionRemark = document.getRevisionRemark();
if (revisionRemark && !revisionRemark.includes('[NOTE]') && !revisionRemark.includes('==')) {
if (
revisionRemark &&
!revisionRemark.includes("[NOTE]") &&
!revisionRemark.includes("==")
) {
metadata.publishedBy = revisionRemark;
}
const revisionDate = document.getRevisionDate();
if (revisionDate && !revisionDate.includes('[NOTE]') && !revisionDate.includes('==')) {
if (
revisionDate &&
!revisionDate.includes("[NOTE]") &&
!revisionDate.includes("==")
) {
metadata.publicationDate = revisionDate;
}
@ -507,7 +546,7 @@ export function extractSectionMetadata(inputSectionContent: string): { @@ -507,7 +546,7 @@ export function extractSectionMetadata(inputSectionContent: string): {
} {
// Extract title directly from the content using regex for more control
const titleMatch = inputSectionContent.match(/^(=+)\s+(.+)$/m);
let title = '';
let title = "";
if (titleMatch) {
title = titleMatch[2].trim();
}
@ -535,7 +574,9 @@ export function extractSectionMetadata(inputSectionContent: string): { @@ -535,7 +574,9 @@ export function extractSectionMetadata(inputSectionContent: string): {
// Extract tags using parseSimpleAttributes (which is what's used in generateNostrEvents)
const simpleAttrs = parseSimpleAttributes(inputSectionContent);
const tags = simpleAttrs.filter(attr => attr[0] === 't').map(attr => attr[1]);
const tags = simpleAttrs
.filter((attr) => attr[0] === "t")
.map((attr) => attr[1]);
if (tags.length > 0) {
metadata.tags = tags;
}
@ -544,7 +585,6 @@ export function extractSectionMetadata(inputSectionContent: string): { @@ -544,7 +585,6 @@ export function extractSectionMetadata(inputSectionContent: string): {
return { metadata, content, title };
}
/**
* Converts metadata to Nostr event tags
*/
@ -644,11 +684,6 @@ export function extractMetadataFromSectionsOnly(content: string): { @@ -644,11 +684,6 @@ export function extractMetadataFromSectionsOnly(content: string): {
return { metadata, content };
}
/**
* Smart metadata extraction that handles both document headers and section-only content
*/
@ -663,9 +698,10 @@ export function extractSmartMetadata(content: string): { @@ -663,9 +698,10 @@ export function extractSmartMetadata(content: string): {
// Check if it's a minimal document header (just title, no other metadata)
const lines = content.split(/\r?\n/);
const titleLine = lines.find((line) => line.match(/^=\s+/));
const hasOtherMetadata = lines.some((line) =>
const hasOtherMetadata = lines.some(
(line) =>
line.includes("<") || // author line
line.match(/^.+,\s*.+:\s*.+$/) // revision line
line.match(/^.+,\s*.+:\s*.+$/), // revision line
);
if (hasOtherMetadata) {

271
src/lib/utils/publication_tree_processor.ts

@ -12,6 +12,8 @@ import { NDKEvent } from "@nostr-dev-kit/ndk"; @@ -12,6 +12,8 @@ import { NDKEvent } from "@nostr-dev-kit/ndk";
import type NDK from "@nostr-dev-kit/ndk";
import { getMimeTags } from "$lib/utils/mime";
// For debugging tree structure
const DEBUG = process.env.DEBUG_TREE_PROCESSOR === false;
export interface ProcessorResult {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -57,7 +59,7 @@ export function registerPublicationTreeProcessor( @@ -57,7 +59,7 @@ export function registerPublicationTreeProcessor(
registry: Registry,
ndk: NDK,
parseLevel: number = 2,
originalContent: string
originalContent: string,
): { getResult: () => ProcessorResult | null } {
let processorResult: ProcessorResult | null = null;
@ -67,35 +69,46 @@ export function registerPublicationTreeProcessor( @@ -67,35 +69,46 @@ export function registerPublicationTreeProcessor(
self.process(function (doc: Document) {
try {
// Extract document metadata from AST
const title = doc.getTitle() || '';
const title = doc.getTitle() || "";
const attributes = doc.getAttributes();
const sections = doc.getSections();
console.log(`[TreeProcessor] Document attributes:`, {
tags: attributes.tags,
author: attributes.author,
type: attributes.type
type: attributes.type,
});
console.log(`[TreeProcessor] Processing document: "${title}" at parse level ${parseLevel}`);
console.log(`[TreeProcessor] Found ${sections.length} top-level sections`);
console.log(
`[TreeProcessor] Processing document: "${title}" at parse level ${parseLevel}`,
);
console.log(
`[TreeProcessor] Found ${sections.length} top-level sections`,
);
// Extract content segments from original text based on parse level
const contentSegments = extractContentSegments(originalContent, sections, parseLevel);
console.log(`[TreeProcessor] Extracted ${contentSegments.length} content segments for level ${parseLevel}`);
const contentSegments = extractContentSegments(
originalContent,
sections,
parseLevel,
);
console.log(
`[TreeProcessor] Extracted ${contentSegments.length} content segments for level ${parseLevel}`,
);
// Determine content type based on structure
const contentType = detectContentType(title, contentSegments);
console.log(`[TreeProcessor] Detected content type: ${contentType}`);
// Build events and tree structure
const { tree, indexEvent, contentEvents, eventStructure } = buildEventsFromSegments(
const { tree, indexEvent, contentEvents, eventStructure } =
buildEventsFromSegments(
contentSegments,
title,
attributes,
contentType,
parseLevel,
ndk
ndk,
);
processorResult = {
@ -108,14 +121,15 @@ export function registerPublicationTreeProcessor( @@ -108,14 +121,15 @@ export function registerPublicationTreeProcessor(
contentType,
attributes,
parseLevel,
eventStructure
}
eventStructure,
},
};
console.log(`[TreeProcessor] Built tree with ${contentEvents.length} content events and ${indexEvent ? '1' : '0'} index events`);
console.log(
`[TreeProcessor] Built tree with ${contentEvents.length} content events and ${indexEvent ? "1" : "0"} index events`,
);
} catch (error) {
console.error('[TreeProcessor] Error processing document:', error);
console.error("[TreeProcessor] Error processing document:", error);
processorResult = null;
}
@ -124,7 +138,7 @@ export function registerPublicationTreeProcessor( @@ -124,7 +138,7 @@ export function registerPublicationTreeProcessor(
});
return {
getResult: () => processorResult
getResult: () => processorResult,
};
}
@ -135,24 +149,25 @@ export function registerPublicationTreeProcessor( @@ -135,24 +149,25 @@ export function registerPublicationTreeProcessor(
function extractContentSegments(
originalContent: string,
sections: any[],
parseLevel: number
parseLevel: number,
): ContentSegment[] {
const lines = originalContent.split('\n');
const lines = originalContent.split("\n");
// Build hierarchy map from AST
const sectionHierarchy = buildSectionHierarchy(sections);
// Debug: Show hierarchy depths
console.log(`[TreeProcessor] Section hierarchy depth analysis:`);
function showDepth(nodes: SectionNode[], depth = 0) {
for (const node of nodes) {
console.log(`${' '.repeat(depth)}Level ${node.level}: ${node.title}`);
console.log(`${" ".repeat(depth)}Level ${node.level}: ${node.title}`);
if (node.children.length > 0) {
showDepth(node.children, depth + 1);
}
}
}
if (DEBUG) {
showDepth(sectionHierarchy);
}
// Extract segments at the target parse level
return extractSegmentsAtLevel(lines, sectionHierarchy, parseLevel);
@ -167,7 +182,7 @@ function buildSectionHierarchy(sections: any[]): SectionNode[] { @@ -167,7 +182,7 @@ function buildSectionHierarchy(sections: any[]): SectionNode[] {
title: section.getTitle(),
level: section.getLevel() + 1, // Convert to app level (Asciidoctor uses 0-based)
attributes: section.getAttributes() || {},
children: (section.getSections() || []).map(buildNode)
children: (section.getSections() || []).map(buildNode),
};
}
@ -188,7 +203,7 @@ interface SectionNode { @@ -188,7 +203,7 @@ interface SectionNode {
function extractSegmentsAtLevel(
lines: string[],
hierarchy: SectionNode[],
parseLevel: number
parseLevel: number,
): ContentSegment[] {
const segments: ContentSegment[] = [];
@ -209,7 +224,10 @@ function extractSegmentsAtLevel( @@ -209,7 +224,10 @@ function extractSegmentsAtLevel(
* Recursively collect sections at or above the specified level
* NKBIP-01: Level N parsing includes sections from level 2 through level N
*/
function collectSectionsAtLevel(hierarchy: SectionNode[], targetLevel: number): SectionNode[] {
function collectSectionsAtLevel(
hierarchy: SectionNode[],
targetLevel: number,
): SectionNode[] {
const collected: SectionNode[] = [];
function traverse(nodes: SectionNode[]) {
@ -236,10 +254,12 @@ function collectSectionsAtLevel(hierarchy: SectionNode[], targetLevel: number): @@ -236,10 +254,12 @@ function collectSectionsAtLevel(hierarchy: SectionNode[], targetLevel: number):
function extractSegmentContent(
lines: string[],
section: SectionNode,
parseLevel: number
parseLevel: number,
): ContentSegment | null {
// Find the section header in the original content
const sectionPattern = new RegExp(`^${'='.repeat(section.level)}\\s+${escapeRegex(section.title)}`);
const sectionPattern = new RegExp(
`^${"=".repeat(section.level)}\\s+${escapeRegex(section.title)}`,
);
let startIdx = -1;
for (let i = 0; i < lines.length; i++) {
@ -250,7 +270,9 @@ function extractSegmentContent( @@ -250,7 +270,9 @@ function extractSegmentContent(
}
if (startIdx === -1) {
console.warn(`[TreeProcessor] Could not find section "${section.title}" at level ${section.level}`);
console.warn(
`[TreeProcessor] Could not find section "${section.title}" at level ${section.level}`,
);
return null;
}
@ -276,14 +298,17 @@ function extractSegmentContent( @@ -276,14 +298,17 @@ function extractSegmentContent(
level: section.level,
attributes,
startLine: startIdx,
endLine: endIdx
endLine: endIdx,
};
}
/**
* Parse attributes and content from section lines
*/
function parseSegmentContent(sectionLines: string[], parseLevel: number): {
function parseSegmentContent(
sectionLines: string[],
parseLevel: number,
): {
attributes: Record<string, string>;
content: string;
} {
@ -293,20 +318,20 @@ function parseSegmentContent(sectionLines: string[], parseLevel: number): { @@ -293,20 +318,20 @@ function parseSegmentContent(sectionLines: string[], parseLevel: number): {
// Look for attribute lines after the title
for (let i = 1; i < sectionLines.length; i++) {
const line = sectionLines[i].trim();
if (line.startsWith(':') && line.includes(':')) {
if (line.startsWith(":") && line.includes(":")) {
const match = line.match(/^:([^:]+):\\s*(.*)$/);
if (match) {
attributes[match[1]] = match[2];
contentStartIdx = i + 1;
}
} else if (line !== '') {
} else if (line !== "") {
// Non-empty, non-attribute line - content starts here
break;
}
}
// Extract content (everything after attributes)
const content = sectionLines.slice(contentStartIdx).join('\n').trim();
const content = sectionLines.slice(contentStartIdx).join("\n").trim();
return { attributes, content };
}
@ -316,13 +341,14 @@ function parseSegmentContent(sectionLines: string[], parseLevel: number): { @@ -316,13 +341,14 @@ function parseSegmentContent(sectionLines: string[], parseLevel: number): {
*/
function detectContentType(
title: string,
segments: ContentSegment[]
segments: ContentSegment[],
): "article" | "scattered-notes" | "none" {
const hasDocTitle = !!title;
const hasSections = segments.length > 0;
// Check if the title matches the first section title
const titleMatchesFirstSection = segments.length > 0 && title === segments[0].title;
const titleMatchesFirstSection =
segments.length > 0 && title === segments[0].title;
if (hasDocTitle && hasSections && !titleMatchesFirstSection) {
return "article";
@ -345,7 +371,7 @@ function buildEventsFromSegments( @@ -345,7 +371,7 @@ function buildEventsFromSegments(
attributes: Record<string, string>,
contentType: "article" | "scattered-notes" | "none",
parseLevel: number,
ndk: NDK
ndk: NDK,
): {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -368,7 +394,7 @@ function buildEventsFromSegments( @@ -368,7 +394,7 @@ function buildEventsFromSegments(
*/
function buildScatteredNotesStructure(
segments: ContentSegment[],
ndk: NDK
ndk: NDK,
): {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -389,7 +415,7 @@ function buildScatteredNotesStructure( @@ -389,7 +415,7 @@ function buildScatteredNotesStructure(
eventType: "content",
eventKind: 30041,
dTag: generateDTag(firstSegment.title),
children: []
children: [],
});
// Add remaining segments
@ -403,7 +429,7 @@ function buildScatteredNotesStructure( @@ -403,7 +429,7 @@ function buildScatteredNotesStructure(
eventType: "content",
eventKind: 30041,
dTag: generateDTag(segments[i].title),
children: []
children: [],
});
}
@ -418,7 +444,7 @@ function buildArticleStructure( @@ -418,7 +444,7 @@ function buildArticleStructure(
title: string,
attributes: Record<string, string>,
parseLevel: number,
ndk: NDK
ndk: NDK,
): {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -431,7 +457,14 @@ function buildArticleStructure( @@ -431,7 +457,14 @@ function buildArticleStructure(
if (parseLevel === 2) {
return buildLevel2Structure(segments, title, indexEvent, tree, ndk);
} else {
return buildHierarchicalStructure(segments, title, indexEvent, tree, parseLevel, ndk);
return buildHierarchicalStructure(
segments,
title,
indexEvent,
tree,
parseLevel,
ndk,
);
}
}
@ -443,7 +476,7 @@ function buildLevel2Structure( @@ -443,7 +476,7 @@ function buildLevel2Structure(
title: string,
indexEvent: NDKEvent,
tree: PublicationTree,
ndk: NDK
ndk: NDK,
): {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -460,7 +493,7 @@ function buildLevel2Structure( @@ -460,7 +493,7 @@ function buildLevel2Structure(
eventType: "index",
eventKind: 30040,
dTag: generateDTag(title),
children: []
children: [],
});
// Group segments by level 2 sections
@ -476,7 +509,7 @@ function buildLevel2Structure( @@ -476,7 +509,7 @@ function buildLevel2Structure(
eventType: "content",
eventKind: 30041,
dTag: generateDTag(group.title),
children: []
children: [],
});
}
@ -492,7 +525,7 @@ function buildHierarchicalStructure( @@ -492,7 +525,7 @@ function buildHierarchicalStructure(
indexEvent: NDKEvent,
tree: PublicationTree,
parseLevel: number,
ndk: NDK
ndk: NDK,
): {
tree: PublicationTree;
indexEvent: NDKEvent | null;
@ -509,7 +542,7 @@ function buildHierarchicalStructure( @@ -509,7 +542,7 @@ function buildHierarchicalStructure(
eventType: "index",
eventKind: 30040,
dTag: generateDTag(title),
children: []
children: [],
});
// Build hierarchical structure
@ -527,7 +560,7 @@ function buildHierarchicalStructure( @@ -527,7 +560,7 @@ function buildHierarchicalStructure(
eventType: "index",
eventKind: 30040,
dTag: generateDTag(level2Section.title),
children: []
children: [],
};
// Add children as 30041 content events
@ -541,7 +574,7 @@ function buildHierarchicalStructure( @@ -541,7 +574,7 @@ function buildHierarchicalStructure(
eventType: "content",
eventKind: 30041,
dTag: generateDTag(child.title),
children: []
children: [],
});
}
@ -557,7 +590,7 @@ function buildHierarchicalStructure( @@ -557,7 +590,7 @@ function buildHierarchicalStructure(
eventType: "content",
eventKind: 30041,
dTag: generateDTag(level2Section.title),
children: []
children: [],
});
}
}
@ -572,7 +605,7 @@ function createIndexEvent( @@ -572,7 +605,7 @@ function createIndexEvent(
title: string,
attributes: Record<string, string>,
segments: ContentSegment[],
ndk: NDK
ndk: NDK,
): NDKEvent {
const event = new NDKEvent(ndk);
event.kind = 30040;
@ -582,18 +615,13 @@ function createIndexEvent( @@ -582,18 +615,13 @@ function createIndexEvent(
const dTag = generateDTag(title);
const [mTag, MTag] = getMimeTags(30040);
const tags: string[][] = [
["d", dTag],
mTag,
MTag,
["title", title]
];
const tags: string[][] = [["d", dTag], mTag, MTag, ["title", title]];
// Add document attributes as tags
addDocumentAttributesToTags(tags, attributes, event.pubkey);
// Add a-tags for each content section
segments.forEach(segment => {
segments.forEach((segment) => {
const sectionDTag = generateDTag(segment.title);
tags.push(["a", `30041:${event.pubkey}:${sectionDTag}`]);
});
@ -618,12 +646,7 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent { @@ -618,12 +646,7 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent {
const dTag = generateDTag(segment.title);
const [mTag, MTag] = getMimeTags(30041);
const tags: string[][] = [
["d", dTag],
mTag,
MTag,
["title", segment.title]
];
const tags: string[][] = [["d", dTag], mTag, MTag, ["title", segment.title]];
// Add segment attributes as tags
addSectionAttributesToTags(tags, segment.attributes);
@ -637,30 +660,35 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent { @@ -637,30 +660,35 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent {
/**
* Generate default index content
*/
function generateIndexContent(title: string, segments: ContentSegment[]): string {
function generateIndexContent(
title: string,
segments: ContentSegment[],
): string {
return `# ${title}
${segments.length} sections available:
${segments.map((segment, i) => `${i + 1}. ${segment.title}`).join('\n')}`;
${segments.map((segment, i) => `${i + 1}. ${segment.title}`).join("\n")}`;
}
/**
* Escape regex special characters
*/
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Generate deterministic d-tag from title
*/
function generateDTag(title: string): string {
return title
return (
title
.toLowerCase()
.replace(/[^\p{L}\p{N}]/gu, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "") || "untitled";
.replace(/^-|-$/g, "") || "untitled"
);
}
/**
@ -669,7 +697,7 @@ function generateDTag(title: string): string { @@ -669,7 +697,7 @@ function generateDTag(title: string): string {
function addDocumentAttributesToTags(
tags: string[][],
attributes: Record<string, string>,
pubkey: string
pubkey: string,
) {
// Standard metadata
if (attributes.author) tags.push(["author", attributes.author]);
@ -682,7 +710,7 @@ function addDocumentAttributesToTags( @@ -682,7 +710,7 @@ function addDocumentAttributesToTags(
// 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
@ -697,11 +725,11 @@ function addDocumentAttributesToTags( @@ -697,11 +725,11 @@ function addDocumentAttributesToTags(
*/
function addSectionAttributesToTags(
tags: string[][],
attributes: Record<string, string>
attributes: Record<string, string>,
) {
// Section tags
if (attributes.tags) {
attributes.tags.split(",").forEach(tag => tags.push(["t", tag.trim()]));
attributes.tags.split(",").forEach((tag) => tags.push(["t", tag.trim()]));
}
// Custom attributes
@ -713,22 +741,61 @@ function addSectionAttributesToTags( @@ -713,22 +741,61 @@ function addSectionAttributesToTags(
*/
function addCustomAttributes(
tags: string[][],
attributes: Record<string, 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]) => {
@ -749,21 +816,24 @@ function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] { @@ -749,21 +816,24 @@ function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] {
for (const segment of segments) {
if (segment.level === 2) {
// Find all content that belongs to this level 2 section
const nestedSegments = segments.filter(s =>
const nestedSegments = segments.filter(
(s) =>
s.level > 2 &&
s.startLine > segment.startLine &&
(segments.find(next => next.level <= 2 && next.startLine > segment.startLine)?.startLine || Infinity) > s.startLine
(segments.find(
(next) => next.level <= 2 && next.startLine > segment.startLine,
)?.startLine || Infinity) > s.startLine,
);
// Combine the level 2 content with all nested content
let combinedContent = segment.content;
for (const nested of nestedSegments) {
combinedContent += `\n\n${'='.repeat(nested.level)} ${nested.title}\n${nested.content}`;
combinedContent += `\n\n${"=".repeat(nested.level)} ${nested.title}\n${nested.content}`;
}
level2Groups.push({
...segment,
content: combinedContent
content: combinedContent,
});
}
}
@ -774,21 +844,26 @@ function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] { @@ -774,21 +844,26 @@ function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] {
/**
* Build hierarchical segment structure for Level 3+ parsing
*/
function buildSegmentHierarchy(segments: ContentSegment[]): HierarchicalSegment[] {
function buildSegmentHierarchy(
segments: ContentSegment[],
): HierarchicalSegment[] {
const hierarchy: HierarchicalSegment[] = [];
// Process level 2 sections
for (const level2Segment of segments.filter(s => s.level === 2)) {
const children = segments.filter(s =>
for (const level2Segment of segments.filter((s) => s.level === 2)) {
const children = segments.filter(
(s) =>
s.level > 2 &&
s.startLine > level2Segment.startLine &&
(segments.find(next => next.level <= 2 && next.startLine > level2Segment.startLine)?.startLine || Infinity) > s.startLine
(segments.find(
(next) => next.level <= 2 && next.startLine > level2Segment.startLine,
)?.startLine || Infinity) > s.startLine,
);
hierarchy.push({
...level2Segment,
hasChildren: children.length > 0,
children
children,
});
}
@ -798,7 +873,10 @@ function buildSegmentHierarchy(segments: ContentSegment[]): HierarchicalSegment[ @@ -798,7 +873,10 @@ function buildSegmentHierarchy(segments: ContentSegment[]): HierarchicalSegment[
/**
* Create a 30040 index event for a section with children
*/
function createIndexEventForSection(section: HierarchicalSegment, ndk: NDK): NDKEvent {
function createIndexEventForSection(
section: HierarchicalSegment,
ndk: NDK,
): NDKEvent {
const event = new NDKEvent(ndk);
event.kind = 30040;
event.created_at = Math.floor(Date.now() / 1000);
@ -807,18 +885,13 @@ function createIndexEventForSection(section: HierarchicalSegment, ndk: NDK): NDK @@ -807,18 +885,13 @@ function createIndexEventForSection(section: HierarchicalSegment, ndk: NDK): NDK
const dTag = generateDTag(section.title);
const [mTag, MTag] = getMimeTags(30040);
const tags: string[][] = [
["d", dTag],
mTag,
MTag,
["title", section.title]
];
const tags: string[][] = [["d", dTag], mTag, MTag, ["title", section.title]];
// Add section attributes as tags
addSectionAttributesToTags(tags, section.attributes);
// Add a-tags for each child content section
section.children.forEach(child => {
section.children.forEach((child) => {
const childDTag = generateDTag(child.title);
tags.push(["a", `30041:${event.pubkey}:${childDTag}`]);
});

15
src/routes/my-notes/+page.svelte

@ -193,8 +193,7 @@ @@ -193,8 +193,7 @@
authCheckTimeout = setTimeout(() => {
const currentUser = get(userStore);
if (!currentUser.signedIn) {
console.debug('[MyNotes] User not signed in after auth restoration, redirecting to home page');
goto('/');
goto("/");
} else {
checkingAuth = false;
}
@ -272,7 +271,9 @@ @@ -272,7 +271,9 @@
</aside>
<!-- Notes Feed -->
<div class="flex-1 w-full lg:max-w-5xl lg:ml-auto px-0 lg:px-4 min-w-0 overflow-hidden">
<div
class="flex-1 w-full lg:max-w-5xl lg:ml-auto px-0 lg:px-4 min-w-0 overflow-hidden"
>
<h1 class="text-2xl font-bold mb-6">My Notes</h1>
{#if checkingAuth}
<div class="text-gray-500">Checking authentication...</div>
@ -285,9 +286,13 @@ @@ -285,9 +286,13 @@
{:else}
<ul class="space-y-4 w-full">
{#each filteredEvents as event}
<li class="p-4 bg-white dark:bg-gray-800 rounded shadow w-full overflow-hidden">
<li
class="p-4 bg-white dark:bg-gray-800 rounded shadow w-full overflow-hidden"
>
<div class="flex items-center justify-between mb-2 min-w-0">
<div class="font-semibold text-lg truncate flex-1 mr-2">{getTitle(event)}</div>
<div class="font-semibold text-lg truncate flex-1 mr-2">
{getTitle(event)}
</div>
<button
class="flex-shrink-0 px-2 py-1 text-xs rounded bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600"
onclick={() => toggleTags(event.id)}

Loading…
Cancel
Save