Compare commits
3 Commits
af40ebc5e6
...
237e5d403b
| Author | SHA1 | Date | |
|---|---|---|---|
| 237e5d403b | |||
| e05295e003 | |||
| 45cfc6acc4 |
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faListUl } from '@fortawesome/free-solid-svg-icons';
|
import { faListUl } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ export function PostToc({ onLinkClick }: { onLinkClick?: () => void }) {
|
|||||||
const listRef = useRef<HTMLDivElement | null>(null);
|
const listRef = useRef<HTMLDivElement | null>(null);
|
||||||
const itemRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
const itemRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||||
const [indicator, setIndicator] = useState({ top: 0, opacity: 0 });
|
const [indicator, setIndicator] = useState({ top: 0, opacity: 0 });
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const headings = Array.from(
|
const headings = Array.from(
|
||||||
@@ -51,7 +53,7 @@ export function PostToc({ onLinkClick }: { onLinkClick?: () => void }) {
|
|||||||
headings.forEach((el) => observer.observe(el));
|
headings.forEach((el) => observer.observe(el));
|
||||||
|
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, [pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeId || !listRef.current) {
|
if (!activeId || !listRef.current) {
|
||||||
|
|||||||
2
content
2
content
Submodule content updated: a859f93327...d976bb08e2
@@ -88,6 +88,7 @@ export default makeSource({
|
|||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [remarkGfm],
|
remarkPlugins: [remarkGfm],
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
|
rehypeCallouts,
|
||||||
[
|
[
|
||||||
rehypePrettyCode,
|
rehypePrettyCode,
|
||||||
{
|
{
|
||||||
@@ -98,7 +99,6 @@ export default makeSource({
|
|||||||
keepBackground: false,
|
keepBackground: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rehypeCallouts,
|
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,31 +6,61 @@ import { visit } from 'unist-util-visit';
|
|||||||
*/
|
*/
|
||||||
export function rehypeCallouts() {
|
export function rehypeCallouts() {
|
||||||
return (tree: any) => {
|
return (tree: any) => {
|
||||||
visit(tree, 'element', (node, index, parent) => {
|
visit(tree, 'element', (node) => {
|
||||||
// Only process blockquotes
|
// Only process blockquotes
|
||||||
if (node.tagName !== 'blockquote') return;
|
if (node.tagName !== 'blockquote') return;
|
||||||
|
|
||||||
// Check if first child is a paragraph
|
|
||||||
if (!node.children || node.children.length === 0) return;
|
if (!node.children || node.children.length === 0) return;
|
||||||
const firstChild = node.children[0];
|
|
||||||
if (firstChild.tagName !== 'p') return;
|
|
||||||
|
|
||||||
// Check if paragraph starts with [!TYPE]
|
// Find the first non-whitespace child
|
||||||
if (!firstChild.children || firstChild.children.length === 0) return;
|
let contentChild: any = null;
|
||||||
const firstText = firstChild.children[0];
|
for (const child of node.children) {
|
||||||
if (firstText.type !== 'text') return;
|
if (child.type === 'text' && child.value.trim()) {
|
||||||
|
contentChild = child;
|
||||||
|
break;
|
||||||
|
} else if (child.tagName === 'p') {
|
||||||
|
contentChild = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const match = firstText.value.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i);
|
if (!contentChild) return;
|
||||||
|
|
||||||
|
// Find the first text node
|
||||||
|
let textNode: any = null;
|
||||||
|
let textParent: any = null;
|
||||||
|
|
||||||
|
if (contentChild.type === 'text') {
|
||||||
|
// Direct text child
|
||||||
|
textNode = contentChild;
|
||||||
|
textParent = node;
|
||||||
|
} else if (contentChild.tagName === 'p' && contentChild.children) {
|
||||||
|
// Text inside paragraph - find first non-whitespace text
|
||||||
|
for (const child of contentChild.children) {
|
||||||
|
if (child.type === 'text' && child.value.trim()) {
|
||||||
|
textNode = child;
|
||||||
|
textParent = contentChild;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!textNode || textNode.type !== 'text') return;
|
||||||
|
|
||||||
|
// Check if text starts with [!TYPE]
|
||||||
|
const match = textNode.value.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i);
|
||||||
if (!match) return;
|
if (!match) return;
|
||||||
|
|
||||||
const type = match[0].replace(/^\[!|\]\s*/g, '').toLowerCase();
|
const type = match[1].toLowerCase();
|
||||||
|
|
||||||
// Remove the [!TYPE] marker from the text
|
// Remove the [!TYPE] marker from the text
|
||||||
firstText.value = firstText.value.replace(match[0], '');
|
textNode.value = textNode.value.replace(match[0], '').trim();
|
||||||
|
|
||||||
// If the text node is now empty, remove it
|
// If the text node is now empty, remove it
|
||||||
if (!firstText.value.trim()) {
|
if (!textNode.value) {
|
||||||
firstChild.children.shift();
|
const index = textParent.children.indexOf(textNode);
|
||||||
|
if (index > -1) {
|
||||||
|
textParent.children.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add callout data attributes and classes
|
// Add callout data attributes and classes
|
||||||
@@ -38,7 +68,7 @@ export function rehypeCallouts() {
|
|||||||
node.properties.className = ['callout', `callout-${type}`];
|
node.properties.className = ['callout', `callout-${type}`];
|
||||||
node.properties['data-callout'] = type;
|
node.properties['data-callout'] = type;
|
||||||
|
|
||||||
// Add icon element at the beginning
|
// Add icon and title elements
|
||||||
const iconMap: Record<string, string> = {
|
const iconMap: Record<string, string> = {
|
||||||
note: '📝',
|
note: '📝',
|
||||||
tip: '💡',
|
tip: '💡',
|
||||||
@@ -72,7 +102,7 @@ export function rehypeCallouts() {
|
|||||||
type: 'element',
|
type: 'element',
|
||||||
tagName: 'div',
|
tagName: 'div',
|
||||||
properties: { className: ['callout-content'] },
|
properties: { className: ['callout-content'] },
|
||||||
children: node.children,
|
children: [...node.children],
|
||||||
};
|
};
|
||||||
|
|
||||||
node.children = [header, content];
|
node.children = [header, content];
|
||||||
|
|||||||
Reference in New Issue
Block a user