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. 275
      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 @@
import { EditorView, basicSetup } from "codemirror"; import { EditorView, basicSetup } from "codemirror";
import { EditorState, StateField, StateEffect } from "@codemirror/state"; import { EditorState, StateField, StateEffect } from "@codemirror/state";
import { markdown } from "@codemirror/lang-markdown"; import { markdown } from "@codemirror/lang-markdown";
import { oneDark } from "@codemirror/theme-one-dark"; import { Decoration, type DecorationSet } from "@codemirror/view";
import { ViewPlugin, Decoration, type DecorationSet } from "@codemirror/view";
import { RangeSet } from "@codemirror/state"; import { RangeSet } from "@codemirror/state";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { import {
@ -92,27 +91,6 @@ import Asciidoctor from "asciidoctor";
// Export events for publishing workflow // Export events for publishing workflow
return exportEventsFromTree(result); 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 => { .catch(error => {
console.error("Tree factory error:", error); console.error("Tree factory error:", error);
publicationResult = null; publicationResult = null;

32
src/lib/services/publisher.ts

@ -191,21 +191,8 @@ export async function publishSingleEvent(
const publishedToRelays = await ndkEvent.publish(relaySet); const publishedToRelays = await ndkEvent.publish(relaySet);
if (publishedToRelays.size > 0) { 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 }; return { success: true, eventId: ndkEvent.id };
} else { } 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"); throw new Error("Failed to publish to any relays");
} }
} catch (error) { } catch (error) {
@ -290,25 +277,6 @@ export async function publishMultipleZettels(
results.push({ success: false, error: errorMessage }); 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; return results;
} catch (error) { } catch (error) {
const errorMessage = const errorMessage =

168
src/lib/utils/asciidoc_metadata.ts

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

275
src/lib/utils/publication_tree_processor.ts

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

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

@ -193,8 +193,7 @@
authCheckTimeout = setTimeout(() => { authCheckTimeout = setTimeout(() => {
const currentUser = get(userStore); const currentUser = get(userStore);
if (!currentUser.signedIn) { if (!currentUser.signedIn) {
console.debug('[MyNotes] User not signed in after auth restoration, redirecting to home page'); goto("/");
goto('/');
} else { } else {
checkingAuth = false; checkingAuth = false;
} }
@ -272,7 +271,9 @@
</aside> </aside>
<!-- Notes Feed --> <!-- 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> <h1 class="text-2xl font-bold mb-6">My Notes</h1>
{#if checkingAuth} {#if checkingAuth}
<div class="text-gray-500">Checking authentication...</div> <div class="text-gray-500">Checking authentication...</div>
@ -285,9 +286,13 @@
{:else} {:else}
<ul class="space-y-4 w-full"> <ul class="space-y-4 w-full">
{#each filteredEvents as event} {#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="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 <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" 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)} onclick={() => toggleTags(event.id)}

Loading…
Cancel
Save