Skip to content

Commit

Permalink
Merge pull request #322 from blinko-space/dev
Browse files Browse the repository at this point in the history
fix: some issue
  • Loading branch information
blinko-space authored Dec 16, 2024
2 parents 7712c5d + 467e441 commit 68e3670
Show file tree
Hide file tree
Showing 18 changed files with 230 additions and 175 deletions.
9 changes: 7 additions & 2 deletions app/api/file/[...filename]/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { NextResponse } from "next/server";
import { NextResponse, NextRequest } from "next/server";
import path from "path";
import { createReadStream, statSync } from "fs";
import { stat, readFile } from "fs/promises";
import mime from "mime-types";
import { UPLOAD_FILE_PATH } from "@/lib/constant";
import crypto from "crypto";
import { getToken } from "next-auth/jwt";

const STREAM_THRESHOLD = 5 * 1024 * 1024;
const ONE_YEAR_IN_SECONDS = 31536000;

export const GET = async (req: Request, { params }: any) => {
export const GET = async (req: NextRequest, { params }: any) => {
const fullPath = decodeURIComponent(params.filename.join('/'));
const token = await getToken({ req });
if (fullPath.endsWith('.bko') && token?.role !== 'superadmin') {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const sanitizedPath = fullPath.replace(/^[./\\]+/, '');
const filePath = path.join(process.cwd(), UPLOAD_FILE_PATH, sanitizedPath);

Expand Down
8 changes: 6 additions & 2 deletions app/api/file/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import path from "path";
import { FileService } from "@/server/plugins/files";
import { getToken } from "next-auth/jwt";


export const POST = async (req: Request, res: NextResponse) => {
export const POST = async (req: NextRequest, res: NextResponse) => {
const token = await getToken({ req });
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const formData = await req.formData();
const file = formData.getAll('file')[0]
if (!file) {
Expand Down
11 changes: 9 additions & 2 deletions src/components/BlinkoCard/blogContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Image } from '@nextui-org/react';
import { Note } from '@/server/types';
import { helper } from '@/lib/helper';
import { RootStore } from '@/store/root';
import router from 'next/router';
import { BlinkoStore } from '@/store/blinkoStore';

interface BlogContentProps {
blinkoItem: Note & {
Expand Down Expand Up @@ -35,12 +38,16 @@ export const BlogContent = ({ blinkoItem, isExpanded }: BlogContentProps) => {
const tagTree = helper.buildHashTagTreeFromDb(blinkoItem.tags.map(t => t.tag));
const tagPaths = tagTree.flatMap(node => helper.generateTagPaths(node));
const uniquePaths = tagPaths.filter(path => {
return !tagPaths.some(otherPath =>
return !tagPaths.some(otherPath =>
otherPath !== path && otherPath.startsWith(path + '/')
);
});
return uniquePaths.map((path) => (
<div key={path} className='text-desc text-xs blinko-tag whitespace-nowrap'>
<div key={path} className='text-desc text-xs blinko-tag whitespace-nowrap font-bold hover:opacity-80 transition-all cursor-pointer' onClick={(e) => {
e.stopPropagation()
router.replace(`/all?searchText=${encodeURIComponent("#" + path)}`)
RootStore.Get(BlinkoStore).forceQuery++
}}>
#{path}
</div>
));
Expand Down
1 change: 1 addition & 0 deletions src/components/Common/AttachmentRender/imageRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const ImageThumbnailRender = ({ file, className }: { file: FileType, className?:
}
setCurrentSrc(file.preview)
}}
crossOrigin="use-credentials"
className={`object-cover w-full ${className} `}
/>
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Common/AttachmentRender/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const AttachmentsRender = observer((props: IProps) => {
<div className={`columns-1 md:columns-1`}>
{files?.filter(i => i.previewType == 'video').map((file, index) => (
<div className='group relative flex p-2 items-center gap-2 cursor-pointer tansition-all rounded-2xl'>
<video onDoubleClick={(e) => e.stopPropagation()} src={file.preview} id="player" playsInline controls className='rounded-2xl w-full z-0' />
<video onDoubleClick={(e) => e.stopPropagation()} src={file.preview} id="player" playsInline controls className='rounded-2xl w-full z-0 max-h-[150px]' />
{!file.uploadPromise?.loading?.value && !preview &&
<DeleteIcon className='absolute z-10 right-[5px] top-[5px]' files={files} file={file} />
}
Expand Down
18 changes: 18 additions & 0 deletions src/components/Common/Editor/editorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,22 @@ export class EditorStore {
}
return showToolbar
}

adjustMobileEditorHeight = () => {
const editor = document.getElementsByClassName('vditor-reset')
try {
for (let i = 0; i < editor?.length; i++) {
//@ts-ignore
const editorHeight = window.innerHeight - 200
//@ts-ignore
if (editor[i].style.height > editorHeight) {
//@ts-ignore
editor[i].style!.height = `${editorHeight}px`
}
//@ts-ignore
editor[i].style!.maxHeight = `${editorHeight}px`
// }
}
} catch (error) { }
}
}
5 changes: 4 additions & 1 deletion src/components/Common/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type IProps = {

const Editor = observer(({ content, onChange, onSend, isSendLoading, originFiles, originReference = [], mode, onHeightChange }: IProps) => {
const cardRef = React.useRef(null)

const isPc = useMediaQuery('(min-width: 768px)')
const store = useLocalObservable(() => new EditorStore())
const blinko = RootStore.Get(BlinkoStore)

Expand Down Expand Up @@ -80,7 +80,10 @@ const Editor = observer(({ content, onChange, onSend, isSendLoading, originFiles
<div ref={cardRef}
className="overflow-visible relative"
onKeyDown={e => {
console.log('onKeyDown')
onHeightChange?.()
if (isPc) return
store.adjustMobileEditorHeight()
}}>

<div id={`vditor-${mode}`} className="vditor" />
Expand Down
9 changes: 0 additions & 9 deletions src/components/Common/MarkdownRender/LinkPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ export const LinkPreview = ({ href, text }: LinkPreviewProps) => {
previewData: new StorageState<LinkInfo | null>({ key: href, default: null })
}))

// try {
// if (typeof href == 'object') {
// return <ImageWrapper src={href?.props?.src} width={href?.props?.width} height={href?.props?.height} />
// }
// } catch (error) {
// console.log(error)
// return href
// }

useEffect(() => {
const fetchData = async () => {
try {
Expand Down
17 changes: 9 additions & 8 deletions src/components/Common/MarkdownRender/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ListItem } from './ListItem';
import dynamic from 'next/dynamic';
import { Skeleton } from '@nextui-org/react';
import { TableWrapper } from './TableWrapper';
import { useRouter } from 'next/router';
import router, { useRouter } from 'next/router';
import remarkTaskList from 'remark-task-list';

const MermaidWrapper = dynamic(() => import('./MermaidWrapper').then(mod => mod.MermaidWrapper), {
Expand All @@ -37,7 +37,7 @@ const EchartsWrapper = dynamic(() => import('./EchartsWrapper'), {
ssr: false
});

const HighlightTags = observer(({ text, }: { text: any}) => {
const HighlightTags = observer(({ text, }: { text: any }) => {
const { pathname } = useRouter()
if (!text) return text
try {
Expand All @@ -49,14 +49,15 @@ const HighlightTags = observer(({ text, }: { text: any}) => {
const isShareMode = pathname.includes('share')
if (isShareMode) return <span key={`${lineIndex}-${index}`} className={`w-fit select-none blinko-tag px-1 font-bold cursor-pointer hover:opacity-80 transition-all`}>{part + " "}</span>
return (
<Link key={`${lineIndex}-${index}`}
<span key={`${lineIndex}-${index}`}
className={`select-none blinko-tag px-1 font-bold cursor-pointer hover:opacity-80 transition-all ${isShareMode ? 'pointer-events-none' : ''}`}
onClick={() => {
onClick={async () => {
if (isShareMode) return;
await router.replace(`/all?searchText=${encodeURIComponent(part)}`)
RootStore.Get(BlinkoStore).forceQuery++
}} href={`/all?searchText=${part}`}>
}}>
{part + " "}
</Link>
</span>
);
} else {
return part + " ";
Expand Down Expand Up @@ -126,8 +127,8 @@ export const MarkdownRender = observer(({ content = '', onChange, isShareMode }:
const isTaskListItem = className?.includes('task-list-item');
if (isTaskListItem && onChange && !isShareMode) {
return (
<ListItem
content={content}
<ListItem
content={content}
onChange={onChange}
className={className}
>
Expand Down
172 changes: 101 additions & 71 deletions src/components/Common/PopoverFloat/aiWritePop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef } from 'react';
import { observer } from 'mobx-react-lite';
import { Card, Input, Button } from '@nextui-org/react';
import { useTranslation } from 'react-i18next';
Expand All @@ -12,7 +12,7 @@ import { SendIcon } from '../Icons';
import { MarkdownRender } from '../MarkdownRender';
import { ScrollArea, ScrollAreaHandles } from '../ScrollArea';
import { useMediaQuery } from 'usehooks-ts';

import ReactDOM from 'react-dom';

export const showAiWriteSuggestions = () => {
setTimeout(() => {
Expand Down Expand Up @@ -69,78 +69,108 @@ const AiWritePop = observer(() => {
scrollRef.current?.scrollToBottom()
}, [ai.writingResponseText])

return (
<PopoverFloat
show={store.show}
onHide={store.hidden}
anchorRect={store.rect}
maxWidth={isPc ? 700 : 400}
maxHeight={isPc ? 600 : 400}
closeOnClickOutside={false}
>
<div className="flex flex-col gap-3 min-w-[300px]">
<div className="flex gap-2">
<Input
className='border-none'
value={ai.writeQuestion}
onChange={(e) => ai.writeQuestion = e.target.value}
placeholder={'Prompt...'}
onKeyDown={(e) => {
if (e.key === 'Enter') {
store.handleSubmit()
}
}}
startContent={<Icon className='text-primary' icon="mingcute:ai-line" width="16" height="16" />}
endContent={<>
{ai.isLoading ?
<Icon icon="mingcute:loading-line" width="16" height="16" /> :
<SendIcon onClick={store.handleSubmit} className='cursor-pointer primary-foreground group-hover:rotate-[-35deg] transition-all' />}
</>}
/>
</div>
{
ai.writingResponseText != '' && <ScrollArea ref={scrollRef} className='p-2 max-h-[200px]' onBottom={() => { }}>
{ai.isLoading ? <div className='text-sm'>{ai.writingResponseText}</div> : <MarkdownRender content={ai.writingResponseText} />}
</ScrollArea>
}
{ai.isWriting && (
<div id='ai-write-suggestions' className='flex gap-2 items-center'>
<Button onPress={() => {
ai.isWriting = false;
eventBus.emit('editor:insert', ai.writingResponseText)
ai.writingResponseText = ''
store.hidden()
}} startContent={<Icon icon="ic:sharp-check" className='green' />} size='sm' variant='light' color='success'>{t('accept')}</Button>
<Button onPress={() => {
ai.isWriting = false;
ai.writingResponseText = ''
store.hidden()
}} startContent={<Icon icon="ic:sharp-close" className='red' />} size='sm' variant='light' color='danger'>{t('reject')}</Button>
<Button onPress={() => {
ai.abort();
}} startContent={<Icon icon="mynaui:stop" className='blinko' />} size='sm' variant='light' color='warning'>{t('stop')} </Button>
const isInsideDialog = () => {
if (!store.rect) return false;
const dialogElement = document.querySelector('.modal-content');
if (!dialogElement) return false;

const dialogRect = dialogElement.getBoundingClientRect();
return (
store.rect.top >= dialogRect.top &&
store.rect.bottom <= dialogRect.bottom &&
store.rect.left >= dialogRect.left &&
store.rect.right <= dialogRect.right
);
};

const renderPopover = () => {
const popover = (
<PopoverFloat
show={store.show}
onHide={store.hidden}
anchorRect={store.rect}
maxWidth={isPc ? 700 : 400}
maxHeight={isPc ? 600 : 400}
closeOnClickOutside={false}
>
<div className="flex flex-col gap-3 min-w-[300px]">
<div className="flex gap-2">
<Input
className='border-none'
value={ai.writeQuestion}
onChange={(e) => ai.writeQuestion = e.target.value}
placeholder={'Prompt...'}
onKeyDown={(e) => {
if (e.key === 'Enter') {
store.handleSubmit()
}
}}
startContent={<Icon className='text-primary' icon="mingcute:ai-line" width="16" height="16" />}
endContent={<>
{ai.isLoading ?
<Icon icon="mingcute:loading-line" width="16" height="16" /> :
<SendIcon onClick={store.handleSubmit} className='cursor-pointer primary-foreground group-hover:rotate-[-35deg] transition-all' />}
</>}
/>
</div>
)}
{
ai.writingResponseText != '' && <ScrollArea ref={scrollRef} className='p-2 max-h-[200px]' onBottom={() => { }}>
{ai.isLoading ? <div className='text-sm'>{ai.writingResponseText}</div> : <MarkdownRender content={ai.writingResponseText} />}
</ScrollArea>
}
{ai.isWriting && (
<div id='ai-write-suggestions' className='flex gap-2 items-center'>
<Button onPress={() => {
ai.isWriting = false;
eventBus.emit('editor:insert', ai.writingResponseText)
ai.writingResponseText = ''
store.hidden()
}} startContent={<Icon icon="ic:sharp-check" className='green' />} size='sm' variant='light' color='success'>{t('accept')}</Button>
<Button onPress={() => {
ai.isWriting = false;
ai.writingResponseText = ''
store.hidden()
}} startContent={<Icon icon="ic:sharp-close" className='red' />} size='sm' variant='light' color='danger'>{t('reject')}</Button>
<Button onPress={() => {
ai.abort();
}} startContent={<Icon icon="mynaui:stop" className='blinko' />} size='sm' variant='light' color='warning'>{t('stop')} </Button>
</div>
)}

<div className='flex items-center gap-2'>
<Button startContent={<Icon icon="proicons:text-expand" width="16" height="16" />} variant='flat' color='warning' size='sm' onPress={e => {
ai.writeStream('expand', blinko.isCreateMode ? blinko.noteContent : blinko.curSelectedNote!.content)
// store.hidden()
}}>{t('ai-expand')}</Button>
<Button startContent={<Icon icon="lucide:scan-text" width="16" height="16" />} variant='flat' color='warning' size='sm' onPress={e => {
ai.writeStream('polish', blinko.isCreateMode ? blinko.noteContent : blinko.curSelectedNote!.content)
// store.hidden()
}}>{t('ai-polish')}</Button>
<Button className='ml-auto' isLoading={ai.isLoading} isIconOnly size='sm' onPress={e => {
store.hidden()
eventBus.emit('editor:deleteLastChar')
}}>
<Icon icon="ic:sharp-close" />
</Button>
<div className='flex items-center gap-2'>
<Button startContent={<Icon icon="proicons:text-expand" width="16" height="16" />} variant='flat' color='warning' size='sm' onPress={e => {
ai.writeStream('expand', blinko.isCreateMode ? blinko.noteContent : blinko.curSelectedNote!.content)
// store.hidden()
}}>{t('ai-expand')}</Button>
<Button startContent={<Icon icon="lucide:scan-text" width="16" height="16" />} variant='flat' color='warning' size='sm' onPress={e => {
ai.writeStream('polish', blinko.isCreateMode ? blinko.noteContent : blinko.curSelectedNote!.content)
// store.hidden()
}}>{t('ai-polish')}</Button>
<Button className='ml-auto' isLoading={ai.isLoading} isIconOnly size='sm' onPress={e => {
store.hidden()
eventBus.emit('editor:deleteLastChar')
}}>
<Icon icon="ic:sharp-close" />
</Button>
</div>
</div>
</div>
</PopoverFloat>
);
</PopoverFloat>
);

if (isInsideDialog()) {
return ReactDOM.createPortal(
popover,
document.querySelector('.modal-content')!
);
}

return ReactDOM.createPortal(
popover,
document.body
);
};

return renderPopover();
});

export default AiWritePop;
3 changes: 1 addition & 2 deletions src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ export const CommonLayout = observer(({
return (
<div className="flex w-full h-mobile-full overflow-x-hidden" id="outer-container">
{blinkoStore.showAi && createPortal(<BlinkoAi />, document.body)}
<AppProvider />
<TagSelectPop />

<AiWritePop />

<Menu
Expand Down
Loading

0 comments on commit 68e3670

Please sign in to comment.