|
1 | 1 | import * as path from "path"; |
2 | 2 | import * as fs from "fs"; |
3 | 3 | import md5 from "md5"; |
4 | | -import { TAbstractFile, TFile, TFolder, htmlToMarkdown, CachedMetadata } from "obsidian"; |
| 4 | +import { TAbstractFile, TFile, TFolder, CachedMetadata } from "obsidian"; |
5 | 5 |
|
6 | 6 | import { |
7 | 7 | ATTACHMENT_URL_REGEXP, |
@@ -449,38 +449,159 @@ export async function tryCopyMarkdown( |
449 | 449 | } |
450 | 450 | } |
451 | 451 |
|
| 452 | +/** |
| 453 | + * Extract heading content from a file (from heading to next heading of same or higher level) |
| 454 | + */ |
| 455 | +async function getHeadingContent( |
| 456 | + plugin: MarkdownExportPlugin, |
| 457 | + filePath: string, |
| 458 | + heading: string |
| 459 | +): Promise<string | null> { |
| 460 | + try { |
| 461 | + const file = plugin.app.vault.getAbstractFileByPath(filePath); |
| 462 | + if (!(file instanceof TFile)) { |
| 463 | + return null; |
| 464 | + } |
| 465 | + |
| 466 | + const content = await plugin.app.vault.cachedRead(file); |
| 467 | + const lines = content.split("\n"); |
| 468 | + |
| 469 | + // Normalize heading for comparison (remove # and spaces, lowercase) |
| 470 | + const normalizedTarget = heading.toLowerCase().trim(); |
| 471 | + |
| 472 | + // Find the heading line |
| 473 | + let startIndex = -1; |
| 474 | + let headingLevel = 0; |
| 475 | + |
| 476 | + for (let i = 0; i < lines.length; i++) { |
| 477 | + const headingMatch = lines[i].match(/^(#{1,6})\s+(.+)$/); |
| 478 | + if (headingMatch) { |
| 479 | + const level = headingMatch[1].length; |
| 480 | + const text = headingMatch[2].toLowerCase().trim(); |
| 481 | + |
| 482 | + if (text === normalizedTarget) { |
| 483 | + startIndex = i; |
| 484 | + headingLevel = level; |
| 485 | + break; |
| 486 | + } |
| 487 | + } |
| 488 | + } |
| 489 | + |
| 490 | + if (startIndex === -1) { |
| 491 | + return null; |
| 492 | + } |
| 493 | + |
| 494 | + // Find the end (next heading of same or higher level, or end of file) |
| 495 | + let endIndex = lines.length; |
| 496 | + for (let i = startIndex + 1; i < lines.length; i++) { |
| 497 | + const nextHeadingMatch = lines[i].match(/^(#{1,6})\s/); |
| 498 | + if (nextHeadingMatch) { |
| 499 | + const nextLevel = nextHeadingMatch[1].length; |
| 500 | + if (nextLevel <= headingLevel) { |
| 501 | + endIndex = i; |
| 502 | + break; |
| 503 | + } |
| 504 | + } |
| 505 | + } |
| 506 | + |
| 507 | + const headingLines = lines.slice(startIndex, endIndex); |
| 508 | + return headingLines.join("\n"); |
| 509 | + } catch (error) { |
| 510 | + console.error("Error getting heading content:", error); |
| 511 | + return null; |
| 512 | + } |
| 513 | +} |
| 514 | + |
| 515 | +/** |
| 516 | + * Get embed content by reading raw markdown from the source file |
| 517 | + * This preserves code blocks (like mermaid) in their original format |
| 518 | + */ |
| 519 | +async function getEmbedContentFromSource( |
| 520 | + plugin: MarkdownExportPlugin, |
| 521 | + embedLink: string, |
| 522 | + currentPath: string |
| 523 | +): Promise<string | null> { |
| 524 | + const parsed = parseEmbedLink(embedLink, currentPath); |
| 525 | + |
| 526 | + if (!parsed.filePath) { |
| 527 | + return null; |
| 528 | + } |
| 529 | + |
| 530 | + try { |
| 531 | + const file = plugin.app.vault.getAbstractFileByPath(parsed.filePath); |
| 532 | + if (!(file instanceof TFile)) { |
| 533 | + return null; |
| 534 | + } |
| 535 | + |
| 536 | + let content = await plugin.app.vault.cachedRead(file); |
| 537 | + |
| 538 | + // Extract specific section if needed |
| 539 | + if (parsed.blockId) { |
| 540 | + const blockContent = await getBlockContent( |
| 541 | + plugin, |
| 542 | + parsed.filePath, |
| 543 | + parsed.blockId |
| 544 | + ); |
| 545 | + // Remove YAML header if configured (in case block is at start of file) |
| 546 | + if (plugin.settings.removeYamlHeader && blockContent) { |
| 547 | + return blockContent.replace(EMBED_METADATA_REGEXP, ""); |
| 548 | + } |
| 549 | + return blockContent; |
| 550 | + } |
| 551 | + |
| 552 | + if (parsed.heading) { |
| 553 | + const headingContent = await getHeadingContent( |
| 554 | + plugin, |
| 555 | + parsed.filePath, |
| 556 | + parsed.heading |
| 557 | + ); |
| 558 | + // Remove YAML header if configured (in case heading is at start of file) |
| 559 | + if (plugin.settings.removeYamlHeader && headingContent) { |
| 560 | + return headingContent.replace(EMBED_METADATA_REGEXP, ""); |
| 561 | + } |
| 562 | + return headingContent; |
| 563 | + } |
| 564 | + |
| 565 | + // Remove YAML header if configured for full file content |
| 566 | + if (plugin.settings.removeYamlHeader) { |
| 567 | + content = content.replace(EMBED_METADATA_REGEXP, ""); |
| 568 | + } |
| 569 | + |
| 570 | + return content; |
| 571 | + } catch (error) { |
| 572 | + console.error("Error getting embed content from source:", error); |
| 573 | + return null; |
| 574 | + } |
| 575 | +} |
| 576 | + |
452 | 577 | export async function getEmbedMap( |
453 | 578 | plugin: MarkdownExportPlugin, |
454 | 579 | content: string, |
455 | 580 | path: string |
456 | 581 | ) { |
457 | | - // key:link url |
458 | | - // value: embed content parse from html document |
459 | 582 | const embedMap = new Map(); |
460 | | - const embedList = Array.from( |
461 | | - document.documentElement.getElementsByClassName("internal-embed") |
462 | | - ); |
| 583 | + const embeds = await getEmbeds(content); |
463 | 584 |
|
464 | | - Array.from(embedList).forEach((el) => { |
465 | | - // markdown-embed-content markdown-embed-page |
466 | | - const embedContentHtml = el.getElementsByClassName( |
467 | | - "markdown-embed-content" |
468 | | - )[0]; |
| 585 | + for (const embedMatch of embeds) { |
| 586 | + const embedLink = embedMatch[1]; |
469 | 587 |
|
470 | | - if (embedContentHtml) { |
471 | | - let embedValue = htmlToMarkdown(embedContentHtml.innerHTML); |
472 | | - if (plugin.settings.removeYamlHeader) { |
473 | | - embedValue = embedValue.replace(EMBED_METADATA_REGEXP, ""); |
474 | | - } |
475 | | - embedValue = |
| 588 | + // Try to get content from raw markdown source |
| 589 | + const rawContent = await getEmbedContentFromSource( |
| 590 | + plugin, |
| 591 | + embedLink, |
| 592 | + path |
| 593 | + ); |
| 594 | + |
| 595 | + if (rawContent !== null) { |
| 596 | + // Format as quote block |
| 597 | + const embedValue = |
476 | 598 | "> " + |
477 | | - (embedValue as string) |
| 599 | + rawContent |
478 | 600 | .replaceAll("# \n\n", "# ") |
479 | 601 | .replaceAll("\n", "\n> "); |
480 | | - const embedKey = el.getAttribute("src"); |
481 | | - embedMap.set(embedKey, embedValue); |
| 602 | + embedMap.set(embedLink, embedValue); |
482 | 603 | } |
483 | | - }); |
| 604 | + } |
484 | 605 |
|
485 | 606 | return embedMap; |
486 | 607 | } |
|
0 commit comments