generated from template/astro-template
temp
This commit is contained in:
parent
22cb48b9f3
commit
26be3b85e8
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"workbench.editorAssociations": {
|
||||||
|
// "*.md": "vscode.markdown.preview.editor" // 预览打开
|
||||||
|
"*.md": "default" // 默认打开
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
|
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
|
||||||
import { useChatStore } from '../store';
|
import { useChatStore } from '../store';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { Button } from '@/components/a/button.tsx';
|
import { Button } from '@/components/a/button.tsx';
|
||||||
@ -68,6 +68,7 @@ export const ChatContextDialog = () => {
|
|||||||
setShowContext(false);
|
setShowContext(false);
|
||||||
};
|
};
|
||||||
const [role, setRole] = useState<string>('user');
|
const [role, setRole] = useState<string>('user');
|
||||||
|
const dragSize = useDragSize();
|
||||||
return (
|
return (
|
||||||
<DragModal
|
<DragModal
|
||||||
focus={modelId === 'chat-context'}
|
focus={modelId === 'chat-context'}
|
||||||
@ -111,14 +112,8 @@ export const ChatContextDialog = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
defaultSize={{
|
defaultSize={dragSize.defaultSize}
|
||||||
width: 600,
|
style={dragSize.style}
|
||||||
height: 400,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
left: computedHeight.width / 2 - 300,
|
|
||||||
top: computedHeight.height / 2 - 200,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
|
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
|
||||||
import { useChatStore } from '../store';
|
import { useChatStore } from '../store';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { ChatCopyList } from './List';
|
import { ChatCopyList } from './List';
|
||||||
@ -16,6 +16,7 @@ export const ChatCopyDialog = () => {
|
|||||||
if (!store.showCopy) {
|
if (!store.showCopy) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const dragSize = useDragSize();
|
||||||
return (
|
return (
|
||||||
<DragModal
|
<DragModal
|
||||||
focus={store.modelId === 'chat-copy'}
|
focus={store.modelId === 'chat-copy'}
|
||||||
@ -33,14 +34,8 @@ export const ChatCopyDialog = () => {
|
|||||||
<ChatCopyList />
|
<ChatCopyList />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
defaultSize={{
|
defaultSize={dragSize.defaultSize}
|
||||||
width: 600,
|
style={dragSize.style}
|
||||||
height: 400,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
left: computedHeight.width / 2 - 300,
|
|
||||||
top: computedHeight.height / 2 - 200,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
|
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
|
||||||
import { useChatStore } from '../store';
|
import { useChatStore } from '../store';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { ChatHistoryList } from './List';
|
import { ChatHistoryList } from './List';
|
||||||
|
|
||||||
export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => {
|
export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => {
|
||||||
const { showList, setShowList, setModelId, modelId } = useChatStore(
|
const { showList, setShowList, setModelId, modelId } = useChatStore(
|
||||||
useShallow((state) => ({ showList: state.showList, setShowList: state.setShowList, setModelId: state.setModelId, modelId: state.modelId })),
|
useShallow((state) => ({ showList: state.showList, setShowList: state.setShowList, setModelId: state.setModelId, modelId: state.modelId })),
|
||||||
);
|
);
|
||||||
const computedHeight = getComputedHeight();
|
const computedHeight = getComputedHeight();
|
||||||
if (!showList) {
|
if (!showList) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const dragSize = useDragSize();
|
||||||
return (
|
return (
|
||||||
<DragModal
|
<DragModal
|
||||||
focus={modelId === 'chat-history'}
|
focus={modelId === 'chat-history'}
|
||||||
@ -28,14 +29,7 @@ export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => {
|
|||||||
<ChatHistoryList storeId={storeId} />
|
<ChatHistoryList storeId={storeId} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
defaultSize={{
|
{...dragSize}
|
||||||
width: 600,
|
|
||||||
height: 400,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
left: computedHeight.width / 2 - 300,
|
|
||||||
top: computedHeight.height / 2 - 200,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
|
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
|
||||||
import { useChatStore } from '../store';
|
import { useChatStore } from '../store';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { ChatSettingList } from './List';
|
import { ChatSettingList } from './List';
|
||||||
@ -16,6 +16,7 @@ export const ChatModelSettingDialog = () => {
|
|||||||
if (!showChatSetting) {
|
if (!showChatSetting) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const dragSize = useDragSize();
|
||||||
return (
|
return (
|
||||||
<DragModal
|
<DragModal
|
||||||
focus={modelId === 'chat-model-setting'}
|
focus={modelId === 'chat-model-setting'}
|
||||||
@ -33,14 +34,7 @@ export const ChatModelSettingDialog = () => {
|
|||||||
<ChatSettingList />
|
<ChatSettingList />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
defaultSize={{
|
{...dragSize}
|
||||||
width: 600,
|
|
||||||
height: 400,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
left: computedHeight.width / 2 - 300,
|
|
||||||
top: computedHeight.height / 2 - 200,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
|
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
|
||||||
import { useChatStore } from '../store';
|
import { useChatStore } from '../store';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { ChatSettingList } from './List';
|
import { ChatSettingList } from './List';
|
||||||
@ -16,6 +16,7 @@ export const ChatSettingDialog = () => {
|
|||||||
if (!showSetting) {
|
if (!showSetting) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const dragSize = useDragSize();
|
||||||
return (
|
return (
|
||||||
<DragModal
|
<DragModal
|
||||||
focus={modelId === 'chat-setting'}
|
focus={modelId === 'chat-setting'}
|
||||||
@ -33,14 +34,7 @@ export const ChatSettingDialog = () => {
|
|||||||
<ChatSettingList />
|
<ChatSettingList />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
defaultSize={{
|
{...dragSize}
|
||||||
width: 600,
|
|
||||||
height: 400,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
left: computedHeight.width / 2 - 300,
|
|
||||||
top: computedHeight.height / 2 - 200,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -75,7 +75,7 @@ export const Chat = ({ storeId }: { storeId: string }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full relative'>
|
<div className='w-full h-full relative flex flex-col lg:flex-row'>
|
||||||
{!id && (
|
{!id && (
|
||||||
<div className='w-full h-full flex flex-col justify-center items-center'>
|
<div className='w-full h-full flex flex-col justify-center items-center'>
|
||||||
<ChatHistoryList storeId={storeId} />
|
<ChatHistoryList storeId={storeId} />
|
||||||
|
@ -41,7 +41,7 @@ export const ModelNav = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='flex gap-2 items-center'>
|
<div className='flex gap-2 items-center'>
|
||||||
<div className='hidden lg:block font-bold text-gray-600'>提示词规划器</div>
|
<div className='hidden lg:block font-bold text-gray-600'>提示词规划器</div>
|
||||||
<div className='flex gap-2 items-center' onClick={() => setShowChatSetting(true)}>
|
{/* <div className='flex gap-2 items-center' onClick={() => setShowChatSetting(true)}>
|
||||||
{currentUserModel && (
|
{currentUserModel && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -58,7 +58,7 @@ export const ModelNav = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{!currentUserModel && <div className='text-sm text-gray-500 cursor-pointer'>Not Selected Model</div>}
|
{!currentUserModel && <div className='text-sm text-gray-500 cursor-pointer'>Not Selected Model</div>}
|
||||||
</div>
|
</div> */}
|
||||||
<div className='flex gap-2 items-center'>
|
<div className='flex gap-2 items-center'>
|
||||||
{/* <Tooltip title='设置'>
|
{/* <Tooltip title='设置'>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
18
src/apps/ai-editor/index.tsx
Normal file
18
src/apps/ai-editor/index.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ToastProvider } from '@/modules/toast/Provider';
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
return (
|
||||||
|
<ToastProvider>
|
||||||
|
<AIEditor />
|
||||||
|
</ToastProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AIEditor = () => {
|
||||||
|
return (
|
||||||
|
<div className='flex h-full w-full flex-col items-center justify-center'>
|
||||||
|
<div className='text-2xl font-bold'>AI Editor</div>
|
||||||
|
<div className='mt-4 text-gray-500'>This is a placeholder for the AI Editor application.</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -5,14 +5,35 @@ import { Tooltip } from '@/components/a/tooltip';
|
|||||||
export const queryMark = new QueryMark({ query: query, markType: 'md' });
|
export const queryMark = new QueryMark({ query: query, markType: 'md' });
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
import { Plus, SquareArrowOutUpRight } from 'lucide-react';
|
||||||
|
import { Pencil, Trash2 } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/a/button';
|
||||||
|
import { Confirm } from '@/components/a/confirm';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { ToastProvider } from '@/modules/toast/Provider';
|
||||||
|
import { Modal } from '@/components/a/modal';
|
||||||
|
import { useForm, Controller } from 'react-hook-form'; // 添加此行
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
|
return (
|
||||||
|
<ToastProvider>
|
||||||
|
<AiMark />
|
||||||
|
</ToastProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const AiMark = () => {
|
||||||
const [list, setList] = useState<Mark[]>([]);
|
const [list, setList] = useState<Mark[]>([]);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<Mark[]>([]);
|
const [searchResults, setSearchResults] = useState<Mark[]>([]);
|
||||||
|
|
||||||
const fuseRef = useRef<Fuse<Mark>>(null);
|
const fuseRef = useRef<Fuse<Mark>>(null);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<Mark>();
|
||||||
|
const onOpenEdit = (data: Mark) => {
|
||||||
|
setOpen(true);
|
||||||
|
setFormData(data);
|
||||||
|
};
|
||||||
// 实际执行搜索的函数
|
// 实际执行搜索的函数
|
||||||
const performSearch = (term: string) => {
|
const performSearch = (term: string) => {
|
||||||
if (!term.trim()) {
|
if (!term.trim()) {
|
||||||
@ -47,6 +68,7 @@ export const App = () => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const list = res.data?.list || [];
|
const list = res.data?.list || [];
|
||||||
setList(list!);
|
setList(list!);
|
||||||
|
|
||||||
const fuse = new Fuse(list, {
|
const fuse = new Fuse(list, {
|
||||||
keys: ['title', 'description', 'tags', 'summary'],
|
keys: ['title', 'description', 'tags', 'summary'],
|
||||||
});
|
});
|
||||||
@ -65,13 +87,23 @@ export const App = () => {
|
|||||||
const link = item.link;
|
const link = item.link;
|
||||||
window.open(link);
|
window.open(link);
|
||||||
};
|
};
|
||||||
|
const refresh = () => {
|
||||||
|
init();
|
||||||
|
};
|
||||||
// 确定要显示的列表:有搜索内容时显示搜索结果,否则显示全部
|
// 确定要显示的列表:有搜索内容时显示搜索结果,否则显示全部
|
||||||
const displayList = searchTerm.trim() ? searchResults : list;
|
const displayList = searchTerm.trim() ? searchResults : list;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full overflow-auto'>
|
<div className='w-full h-full overflow-auto scrollbar'>
|
||||||
<div className='sticky top-0 p-4 bg-white z-10 shadow-sm'>
|
<div className='sticky top-0 p-4 bg-white z-10 shadow-sm flex gap-2'>
|
||||||
|
<Button
|
||||||
|
size='icon'
|
||||||
|
onClick={() => {
|
||||||
|
setFormData(undefined);
|
||||||
|
setOpen(true);
|
||||||
|
}}>
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
@ -102,11 +134,141 @@ export const App = () => {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<Action data={item} onEdit={onOpenEdit} refresh={refresh} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<Modal open={open} setOpen={setOpen}>
|
||||||
|
<MarkForm
|
||||||
|
data={formData}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onSuccess={() => {
|
||||||
|
setOpen(false);
|
||||||
|
init();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MarkForm = (props?: { data?: Mark; onSuccess?: () => void; onCancel?: () => void }) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Mark>({
|
||||||
|
defaultValues: props?.data || {
|
||||||
|
title: '',
|
||||||
|
summary: '',
|
||||||
|
link: '',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: Mark) => {
|
||||||
|
try {
|
||||||
|
const res = await queryMark.updateMark(data);
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success(data.id ? '更新成功' : '创建成功');
|
||||||
|
props?.onSuccess?.();
|
||||||
|
} else {
|
||||||
|
toast.error(res?.message || '操作失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('操作失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4 p-2'>
|
||||||
|
{props?.data?.id && <input type='hidden' {...register('id')} />}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className='block text-sm font-medium mb-1'>标题</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
{...register('title', { required: '标题是必填项' })}
|
||||||
|
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
|
||||||
|
/>
|
||||||
|
{errors.title && <p className='text-red-500 text-xs mt-1'>{errors.title.message}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className='block text-sm font-medium mb-1'>链接</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
{...register('link', { required: '链接是必填项' })}
|
||||||
|
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
|
||||||
|
/>
|
||||||
|
{errors.link && <p className='text-red-500 text-xs mt-1'>{errors.link.message}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className='block text-sm font-medium mb-1'>摘要</label>
|
||||||
|
<textarea {...register('summary')} rows={3} className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className='block text-sm font-medium mb-1'>标签</label>
|
||||||
|
<Controller
|
||||||
|
name='tags'
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={field.value?.join(', ') || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
field.onChange(
|
||||||
|
value
|
||||||
|
.split(',')
|
||||||
|
.map((tag) => tag.trim())
|
||||||
|
.filter((tag) => tag !== ''),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
|
||||||
|
placeholder='用逗号分隔多个标签'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-end space-x-2 mt-4'>
|
||||||
|
<Button onClick={props?.onCancel} type='button'>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type='submit'>保存</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const Action = (props: { data: Mark; onEdit?: (data: Mark) => any; refresh?: any }) => {
|
||||||
|
const onDelete = async () => {
|
||||||
|
const res = await queryMark.deleteMark(props.data.id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('删除成功');
|
||||||
|
props?.refresh?.();
|
||||||
|
} else {
|
||||||
|
toast.error(res?.message || '请求失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='flex gap-2 mt-2'>
|
||||||
|
<Button className='flex items-center gap-1 transition' onClick={() => props?.onEdit?.(props.data)} title='编辑'>
|
||||||
|
<Pencil className='w-4 h-4' />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Confirm onOk={onDelete}>
|
||||||
|
<Button className='flex items-center gap-1 text-red-600 transition' title='删除'>
|
||||||
|
<Trash2 className='w-4 h-4' />
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Confirm>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
13
src/apps/kevisual-lib/Layout.tsx
Normal file
13
src/apps/kevisual-lib/Layout.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
type LayoutProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
export const Layout = (props: LayoutProps) => {
|
||||||
|
const { children, className, style } = props;
|
||||||
|
return (
|
||||||
|
<div className={className} style={style}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -4,9 +4,10 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang='zh'>
|
<html lang='zh-CN'>
|
||||||
<header>
|
<head>
|
||||||
<meta charset='UTF-8' />
|
<meta charset='UTF-8' />
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||||
<title>Docs</title>
|
<title>Docs</title>
|
||||||
<link
|
<link
|
||||||
rel='stylesheet'
|
rel='stylesheet'
|
||||||
@ -15,13 +16,23 @@ export interface Props {
|
|||||||
crossorigin='anonymous'
|
crossorigin='anonymous'
|
||||||
referrerpolicy='no-referrer'
|
referrerpolicy='no-referrer'
|
||||||
/>
|
/>
|
||||||
</header>
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<div>
|
||||||
<slot name='header'>
|
<slot name='header'>
|
||||||
<h1>My Site Header</h1>
|
<h1>My Site Header</h1>
|
||||||
</slot>
|
</slot>
|
||||||
</header>
|
</div>
|
||||||
<main class='markdown-body' style='padding: 1rem'>
|
<main class='markdown-body' style='padding: 1rem'>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
@ -115,3 +115,18 @@ export const useComputedHeight = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
return computedHeight;
|
return computedHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDragSize = (width = 600, heigth = 400) => {
|
||||||
|
const computedHeight = getComputedHeight();
|
||||||
|
const isMin = computedHeight.width < width;
|
||||||
|
return {
|
||||||
|
defaultSize: {
|
||||||
|
width: isMin ? computedHeight.width : 600,
|
||||||
|
height: 400,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
left: isMin ? 0 : computedHeight.width / 2 - width / 2,
|
||||||
|
top: computedHeight.height / 2 - heigth / 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
24
src/components/a/modal.tsx
Normal file
24
src/components/a/modal.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog';
|
||||||
|
import React, { Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
type ModalProps = {
|
||||||
|
open?: boolean;
|
||||||
|
setOpen?: (open: boolean) => any;
|
||||||
|
title?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Modal = (props: ModalProps) => {
|
||||||
|
const { open = false, setOpen, title, children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
{children}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
@ -5,6 +5,7 @@
|
|||||||
<html lang='zh-CN'>
|
<html lang='zh-CN'>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='UTF-8' />
|
<meta charset='UTF-8' />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>AI Pages</title>
|
<title>AI Pages</title>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
|
@ -12,4 +12,15 @@ const blog = defineCollection({
|
|||||||
// updatedDate: z.coerce.date().optional(),
|
// updatedDate: z.coerce.date().optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
export const collections = { blog };
|
|
||||||
|
const kevisual = defineCollection({
|
||||||
|
// loader: glob({ pattern: '**/*.md', base: './src/data/blogs' }),
|
||||||
|
loader: glob({ pattern: '**/[^_]*.md', base: './src/data/kevisual' }),
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
// pubDate: z.coerce.date(),
|
||||||
|
// updatedDate: z.coerce.date().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
export const collections = { blog, kevisual };
|
||||||
|
12
src/data/kevisual/router/base.md
Normal file
12
src/data/kevisual/router/base.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title: router
|
||||||
|
tags: ['kevisual', 'lib']
|
||||||
|
---
|
||||||
|
|
||||||
|
# @kevisual/router
|
||||||
|
|
||||||
|
在使用 node 开发的时候,http 服务和 api 是首要关注的内容。但是真实的在开发的时候,发现一件事情,开发应该只关注于对应的内容的东西。
|
||||||
|
|
||||||
|
## 介绍
|
||||||
|
|
||||||
|
|
27
src/pages/docs/kevisual/[...id].astro
Normal file
27
src/pages/docs/kevisual/[...id].astro
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
import '@/styles/global.css';
|
||||||
|
import '@/styles/theme.css';
|
||||||
|
import { getCollection, render } from 'astro:content';
|
||||||
|
import Main from '@/astro/layouts/mdx/main.astro';
|
||||||
|
// import Blank from '@/components/html/blank.astro';
|
||||||
|
// 1. 为每个集合条目生成一个新路径
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getCollection('kevisual');
|
||||||
|
return posts.map((post) => ({
|
||||||
|
params: { id: post.id },
|
||||||
|
props: { post },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
type Post = {
|
||||||
|
data: { title: string };
|
||||||
|
};
|
||||||
|
// 2. 对于你的模板,你可以直接从 prop 获取条目
|
||||||
|
const { post } = Astro.props as { post: Post };
|
||||||
|
const { Content } = await render(post);
|
||||||
|
---
|
||||||
|
|
||||||
|
<Main>
|
||||||
|
<meta charset='UTF-8' slot='head' />
|
||||||
|
<div slot={'header'}>{post.data.title}</div>
|
||||||
|
<Content />
|
||||||
|
</Main>
|
21
src/pages/docs/kevisual/index.astro
Normal file
21
src/pages/docs/kevisual/index.astro
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
const posts = await getCollection('kevisual');
|
||||||
|
console.log('post', posts);
|
||||||
|
import { basename } from '@/modules/basename';
|
||||||
|
import Blank from '@/components/html/blank.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Blank>
|
||||||
|
<h1>kevisual posts</h1>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
posts.map((post) => (
|
||||||
|
<li>
|
||||||
|
{/* <a href={`${basename}/demo/${post.id}`}>{post.data.title}</a> */}
|
||||||
|
<a href={`/docs/kevisual/${post.id}`}>{post.data.title}</a>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</Blank>
|
@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import '@/styles/theme.css';
|
||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Blank from '@/components/html/blank.astro';
|
import Blank from '@/components/html/blank.astro';
|
||||||
import { App } from '@/apps/ai-mark';
|
import { App } from '@/apps/ai-mark';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import '@/styles/theme.css';
|
||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Blank from '@/components/html/blank.astro';
|
import Blank from '@/components/html/blank.astro';
|
||||||
import { App as AIChat } from '@/apps/ai-chat/index.tsx';
|
import { App as AIChat } from '@/apps/ai-chat/index.tsx';
|
@ -1,119 +0,0 @@
|
|||||||
@import 'tailwindcss';
|
|
||||||
@import "tw-animate-css";
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.205 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.269 0 0);
|
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
@import 'tailwindcss';
|
|
||||||
@import "tw-animate-css";
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.205 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.269 0 0);
|
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,16 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary: #ffc107;
|
/* --color-primary: #ffc107;
|
||||||
--color-secondary: #ffa000;
|
--color-secondary: #ffa000;
|
||||||
--color-text-primary: #000000;
|
--color-text-primary: #000000;
|
||||||
--color-text-secondary: #000000;
|
--color-text-secondary: #000000;
|
||||||
--color-success: #28a745;
|
--color-success: #28a745; */
|
||||||
|
|
||||||
--color-scrollbar-thumb: #999999;
|
--color-scrollbar-thumb: #999999;
|
||||||
--color-scrollbar-track: rgba(0, 0, 0, 0.1);
|
--color-scrollbar-track: rgba(0, 0, 0, 0.1);
|
||||||
--color-scrollbar-thumb-hover: #666666;
|
--color-scrollbar-thumb-hover: #666666;
|
||||||
--scrollbar-color: #ffc107; /* 滚动条颜色 */
|
--scrollbar-color: #ffc107;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user