:::")',
+ )
+ }
+
+ let label: Element | string | null = null
+
+ if (properties && properties['has-directive-label']) {
+ const [firstChild, ...restChildren] = children
+ children = restChildren
+
+ if (firstChild && firstChild.type === 'element') {
+ label = firstChild as Element
+ label.tagName = 'div' // Change the tag to
+ }
+ else {
+ label = ''
+ }
+ }
+
+ return h('blockquote', { class: `admonition bdm-${type}` }, [
+ h('span', { class: 'bdm-title' }, label || type.toUpperCase()),
+ ...(children as Element[]),
+ ] as Element[])
+}
diff --git a/src/plugins/rehype-component-github-card.ts b/src/plugins/rehype-component-github-card.ts
new file mode 100644
index 0000000..84c0253
--- /dev/null
+++ b/src/plugins/rehype-component-github-card.ts
@@ -0,0 +1,108 @@
+///
+import { h } from 'hastscript'
+
+/**
+ * Creates a GitHub Card component.
+ *
+ * @param {object} properties - The properties of the component.
+ * @param {string} properties.repo - The GitHub repository in the format "owner/repo".
+ * @param {import('mdast').RootContent[]} children - The children elements of the component.
+ * @returns {import('mdast').Parent} The created GitHub Card component.
+ */
+export function GithubCardComponent(
+ properties: { repo: string },
+ children: import('mdast').RootContent[],
+): import('mdast').Parent {
+ if (Array.isArray(children) && children.length !== 0) {
+ return h('div', { class: 'hidden' }, [
+ 'Invalid directive. ("github" directive must be leaf type "::github{repo="owner/repo"}")',
+ ]) as unknown as import('mdast').Parent
+ }
+
+ if (!properties.repo || !properties.repo.includes('/')) {
+ return h(
+ 'div',
+ { class: 'hidden' },
+ 'Invalid repository. ("repo" attributte must be in the format "owner/repo")',
+ ) as unknown as import('mdast').Parent
+ }
+
+ const repo = properties.repo
+ const cardUuid = `GC${Math.random().toString(36).slice(-6)}` // Collisions are not important
+
+ const nAvatar = h(`div#${cardUuid}-avatar`, { class: 'gc-avatar' })
+ const nLanguage = h(
+ `span#${cardUuid}-language`,
+ { class: 'gc-language' },
+ 'Waiting...',
+ )
+
+ const nTitle = h(`div`, { class: 'gc-titlebar' }, [
+ h('div', { class: 'gc-titlebar-left' }, [
+ h('div', { class: 'gc-owner' }, [
+ nAvatar,
+ h('div', { class: 'gc-user' }, repo.split('/')[0]),
+ ]),
+ h('div', { class: 'gc-divider' }, '/'),
+ h('div', { class: 'gc-repo' }, repo.split('/')[1]),
+ ]),
+ h('div', { class: 'github-logo' }),
+ ])
+
+ const nDescription = h(
+ `div#${cardUuid}-description`,
+ { class: 'gc-description' },
+ 'Waiting for api.github.com...',
+ )
+
+ const nStars = h(`div#${cardUuid}-stars`, { class: 'gc-stars' }, '00K')
+ const nForks = h(`div#${cardUuid}-forks`, { class: 'gc-forks' }, '0K')
+ const nLicense = h(`div#${cardUuid}-license`, { class: 'gc-license' }, '0K')
+
+ const nScript = h(
+ `script#${cardUuid}-script`,
+ { type: 'text/javascript', defer: true },
+ `
+ fetch('https://api.github.com/repos/${repo}', { referrerPolicy: "no-referrer" }).then(response => response.json()).then(data => {
+ if (data.description) {
+ document.getElementById('${cardUuid}-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, '');
+ } else {
+ document.getElementById('${cardUuid}-description').innerText = "Description not set"
+ }
+ document.getElementById('${cardUuid}-language').innerText = data.language;
+ document.getElementById('${cardUuid}-forks').innerText = Intl.NumberFormat('en-us', { notation: "compact", maximumFractionDigits: 1 }).format(data.forks).replaceAll("\\u202F", '');
+ document.getElementById('${cardUuid}-stars').innerText = Intl.NumberFormat('en-us', { notation: "compact", maximumFractionDigits: 1 }).format(data.stargazers_count).replaceAll("\\u202F", '');
+ const avatarEl = document.getElementById('${cardUuid}-avatar');
+ avatarEl.style.backgroundImage = 'url(' + data.owner.avatar_url + ')';
+ avatarEl.style.backgroundColor = 'transparent';
+ if (data.license?.spdx_id) {
+ document.getElementById('${cardUuid}-license').innerText = data.license?.spdx_id
+ } else {
+ document.getElementById('${cardUuid}-license').innerText = "no-license"
+ };
+ document.getElementById('${cardUuid}-card').classList.remove("fetch-waiting");
+ console.log("[GITHUB-CARD] Loaded card for ${repo} | ${cardUuid}.")
+ }).catch(err => {
+ const c = document.getElementById('${cardUuid}-card');
+ c.classList.add("fetch-error");
+ console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
+ })
+ `,
+ )
+
+ return h(
+ `a#${cardUuid}-card`,
+ {
+ class: 'card-github fetch-waiting no-styling',
+ href: `https://github.com/${repo}`,
+ target: '_blank',
+ repo,
+ },
+ [
+ nTitle,
+ nDescription,
+ h('div', { class: 'gc-infobar' }, [nStars, nForks, nLicense, nLanguage]),
+ nScript,
+ ],
+ ) as unknown as import('mdast').Parent
+}
diff --git a/src/plugins/remark-directive-rehype.ts b/src/plugins/remark-directive-rehype.ts
new file mode 100644
index 0000000..e3b085e
--- /dev/null
+++ b/src/plugins/remark-directive-rehype.ts
@@ -0,0 +1,44 @@
+import type { Node } from 'unist'
+import { h } from 'hastscript'
+import { visit } from 'unist-util-visit'
+
+interface DirectiveNode extends Node {
+ type: 'containerDirective' | 'leafDirective' | 'textDirective'
+ name: string
+ attributes?: Record
+ children: Array<{
+ data?: {
+ directiveLabel?: boolean
+ }
+ }>
+ data?: {
+ hName?: string
+ hProperties?: Record
+ }
+}
+
+export function parseDirectiveNode() {
+ return (tree: Node) => {
+ visit(tree, (node: unknown) => {
+ const directiveNode = node as DirectiveNode
+ if (
+ directiveNode.type === 'containerDirective'
+ || directiveNode.type === 'leafDirective'
+ || directiveNode.type === 'textDirective'
+ ) {
+ const data = directiveNode.data || (directiveNode.data = {})
+ directiveNode.attributes = directiveNode.attributes || {}
+ if (
+ directiveNode.children.length > 0
+ && directiveNode.children[0].data?.directiveLabel
+ ) {
+ directiveNode.attributes['has-directive-label'] = true
+ }
+ const hast = h(directiveNode.name, directiveNode.attributes)
+
+ data.hName = hast.tagName
+ data.hProperties = hast.properties
+ }
+ })
+ }
+}
diff --git a/src/plugins/remark-excerpt.ts b/src/plugins/remark-excerpt.ts
new file mode 100644
index 0000000..a2e3e90
--- /dev/null
+++ b/src/plugins/remark-excerpt.ts
@@ -0,0 +1,26 @@
+import type { Root } from 'mdast'
+import { toString } from 'mdast-util-to-string'
+
+interface AstroData {
+ data: {
+ astro: {
+ frontmatter: {
+ excerpt: string
+ }
+ }
+ }
+}
+
+export function remarkExcerpt() {
+ return (tree: Root, file: AstroData) => {
+ let excerpt = ''
+ for (const node of tree.children) {
+ if (node.type !== 'paragraph') {
+ continue
+ }
+ excerpt = toString(node)
+ break
+ }
+ file.data.astro.frontmatter.excerpt = excerpt
+ }
+}
diff --git a/src/plugins/remark-reading-time.ts b/src/plugins/remark-reading-time.ts
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/src/plugins/remark-reading-time.ts
@@ -0,0 +1 @@
+
\ No newline at end of file