generated from template/astro-template
	upload
This commit is contained in:
		@@ -27,6 +27,7 @@ let proxy = {
 | 
				
			|||||||
  '/client': apiProxy,
 | 
					  '/client': apiProxy,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const basename = isDev ? '' : pkgs.basename;
 | 
				
			||||||
export default defineConfig({
 | 
					export default defineConfig({
 | 
				
			||||||
  // ...
 | 
					  // ...
 | 
				
			||||||
  // site: 'https://kevisual.xiongxiao.me/root/astro',
 | 
					  // site: 'https://kevisual.xiongxiao.me/root/astro',
 | 
				
			||||||
@@ -41,7 +42,7 @@ export default defineConfig({
 | 
				
			|||||||
  vite: {
 | 
					  vite: {
 | 
				
			||||||
    plugins,
 | 
					    plugins,
 | 
				
			||||||
    define: {
 | 
					    define: {
 | 
				
			||||||
      BASE_NAME: JSON.stringify(pkgs.basename),
 | 
					      BASE_NAME: JSON.stringify(basename),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    server: {
 | 
					    server: {
 | 
				
			||||||
      port: 7008,
 | 
					      port: 7008,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,8 @@ import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts';
 | 
				
			|||||||
import { Select } from '@/components/a/select.tsx';
 | 
					import { Select } from '@/components/a/select.tsx';
 | 
				
			||||||
import { useEffect, useRef, useState } from 'react';
 | 
					import { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
import { getSuggestionItems } from '../ai-chat/editor/suggestion/item';
 | 
					import { getSuggestionItems } from '../ai-chat/editor/suggestion/item';
 | 
				
			||||||
import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts';
 | 
					// import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts';
 | 
				
			||||||
import { chatId } from '../ai-chat/utils/uuid';
 | 
					// import { chatId } from '../ai-chat/utils/uuid';
 | 
				
			||||||
import '../ai-chat/index.css';
 | 
					import '../ai-chat/index.css';
 | 
				
			||||||
import { links } from './data/link.ts';
 | 
					import { links } from './data/link.ts';
 | 
				
			||||||
// const testImport = async () => {
 | 
					// const testImport = async () => {
 | 
				
			||||||
@@ -44,6 +44,10 @@ export const App = (props: any) => {
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className='w-full h-full flex flex-col'>
 | 
					    <div className='w-full h-full flex flex-col'>
 | 
				
			||||||
      <ShowLinks links={links} />
 | 
					      <ShowLinks links={links} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div>
 | 
				
			||||||
 | 
					        <i>登陆的demo账号: demo 密码为: 123456 </i>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										81
									
								
								src/apps/layout/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/apps/layout/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import { Layout, Header } from '@/components/b/layouts/main/layout';
 | 
				
			||||||
 | 
					import { basename } from '@/modules/basename';
 | 
				
			||||||
 | 
					import { queryLogin } from '@/modules/query';
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const AppLayout = (props: Props) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Layout
 | 
				
			||||||
 | 
					      header={
 | 
				
			||||||
 | 
					        <Header
 | 
				
			||||||
 | 
					          title={
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					              className='px-2 cursor-pointer'
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                const url = new URL(window.location.href);
 | 
				
			||||||
 | 
					                const pathname = url.pathname;
 | 
				
			||||||
 | 
					                if (pathname === basename + '/') {
 | 
				
			||||||
 | 
					                  return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // 如果当前路径不是根路径,则跳转到根路径
 | 
				
			||||||
 | 
					                location.href = basename + '/';
 | 
				
			||||||
 | 
					              }}>
 | 
				
			||||||
 | 
					              AI Generative
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          className='bg-background border-b border-b-gray-200 text-foreground'
 | 
				
			||||||
 | 
					          right={<UserInfo />}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      }>
 | 
				
			||||||
 | 
					      {props.children}
 | 
				
			||||||
 | 
					    </Layout>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type User = Awaited<ReturnType<typeof queryLogin.checkLocalUser>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const UserInfo = () => {
 | 
				
			||||||
 | 
					  const [user, setUser] = useState<User | null>(null);
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    init();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					  const init = async () => {
 | 
				
			||||||
 | 
					    const userInfo = await queryLogin.checkLocalUser();
 | 
				
			||||||
 | 
					    const token = await queryLogin.getToken();
 | 
				
			||||||
 | 
					    if (userInfo) {
 | 
				
			||||||
 | 
					      setUser(userInfo);
 | 
				
			||||||
 | 
					    } else if (token) {
 | 
				
			||||||
 | 
					      const userinfo = await queryLogin.getLoginUserByToken(token);
 | 
				
			||||||
 | 
					      if (userinfo) {
 | 
				
			||||||
 | 
					        setUser(userinfo);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='flex items-center gap-2'>
 | 
				
			||||||
 | 
					      {!user && (
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          onClick={() => {
 | 
				
			||||||
 | 
					            const currentUrl = new URL(window.location.href);
 | 
				
			||||||
 | 
					            const loginUrl = '/user/login/';
 | 
				
			||||||
 | 
					            if (currentUrl.pathname !== loginUrl) {
 | 
				
			||||||
 | 
					              const newUrl = new URL(loginUrl, window.location.origin);
 | 
				
			||||||
 | 
					              newUrl.searchParams.set('redirect', currentUrl.pathname + currentUrl.search);
 | 
				
			||||||
 | 
					              location.href = newUrl.toString();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }}>
 | 
				
			||||||
 | 
					          login
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {user && (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					          <img src={user?.avatar || '/root/center/panda.jpg'} alt='User Avatar' className='w-8 h-8 rounded-full' />
 | 
				
			||||||
 | 
					          <span className='text-sm text-gray-700'>{user?.username}</span>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -237,7 +237,7 @@ export const EditMark = () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return <div className='w-full h-full'></div>;
 | 
					  return <div className='w-full h-full'></div>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?: React.ReactNode; open?: boolean }) => {
 | 
					export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?: React.ReactNode; open?: boolean; hasTopTitle?: boolean }) => {
 | 
				
			||||||
  const getDocumentHeight = () => {
 | 
					  const getDocumentHeight = () => {
 | 
				
			||||||
    return document.documentElement.scrollHeight;
 | 
					    return document.documentElement.scrollHeight;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -261,13 +261,16 @@ export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?:
 | 
				
			|||||||
  const isEdit = !!markData;
 | 
					  const isEdit = !!markData;
 | 
				
			||||||
  const hasExpandChildren = !!props.expandChildren;
 | 
					  const hasExpandChildren = !!props.expandChildren;
 | 
				
			||||||
  const style = useMemo(() => {
 | 
					  const style = useMemo(() => {
 | 
				
			||||||
 | 
					    const top = props.hasTopTitle ? 70 : 0; // Adjust top based on whether there's a title
 | 
				
			||||||
    if (!hasExpandChildren || openMenu) {
 | 
					    if (!hasExpandChildren || openMenu) {
 | 
				
			||||||
      return {};
 | 
					      return {
 | 
				
			||||||
 | 
					        top,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      top: getDocumentHeight() / 2 + 10,
 | 
					      top: getDocumentHeight() / 2 + 10 + top,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, [getDocumentHeight, hasExpandChildren, openMenu]);
 | 
					  }, [getDocumentHeight, hasExpandChildren, openMenu, props.hasTopTitle]);
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className='w-full h-full flex'>
 | 
					    <div className='w-full h-full flex'>
 | 
				
			||||||
      <div className={clsx('absolute top-4 z-10', openMenu ? 'left-4' : '-left-4')} style={style}>
 | 
					      <div className={clsx('absolute top-4 z-10', openMenu ? 'left-4' : '-left-4')} style={style}>
 | 
				
			||||||
@@ -313,12 +316,13 @@ export type AppProps = {
 | 
				
			|||||||
  children?: React.ReactNode;
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
  showSelect?: boolean;
 | 
					  showSelect?: boolean;
 | 
				
			||||||
  openMenu?: boolean;
 | 
					  openMenu?: boolean;
 | 
				
			||||||
 | 
					  hasTopTitle?: boolean; // 是否有顶部标题
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export const ProviderManagerName = 'mark-manager';
 | 
					export const ProviderManagerName = 'mark-manager';
 | 
				
			||||||
export const App = (props: AppProps) => {
 | 
					export const App = (props: AppProps) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <ManagerProvider id={props.managerId}>
 | 
					    <ManagerProvider id={props.managerId}>
 | 
				
			||||||
      <LayoutMain expandChildren={props.children} open={props.openMenu}>
 | 
					      <LayoutMain expandChildren={props.children} open={props.openMenu} hasTopTitle={props.hasTopTitle}>
 | 
				
			||||||
        <Manager
 | 
					        <Manager
 | 
				
			||||||
          markType={props.markType}
 | 
					          markType={props.markType}
 | 
				
			||||||
          showSearch={props.showSearch}
 | 
					          showSearch={props.showSearch}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/components/b/layouts/main/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/components/b/layouts/main/layout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { cn } from '@/lib/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LayoutProps = {
 | 
				
			||||||
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					  header?: React.ReactNode;
 | 
				
			||||||
 | 
					  footer?: React.ReactNode;
 | 
				
			||||||
 | 
					  mainClassName?: string;
 | 
				
			||||||
 | 
					  mainStyle?: React.CSSProperties;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const Layout = (props: LayoutProps) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='w-full h-full'>
 | 
				
			||||||
 | 
					      {props.header}
 | 
				
			||||||
 | 
					      <main
 | 
				
			||||||
 | 
					        className={cn(props.mainClassName, 'main-content')}
 | 
				
			||||||
 | 
					        style={{
 | 
				
			||||||
 | 
					          height: props.header ? 'calc(100vh - 64px)' : '100vh', // Assuming header height is 64px
 | 
				
			||||||
 | 
					          overflowY: 'auto',
 | 
				
			||||||
 | 
					          ...props.mainStyle,
 | 
				
			||||||
 | 
					        }}>
 | 
				
			||||||
 | 
					        {props.children}
 | 
				
			||||||
 | 
					      </main>
 | 
				
			||||||
 | 
					      {props.footer}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HeaderProps = {
 | 
				
			||||||
 | 
					  title?: React.ReactNode;
 | 
				
			||||||
 | 
					  className?: string;
 | 
				
			||||||
 | 
					  style?: React.CSSProperties;
 | 
				
			||||||
 | 
					  right?: React.ReactNode;
 | 
				
			||||||
 | 
					  left?: React.ReactNode;
 | 
				
			||||||
 | 
					  center?: React.ReactNode;
 | 
				
			||||||
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const Header = (props: HeaderProps) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <header className={cn('h-16 w-full flex items-center', props.className)} style={props.style}>
 | 
				
			||||||
 | 
					      {props.title}
 | 
				
			||||||
 | 
					      <div className='grow flex justify-between items-center px-4 '>
 | 
				
			||||||
 | 
					        {props.left && <div className='flex-1'>{props.left}</div>}
 | 
				
			||||||
 | 
					        {!props.left && <div> </div>}
 | 
				
			||||||
 | 
					        {props.center && <div className='flex-1 text-center'>{props.center}</div>}
 | 
				
			||||||
 | 
					        {props.right && <>{props.right}</>}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -16,7 +16,7 @@ import { MarkdownPreview } from '@/components/html/md/Preview';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<Blank>
 | 
					<Blank>
 | 
				
			||||||
  <div class="p-4 h-full">
 | 
					  <div class="p-4 h-full">
 | 
				
			||||||
    <MarkdownPreview>
 | 
					    <MarkdownPreview client:only>
 | 
				
			||||||
      <Readme />
 | 
					      <Readme />
 | 
				
			||||||
    </MarkdownPreview>
 | 
					    </MarkdownPreview>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,11 @@
 | 
				
			|||||||
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 AssistantHome } from '@/apps/assistant-home/index.tsx';
 | 
					import { App as AssistantHome } from '@/apps/assistant-home/index.tsx';
 | 
				
			||||||
 | 
					import { AppLayout } from '@/apps/layout';
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<Blank>
 | 
					<Blank>
 | 
				
			||||||
 | 
					  <AppLayout client:only>
 | 
				
			||||||
    <AssistantHome client:only />
 | 
					    <AssistantHome client:only />
 | 
				
			||||||
 | 
					  </AppLayout>
 | 
				
			||||||
</Blank>
 | 
					</Blank>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,11 @@ 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/mark/manager/Manager';
 | 
					import { App } from '@/apps/mark/manager/Manager';
 | 
				
			||||||
 | 
					import { AppLayout } from '@/apps/layout';
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<Blank>
 | 
					<Blank>
 | 
				
			||||||
  <App client:only openMenu={true} />
 | 
					  <AppLayout client:only>
 | 
				
			||||||
 | 
					    <App client:only openMenu={true} hasTopTitle={true}/>
 | 
				
			||||||
 | 
					  </AppLayout>
 | 
				
			||||||
</Blank>
 | 
					</Blank>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ 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/preview/md.tsx';
 | 
					import { App } from '@/apps/preview/md.tsx';
 | 
				
			||||||
 | 
					import { AppLayout } from '@/apps/layout';
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<link
 | 
					<link
 | 
				
			||||||
@@ -14,5 +15,7 @@ import { App } from '@/apps/preview/md.tsx';
 | 
				
			|||||||
/>
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<Blank>
 | 
					<Blank>
 | 
				
			||||||
 | 
					  <AppLayout client:only>
 | 
				
			||||||
    <App client:only />
 | 
					    <App client:only />
 | 
				
			||||||
 | 
					  </AppLayout>
 | 
				
			||||||
</Blank>
 | 
					</Blank>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ export interface Cache {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  init?: () => Promise<any>;
 | 
					  init?: () => Promise<any>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
type User = {
 | 
					export type User = {
 | 
				
			||||||
  avatar?: string;
 | 
					  avatar?: string;
 | 
				
			||||||
  description?: string;
 | 
					  description?: string;
 | 
				
			||||||
  id?: string;
 | 
					  id?: string;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Query, BaseQuery } from '@kevisual/query';
 | 
					import { Query, BaseQuery } from '@kevisual/query';
 | 
				
			||||||
import type { Result, DataOpts } from '@kevisual/query/query';
 | 
					import type { Result, DataOpts } from '@kevisual/query/query';
 | 
				
			||||||
import { setBaseResponse } from '@kevisual/query/query';
 | 
					import { setBaseResponse } from '@kevisual/query/query';
 | 
				
			||||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
 | 
					import { LoginCacheStore, CacheStore, User } from './login-cache.ts';
 | 
				
			||||||
import { Cache } from './login-cache.ts';
 | 
					import { Cache } from './login-cache.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type QueryLoginOpts = {
 | 
					export type QueryLoginOpts = {
 | 
				
			||||||
@@ -397,8 +397,22 @@ export class QueryLogin extends BaseQuery {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  async getLoginUserByToken(token: string): Promise<User | null> {
 | 
				
			||||||
 | 
					    const me = await this.getMe(token, false);
 | 
				
			||||||
 | 
					    if (me.code === 200) {
 | 
				
			||||||
 | 
					      const user = me.data;
 | 
				
			||||||
 | 
					      this.cacheStore.setLoginUser({
 | 
				
			||||||
 | 
					        user,
 | 
				
			||||||
 | 
					        id: user.id,
 | 
				
			||||||
 | 
					        accessToken: token,
 | 
				
			||||||
 | 
					        refreshToken: me.data?.refreshToken || '',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return user;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * 检查登录状态
 | 
					   * 检查登录状态,根据 login by web
 | 
				
			||||||
   * @param token
 | 
					   * @param token
 | 
				
			||||||
   * @returns
 | 
					   * @returns
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user