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. 250
      src/lib/utils/asciidoc_metadata.ts
  4. 479
      src/lib/utils/publication_tree_processor.ts
  5. 27
      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 =

250
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 = {};
@ -157,7 +159,7 @@ function mapAttributesToMetadata( @@ -157,7 +159,7 @@ function mapAttributesToMetadata(
function extractDocumentAuthors(sourceContent: string): string[] {
const authors: string[] = [];
const lines = sourceContent.split(/\r?\n/);
// Find the document title line
let titleLineIndex = -1;
for (let i = 0; i < lines.length; i++) {
@ -166,21 +168,21 @@ function extractDocumentAuthors(sourceContent: string): string[] { @@ -166,21 +168,21 @@ function extractDocumentAuthors(sourceContent: string): string[] {
break;
}
}
if (titleLineIndex === -1) {
return authors;
}
// Look for authors in the lines immediately following the title
let i = titleLineIndex + 1;
while (i < lines.length) {
const line = lines[i];
// Stop if we hit a blank line, section header, or content that's not an author
if (line.trim() === "" || line.match(/^==\s+/)) {
break;
}
if (line.includes("<") && !line.startsWith(":")) {
// This is an author line like "John Doe <john@example.com>"
const authorName = line.split("<")[0].trim();
@ -194,10 +196,10 @@ function extractDocumentAuthors(sourceContent: string): string[] { @@ -194,10 +196,10 @@ function extractDocumentAuthors(sourceContent: string): string[] {
// Not an author line, stop looking
break;
}
i++;
}
return authors;
}
@ -207,7 +209,7 @@ function extractDocumentAuthors(sourceContent: string): string[] { @@ -207,7 +209,7 @@ function extractDocumentAuthors(sourceContent: string): string[] {
function extractSectionAuthors(sectionContent: string): string[] {
const authors: string[] = [];
const lines = sectionContent.split(/\r?\n/);
// Find the section title line
let titleLineIndex = -1;
for (let i = 0; i < lines.length; i++) {
@ -216,21 +218,21 @@ function extractSectionAuthors(sectionContent: string): string[] { @@ -216,21 +218,21 @@ function extractSectionAuthors(sectionContent: string): string[] {
break;
}
}
if (titleLineIndex === -1) {
return authors;
}
// Look for authors in the lines immediately following the section title
let i = titleLineIndex + 1;
while (i < lines.length) {
const line = lines[i];
// Stop if we hit a blank line, another section header, or content that's not an author
if (line.trim() === "" || line.match(/^==\s+/)) {
break;
}
if (line.includes("<") && !line.startsWith(":")) {
// This is an author line like "John Doe <john@example.com>"
const authorName = line.split("<")[0].trim();
@ -239,7 +241,7 @@ function extractSectionAuthors(sectionContent: string): string[] { @@ -239,7 +241,7 @@ function extractSectionAuthors(sectionContent: string): string[] {
}
} else if (
line.match(/^[A-Za-z\s]+$/) &&
line.trim() !== "" &&
line.trim() !== "" &&
line.trim().split(/\s+/).length <= 2 &&
!line.startsWith(":")
) {
@ -252,20 +254,36 @@ function extractSectionAuthors(sectionContent: string): string[] { @@ -252,20 +254,36 @@ function extractSectionAuthors(sectionContent: string): string[] {
// Not an author line, stop looking
break;
}
i++;
}
return authors;
}
// 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",
];
/**
@ -274,38 +292,38 @@ const systemAttributes = [ @@ -274,38 +292,38 @@ const systemAttributes = [
function stripSectionHeader(sectionContent: string): string {
const lines = sectionContent.split(/\r?\n/);
let contentStart = 0;
// Find where the section header ends
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Skip section title line and attribute lines
if (
!line.match(/^=+\s+/) &&
!line.match(/^=+\s+/) &&
!line.includes("<") &&
!line.match(/^.+,\s*.+:\s*.+$/) &&
!line.match(/^:[^:]+:\s*.+$/) &&
!line.match(/^:[^:]+:\s*.+$/) &&
line.trim() !== ""
) {
contentStart = i;
break;
}
}
const processedLines: string[] = [];
let lastWasEmpty = false;
for (let i = contentStart; i < lines.length; i++) {
const line = lines[i];
// Skip attribute lines within content
if (line.match(/^:[^:]+:\s*.+$/)) {
continue;
}
// 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 {
@ -313,9 +331,12 @@ function stripSectionHeader(sectionContent: string): string { @@ -313,9 +331,12 @@ function stripSectionHeader(sectionContent: string): string {
lastWasEmpty = false;
}
}
// 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();
}
/**
@ -324,23 +345,23 @@ function stripSectionHeader(sectionContent: string): string { @@ -324,23 +345,23 @@ function stripSectionHeader(sectionContent: string): string {
function stripDocumentHeader(content: string): string {
const lines = content.split(/\r?\n/);
let contentStart = 0;
// Find the first line that is actual content (not header, author, or attribute)
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Skip title line, author line, revision line, and attribute lines
if (
!line.match(/^=\s+/) &&
!line.match(/^=\s+/) &&
!line.includes("<") &&
!line.match(/^.+,\s*.+:\s*.+$/) &&
!line.match(/^:[^:]+:\s*.+$/) &&
!line.match(/^:[^:]+:\s*.+$/) &&
line.trim() !== ""
) {
contentStart = i;
break;
}
}
// Filter out all attribute lines and author lines from the content
const contentLines = lines.slice(contentStart);
const filteredLines = contentLines.filter((line) => {
@ -350,55 +371,61 @@ function stripDocumentHeader(content: string): string { @@ -350,55 +371,61 @@ function stripDocumentHeader(content: string): string {
}
return true;
});
// Ensure deeper headers (====) have proper newlines around them
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);
}
}
// 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();
}
/**
* Parses attributes from section content using simple regex
* Converts :tagname: tagvalue -> [tagname, tagvalue]
* Converts :tagname: tagvalue -> [tagname, tagvalue]
* Converts :tags: comma,separated -> [t, tag1], [t, tag2], etc.
*/
export function parseSimpleAttributes(content: string): [string, string][] {
const tags: [string, string][] = [];
const lines = content.split(/\r?\n/);
for (const line of lines) {
const match = line.match(/^:([^:]+):\s*(.+)$/);
if (match) {
const [, key, value] = match;
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]
@ -406,7 +433,7 @@ export function parseSimpleAttributes(content: string): [string, string][] { @@ -406,7 +433,7 @@ export function parseSimpleAttributes(content: string): [string, string][] {
}
}
}
return tags;
}
@ -441,7 +468,7 @@ export function extractDocumentMetadata(inputContent: string): { @@ -441,7 +468,7 @@ export function extractDocumentMetadata(inputContent: string): {
inDocumentHeader = false;
break;
}
// Process :author: attributes regardless of other content
if (inDocumentHeader) {
const match = line.match(/^:author:\s*(.+)$/);
@ -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,16 +546,16 @@ export function extractSectionMetadata(inputSectionContent: string): { @@ -507,16 +546,16 @@ 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();
}
const metadata: SectionMetadata = { title };
// Extract authors from section content
const authors = extractSectionAuthors(inputSectionContent);
// Get authors from attributes (including multiple :author: lines)
const lines = inputSectionContent.split(/\r?\n/);
for (const line of lines) {
@ -528,14 +567,16 @@ export function extractSectionMetadata(inputSectionContent: string): { @@ -528,14 +567,16 @@ export function extractSectionMetadata(inputSectionContent: string): {
}
}
}
if (authors.length > 0) {
metadata.authors = authors;
}
// 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) =>
line.includes("<") || // author line
line.match(/^.+,\s*.+:\s*.+$/) // revision line
const hasOtherMetadata = lines.some(
(line) =>
line.includes("<") || // author line
line.match(/^.+,\s*.+:\s*.+$/), // revision line
);
if (hasOtherMetadata) {

479
src/lib/utils/publication_tree_processor.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/**
* NKBIP-01 Compliant Publication Tree Processor
*
*
* Implements proper Asciidoctor tree processor extension pattern for building
* PublicationTree structures during document parsing. Supports iterative parsing
* at different hierarchy levels (2-7) as defined in NKBIP-01 specification.
@ -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,47 +59,58 @@ export function registerPublicationTreeProcessor( @@ -57,47 +59,58 @@ export function registerPublicationTreeProcessor(
registry: Registry,
ndk: NDK,
parseLevel: number = 2,
originalContent: string
originalContent: string,
): { getResult: () => ProcessorResult | null } {
let processorResult: ProcessorResult | null = null;
registry.treeProcessor(function() {
registry.treeProcessor(function () {
const self = this;
self.process(function(doc: Document) {
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
author: attributes.author,
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(
contentSegments,
title,
attributes,
contentType,
parseLevel,
ndk
);
const { tree, indexEvent, contentEvents, eventStructure } =
buildEventsFromSegments(
contentSegments,
title,
attributes,
contentType,
parseLevel,
ndk,
);
processorResult = {
tree,
indexEvent,
@ -108,23 +121,24 @@ export function registerPublicationTreeProcessor( @@ -108,23 +121,24 @@ 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;
}
return doc;
});
});
return {
getResult: () => processorResult
getResult: () => processorResult,
};
}
@ -135,25 +149,26 @@ export function registerPublicationTreeProcessor( @@ -135,25 +149,26 @@ 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);
}
}
}
showDepth(sectionHierarchy);
if (DEBUG) {
showDepth(sectionHierarchy);
}
// Extract segments at the target parse level
return extractSegmentsAtLevel(lines, sectionHierarchy, parseLevel);
}
@ -167,10 +182,10 @@ function buildSectionHierarchy(sections: any[]): SectionNode[] { @@ -167,10 +182,10 @@ 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),
};
}
return sections.map(buildNode);
}
@ -188,20 +203,20 @@ interface SectionNode { @@ -188,20 +203,20 @@ interface SectionNode {
function extractSegmentsAtLevel(
lines: string[],
hierarchy: SectionNode[],
parseLevel: number
parseLevel: number,
): ContentSegment[] {
const segments: ContentSegment[] = [];
// Collect all sections at the target parse level
const targetSections = collectSectionsAtLevel(hierarchy, parseLevel);
for (const section of targetSections) {
const segment = extractSegmentContent(lines, section, parseLevel);
if (segment) {
segments.push(segment);
}
}
return segments;
}
@ -209,23 +224,26 @@ function extractSegmentsAtLevel( @@ -209,23 +224,26 @@ 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[]) {
for (const node of nodes) {
// Include sections from level 2 up to target level
if (node.level >= 2 && node.level <= targetLevel) {
collected.push(node);
}
// Continue traversing children to find more sections
if (node.children.length > 0) {
traverse(node.children);
}
}
}
traverse(hierarchy);
return collected;
}
@ -236,24 +254,28 @@ function collectSectionsAtLevel(hierarchy: SectionNode[], targetLevel: number): @@ -236,24 +254,28 @@ 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++) {
if (sectionPattern.test(lines[i])) {
startIdx = i;
break;
}
}
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;
}
// Find the end of this section
let endIdx = lines.length;
for (let i = startIdx + 1; i < lines.length; i++) {
@ -263,51 +285,54 @@ function extractSegmentContent( @@ -263,51 +285,54 @@ function extractSegmentContent(
break;
}
}
// Extract section content
const sectionLines = lines.slice(startIdx, endIdx);
// Parse attributes and content
const { attributes, content } = parseSegmentContent(sectionLines, parseLevel);
return {
title: section.title,
content,
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;
} {
const attributes: Record<string, string> = {};
let contentStartIdx = 1; // Skip the title line
// 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,20 +341,21 @@ function parseSegmentContent(sectionLines: string[], parseLevel: number): { @@ -316,20 +341,21 @@ 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";
} else if (hasSections) {
return "scattered-notes";
}
return "none";
}
@ -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;
@ -355,11 +381,11 @@ function buildEventsFromSegments( @@ -355,11 +381,11 @@ function buildEventsFromSegments(
if (contentType === "scattered-notes" && segments.length > 0) {
return buildScatteredNotesStructure(segments, ndk);
}
if (contentType === "article" && title) {
return buildArticleStructure(segments, title, attributes, parseLevel, ndk);
}
throw new Error("No valid content found to create publication tree");
}
@ -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;
@ -377,36 +403,36 @@ function buildScatteredNotesStructure( @@ -377,36 +403,36 @@ function buildScatteredNotesStructure(
} {
const contentEvents: NDKEvent[] = [];
const eventStructure: EventStructureNode[] = [];
const firstSegment = segments[0];
const rootEvent = createContentEvent(firstSegment, ndk);
const tree = new PublicationTree(rootEvent, ndk);
contentEvents.push(rootEvent);
eventStructure.push({
title: firstSegment.title,
level: firstSegment.level,
eventType: "content",
eventKind: 30041,
dTag: generateDTag(firstSegment.title),
children: []
children: [],
});
// Add remaining segments
for (let i = 1; i < segments.length; i++) {
const contentEvent = createContentEvent(segments[i], ndk);
contentEvents.push(contentEvent);
eventStructure.push({
title: segments[i].title,
level: segments[i].level,
eventType: "content",
eventKind: 30041,
dTag: generateDTag(segments[i].title),
children: []
children: [],
});
}
return { tree, indexEvent: null, contentEvents, eventStructure };
}
@ -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;
@ -427,11 +453,18 @@ function buildArticleStructure( @@ -427,11 +453,18 @@ function buildArticleStructure(
} {
const indexEvent = createIndexEvent(title, attributes, segments, ndk);
const tree = new PublicationTree(indexEvent, ndk);
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;
@ -452,7 +485,7 @@ function buildLevel2Structure( @@ -452,7 +485,7 @@ function buildLevel2Structure(
} {
const contentEvents: NDKEvent[] = [];
const eventStructure: EventStructureNode[] = [];
// Add index to structure
eventStructure.push({
title,
@ -460,26 +493,26 @@ function buildLevel2Structure( @@ -460,26 +493,26 @@ function buildLevel2Structure(
eventType: "index",
eventKind: 30040,
dTag: generateDTag(title),
children: []
children: [],
});
// Group segments by level 2 sections
const level2Groups = groupSegmentsByLevel2(segments);
for (const group of level2Groups) {
const contentEvent = createContentEvent(group, ndk);
contentEvents.push(contentEvent);
eventStructure[0].children.push({
title: group.title,
level: group.level,
eventType: "content",
eventType: "content",
eventKind: 30041,
dTag: generateDTag(group.title),
children: []
children: [],
});
}
return { tree, indexEvent, contentEvents, eventStructure };
}
@ -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;
@ -501,7 +534,7 @@ function buildHierarchicalStructure( @@ -501,7 +534,7 @@ function buildHierarchicalStructure(
} {
const contentEvents: NDKEvent[] = [];
const eventStructure: EventStructureNode[] = [];
// Add root index to structure
eventStructure.push({
title,
@ -509,59 +542,59 @@ function buildHierarchicalStructure( @@ -509,59 +542,59 @@ function buildHierarchicalStructure(
eventType: "index",
eventKind: 30040,
dTag: generateDTag(title),
children: []
children: [],
});
// Build hierarchical structure
const hierarchy = buildSegmentHierarchy(segments);
for (const level2Section of hierarchy) {
if (level2Section.hasChildren) {
// Create 30040 for level 2 section with children
const level2Index = createIndexEventForSection(level2Section, ndk);
contentEvents.push(level2Index);
const level2Node: EventStructureNode = {
title: level2Section.title,
level: level2Section.level,
eventType: "index",
eventKind: 30040,
dTag: generateDTag(level2Section.title),
children: []
children: [],
};
// Add children as 30041 content events
for (const child of level2Section.children) {
const childEvent = createContentEvent(child, ndk);
contentEvents.push(childEvent);
level2Node.children.push({
title: child.title,
level: child.level,
eventType: "content",
eventKind: 30041,
dTag: generateDTag(child.title),
children: []
children: [],
});
}
eventStructure[0].children.push(level2Node);
} else {
// Create 30041 for level 2 section without children
const contentEvent = createContentEvent(level2Section, ndk);
contentEvents.push(contentEvent);
eventStructure[0].children.push({
title: level2Section.title,
level: level2Section.level,
eventType: "content",
eventKind: 30041,
dTag: generateDTag(level2Section.title),
children: []
children: [],
});
}
}
return { tree, indexEvent, contentEvents, eventStructure };
}
@ -572,37 +605,32 @@ function createIndexEvent( @@ -572,37 +605,32 @@ function createIndexEvent(
title: string,
attributes: Record<string, string>,
segments: ContentSegment[],
ndk: NDK
ndk: NDK,
): NDKEvent {
const event = new NDKEvent(ndk);
event.kind = 30040;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = ndk.activeUser?.pubkey || "preview-placeholder-pubkey";
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}`]);
});
event.tags = tags;
console.log(`[TreeProcessor] Index event tags:`, tags.slice(0, 10));
// NKBIP-01: Index events must have empty content
event.content = "";
return event;
}
@ -614,53 +642,53 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent { @@ -614,53 +642,53 @@ function createContentEvent(segment: ContentSegment, ndk: NDK): NDKEvent {
event.kind = 30041;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = ndk.activeUser?.pubkey || "preview-placeholder-pubkey";
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);
event.tags = tags;
event.content = segment.content;
return event;
}
/**
* 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
.toLowerCase()
.replace(/[^\p{L}\p{N}]/gu, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "") || "untitled";
return (
title
.toLowerCase()
.replace(/[^\p{L}\p{N}]/gu, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "") || "untitled"
);
}
/**
@ -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]);
@ -679,15 +707,15 @@ function addDocumentAttributesToTags( @@ -679,15 +707,15 @@ function addDocumentAttributesToTags(
if (attributes.image) tags.push(["image", attributes.image]);
if (attributes.description) tags.push(["summary", attributes.description]);
if (attributes.type) tags.push(["type", attributes.type]);
// 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
tags.push(["p", pubkey]);
// Custom attributes
addCustomAttributes(tags, attributes);
}
@ -697,13 +725,13 @@ function addDocumentAttributesToTags( @@ -697,13 +725,13 @@ 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
addCustomAttributes(tags, attributes);
}
@ -713,24 +741,63 @@ function addSectionAttributesToTags( @@ -713,24 +741,63 @@ 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]) => {
if (!systemAttributes.includes(key) && value && typeof value === "string") {
tags.push([key, value]);
@ -744,88 +811,94 @@ function addCustomAttributes( @@ -744,88 +811,94 @@ function addCustomAttributes(
*/
function groupSegmentsByLevel2(segments: ContentSegment[]): ContentSegment[] {
const level2Groups: ContentSegment[] = [];
// Find all level 2 segments and include their nested content
for (const segment of segments) {
if (segment.level === 2) {
// Find all content that belongs to this level 2 section
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
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,
);
// 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,
});
}
}
return level2Groups;
}
/**
* 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 =>
s.level > 2 &&
s.startLine > level2Segment.startLine &&
(segments.find(next => next.level <= 2 && next.startLine > level2Segment.startLine)?.startLine || Infinity) > s.startLine
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,
);
hierarchy.push({
...level2Segment,
hasChildren: children.length > 0,
children
children,
});
}
return hierarchy;
}
/**
* 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);
event.pubkey = ndk.activeUser?.pubkey || "preview-placeholder-pubkey";
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}`]);
});
event.tags = tags;
// NKBIP-01: Index events must have empty content
event.content = "";
return event;
}
}

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

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
import asciidoctor from "asciidoctor";
import { postProcessAsciidoctorHtml } from "$lib/utils/markup/asciidoctorPostProcessor";
import { getNdkContext } from "$lib/ndk";
const ndk = getNdkContext();
let events: NDKEvent[] = $state([]);
@ -172,34 +172,33 @@ @@ -172,34 +172,33 @@
// AI-NOTE: Check authentication status and redirect if not logged in
// Wait for authentication state to be properly initialized before checking
let authCheckTimeout: ReturnType<typeof setTimeout> | null = null;
$effect(() => {
const user = $userStore;
// Clear any existing timeout
if (authCheckTimeout) {
clearTimeout(authCheckTimeout);
authCheckTimeout = null;
}
// If user is signed in, we're good
if (user.signedIn) {
checkingAuth = false;
return;
}
// If user is not signed in, wait a bit for auth restoration to complete
// This handles the case where the page loads before auth restoration finishes
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;
}
}, 1500); // 1.5 second delay to allow auth restoration to complete
// Cleanup function
return () => {
if (authCheckTimeout) {
@ -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