import { fromHtml } from 'hast-util-from-html'
import { toHtml } from 'hast-util-to-html'
import type { Element, ElementContent, Node, Root, RootContent, Parent } from "hast";
import type { StoreDocument } from "../types";
import type { ObsidianDocument } from "../schemas";
import type { Wikilink } from './wikiLink';
import type { AstroIntegrationLogger } from 'astro';
const isElement = (node: Node): node is Element => node.type === "element";
const getTextFromElement = (el: Element): string => {
return el.children
.filter(child => child.type === 'text')
.map(child => child.value)
.join('')
.trim();
};
const collectSection = (parent: Parent, sectionStart: string): RootContent[] | null => {
const { children } = parent;
const result: RootContent[] = [];
let capturing = false;
let startLevel = 0;
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (!node){
continue;
}
// --- si ya estamos capturando, añadimos hasta que aparezca corte
if (capturing) {
if (isElement(node)) {
const tag = node.tagName;
if (/^h[1-6]$/i.test(tag)) {
const level = Number.parseInt(tag.charAt(1), 10);
if (level <= startLevel) break; // corte en mismo nivel o superior
}
}
result.push(node);
continue;
}
// --- aún no estamos capturando: ¿es el heading de inicio?
if (isElement(node)) {
const tag = node.tagName;
if (/^h[1-6]$/i.test(tag)) {
const headingText = getTextFromElement(node);
if (headingText === sectionStart) {
startLevel = Number.parseInt(tag.charAt(1), 10);
capturing = true;
result.push(node); // incluir el propio heading
continue;
}
}
// Buscar recursivamente dentro de este elemento
const deep = collectSection(node as unknown as Parent, sectionStart);
if (deep?.length) return deep;
}
// nodos no-element no disparan nada mientras no capturamos
}
return capturing ? result : null;
}
const getSectionFromRoot = (root: Root, sectionStart: string): RootContent[] => {
const out = collectSection(root as unknown as Parent, sectionStart);
return out ?? [];
};
export const renderEmbed = async (htmlBody: string, link: Wikilink, document: StoreDocument, logger: AstroIntegrationLogger) => {
const [_, section] = link.link.caption?.split('#') ?? '';
if (!document) {
logger.warn(`Embed file ${link.link.id} not found`);
return htmlBody;
}
const root = fromHtml(document.rendered.html, { fragment: true });
const children = section ? getSectionFromRoot(root, section) : root.children;
const embed = toHtml([{
type: 'element',
tagName: 'div',
properties: {
class: 'file-embed'
},
children: [
{
type: 'element',
tagName: 'a',
properties: {
class: 'file-embed-link',
href: document.data.permalink,
},
children: [
{
type: 'element',
tagName: 'span',
children: [
{
type: 'text',
value: 'Open Link'
},
]
},
]
},
!section && {
type: 'element',
tagName: 'h5',
properties: {
class: 'file-embed-title'
},
children: [
{
type: 'text',
value: document.data.title,
},
]
},
{
type: 'element',
tagName: 'div',
properties: {
class: 'file-embed-content'
},
children,
}
].filter(Boolean) as ElementContent[]
}]);
return embed;
}