diff --git a/docs/src/app/[locale]/[type]/page.tsx b/docs/src/app/[locale]/[type]/page.tsx
index 0b15df0..210b161 100644
--- a/docs/src/app/[locale]/[type]/page.tsx
+++ b/docs/src/app/[locale]/[type]/page.tsx
@@ -75,7 +75,7 @@ export default async function Page({ params }: Props) {
}
// Fallback redirect if no content found
- console.log("redirecting to", `/${locale}`);
+ // console.log("redirecting to", `/${locale}`);
redirect(`/${locale}`);
}
diff --git a/docs/src/app/aiSearch/page.tsx b/docs/src/app/aiSearch/page.tsx
index a620cdf..5e849e3 100644
--- a/docs/src/app/aiSearch/page.tsx
+++ b/docs/src/app/aiSearch/page.tsx
@@ -15,6 +15,8 @@ import { getAkiradocsConfig } from '@/lib/getAkiradocsConfig'
import { ChatCompletionMessageParam, CreateMLCEngine } from "@mlc-ai/web-llm";
import { Source } from '@/types/Source'
import AILoader from '@/components/aiSearch/AILoader'
+import { getHeaderConfig } from '@/lib/headerConfig'
+import { Header } from '@/components/layout/Header'
export default function Home() {
const [query, setQuery] = useState('')
@@ -23,6 +25,7 @@ export default function Home() {
const [error, setError] = useState
(null)
const recommendedArticles = getRecommendedArticles()
const searchConfig = getSearchConfig()
+ const headerConfig = getHeaderConfig()
const config = getAkiradocsConfig()
const [sources, setSources] = useState([])
@@ -160,7 +163,10 @@ export default function Home() {
}
return (
+
)
}
diff --git a/docs/src/app/apiReference/page.tsx b/docs/src/app/apiReference/page.tsx
index e36f59c..d1c34ff 100644
--- a/docs/src/app/apiReference/page.tsx
+++ b/docs/src/app/apiReference/page.tsx
@@ -312,7 +312,7 @@ export default function Page() {
}>
-
+
diff --git a/docs/src/components/blocks/ButtonBlock.tsx b/docs/src/components/blocks/ButtonBlock.tsx
new file mode 100644
index 0000000..a54e8b1
--- /dev/null
+++ b/docs/src/components/blocks/ButtonBlock.tsx
@@ -0,0 +1,103 @@
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+import { useState } from "react"
+
+interface ButtonBlockProps {
+ content: string
+ metadata?: {
+ buttonUrl?: string
+ buttonStyle?: {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
+ size?: 'default' | 'sm' | 'lg'
+ radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'
+ }
+ }
+ isEditing?: boolean
+ onUpdate?: (content: string) => void
+ align?: 'left' | 'center' | 'right'
+}
+
+export function ButtonBlock({
+ content,
+ metadata,
+ isEditing,
+ onUpdate,
+ align = 'left'
+}: ButtonBlockProps) {
+ const [isFocused, setIsFocused] = useState(false);
+ const buttonStyle = metadata?.buttonStyle || {}
+ const url = metadata?.buttonUrl || '#'
+
+ const buttonClasses = cn(
+ "relative",
+ {
+ 'rounded-none': buttonStyle.radius === 'none',
+ 'rounded-sm': buttonStyle.radius === 'sm',
+ 'rounded-md': buttonStyle.radius === 'md',
+ 'rounded-lg': buttonStyle.radius === 'lg',
+ 'rounded-full': buttonStyle.radius === 'full',
+ 'w-full': buttonStyle.size === 'lg',
+ 'w-24': buttonStyle.size === 'sm',
+ 'w-auto': buttonStyle.size === 'default'
+ }
+ )
+
+ const wrapperClasses = cn(
+ 'w-full',
+ {
+ 'text-left': align === 'left',
+ 'text-center': align === 'center',
+ 'text-right': align === 'right'
+ }
+ )
+
+ if (isEditing) {
+ return (
+
+
+
+ onUpdate?.(e.target.value)}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ className={cn(
+ buttonVariants({
+ variant: buttonStyle.variant || 'default',
+ size: buttonStyle.size || 'default'
+ }),
+ buttonClasses,
+ "absolute inset-0 border-0 outline-none",
+ !isFocused && "opacity-0"
+ )}
+ placeholder="Button text..."
+ />
+
+
+ )
+ }
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/docs/src/components/blocks/SortableBlock.tsx b/docs/src/components/blocks/SortableBlock.tsx
index 9afa175..020e810 100644
--- a/docs/src/components/blocks/SortableBlock.tsx
+++ b/docs/src/components/blocks/SortableBlock.tsx
@@ -35,6 +35,12 @@ interface SortableBlockProps {
align?: 'left' | 'center' | 'right'
type?: 'info' | 'warning' | 'success' | 'error'
title?: string
+ buttonUrl?: string
+ buttonStyle?: {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
+ size?: 'default' | 'sm' | 'lg'
+ radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'
+ }
}
}
updateBlock: (id: string, content: string) => void
@@ -514,6 +520,21 @@ export function SortableBlock({
updateBlock(block.id, JSON.stringify(updatedContent));
}
}}
+ showButtonControls={block.type === 'button'}
+ buttonMetadata={{
+ url: block.metadata?.buttonUrl,
+ style: block.metadata?.buttonStyle
+ }}
+ onButtonMetadataChange={(metadata) => {
+ updateBlockMetadata(block.id, {
+ ...block.metadata,
+ buttonUrl: metadata.buttonUrl,
+ buttonStyle: {
+ ...block.metadata?.buttonStyle,
+ ...metadata.buttonStyle
+ }
+ })
+ }}
/>
)}
void
+}
+
+const spacingSizes = {
+ small: 'h-4',
+ medium: 'h-8',
+ large: 'h-16',
+ xlarge: 'h-24'
+}
+
+export function SpacerBlock({ size = 'medium', isEditing, onUpdate }: SpacerBlockProps) {
+ const [isFocused, setIsFocused] = useState(false)
+
+ if (!isEditing) {
+ return
+ }
+
+ return (
+ setIsFocused(true)}
+ onBlur={(e) => {
+ if (!e.currentTarget.contains(e.relatedTarget)) {
+ setIsFocused(false)
+ }
+ }}
+ >
+
+ {isFocused && (
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/docs/src/components/editor/AIRewriteButton.tsx b/docs/src/components/editor/AIRewriteButton.tsx
index eb1c654..589ef86 100644
--- a/docs/src/components/editor/AIRewriteButton.tsx
+++ b/docs/src/components/editor/AIRewriteButton.tsx
@@ -193,6 +193,25 @@ const blockStyles = {
label: 'Descriptive',
prompt: 'Rewrite the API reference to be more descriptive and engaging.'
}
+ ],
+ button: [
+ {
+ value: 'descriptive',
+ label: 'Descriptive',
+ prompt: 'Rewrite the button text to be more descriptive and engaging.'
+ },
+ {
+ value: 'concise',
+ label: 'Concise',
+ prompt: 'Rewrite the button text to be more concise and clear.'
+ }
+ ],
+ spacer: [
+ {
+ value: 'default',
+ label: 'Default',
+ prompt: 'No rewriting options available for spacer'
+ }
]
} as const;
@@ -213,6 +232,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite
};
const handleRewrite = async () => {
+ if (blockType === 'spacer') {
+ return;
+ }
+
try {
await onRewrite(style)
} catch (error) {
@@ -224,6 +247,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite
}
}
+ if (blockType === 'spacer') {
+ return null;
+ }
+
return (
diff --git a/docs/src/components/editor/AddBlockButton.tsx b/docs/src/components/editor/AddBlockButton.tsx
index 00a1770..ec5998f 100644
--- a/docs/src/components/editor/AddBlockButton.tsx
+++ b/docs/src/components/editor/AddBlockButton.tsx
@@ -16,19 +16,14 @@ import {
Minus,
Table,
Quote,
- // ToggleLeft,
- // CheckSquare,
- // Video,
- // Music,
- // File,
- // Smile,
AlertCircle,
Plus,
- // Search,
Video,
Music,
File,
CheckSquare,
+ ArrowUpDown,
+ Link2,
} from 'lucide-react'
interface AddBlockButtonProps {
@@ -83,6 +78,8 @@ export const AddBlockButton = forwardRef<
{ type: 'audio', icon: , label: 'Audio', description: 'Embed audio content.', group: 'Media' },
{ type: 'file', icon: , label: 'File', description: 'Upload or link to a file.', group: 'Media' },
{ type: 'checkList', icon: , label: 'To-do list', description: 'Track tasks with a to-do list.', group: 'Basic' },
+ { type: 'spacer', icon: , label: 'Spacing', description: 'Add vertical space between blocks.', group: 'Basic' },
+ { type: 'button', icon: , label: 'Button', description: 'Add a clickable button with link.', group: 'Basic' },
]
const filteredOptions = blockOptions.filter((option) =>
diff --git a/docs/src/components/editor/BlockFormatToolbar.tsx b/docs/src/components/editor/BlockFormatToolbar.tsx
index 8a70ee5..e575d98 100644
--- a/docs/src/components/editor/BlockFormatToolbar.tsx
+++ b/docs/src/components/editor/BlockFormatToolbar.tsx
@@ -76,6 +76,23 @@ interface BlockFormatToolbarProps {
caption: string;
alignment: 'left' | 'center' | 'right';
}>) => void;
+ showButtonControls?: boolean;
+ buttonMetadata?: {
+ url?: string;
+ style?: {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
+ size?: 'default' | 'sm' | 'lg';
+ radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
+ };
+ };
+ onButtonMetadataChange?: (metadata: Partial<{
+ buttonUrl: string;
+ buttonStyle: {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
+ size?: 'default' | 'sm' | 'lg';
+ radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
+ };
+ }>) => void;
}
export function BlockFormatToolbar({
@@ -119,8 +136,11 @@ export function BlockFormatToolbar({
showAudioControls = false,
audioContent,
onAudioMetadataChange,
+ showButtonControls = false,
+ buttonMetadata,
+ onButtonMetadataChange,
}: BlockFormatToolbarProps) {
- if (blockType === 'file') {
+ if (blockType === 'file' || blockType === 'spacer') {
return null;
}
@@ -133,7 +153,7 @@ export function BlockFormatToolbar({
"bg-popover border shadow-md rounded-md",
className
)}>
- {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && blockType !== 'table' && (
+ {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && !showButtonControls && blockType !== 'table' && (
<>
)}
+ {showButtonControls && (
+ <>
+
+
+ onButtonMetadataChange?.({ buttonUrl: e.target.value })}
+ placeholder="URL"
+ className="h-7 w-32 text-xs"
+ />
+
+
+
+
+
+
+ >
+ )}
+
{!showImageControls && !showVideoControls && !showAudioControls && (
<>
{!showCalloutControls && blockType !== 'table' && }
diff --git a/docs/src/components/layout/Header.tsx b/docs/src/components/layout/Header.tsx
index 56a363a..ba1bc04 100644
--- a/docs/src/components/layout/Header.tsx
+++ b/docs/src/components/layout/Header.tsx
@@ -34,7 +34,8 @@ export const Header = memo(function Header({
navItems,
socialLinks,
languages,
- currentLocale = 'en'
+ currentLocale = 'en',
+ currentType = 'docs'
}: HeaderConfig) {
const [isMounted, setIsMounted] = useState(false)
const { theme, setTheme } = useTheme()
@@ -104,31 +105,36 @@ export const Header = memo(function Header({
// Memoize the navigation items
const navigationItems = useMemo(() => {
- return navItems?.filter((item) => item.show).map((item, index) => (
-
- item.show).map((item, index) => {
+ // Check if the current nav item matches the page type
+ const isActive = currentType ? item.href.includes(`/${currentType}`) : pathname === item.href;
+
+ return (
+
- {t(item.label as string)}
-
-
-
- ))
- }, [navItems, pathname])
+
+ {t(item.label as string)}
+
+
+
+ );
+ });
+ }, [navItems, pathname, currentType]);
return (
(defaultValue: T, locale: string, t
try {
const navigationFile = `${locale}/${type}/_meta.json`
const navigation = await getJsonContent(navigationFile)
- console.log(navigation)
+ // console.log(navigation)
return navigation
} catch (error) {
console.warn(`Failed to read ${type} _meta.json file. Using default value.`)
@@ -131,7 +131,7 @@ interface ApiNavItem {
export async function getApiNavigation(): Promise {
try {
const apiSpec = await get_api_spec();
- console.log("apiSpec", apiSpec)
+ // console.log("apiSpec", apiSpec)
if (!apiSpec || !apiSpec.paths) {
return [];
}
diff --git a/docs/src/lib/renderers/BlockRenderer.tsx b/docs/src/lib/renderers/BlockRenderer.tsx
index b640646..6247349 100644
--- a/docs/src/lib/renderers/BlockRenderer.tsx
+++ b/docs/src/lib/renderers/BlockRenderer.tsx
@@ -6,22 +6,15 @@ import { List } from "@/components/blocks/ListBlock"
import { Blockquote } from "@/components/blocks/BlockquoteBlock"
import { Divider } from "@/components/blocks/DividerBlock"
import { CodeBlock } from "@/components/blocks/CodeBlock"
-// import { Image } from '../blocks/Image'
-// import { Table } from '../blocks/Table'
-// import { ToggleList } from '../blocks/ToggleList'
import { CheckList } from "@/components/blocks/CheckListBlock"
-// import { Video } from '../blocks/Video'
-// import { Audio } from '../blocks/Audio'
-// import { File } from '../blocks/File'
-// import { Emoji } from '../blocks/Emoji'
import { Callout } from "@/components/blocks/CalloutBlock"
-import { cn } from '@/lib/utils'
-import { ErrorBoundary } from 'react-error-boundary'
import { ImageBlock } from "@/components/blocks/ImageBlock"
import { Table } from '@/components/blocks/TableBlock'
import { VideoBlock } from "@/components/blocks/VideoBlock"
import { AudioBlock } from "@/components/blocks/AudioBlock"
import { FileBlock } from "@/components/blocks/FileBlock"
+import { SpacerBlock } from "@/components/blocks/SpacerBlock"
+import { ButtonBlock } from "@/components/blocks/ButtonBlock"
interface ImageBlockContent {
url: string;
@@ -64,20 +57,16 @@ export function BlockRenderer({ block, isEditing, onUpdate }: BlockRendererProps
);
case 'heading':
- const hasStrong = block.content.includes('');
return (
onUpdate?.(block.id, content)}
>
- {hasStrong ? block.content.replace(/<\/?strong>/g, '') : block.content}
+ {block.content}
);
case 'list':
@@ -234,6 +223,24 @@ export function BlockRenderer({ block, isEditing, onUpdate }: BlockRendererProps
onUpdate={(content) => onUpdate?.(block.id, content)}
/>
);
+ case 'spacer':
+ return (
+ onUpdate?.(block.id, size)}
+ />
+ );
+ case 'button':
+ return (
+ onUpdate?.(block.id, content)}
+ align={block.metadata?.align}
+ />
+ );
default:
return null
}
diff --git a/docs/src/types/Block.ts b/docs/src/types/Block.ts
index 61f1b7a..044cc58 100644
--- a/docs/src/types/Block.ts
+++ b/docs/src/types/Block.ts
@@ -12,9 +12,10 @@ export type BlockType =
| 'video'
| 'audio'
| 'file'
- // | 'emoji'
| 'callout'
- | 'apiReference';
+ | 'spacer'
+ | 'apiReference'
+ | 'button';
export interface Block {
id: string;
@@ -44,6 +45,12 @@ export interface Block {
align?: 'left' | 'center' | 'right'; // For alignment
type?: 'info' | 'warning' | 'success' | 'error'; // For callouts
title?: string; // For callouts
+ buttonUrl?: string;
+ buttonStyle?: {
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
+ size?: 'default' | 'sm' | 'lg';
+ radius?: 'none' | 'sm' | 'md' | 'lg' | 'full';
+ };
};
}
diff --git a/docs/src/types/config.ts b/docs/src/types/config.ts
index ddf0804..5aaa68f 100644
--- a/docs/src/types/config.ts
+++ b/docs/src/types/config.ts
@@ -39,6 +39,7 @@ interface NavItem {
}[];
};
currentLocale?: string;
+ currentType?: string;
}
export interface FooterConfig {
diff --git a/docs/wrangler.toml b/docs/wrangler.toml
new file mode 100644
index 0000000..7188cd4
--- /dev/null
+++ b/docs/wrangler.toml
@@ -0,0 +1,16 @@
+name = "akiradocs"
+
+pages_build_output_dir = ".next"
+
+[build]
+command = "npx @cloudflare/next-on-pages@1"
+
+
+[vars]
+NODE_VERSION = "18.8.0"
+
+compatibility_flags = ["nodejs_compat"]
+
+
+[env.production]
+NODE_ENV = "production"
\ No newline at end of file