cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

feat(pub): update publication commands with output options and plaintext support

+231 -110
+24 -4
cmd/publication_commands.go
··· 214 214 isDraft, _ := cmd.Flags().GetBool("draft") 215 215 preview, _ := cmd.Flags().GetBool("preview") 216 216 validate, _ := cmd.Flags().GetBool("validate") 217 + output, _ := cmd.Flags().GetString("output") 218 + plaintext, _ := cmd.Flags().GetBool("plaintext") 219 + txt, _ := cmd.Flags().GetBool("txt") 220 + 221 + if txt { 222 + plaintext = true 223 + } 217 224 218 225 defer c.handler.Close() 219 226 220 227 if preview { 221 - return c.handler.PostPreview(cmd.Context(), noteID, isDraft) 228 + return c.handler.PostPreview(cmd.Context(), noteID, isDraft, output, plaintext) 222 229 } 223 230 224 231 if validate { 225 - return c.handler.PostValidate(cmd.Context(), noteID, isDraft) 232 + return c.handler.PostValidate(cmd.Context(), noteID, isDraft, output, plaintext) 226 233 } 227 234 228 235 return c.handler.Post(cmd.Context(), noteID, isDraft) ··· 231 238 postCmd.Flags().Bool("draft", false, "Create as draft instead of publishing") 232 239 postCmd.Flags().Bool("preview", false, "Show what would be posted without actually posting") 233 240 postCmd.Flags().Bool("validate", false, "Validate markdown conversion without posting") 241 + postCmd.Flags().StringP("output", "o", "", "Write document to file (defaults to JSON format)") 242 + postCmd.Flags().Bool("plaintext", false, "Use plaintext format for output file") 243 + postCmd.Flags().Bool("txt", false, "Alias for --plaintext") 234 244 root.AddCommand(postCmd) 235 245 236 246 patchCmd := &cobra.Command{ ··· 257 267 258 268 preview, _ := cmd.Flags().GetBool("preview") 259 269 validate, _ := cmd.Flags().GetBool("validate") 270 + output, _ := cmd.Flags().GetString("output") 271 + plaintext, _ := cmd.Flags().GetBool("plaintext") 272 + txt, _ := cmd.Flags().GetBool("txt") 273 + 274 + if txt { 275 + plaintext = true 276 + } 260 277 261 278 defer c.handler.Close() 262 279 263 280 if preview { 264 - return c.handler.PatchPreview(cmd.Context(), noteID) 281 + return c.handler.PatchPreview(cmd.Context(), noteID, output, plaintext) 265 282 } 266 283 267 284 if validate { 268 - return c.handler.PatchValidate(cmd.Context(), noteID) 285 + return c.handler.PatchValidate(cmd.Context(), noteID, output, plaintext) 269 286 } 270 287 271 288 return c.handler.Patch(cmd.Context(), noteID) ··· 273 290 } 274 291 patchCmd.Flags().Bool("preview", false, "Show what would be updated without actually patching") 275 292 patchCmd.Flags().Bool("validate", false, "Validate markdown conversion without patching") 293 + patchCmd.Flags().StringP("output", "o", "", "Write document to file (defaults to JSON format)") 294 + patchCmd.Flags().Bool("plaintext", false, "Use plaintext format for output file") 295 + patchCmd.Flags().Bool("txt", false, "Alias for --plaintext") 276 296 root.AddCommand(patchCmd) 277 297 278 298 pushCmd := &cobra.Command{
+99 -4
internal/handlers/publication.go
··· 3 3 4 4 import ( 5 5 "context" 6 + "encoding/json" 6 7 "fmt" 8 + "os" 7 9 "path/filepath" 8 10 "time" 9 11 ··· 549 551 return note, doc, nil 550 552 } 551 553 554 + // writeDocumentOutput writes document to a file in JSON or plaintext format 555 + func writeDocumentOutput(doc *public.Document, note *models.Note, outputPath string, plaintext bool) error { 556 + var content []byte 557 + var err error 558 + 559 + if plaintext { 560 + status := "published" 561 + if note != nil && note.IsDraft { 562 + status = "draft" 563 + } 564 + 565 + output := "Document Preview\n" 566 + output += "================\n\n" 567 + output += fmt.Sprintf("Title: %s\n", doc.Title) 568 + output += fmt.Sprintf("Status: %s\n", status) 569 + if note != nil { 570 + output += fmt.Sprintf("Note ID: %d\n", note.ID) 571 + if note.LeafletRKey != nil { 572 + output += fmt.Sprintf("RKey: %s\n", *note.LeafletRKey) 573 + } 574 + } 575 + output += fmt.Sprintf("Pages: %d\n", len(doc.Pages)) 576 + if len(doc.Pages) > 0 { 577 + output += fmt.Sprintf("Blocks: %d\n", len(doc.Pages[0].Blocks)) 578 + } 579 + if doc.PublishedAt != "" { 580 + output += fmt.Sprintf("PublishedAt: %s\n", doc.PublishedAt) 581 + } 582 + if doc.Author != "" { 583 + output += fmt.Sprintf("Author: %s\n", doc.Author) 584 + } 585 + 586 + content = []byte(output) 587 + } else { 588 + content, err = json.MarshalIndent(doc, "", " ") 589 + if err != nil { 590 + return fmt.Errorf("failed to marshal document to JSON: %w", err) 591 + } 592 + } 593 + 594 + if err := os.WriteFile(outputPath, content, 0644); err != nil { 595 + return fmt.Errorf("failed to write output to %s: %w", outputPath, err) 596 + } 597 + 598 + return nil 599 + } 600 + 552 601 // PostPreview shows what would be posted without actually posting 553 - func (h *PublicationHandler) PostPreview(ctx context.Context, noteID int64, isDraft bool) error { 602 + func (h *PublicationHandler) PostPreview(ctx context.Context, noteID int64, isDraft bool, outputPath string, plaintext bool) error { 554 603 if !h.atproto.IsAuthenticated() { 555 604 return fmt.Errorf("not authenticated - run 'noteleaf pub auth' first") 556 605 } ··· 574 623 ui.Infoln(" PublishedAt: %s", doc.PublishedAt) 575 624 } 576 625 ui.Infoln(" Note ID: %d", note.ID) 626 + 627 + if outputPath != "" { 628 + if err := writeDocumentOutput(doc, note, outputPath, plaintext); err != nil { 629 + return err 630 + } 631 + format := "JSON" 632 + if plaintext { 633 + format = "plaintext" 634 + } 635 + ui.Successln("Output written to %s (%s format)", outputPath, format) 636 + } 637 + 577 638 ui.Successln("Preview complete - no changes made") 578 639 579 640 return nil 580 641 } 581 642 582 643 // PostValidate validates markdown conversion without posting 583 - func (h *PublicationHandler) PostValidate(ctx context.Context, noteID int64, isDraft bool) error { 644 + func (h *PublicationHandler) PostValidate(ctx context.Context, noteID int64, isDraft bool, outputPath string, plaintext bool) error { 584 645 if !h.atproto.IsAuthenticated() { 585 646 return fmt.Errorf("not authenticated - run 'noteleaf pub auth' first") 586 647 } ··· 595 656 ui.Infoln(" Title: %s", doc.Title) 596 657 ui.Infoln(" Blocks converted: %d", len(doc.Pages[0].Blocks)) 597 658 659 + if outputPath != "" { 660 + if err := writeDocumentOutput(doc, note, outputPath, plaintext); err != nil { 661 + return err 662 + } 663 + format := "JSON" 664 + if plaintext { 665 + format = "plaintext" 666 + } 667 + ui.Successln("Output written to %s (%s format)", outputPath, format) 668 + } 669 + 598 670 return nil 599 671 } 600 672 601 673 // PatchPreview shows what would be patched without actually patching 602 - func (h *PublicationHandler) PatchPreview(ctx context.Context, noteID int64) error { 674 + func (h *PublicationHandler) PatchPreview(ctx context.Context, noteID int64, outputPath string, plaintext bool) error { 603 675 if !h.atproto.IsAuthenticated() { 604 676 return fmt.Errorf("not authenticated - run 'noteleaf pub auth' first") 605 677 } ··· 628 700 if doc.PublishedAt != "" { 629 701 ui.Infoln(" PublishedAt: %s", doc.PublishedAt) 630 702 } 703 + 704 + if outputPath != "" { 705 + if err := writeDocumentOutput(doc, note, outputPath, plaintext); err != nil { 706 + return err 707 + } 708 + format := "JSON" 709 + if plaintext { 710 + format = "plaintext" 711 + } 712 + ui.Successln("Output written to %s (%s format)", outputPath, format) 713 + } 714 + 631 715 ui.Successln("Preview complete - no changes made") 632 716 633 717 return nil 634 718 } 635 719 636 720 // PatchValidate validates markdown conversion without patching 637 - func (h *PublicationHandler) PatchValidate(ctx context.Context, noteID int64) error { 721 + func (h *PublicationHandler) PatchValidate(ctx context.Context, noteID int64, outputPath string, plaintext bool) error { 638 722 if !h.atproto.IsAuthenticated() { 639 723 return fmt.Errorf("not authenticated - run 'noteleaf pub auth' first") 640 724 } ··· 654 738 ui.Infoln(" Title: %s", doc.Title) 655 739 ui.Infoln(" RKey: %s", *note.LeafletRKey) 656 740 ui.Infoln(" Blocks converted: %d", len(doc.Pages[0].Blocks)) 741 + 742 + if outputPath != "" { 743 + if err := writeDocumentOutput(doc, note, outputPath, plaintext); err != nil { 744 + return err 745 + } 746 + format := "JSON" 747 + if plaintext { 748 + format = "plaintext" 749 + } 750 + ui.Successln("Output written to %s (%s format)", outputPath, format) 751 + } 657 752 658 753 return nil 659 754 }
+13 -13
internal/handlers/publication_test.go
··· 1019 1019 handler := CreateHandler(t, NewPublicationHandler) 1020 1020 ctx := context.Background() 1021 1021 1022 - err := handler.PostPreview(ctx, 1, false) 1022 + err := handler.PostPreview(ctx, 1, false, "", false) 1023 1023 if err == nil { 1024 1024 t.Error("Expected error when not authenticated") 1025 1025 } ··· 1051 1051 t.Fatalf("Failed to restore session: %v", err) 1052 1052 } 1053 1053 1054 - err = handler.PostPreview(ctx, 999, false) 1054 + err = handler.PostPreview(ctx, 999, false, "", false) 1055 1055 if err == nil { 1056 1056 t.Error("Expected error when note does not exist") 1057 1057 } ··· 1095 1095 t.Fatalf("Failed to restore session: %v", err) 1096 1096 } 1097 1097 1098 - err = handler.PostPreview(ctx, id, false) 1098 + err = handler.PostPreview(ctx, id, false, "", false) 1099 1099 if err == nil { 1100 1100 t.Error("Expected error when note already published") 1101 1101 } ··· 1135 1135 t.Fatalf("Failed to restore session: %v", err) 1136 1136 } 1137 1137 1138 - err = handler.PostPreview(ctx, id, false) 1138 + err = handler.PostPreview(ctx, id, false, "", false) 1139 1139 suite.AssertNoError(err, "preview should succeed") 1140 1140 }) 1141 1141 ··· 1169 1169 t.Fatalf("Failed to restore session: %v", err) 1170 1170 } 1171 1171 1172 - err = handler.PostPreview(ctx, id, true) 1172 + err = handler.PostPreview(ctx, id, true, "", false) 1173 1173 suite.AssertNoError(err, "preview draft should succeed") 1174 1174 }) 1175 1175 }) ··· 1182 1182 handler := CreateHandler(t, NewPublicationHandler) 1183 1183 ctx := context.Background() 1184 1184 1185 - err := handler.PostValidate(ctx, 1, false) 1185 + err := handler.PostValidate(ctx, 1, false, "", false) 1186 1186 if err == nil { 1187 1187 t.Error("Expected error when not authenticated") 1188 1188 } ··· 1222 1222 t.Fatalf("Failed to restore session: %v", err) 1223 1223 } 1224 1224 1225 - err = handler.PostValidate(ctx, id, false) 1225 + err = handler.PostValidate(ctx, id, false, "", false) 1226 1226 suite.AssertNoError(err, "validation should succeed") 1227 1227 }) 1228 1228 }) ··· 1235 1235 handler := CreateHandler(t, NewPublicationHandler) 1236 1236 ctx := context.Background() 1237 1237 1238 - err := handler.PatchPreview(ctx, 1) 1238 + err := handler.PatchPreview(ctx, 1, "", false) 1239 1239 if err == nil { 1240 1240 t.Error("Expected error when not authenticated") 1241 1241 } ··· 1267 1267 t.Fatalf("Failed to restore session: %v", err) 1268 1268 } 1269 1269 1270 - err = handler.PatchPreview(ctx, 999) 1270 + err = handler.PatchPreview(ctx, 999, "", false) 1271 1271 if err == nil { 1272 1272 t.Error("Expected error when note does not exist") 1273 1273 } ··· 1307 1307 t.Fatalf("Failed to restore session: %v", err) 1308 1308 } 1309 1309 1310 - err = handler.PatchPreview(ctx, id) 1310 + err = handler.PatchPreview(ctx, id, "", false) 1311 1311 if err == nil { 1312 1312 t.Error("Expected error when note not published") 1313 1313 } ··· 1354 1354 t.Fatalf("Failed to restore session: %v", err) 1355 1355 } 1356 1356 1357 - err = handler.PatchPreview(ctx, id) 1357 + err = handler.PatchPreview(ctx, id, "", false) 1358 1358 suite.AssertNoError(err, "preview should succeed") 1359 1359 }) 1360 1360 }) ··· 1367 1367 handler := CreateHandler(t, NewPublicationHandler) 1368 1368 ctx := context.Background() 1369 1369 1370 - err := handler.PatchValidate(ctx, 1) 1370 + err := handler.PatchValidate(ctx, 1, "", false) 1371 1371 if err == nil { 1372 1372 t.Error("Expected error when not authenticated") 1373 1373 } ··· 1412 1412 t.Fatalf("Failed to restore session: %v", err) 1413 1413 } 1414 1414 1415 - err = handler.PatchValidate(ctx, id) 1415 + err = handler.PatchValidate(ctx, id, "", false) 1416 1416 suite.AssertNoError(err, "validation should succeed") 1417 1417 }) 1418 1418 })
+18 -11
internal/ui/publication_list_adapter.go
··· 170 170 return NewPublicationDataList(repo, opts, filter) 171 171 } 172 172 173 - // formatPublicationForView formats a publication for display with glamour 174 - func formatPublicationForView(note *models.Note) string { 173 + // buildPublicationMarkdown builds markdown content for a publication without rendering 174 + func buildPublicationMarkdown(note *models.Note) string { 175 175 var content strings.Builder 176 176 177 177 content.WriteString("# " + note.Title + "\n\n") ··· 180 180 if note.IsDraft { 181 181 status = "draft" 182 182 } 183 - content.WriteString("**Status:** " + status + "\n") 183 + content.WriteString("- **Status:** " + status + "\n") 184 184 185 185 if note.PublishedAt != nil { 186 - content.WriteString("**Published:** " + note.PublishedAt.Format("2006-01-02 15:04") + "\n") 186 + content.WriteString("- **Published:** " + note.PublishedAt.Format("2006-01-02 15:04") + "\n") 187 187 } 188 188 189 - content.WriteString("**Modified:** " + note.Modified.Format("2006-01-02 15:04") + "\n") 189 + content.WriteString("- **Modified:** " + note.Modified.Format("2006-01-02 15:04") + "\n") 190 190 191 191 if note.LeafletRKey != nil { 192 - content.WriteString("**RKey:** `" + *note.LeafletRKey + "`\n") 192 + content.WriteString("- **RKey:** `" + ObfuscateMiddle(*note.LeafletRKey, 3, 3) + "`\n") 193 193 } 194 194 195 195 if note.LeafletCID != nil { 196 - content.WriteString("**CID:** `" + *note.LeafletCID + "`\n") 196 + content.WriteString("- **CID:** `" + ObfuscateMiddle(*note.LeafletCID, 3, 3) + "`\n") 197 197 } 198 198 199 199 content.WriteString("\n---\n\n") ··· 208 208 } 209 209 } 210 210 211 + return content.String() 212 + } 213 + 214 + // formatPublicationForView formats a publication for display with glamour 215 + func formatPublicationForView(note *models.Note) string { 216 + markdown := buildPublicationMarkdown(note) 217 + 211 218 renderer, err := glamour.NewTermRenderer( 212 - glamour.WithAutoStyle(), 219 + glamour.WithStandardStyle("tokyo-night"), 213 220 glamour.WithWordWrap(80), 214 221 ) 215 222 if err != nil { 216 - return content.String() 223 + return markdown 217 224 } 218 225 219 - rendered, err := renderer.Render(content.String()) 226 + rendered, err := renderer.Render(markdown) 220 227 if err != nil { 221 - return content.String() 228 + return markdown 222 229 } 223 230 224 231 return rendered
+18 -24
internal/ui/publication_list_adapter_test.go
··· 493 493 } 494 494 }) 495 495 496 - t.Run("formatPublicationForView", func(t *testing.T) { 496 + t.Run("buildPublicationMarkdown", func(t *testing.T) { 497 497 t.Run("formats published note with all metadata", func(t *testing.T) { 498 498 rkey := "test-rkey" 499 499 cid := "test-cid" ··· 510 510 LeafletCID: &cid, 511 511 } 512 512 513 - result := formatPublicationForView(note) 513 + result := buildPublicationMarkdown(note) 514 514 515 515 if !strings.Contains(result, "Test Article") { 516 - t.Errorf("Formatted view should contain title\nGot: %s", result) 516 + t.Error("Markdown should contain title") 517 517 } 518 518 if !strings.Contains(result, "published") { 519 - t.Errorf("Formatted view should contain status 'published'\nGot: %s", result) 519 + t.Error("Markdown should contain status 'published'") 520 520 } 521 521 if !strings.Contains(result, "2024-01-15") { 522 - t.Errorf("Formatted view should contain published date\nGot: %s", result) 522 + t.Error("Markdown should contain published date") 523 523 } 524 - if !strings.Contains(result, "Modified") && !strings.Contains(result, "2024-01-16") { 525 - t.Errorf("Formatted view should contain modified date\nGot: %s", result) 526 - } 527 - if !strings.Contains(result, "test-rkey") { 528 - t.Error("Formatted view should contain rkey") 529 - } 530 - if !strings.Contains(result, "test-cid") { 531 - t.Error("Formatted view should contain cid") 524 + if !strings.Contains(result, "2024-01-16") { 525 + t.Error("Markdown should contain modified date") 532 526 } 533 527 }) 534 528 ··· 541 535 Modified: time.Date(2024, 1, 20, 14, 0, 0, 0, time.UTC), 542 536 } 543 537 544 - result := formatPublicationForView(note) 538 + result := buildPublicationMarkdown(note) 545 539 546 540 if !strings.Contains(result, "Draft Article") { 547 - t.Error("Formatted view should contain title") 541 + t.Error("Markdown should contain title") 548 542 } 549 543 if !strings.Contains(result, "draft") { 550 - t.Error("Formatted view should contain status 'draft'") 544 + t.Error("Markdown should contain status 'draft'") 551 545 } 552 546 if strings.Contains(result, "Published:") { 553 - t.Error("Formatted draft view should not contain published date") 547 + t.Error("Draft markdown should not contain published date") 554 548 } 555 549 if !strings.Contains(result, "2024-01-20 14:00") { 556 - t.Error("Formatted view should contain modified date") 550 + t.Error("Markdown should contain modified date") 557 551 } 558 552 }) 559 553 ··· 566 560 Modified: time.Now(), 567 561 } 568 562 569 - result := formatPublicationForView(note) 563 + result := buildPublicationMarkdown(note) 570 564 571 565 if !strings.Contains(result, "Plain Content") { 572 - t.Error("Formatted view should contain title") 566 + t.Error("Markdown should contain title") 573 567 } 574 568 if !strings.Contains(result, "This content has no markdown header") { 575 - t.Error("Formatted view should contain full content") 569 + t.Error("Markdown should contain full content") 576 570 } 577 571 }) 578 572 ··· 585 579 Modified: time.Now(), 586 580 } 587 581 588 - result := formatPublicationForView(note) 582 + result := buildPublicationMarkdown(note) 589 583 590 584 titleCount := strings.Count(result, "Article Title") 591 585 if titleCount < 1 { 592 - t.Error("Formatted view should contain title at least once") 586 + t.Error("Markdown should contain title at least once") 593 587 } 594 588 if !strings.Contains(result, "Content after title") { 595 - t.Error("Formatted view should contain content after title") 589 + t.Error("Markdown should contain content after title") 596 590 } 597 591 }) 598 592 })
+48 -43
internal/ui/publication_view.go
··· 5 5 "fmt" 6 6 "io" 7 7 "os" 8 - "strings" 8 + "unicode/utf8" 9 9 10 10 "github.com/charmbracelet/bubbles/help" 11 11 "github.com/charmbracelet/bubbles/key" ··· 173 173 return lipgloss.JoinVertical(lipgloss.Left, title, "", content, "", help) 174 174 } 175 175 176 - // buildPublicationMarkdown creates the markdown content for rendering 177 - func buildPublicationMarkdown(note *models.Note) string { 178 - var content strings.Builder 179 - 180 - content.WriteString("# " + note.Title + "\n\n") 181 - 182 - status := "published" 183 - if note.IsDraft { 184 - status = "draft" 185 - } 186 - content.WriteString("**Status:** " + status + "\n") 187 - 188 - if note.PublishedAt != nil { 189 - content.WriteString("**Published:** " + note.PublishedAt.Format("2006-01-02 15:04") + "\n") 190 - } 191 - 192 - content.WriteString("**Modified:** " + note.Modified.Format("2006-01-02 15:04") + "\n") 193 - 194 - if note.LeafletRKey != nil { 195 - content.WriteString("**RKey:** `" + *note.LeafletRKey + "`\n") 196 - } 197 - 198 - if note.LeafletCID != nil { 199 - content.WriteString("**CID:** `" + *note.LeafletCID + "`\n") 200 - } 201 - 202 - content.WriteString("\n---\n\n") 203 - 204 - noteContent := strings.TrimSpace(note.Content) 205 - if !strings.HasPrefix(noteContent, "# ") { 206 - content.WriteString(noteContent) 207 - } else { 208 - lines := strings.Split(noteContent, "\n") 209 - if len(lines) > 1 { 210 - content.WriteString(strings.Join(lines[1:], "\n")) 211 - } 212 - } 213 - 214 - return content.String() 215 - } 216 - 217 176 // formatPublicationContent renders markdown with glamour for viewport display 218 177 func formatPublicationContent(note *models.Note) (string, error) { 219 178 markdown := buildPublicationMarkdown(note) 220 179 221 180 renderer, err := glamour.NewTermRenderer( 222 181 glamour.WithAutoStyle(), 223 - glamour.WithWordWrap(80), 182 + glamour.WithStandardStyle("tokyo-night"), 183 + glamour.WithPreservedNewLines(), 184 + glamour.WithWordWrap(79), 224 185 ) 225 186 if err != nil { 226 187 return markdown, fmt.Errorf("failed to create renderer: %w", err) ··· 277 238 fmt.Fprint(pv.opts.Output, content) 278 239 return nil 279 240 } 241 + 242 + // ObfuscateMiddle returns a string where the middle portion is replaced by "..." 243 + // TODO: move to package utils or shared 244 + func ObfuscateMiddle(s string, left, right int) string { 245 + if s == "" { 246 + return s 247 + } 248 + if left < 0 { 249 + left = 0 250 + } 251 + if right < 0 { 252 + right = 0 253 + } 254 + 255 + n := utf8.RuneCountInString(s) 256 + if left+right >= n { 257 + return s 258 + } 259 + 260 + var ( 261 + prefixRunes = make([]rune, 0, left) 262 + suffixRunes = make([]rune, 0, right) 263 + ) 264 + i := 0 265 + for _, r := range s { 266 + if i >= left { 267 + break 268 + } 269 + prefixRunes = append(prefixRunes, r) 270 + i++ 271 + } 272 + 273 + if right > 0 { 274 + allRunes := []rune(s) 275 + start := max(n-right, 0) 276 + suffixRunes = append(suffixRunes, allRunes[start:]...) 277 + } 278 + 279 + const repl = "..." 280 + if right == 0 { 281 + return string(prefixRunes) + repl 282 + } 283 + return string(prefixRunes) + repl + string(suffixRunes) 284 + }
+11 -11
internal/ui/publication_view_test.go
··· 138 138 139 139 output := buf.String() 140 140 141 - if !strings.Contains(output, "Test Publication") { 141 + if !strings.Contains(output, "Test") || !strings.Contains(output, "Publication") { 142 142 t.Error("Note title not displayed") 143 143 } 144 144 if !strings.Contains(output, "published") { ··· 153 153 if !strings.Contains(output, "RKey:") { 154 154 t.Error("RKey not displayed") 155 155 } 156 - if !strings.Contains(output, "test-rkey-123") { 156 + if !strings.Contains(output, "tes") || !strings.Contains(output, "123") { 157 157 t.Error("RKey value not displayed") 158 158 } 159 159 if !strings.Contains(output, "CID:") { 160 160 t.Error("CID not displayed") 161 161 } 162 - if !strings.Contains(output, "test-cid-456") { 162 + if !strings.Contains(output, "tes") || !strings.Contains(output, "456") { 163 163 t.Error("CID value not displayed") 164 164 } 165 - if !strings.Contains(output, "This is the content") { 165 + if !strings.Contains(output, "This") || !strings.Contains(output, "content") { 166 166 t.Error("Note content not displayed") 167 167 } 168 168 }) ··· 213 213 214 214 output := buf.String() 215 215 216 - if !strings.Contains(output, "Minimal Note") { 216 + if !strings.Contains(output, "Minimal") || !strings.Contains(output, "Note") { 217 217 t.Error("Note title not displayed") 218 218 } 219 - if !strings.Contains(output, "Simple content") { 219 + if !strings.Contains(output, "Simple") || !strings.Contains(output, "content") { 220 220 t.Error("Note content not displayed") 221 221 } 222 222 if !strings.Contains(output, "Modified:") { ··· 235 235 "**Status:** published", 236 236 "**Published:**", 237 237 "**Modified:**", 238 - "**RKey:** `test-rkey-123`", 239 - "**CID:** `test-cid-456`", 238 + "**RKey:**", 239 + "**CID:**", 240 240 "---", 241 241 "This is the content", 242 242 } ··· 308 308 t.Fatalf("formatPublicationContent failed: %v", err) 309 309 } 310 310 311 - if !strings.Contains(content, "Test Publication") { 311 + if !strings.Contains(content, "Test") || !strings.Contains(content, "Publication") { 312 312 t.Error("Formatted content should include note title") 313 313 } 314 314 }) ··· 634 634 t.Error("No output generated") 635 635 } 636 636 637 - if !strings.Contains(output, note.Title) { 637 + if !strings.Contains(output, "Test") || !strings.Contains(output, "Publication") { 638 638 t.Error("Note title not displayed") 639 639 } 640 - if !strings.Contains(output, "This is the content") { 640 + if !strings.Contains(output, "This") || !strings.Contains(output, "content") { 641 641 t.Error("Note content not displayed") 642 642 } 643 643 })