mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
1 Commits
487db8e944
...
c9a6b6e403
| Author | SHA1 | Date |
|---|---|---|
|
|
c9a6b6e403 |
|
|
@ -4964,34 +4964,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.BlockGridConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.BrandGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -6778,31 +6750,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.QuestionConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RagInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -7283,9 +7230,6 @@ const docTemplate = `{
|
|||
"basic_doc_config": {
|
||||
"$ref": "#/definitions/domain.BasicDocConfig"
|
||||
},
|
||||
"block_grid_config": {
|
||||
"$ref": "#/definitions/domain.BlockGridConfig"
|
||||
},
|
||||
"carousel_config": {
|
||||
"$ref": "#/definitions/domain.CarouselConfig"
|
||||
},
|
||||
|
|
@ -7322,9 +7266,6 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"question_config": {
|
||||
"$ref": "#/definitions/domain.QuestionConfig"
|
||||
},
|
||||
"simple_doc_config": {
|
||||
"$ref": "#/definitions/domain.SimpleDocConfig"
|
||||
},
|
||||
|
|
@ -7348,9 +7289,6 @@ const docTemplate = `{
|
|||
"basic_doc_config": {
|
||||
"$ref": "#/definitions/domain.BasicDocConfig"
|
||||
},
|
||||
"block_grid_config": {
|
||||
"$ref": "#/definitions/domain.BlockGridConfig"
|
||||
},
|
||||
"carousel_config": {
|
||||
"$ref": "#/definitions/domain.CarouselConfig"
|
||||
},
|
||||
|
|
@ -7393,9 +7331,6 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/domain.RecommendNodeListResp"
|
||||
}
|
||||
},
|
||||
"question_config": {
|
||||
"$ref": "#/definitions/domain.QuestionConfig"
|
||||
},
|
||||
"simple_doc_config": {
|
||||
"$ref": "#/definitions/domain.SimpleDocConfig"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4957,34 +4957,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.BlockGridConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.BrandGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -6771,31 +6743,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.QuestionConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RagInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -7276,9 +7223,6 @@
|
|||
"basic_doc_config": {
|
||||
"$ref": "#/definitions/domain.BasicDocConfig"
|
||||
},
|
||||
"block_grid_config": {
|
||||
"$ref": "#/definitions/domain.BlockGridConfig"
|
||||
},
|
||||
"carousel_config": {
|
||||
"$ref": "#/definitions/domain.CarouselConfig"
|
||||
},
|
||||
|
|
@ -7315,9 +7259,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"question_config": {
|
||||
"$ref": "#/definitions/domain.QuestionConfig"
|
||||
},
|
||||
"simple_doc_config": {
|
||||
"$ref": "#/definitions/domain.SimpleDocConfig"
|
||||
},
|
||||
|
|
@ -7341,9 +7282,6 @@
|
|||
"basic_doc_config": {
|
||||
"$ref": "#/definitions/domain.BasicDocConfig"
|
||||
},
|
||||
"block_grid_config": {
|
||||
"$ref": "#/definitions/domain.BlockGridConfig"
|
||||
},
|
||||
"carousel_config": {
|
||||
"$ref": "#/definitions/domain.CarouselConfig"
|
||||
},
|
||||
|
|
@ -7386,9 +7324,6 @@
|
|||
"$ref": "#/definitions/domain.RecommendNodeListResp"
|
||||
}
|
||||
},
|
||||
"question_config": {
|
||||
"$ref": "#/definitions/domain.QuestionConfig"
|
||||
},
|
||||
"simple_doc_config": {
|
||||
"$ref": "#/definitions/domain.SimpleDocConfig"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -833,24 +833,6 @@ definitions:
|
|||
- ids
|
||||
- kb_id
|
||||
type: object
|
||||
domain.BlockGridConfig:
|
||||
properties:
|
||||
list:
|
||||
items:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
title:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
domain.BrandGroup:
|
||||
properties:
|
||||
links:
|
||||
|
|
@ -2011,22 +1993,6 @@ definitions:
|
|||
model:
|
||||
type: string
|
||||
type: object
|
||||
domain.QuestionConfig:
|
||||
properties:
|
||||
list:
|
||||
items:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
question:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
title:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
domain.RagInfo:
|
||||
properties:
|
||||
message:
|
||||
|
|
@ -2343,8 +2309,6 @@ definitions:
|
|||
$ref: '#/definitions/domain.BannerConfig'
|
||||
basic_doc_config:
|
||||
$ref: '#/definitions/domain.BasicDocConfig'
|
||||
block_grid_config:
|
||||
$ref: '#/definitions/domain.BlockGridConfig'
|
||||
carousel_config:
|
||||
$ref: '#/definitions/domain.CarouselConfig'
|
||||
case_config:
|
||||
|
|
@ -2369,8 +2333,6 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
question_config:
|
||||
$ref: '#/definitions/domain.QuestionConfig'
|
||||
simple_doc_config:
|
||||
$ref: '#/definitions/domain.SimpleDocConfig'
|
||||
text_config:
|
||||
|
|
@ -2386,8 +2348,6 @@ definitions:
|
|||
$ref: '#/definitions/domain.BannerConfig'
|
||||
basic_doc_config:
|
||||
$ref: '#/definitions/domain.BasicDocConfig'
|
||||
block_grid_config:
|
||||
$ref: '#/definitions/domain.BlockGridConfig'
|
||||
carousel_config:
|
||||
$ref: '#/definitions/domain.CarouselConfig'
|
||||
case_config:
|
||||
|
|
@ -2416,8 +2376,6 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/domain.RecommendNodeListResp'
|
||||
type: array
|
||||
question_config:
|
||||
$ref: '#/definitions/domain.QuestionConfig'
|
||||
simple_doc_config:
|
||||
$ref: '#/definitions/domain.SimpleDocConfig'
|
||||
text_config:
|
||||
|
|
|
|||
|
|
@ -293,23 +293,6 @@ type TextImgConfig struct {
|
|||
Desc string `json:"desc"`
|
||||
} `json:"item"`
|
||||
}
|
||||
type QuestionConfig struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
List []struct {
|
||||
ID string `json:"id"`
|
||||
Question string `json:"question"`
|
||||
} `json:"list"`
|
||||
}
|
||||
type BlockGridConfig struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
List []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
} `json:"list"`
|
||||
}
|
||||
|
||||
type WebAppLandingConfig struct {
|
||||
Type string `json:"type"`
|
||||
|
|
@ -327,8 +310,6 @@ type WebAppLandingConfig struct {
|
|||
FeatureConfig *FeatureConfig `json:"feature_config,omitempty"`
|
||||
ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"`
|
||||
TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"`
|
||||
QuestionConfig *QuestionConfig `json:"question_config,omitempty"`
|
||||
BlockGridConfig *BlockGridConfig `json:"block_grid_config,omitempty"`
|
||||
ComConfigOrder []string `json:"com_config_order"`
|
||||
}
|
||||
|
||||
|
|
@ -543,8 +524,6 @@ type WebAppLandingConfigResp struct {
|
|||
FeatureConfig *FeatureConfig `json:"feature_config,omitempty"`
|
||||
ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"`
|
||||
TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"`
|
||||
QuestionConfig *QuestionConfig `json:"question_config,omitempty"`
|
||||
BlockGridConfig *BlockGridConfig `json:"block_grid_config,omitempty"`
|
||||
ComConfigOrder []string `json:"com_config_order"`
|
||||
NodeIds []string `json:"node_ids"`
|
||||
Nodes []*RecommendNodeListResp `json:"nodes" gorm:"-"`
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b6e6fb64429a827fb4a95fa9ee3bb4379ac18cd9
|
||||
Subproject commit e7bf0a030242425c83291b369770c113fd503a5a
|
||||
|
|
@ -422,8 +422,6 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
FeatureConfig: app.Settings.WebAppLandingConfigs[i].FeatureConfig,
|
||||
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
|
||||
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
|
||||
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
|
||||
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
|
||||
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
|
||||
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
|
||||
}
|
||||
|
|
@ -554,8 +552,6 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
|||
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
|
||||
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
|
||||
MetricsConfig: app.Settings.WebAppLandingConfigs[i].MetricsConfig,
|
||||
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
|
||||
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
|
||||
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
|
||||
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@tiptap/extension-collaboration": "^3.3.0",
|
||||
"@tiptap/extension-collaboration-caret": "^3.3.0",
|
||||
"ace-builds": "^1.43.4",
|
||||
"axios": "^1.7.9",
|
||||
"clsx": "^2.1.1",
|
||||
"echarts": "^5.6.0",
|
||||
|
|
@ -29,9 +32,9 @@
|
|||
"lottie-react": "^2.4.1",
|
||||
"lowlight": "^3.3.0",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"react-ace": "^14.0.1",
|
||||
"react-color-palette": "^7.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-image-crop": "^11.0.10",
|
||||
"react-markdown": "^10.1.0",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
|
|||
};
|
||||
|
||||
const handleListChange = (
|
||||
newList: (typeof DEFAULT_DATA.block_grid)['list'],
|
||||
newList: (typeof DEFAULT_DATA.blockGrid)['list'],
|
||||
) => {
|
||||
setValue('list', newList);
|
||||
setIsEdit(true);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
|
|||
))}
|
||||
</Stack>
|
||||
</SortableContext>
|
||||
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
|
||||
{/* <DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
|
||||
{activeId ? (
|
||||
<Item
|
||||
isDragging
|
||||
|
|
@ -105,7 +105,7 @@ const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
|
|||
handleUpdateItem={handleUpdateItem}
|
||||
/>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DragOverlay> */}
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
import {
|
||||
ConstsContributeStatus,
|
||||
ConstsContributeType,
|
||||
getApiProV1ContributeDetail,
|
||||
GithubComChaitinPandaWikiProApiContributeV1ContributeDetailResp,
|
||||
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
|
||||
} from '@/request/pro';
|
||||
import { useAppSelector } from '@/store';
|
||||
import { Modal } from '@ctzhian/ui';
|
||||
import { Box, Button, Stack } from '@mui/material';
|
||||
import { IconWenjian } from '@panda-wiki/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactDiffViewer from 'react-diff-viewer';
|
||||
|
||||
type MarkdownPreviewModalProps = {
|
||||
open: boolean;
|
||||
row: GithubComChaitinPandaWikiProApiContributeV1ContributeItem | null;
|
||||
onClose: () => void;
|
||||
onAccept: () => void;
|
||||
onReject: () => void;
|
||||
};
|
||||
|
||||
const MarkdownPreviewModal = ({
|
||||
open,
|
||||
row,
|
||||
onClose,
|
||||
onAccept,
|
||||
onReject,
|
||||
}: MarkdownPreviewModalProps) => {
|
||||
const { kb_id = '' } = useAppSelector(state => state.config);
|
||||
const [data, setData] =
|
||||
useState<GithubComChaitinPandaWikiProApiContributeV1ContributeDetailResp | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && row) {
|
||||
getApiProV1ContributeDetail({ id: row.id!, kb_id }).then(res => {
|
||||
setData(res);
|
||||
});
|
||||
}
|
||||
}, [open, row, kb_id]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
width={'1200px'}
|
||||
sx={{
|
||||
'.MuiDialogContent-root': {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<Stack direction='row' alignItems='center' gap={2}>
|
||||
<Box>
|
||||
来自 {row?.auth_name || '匿名用户'} 的
|
||||
{row?.type === ConstsContributeType.ContributeTypeAdd
|
||||
? '新增'
|
||||
: '修改'}
|
||||
</Box>
|
||||
<Box sx={{ fontSize: 14, color: 'text.auxiliary', fontWeight: 400 }}>
|
||||
{dayjs(row?.created_at).fromNow()}
|
||||
</Box>
|
||||
</Stack>
|
||||
}
|
||||
footer={
|
||||
row?.status === ConstsContributeStatus.ContributeStatusPending ||
|
||||
row?.status === ConstsContributeStatus.ContributeStatusRejected ? (
|
||||
<Stack
|
||||
direction='row'
|
||||
gap={1}
|
||||
justifyContent='flex-end'
|
||||
sx={{ p: 3, pt: 0 }}
|
||||
>
|
||||
{row?.status === ConstsContributeStatus.ContributeStatusPending ? (
|
||||
<>
|
||||
<Button
|
||||
size='small'
|
||||
variant='outlined'
|
||||
color='error'
|
||||
onClick={onReject}
|
||||
>
|
||||
拒绝
|
||||
</Button>
|
||||
<Button size='small' variant='contained' onClick={onAccept}>
|
||||
采纳
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={onClose} size='small' variant='contained'>
|
||||
关闭
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Stack direction='row'>
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
overflow: 'auto',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction='row'
|
||||
gap={1}
|
||||
sx={{ bgcolor: 'background.paper2', p: 1, borderRadius: '10px' }}
|
||||
>
|
||||
<Box sx={{ fontSize: 14, fontWeight: 'bold', flexShrink: 0 }}>
|
||||
提交说明:
|
||||
</Box>
|
||||
|
||||
<Box sx={{ fontSize: 14, color: 'text.tertiary' }}>
|
||||
{data?.reason || '-'}
|
||||
</Box>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction='row'
|
||||
alignItems='center'
|
||||
gap={1}
|
||||
sx={{ fontSize: 24, fontWeight: 700, pb: 2 }}
|
||||
>
|
||||
<IconWenjian /> {row?.node_name || '-'}
|
||||
</Stack>
|
||||
<Box sx={{ overflowY: 'auto', maxHeight: 'calc(100vh - 400px)' }}>
|
||||
<ReactDiffViewer
|
||||
oldValue={data?.original_node?.content || ''}
|
||||
newValue={data?.content || ''}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPreviewModal;
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Logo from '@/assets/images/logo.png';
|
||||
import { Box, Chip, Stack, TextField } from '@mui/material';
|
||||
import Card from '@/components/Card';
|
||||
import { tableSx } from '@/constant/styles';
|
||||
import { Ellipsis, message, Modal, Table } from '@ctzhian/ui';
|
||||
import type { ColumnType } from '@ctzhian/ui/dist/Table';
|
||||
import { Box, Chip, Stack, TextField } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Table, Ellipsis, message, Modal } from '@ctzhian/ui';
|
||||
import type { ColumnType } from '@ctzhian/ui/dist/Table';
|
||||
import DocModal from './DocModal';
|
||||
|
||||
import { useURLSearchParams } from '@/hooks';
|
||||
import {
|
||||
getApiProV1ContributeList,
|
||||
postApiProV1ContributeAudit,
|
||||
} from '@/request/pro/Contribute';
|
||||
import {
|
||||
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
|
||||
ConstsContributeStatus,
|
||||
ConstsContributeType,
|
||||
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
|
||||
} from '@/request/pro/types';
|
||||
import { useURLSearchParams } from '@/hooks';
|
||||
import { useAppSelector } from '@/store';
|
||||
import ContributePreviewModal from './ContributePreviewModal';
|
||||
import MarkdownPreviewModal from './MarkdownPreviewModal';
|
||||
|
||||
const StyledSearchRow = styled(Stack)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
|
|
@ -366,23 +365,13 @@ export default function ContributionPage() {
|
|||
}}
|
||||
/>
|
||||
|
||||
{previewRow?.meta?.content_type === 'md' ? (
|
||||
<MarkdownPreviewModal
|
||||
open={open}
|
||||
row={previewRow}
|
||||
onClose={closeDialog}
|
||||
onAccept={handleAccept}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
) : (
|
||||
<ContributePreviewModal
|
||||
open={open}
|
||||
row={previewRow}
|
||||
onClose={closeDialog}
|
||||
onAccept={handleAccept}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
)}
|
||||
<ContributePreviewModal
|
||||
open={open}
|
||||
row={previewRow}
|
||||
onClose={closeDialog}
|
||||
onAccept={handleAccept}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
<DocModal
|
||||
open={docModalOpen}
|
||||
onClose={() => setDocModalOpen(false)}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { DomainNodeListItemResp, V1NodeDetailResp } from '@/request/types';
|
|||
import { useAppSelector } from '@/store';
|
||||
import { addOpacityToColor } from '@/utils';
|
||||
import { convertToTree } from '@/utils/drag';
|
||||
import { filterEmptyFolders } from '@/utils/tree';
|
||||
import { Ellipsis, Icon } from '@ctzhian/ui';
|
||||
import { alpha, Box, IconButton, Stack, useTheme } from '@mui/material';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
|
@ -65,7 +66,7 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
|
|||
kb_id: kb_id || localStorage.getItem('kb_id') || '',
|
||||
};
|
||||
getApiV1NodeList(params).then(res => {
|
||||
const v = convertToTree(res || []);
|
||||
const v = filterEmptyFolders(convertToTree(res || []));
|
||||
setData(v);
|
||||
// 计算当前文档的所有父级文件夹,并默认展开
|
||||
try {
|
||||
|
|
@ -109,75 +110,6 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
|
|||
});
|
||||
};
|
||||
|
||||
const renderAdd = (parentId: string) => {
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0 }} onClick={e => e.stopPropagation()}>
|
||||
<Cascader
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
context={
|
||||
<IconButton>
|
||||
<Icon
|
||||
className='catalog-folder-add-icon'
|
||||
type='icon-icon_tool_close'
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
color: 'action.selected',
|
||||
transform: 'rotate(45deg)',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}
|
||||
list={Object.entries(ImportContentWays).map(([key, value]) => ({
|
||||
key,
|
||||
label: (
|
||||
<Box key={key}>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
gap={1}
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
px: 2,
|
||||
lineHeight: '40px',
|
||||
height: 40,
|
||||
width: 180,
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
bgcolor: addOpacityToColor(
|
||||
theme.palette.primary.main,
|
||||
0.1,
|
||||
),
|
||||
},
|
||||
}}
|
||||
onClick={() => value.onClick(parentId)}
|
||||
>
|
||||
{value.label}
|
||||
</Stack>
|
||||
{key === 'OfflineFile' && (
|
||||
<Box
|
||||
sx={{
|
||||
borderTop: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
my: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTree = (items: ITreeItem[], pl = 2.5, depth = 1) => {
|
||||
const sortedItems = [...items].sort(
|
||||
(a, b) => (a.order ?? 0) - (b.order ?? 0),
|
||||
|
|
@ -278,7 +210,72 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
|
|||
MD
|
||||
</Box>
|
||||
)}
|
||||
{item.type === 1 && renderAdd(item.id)}
|
||||
{item.type === 1 && (
|
||||
<Box sx={{ flexShrink: 0 }} onClick={e => e.stopPropagation()}>
|
||||
<Cascader
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
context={
|
||||
<IconButton>
|
||||
<Icon
|
||||
className='catalog-folder-add-icon'
|
||||
type='icon-icon_tool_close'
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
color: 'action.selected',
|
||||
transform: 'rotate(45deg)',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}
|
||||
list={Object.entries(ImportContentWays).map(([key, value]) => ({
|
||||
key,
|
||||
label: (
|
||||
<Box key={key}>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
gap={1}
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
px: 2,
|
||||
lineHeight: '40px',
|
||||
height: 40,
|
||||
width: 180,
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
bgcolor: addOpacityToColor(
|
||||
theme.palette.primary.main,
|
||||
0.1,
|
||||
),
|
||||
},
|
||||
}}
|
||||
onClick={() => value.onClick(item.id)}
|
||||
>
|
||||
{value.label}
|
||||
</Stack>
|
||||
{key === 'OfflineFile' && (
|
||||
<Box
|
||||
sx={{
|
||||
borderTop: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
my: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
{item.children &&
|
||||
item.children.length > 0 &&
|
||||
|
|
@ -344,24 +341,16 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
|
|||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
sx={{ pr: 1 }}
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: 'text.tertiary',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: 'text.tertiary',
|
||||
}}
|
||||
>
|
||||
目录
|
||||
</Box>
|
||||
{renderAdd('')}
|
||||
</Stack>
|
||||
目录
|
||||
</Box>
|
||||
<Stack
|
||||
sx={{
|
||||
my: 1,
|
||||
|
|
@ -379,59 +368,38 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
|
|||
open={customDocOpen}
|
||||
parentId={opraParentId}
|
||||
onCreated={node => {
|
||||
if (opraParentId) {
|
||||
// 复用工具方法:findItemDeep / setProperty
|
||||
setData(prev => {
|
||||
const parent = findItemDeep(prev, opraParentId);
|
||||
if (!parent) return prev;
|
||||
const children =
|
||||
(parent.children as ITreeItem[] | undefined) ?? [];
|
||||
const lastOrder = children.length
|
||||
? (children[children.length - 1].order ?? children.length - 1)
|
||||
: -1;
|
||||
const newChild: ITreeItem = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
content_type: node.content_type,
|
||||
type: node.type,
|
||||
emoji: node.emoji,
|
||||
parentId: parent.id,
|
||||
level: (parent.level ?? 0) + 1,
|
||||
order: lastOrder + 1,
|
||||
status: 1,
|
||||
children: node.type === 1 ? [] : undefined,
|
||||
};
|
||||
const next = setProperty(prev, opraParentId, 'children', val => [
|
||||
...((val as ITreeItem[] | undefined) ?? []),
|
||||
newChild,
|
||||
]) as ITreeItem[];
|
||||
return [...next];
|
||||
});
|
||||
// 展开父级,确保新项可见
|
||||
setExpandedFolders(prev => {
|
||||
const ns = new Set(prev);
|
||||
if (opraParentId) ns.add(opraParentId);
|
||||
return ns;
|
||||
});
|
||||
} else {
|
||||
// 复用工具方法:findItemDeep / setProperty
|
||||
setData(prev => {
|
||||
const parent = findItemDeep(prev, opraParentId);
|
||||
if (!parent) return prev;
|
||||
const children = (parent.children as ITreeItem[] | undefined) ?? [];
|
||||
const lastOrder = children.length
|
||||
? (children[children.length - 1].order ?? children.length - 1)
|
||||
: -1;
|
||||
const newChild: ITreeItem = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
content_type: node.content_type,
|
||||
type: node.type,
|
||||
emoji: node.emoji,
|
||||
parentId: '',
|
||||
level: 1,
|
||||
order: data.length
|
||||
? (data[data.length - 1].order ?? data.length - 1)
|
||||
: -1,
|
||||
parentId: parent.id,
|
||||
level: (parent.level ?? 0) + 1,
|
||||
order: lastOrder + 1,
|
||||
status: 1,
|
||||
children: node.type === 1 ? [] : undefined,
|
||||
};
|
||||
setData(prev => {
|
||||
return [...prev, newChild];
|
||||
});
|
||||
}
|
||||
const next = setProperty(prev, opraParentId, 'children', val => [
|
||||
...((val as ITreeItem[] | undefined) ?? []),
|
||||
newChild,
|
||||
]) as ITreeItem[];
|
||||
return [...next];
|
||||
});
|
||||
// 展开父级,确保新项可见
|
||||
setExpandedFolders(prev => {
|
||||
const ns = new Set(prev);
|
||||
if (opraParentId) ns.add(opraParentId);
|
||||
return ns;
|
||||
});
|
||||
}}
|
||||
onClose={() => setCustomDocOpen(false)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import {
|
||||
EditorMarkdown,
|
||||
MarkdownEditorRef,
|
||||
UseTiptapReturn,
|
||||
} from '@ctzhian/tiptap';
|
||||
import { Box } from '@mui/material';
|
||||
import { forwardRef } from 'react';
|
||||
import { Editor, UseTiptapReturn } from '@ctzhian/tiptap';
|
||||
import { alpha, Box, Divider, Stack, useTheme } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import 'ace-builds/src-noconflict/ext-language_tools';
|
||||
import 'ace-builds/src-noconflict/mode-markdown';
|
||||
import 'ace-builds/src-noconflict/theme-github';
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
editor: UseTiptapReturn['editor'];
|
||||
|
|
@ -13,29 +14,132 @@ interface MarkdownEditorProps {
|
|||
header: React.ReactNode;
|
||||
}
|
||||
|
||||
const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>(
|
||||
({ editor, value, onChange, header }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
const MarkdownEditor = ({
|
||||
editor,
|
||||
value,
|
||||
onChange,
|
||||
header,
|
||||
}: MarkdownEditorProps) => {
|
||||
const theme = useTheme();
|
||||
const [displayMode, setDisplayMode] = useState<'edit' | 'preview' | 'split'>(
|
||||
'split',
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: '56px',
|
||||
px: 10,
|
||||
pt: 4,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Box sx={{}}>{header}</Box>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'stretch'}
|
||||
sx={{
|
||||
mt: '56px',
|
||||
px: 10,
|
||||
pt: 4,
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Box sx={{}}>{header}</Box>
|
||||
<EditorMarkdown
|
||||
editor={editor}
|
||||
value={value}
|
||||
onAceChange={onChange}
|
||||
height='calc(100vh - 340px)'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
MarkdownEditor.displayName = 'MarkdownEditor';
|
||||
<Stack
|
||||
direction='row'
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
p: 0.5,
|
||||
top: -32,
|
||||
left: -1,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderBottom: 'none',
|
||||
borderRadius: '4px 4px 0 0',
|
||||
fontSize: 12,
|
||||
color: 'text.tertiary',
|
||||
'.md-display-mode-active': {
|
||||
color: 'primary.main',
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.1),
|
||||
},
|
||||
'& :hover:not(.md-display-mode-active)': {
|
||||
borderRadius: '4px',
|
||||
bgcolor: 'background.paper3',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className={displayMode === 'split' ? 'md-display-mode-active' : ''}
|
||||
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
|
||||
onClick={() => setDisplayMode('split')}
|
||||
>
|
||||
分屏模式
|
||||
</Box>
|
||||
<Box
|
||||
className={displayMode === 'edit' ? 'md-display-mode-active' : ''}
|
||||
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
|
||||
onClick={() => setDisplayMode('edit')}
|
||||
>
|
||||
编辑模式
|
||||
</Box>
|
||||
<Box
|
||||
className={
|
||||
displayMode === 'preview' ? 'md-display-mode-active' : ''
|
||||
}
|
||||
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
|
||||
onClick={() => setDisplayMode('preview')}
|
||||
>
|
||||
预览模式
|
||||
</Box>
|
||||
</Stack>
|
||||
{['edit', 'split'].includes(displayMode) && (
|
||||
<Stack
|
||||
direction='column'
|
||||
sx={{
|
||||
flex: 1,
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
<AceEditor
|
||||
mode='markdown'
|
||||
theme='twilight'
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
name='project-doc-editor'
|
||||
wrapEnabled={true}
|
||||
showPrintMargin={false}
|
||||
fontSize={16}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'calc(100vh - 56px)',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{displayMode === 'split' && <Divider orientation='vertical' flexItem />}
|
||||
{['split', 'preview'].includes(displayMode) && (
|
||||
<Box
|
||||
id='markdown-preview-container'
|
||||
sx={{
|
||||
overflowY: 'scroll',
|
||||
flex: 1,
|
||||
p: 2,
|
||||
height: 'calc(100vh - 56px)',
|
||||
}}
|
||||
>
|
||||
<Editor editor={editor} />
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownEditor;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ interface TocProps {
|
|||
setFixed: (fixed: boolean) => void;
|
||||
setShowSummary: (showSummary: boolean) => void;
|
||||
isMarkdown: boolean;
|
||||
scrollToHeading?: (headingText: string) => void;
|
||||
}
|
||||
|
||||
const HeadingIcon = [
|
||||
|
|
@ -35,13 +34,7 @@ const HeadingSx = [
|
|||
{ fontSize: 14, fontWeight: 400, color: 'text.disabled' },
|
||||
];
|
||||
|
||||
const Toc = ({
|
||||
headings,
|
||||
fixed,
|
||||
setFixed,
|
||||
isMarkdown,
|
||||
scrollToHeading,
|
||||
}: TocProps) => {
|
||||
const Toc = ({ headings, fixed, setFixed, isMarkdown }: TocProps) => {
|
||||
const storageTocOpen = localStorage.getItem('toc-open');
|
||||
const [open, setOpen] = useState(!!storageTocOpen);
|
||||
const levels = Array.from(
|
||||
|
|
@ -198,10 +191,6 @@ const Toc = ({
|
|||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
// 同时滚动 AceEditor
|
||||
if (scrollToHeading) {
|
||||
scrollToHeading(it.textContent);
|
||||
}
|
||||
} else {
|
||||
// 在富文本编辑器模式下,滚动整个窗口
|
||||
const offset = 100;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,7 @@ import Emoji from '@/components/Emoji';
|
|||
import { postApiV1CreationTabComplete, putApiV1NodeDetail } from '@/request';
|
||||
import { V1NodeDetailResp } from '@/request/types';
|
||||
import { useAppSelector } from '@/store';
|
||||
import {
|
||||
EditorMarkdown,
|
||||
MarkdownEditorRef,
|
||||
TocList,
|
||||
useTiptap,
|
||||
UseTiptapReturn,
|
||||
} from '@ctzhian/tiptap';
|
||||
import { TocList, useTiptap, UseTiptapReturn } from '@ctzhian/tiptap';
|
||||
import { Icon, message } from '@ctzhian/ui';
|
||||
import { Box, Stack, TextField, Tooltip } from '@mui/material';
|
||||
import dayjs from 'dayjs';
|
||||
|
|
@ -25,6 +19,7 @@ import { WrapContext } from '..';
|
|||
import AIGenerate from './AIGenerate';
|
||||
import FullTextEditor from './FullTextEditor';
|
||||
import Header from './Header';
|
||||
import MarkdownEditor from './MarkdownEditor';
|
||||
import Summary from './Summary';
|
||||
import Toc from './Toc';
|
||||
import Toolbar from './Toolbar';
|
||||
|
|
@ -48,8 +43,6 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
null,
|
||||
);
|
||||
|
||||
const markdownEditorRef = useRef<MarkdownEditorRef>(null);
|
||||
|
||||
const isMarkdown = useMemo(() => {
|
||||
return defaultDetail.meta?.content_type === 'md';
|
||||
}, [defaultDetail.meta?.content_type]);
|
||||
|
|
@ -192,7 +185,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
editable: !isMarkdown,
|
||||
contentType: isMarkdown ? 'markdown' : 'html',
|
||||
immediatelyRender: true,
|
||||
content: defaultDetail.content,
|
||||
content: defaultDetail.content || '',
|
||||
exclude: ['invisibleCharacters', 'youtube', 'mention'],
|
||||
onCreate: ({ editor: tiptapEditor }) => {
|
||||
const characterCount = (
|
||||
|
|
@ -591,27 +584,17 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
</Box>
|
||||
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
|
||||
{isMarkdown ? (
|
||||
<Box
|
||||
sx={{
|
||||
mt: '56px',
|
||||
px: 10,
|
||||
pt: 4,
|
||||
flex: 1,
|
||||
<MarkdownEditor
|
||||
editor={editorRef.editor}
|
||||
value={nodeDetail?.content || ''}
|
||||
onChange={value => {
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
editorRef.setContent(value);
|
||||
}}
|
||||
>
|
||||
<Box sx={{}}>{renderEditorTitleEmojiSummary()}</Box>
|
||||
<EditorMarkdown
|
||||
ref={markdownEditorRef}
|
||||
editor={editorRef.editor}
|
||||
value={nodeDetail?.content || ''}
|
||||
onAceChange={value => {
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
}}
|
||||
height='calc(100vh - 340px)'
|
||||
/>
|
||||
</Box>
|
||||
header={renderEditorTitleEmojiSummary()}
|
||||
/>
|
||||
) : (
|
||||
<FullTextEditor
|
||||
editor={editorRef.editor}
|
||||
|
|
@ -626,12 +609,6 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
isMarkdown={isMarkdown}
|
||||
setFixed={setFixedToc}
|
||||
setShowSummary={setShowSummary}
|
||||
scrollToHeading={
|
||||
isMarkdown
|
||||
? headingText =>
|
||||
markdownEditorRef.current?.scrollToHeading(headingText)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<AIGenerate
|
||||
open={aiGenerateOpen}
|
||||
|
|
|
|||
|
|
@ -376,7 +376,6 @@ export interface GithubComChaitinPandaWikiProApiContributeV1ContributeListResp {
|
|||
}
|
||||
|
||||
export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta {
|
||||
content_type?: string;
|
||||
doc_width?: string;
|
||||
emoji?: string;
|
||||
}
|
||||
|
|
@ -493,7 +492,6 @@ export type GithubComChaitinPandaWikiProApiShareV1OAuthCallbackResp = Record<
|
|||
export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq {
|
||||
captcha_token: string;
|
||||
content?: string;
|
||||
content_type: "html" | "md";
|
||||
emoji?: string;
|
||||
name?: string;
|
||||
node_id?: string;
|
||||
|
|
|
|||
|
|
@ -134,25 +134,6 @@ export enum ConstsSourceType {
|
|||
SourceTypeOpenAIAPI = "openai_api",
|
||||
}
|
||||
|
||||
export enum ConstsNodeRagInfoStatus {
|
||||
/** 等待基础处理 */
|
||||
NodeRagStatusBasicPending = "BASIC_PENDING",
|
||||
/** 正在进行基础处理(文本分割、向量化等) */
|
||||
NodeRagStatusBasicRunning = "BASIC_RUNNING",
|
||||
/** 基础处理失败 */
|
||||
NodeRagStatusBasicFailed = "BASIC_FAILED",
|
||||
/** 基础处理成功 */
|
||||
NodeRagStatusBasicSucceeded = "BASIC_SUCCEEDED",
|
||||
/** 基础处理完成,等待增强处理 */
|
||||
NodeRagStatusEnhancePending = "ENHANCE_PENDING",
|
||||
/** 正在进行增强处理(关键词提取等) */
|
||||
NodeRagStatusEnhanceRunning = "ENHANCE_RUNNING",
|
||||
/** 增强处理失败 */
|
||||
NodeRagStatusEnhanceFailed = "ENHANCE_FAILED",
|
||||
/** 增强处理成功 */
|
||||
NodeRagStatusEnhanceSucceeded = "ENHANCE_SUCCEEDED",
|
||||
}
|
||||
|
||||
export enum ConstsNodePermName {
|
||||
/** 导航内可见 */
|
||||
NodePermNameVisible = "visible",
|
||||
|
|
@ -493,16 +474,6 @@ export interface DomainBatchMoveReq {
|
|||
parent_id?: string;
|
||||
}
|
||||
|
||||
export interface DomainBlockGridConfig {
|
||||
list?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
}[];
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface DomainBrandGroup {
|
||||
links?: DomainLink[];
|
||||
name?: string;
|
||||
|
|
@ -966,7 +937,6 @@ export interface DomainNodeListItemResp {
|
|||
parent_id?: string;
|
||||
permissions?: DomainNodePermissions;
|
||||
position?: number;
|
||||
rag_info?: DomainRagInfo;
|
||||
status?: DomainNodeStatus;
|
||||
summary?: string;
|
||||
type?: DomainNodeType;
|
||||
|
|
@ -1112,20 +1082,6 @@ export interface DomainProviderModelListItem {
|
|||
model?: string;
|
||||
}
|
||||
|
||||
export interface DomainQuestionConfig {
|
||||
list?: {
|
||||
id?: string;
|
||||
question?: string;
|
||||
}[];
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface DomainRagInfo {
|
||||
message?: string;
|
||||
status?: ConstsNodeRagInfoStatus;
|
||||
}
|
||||
|
||||
export interface DomainRecommendNodeListResp {
|
||||
emoji?: string;
|
||||
id?: string;
|
||||
|
|
@ -1285,7 +1241,6 @@ export interface DomainWebAppCustomSettings {
|
|||
export interface DomainWebAppLandingConfig {
|
||||
banner_config?: DomainBannerConfig;
|
||||
basic_doc_config?: DomainBasicDocConfig;
|
||||
block_grid_config?: DomainBlockGridConfig;
|
||||
carousel_config?: DomainCarouselConfig;
|
||||
case_config?: DomainCaseConfig;
|
||||
com_config_order?: string[];
|
||||
|
|
@ -1296,7 +1251,6 @@ export interface DomainWebAppLandingConfig {
|
|||
img_text_config?: DomainImgTextConfig;
|
||||
metrics_config?: DomainMetricsConfig;
|
||||
node_ids?: string[];
|
||||
question_config?: DomainQuestionConfig;
|
||||
simple_doc_config?: DomainSimpleDocConfig;
|
||||
text_config?: DomainTextConfig;
|
||||
text_img_config?: DomainTextImgConfig;
|
||||
|
|
@ -1306,7 +1260,6 @@ export interface DomainWebAppLandingConfig {
|
|||
export interface DomainWebAppLandingConfigResp {
|
||||
banner_config?: DomainBannerConfig;
|
||||
basic_doc_config?: DomainBasicDocConfig;
|
||||
block_grid_config?: DomainBlockGridConfig;
|
||||
carousel_config?: DomainCarouselConfig;
|
||||
case_config?: DomainCaseConfig;
|
||||
com_config_order?: string[];
|
||||
|
|
@ -1318,7 +1271,6 @@ export interface DomainWebAppLandingConfigResp {
|
|||
metrics_config?: DomainMetricsConfig;
|
||||
node_ids?: string[];
|
||||
nodes?: DomainRecommendNodeListResp[];
|
||||
question_config?: DomainQuestionConfig;
|
||||
simple_doc_config?: DomainSimpleDocConfig;
|
||||
text_config?: DomainTextConfig;
|
||||
text_img_config?: DomainTextImgConfig;
|
||||
|
|
|
|||
|
|
@ -1,128 +1,86 @@
|
|||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import { useStore } from '@/provider';
|
||||
import { Modal } from '@ctzhian/ui';
|
||||
import { Stack, Tooltip, Fab, Zoom } from '@mui/material';
|
||||
import { usePathname, useParams } from 'next/navigation';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import {
|
||||
Fab,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Zoom,
|
||||
} from '@mui/material';
|
||||
import { useParams, usePathname } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DocFab = () => {
|
||||
const pathname = usePathname();
|
||||
const { id: docId } = useParams() || {};
|
||||
const { kbDetail, mobile } = useStore();
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [contentType, setContentType] = useState<'html' | 'md'>('html');
|
||||
const [openSelectContentTypeModal, setOpenSelectContentTypeModal] =
|
||||
useState(false);
|
||||
|
||||
if (mobile) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title='新建文档类型'
|
||||
open={openSelectContentTypeModal}
|
||||
onCancel={() => {
|
||||
setOpenSelectContentTypeModal(false);
|
||||
setContentType('html');
|
||||
}}
|
||||
onOk={() => {
|
||||
setOpenSelectContentTypeModal(false);
|
||||
window.open(`/editor?contentType=${contentType}`, '_blank');
|
||||
}}
|
||||
>
|
||||
<RadioGroup
|
||||
value={contentType}
|
||||
onChange={e => setContentType(e.target.value as 'html' | 'md')}
|
||||
>
|
||||
<FormControlLabel
|
||||
value='html'
|
||||
control={<Radio size='small' />}
|
||||
label='富文本'
|
||||
/>
|
||||
<FormControlLabel
|
||||
value='md'
|
||||
control={<Radio size='small' />}
|
||||
label='Markdown'
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Modal>
|
||||
<Stack
|
||||
gap={1}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 70,
|
||||
right: 16,
|
||||
zIndex: 10000,
|
||||
}}
|
||||
onMouseLeave={() => setShowActions(false)}
|
||||
>
|
||||
{kbDetail?.settings.contribute_settings?.is_enable && (
|
||||
<>
|
||||
<Stack
|
||||
gap={1}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 70,
|
||||
right: 16,
|
||||
zIndex: 10000,
|
||||
}}
|
||||
onMouseLeave={() => setShowActions(false)}
|
||||
>
|
||||
{kbDetail?.settings.contribute_settings?.is_enable && (
|
||||
<>
|
||||
<Zoom
|
||||
in={showActions}
|
||||
style={{ transitionDelay: showActions ? '100ms' : '0ms' }}
|
||||
>
|
||||
<Tooltip title='创建文档' placement='left' arrow>
|
||||
<Fab
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
window.open(`/editor`, '_blank');
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
</Zoom>
|
||||
{pathname.startsWith('/node/') && (
|
||||
<Zoom
|
||||
in={showActions}
|
||||
style={{ transitionDelay: showActions ? '100ms' : '0ms' }}
|
||||
style={{ transitionDelay: showActions ? '40ms' : '0ms' }}
|
||||
>
|
||||
<Tooltip title='创建文档' placement='left' arrow>
|
||||
<Tooltip title='编辑文档' placement='left' arrow>
|
||||
<Fab
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
setOpenSelectContentTypeModal(true);
|
||||
window.open(`/editor/${docId}`, '_blank');
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
<EditIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
</Zoom>
|
||||
{pathname.startsWith('/node/') && (
|
||||
<Zoom
|
||||
in={showActions}
|
||||
style={{ transitionDelay: showActions ? '40ms' : '0ms' }}
|
||||
>
|
||||
<Tooltip title='编辑文档' placement='left' arrow>
|
||||
<Fab
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
window.open(`/editor/${docId}`, '_blank');
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
</Zoom>
|
||||
)}
|
||||
<Fab
|
||||
size='small'
|
||||
)}
|
||||
<Fab
|
||||
size='small'
|
||||
sx={{
|
||||
backgroundColor: 'background.paper2',
|
||||
color: 'text.secondary',
|
||||
'&:hover': { backgroundColor: 'background.paper2' },
|
||||
}}
|
||||
onMouseEnter={() => setShowActions(true)}
|
||||
>
|
||||
<MenuIcon
|
||||
sx={{
|
||||
backgroundColor: 'background.paper2',
|
||||
color: 'text.secondary',
|
||||
'&:hover': { backgroundColor: 'background.paper2' },
|
||||
transition: 'transform 200ms',
|
||||
transform: showActions ? 'rotate(90deg)' : 'rotate(0deg)',
|
||||
}}
|
||||
onMouseEnter={() => setShowActions(true)}
|
||||
>
|
||||
<MenuIcon
|
||||
sx={{
|
||||
transition: 'transform 200ms',
|
||||
transform: showActions ? 'rotate(90deg)' : 'rotate(0deg)',
|
||||
}}
|
||||
/>
|
||||
</Fab>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
/>
|
||||
</Fab>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -376,7 +376,6 @@ export interface GithubComChaitinPandaWikiProApiContributeV1ContributeListResp {
|
|||
}
|
||||
|
||||
export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta {
|
||||
content_type?: string;
|
||||
doc_width?: string;
|
||||
emoji?: string;
|
||||
}
|
||||
|
|
@ -493,7 +492,6 @@ export type GithubComChaitinPandaWikiProApiShareV1OAuthCallbackResp = Record<
|
|||
export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq {
|
||||
captcha_token: string;
|
||||
content?: string;
|
||||
content_type: "html" | "md";
|
||||
emoji?: string;
|
||||
name?: string;
|
||||
node_id?: string;
|
||||
|
|
|
|||
|
|
@ -134,25 +134,6 @@ export enum ConstsSourceType {
|
|||
SourceTypeOpenAIAPI = "openai_api",
|
||||
}
|
||||
|
||||
export enum ConstsNodeRagInfoStatus {
|
||||
/** 等待基础处理 */
|
||||
NodeRagStatusBasicPending = "BASIC_PENDING",
|
||||
/** 正在进行基础处理(文本分割、向量化等) */
|
||||
NodeRagStatusBasicRunning = "BASIC_RUNNING",
|
||||
/** 基础处理失败 */
|
||||
NodeRagStatusBasicFailed = "BASIC_FAILED",
|
||||
/** 基础处理成功 */
|
||||
NodeRagStatusBasicSucceeded = "BASIC_SUCCEEDED",
|
||||
/** 基础处理完成,等待增强处理 */
|
||||
NodeRagStatusEnhancePending = "ENHANCE_PENDING",
|
||||
/** 正在进行增强处理(关键词提取等) */
|
||||
NodeRagStatusEnhanceRunning = "ENHANCE_RUNNING",
|
||||
/** 增强处理失败 */
|
||||
NodeRagStatusEnhanceFailed = "ENHANCE_FAILED",
|
||||
/** 增强处理成功 */
|
||||
NodeRagStatusEnhanceSucceeded = "ENHANCE_SUCCEEDED",
|
||||
}
|
||||
|
||||
export enum ConstsNodePermName {
|
||||
/** 导航内可见 */
|
||||
NodePermNameVisible = "visible",
|
||||
|
|
@ -493,16 +474,6 @@ export interface DomainBatchMoveReq {
|
|||
parent_id?: string;
|
||||
}
|
||||
|
||||
export interface DomainBlockGridConfig {
|
||||
list?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
}[];
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface DomainBrandGroup {
|
||||
links?: DomainLink[];
|
||||
name?: string;
|
||||
|
|
@ -966,7 +937,6 @@ export interface DomainNodeListItemResp {
|
|||
parent_id?: string;
|
||||
permissions?: DomainNodePermissions;
|
||||
position?: number;
|
||||
rag_info?: DomainRagInfo;
|
||||
status?: DomainNodeStatus;
|
||||
summary?: string;
|
||||
type?: DomainNodeType;
|
||||
|
|
@ -1112,20 +1082,6 @@ export interface DomainProviderModelListItem {
|
|||
model?: string;
|
||||
}
|
||||
|
||||
export interface DomainQuestionConfig {
|
||||
list?: {
|
||||
id?: string;
|
||||
question?: string;
|
||||
}[];
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface DomainRagInfo {
|
||||
message?: string;
|
||||
status?: ConstsNodeRagInfoStatus;
|
||||
}
|
||||
|
||||
export interface DomainRecommendNodeListResp {
|
||||
emoji?: string;
|
||||
id?: string;
|
||||
|
|
@ -1285,7 +1241,6 @@ export interface DomainWebAppCustomSettings {
|
|||
export interface DomainWebAppLandingConfig {
|
||||
banner_config?: DomainBannerConfig;
|
||||
basic_doc_config?: DomainBasicDocConfig;
|
||||
block_grid_config?: DomainBlockGridConfig;
|
||||
carousel_config?: DomainCarouselConfig;
|
||||
case_config?: DomainCaseConfig;
|
||||
com_config_order?: string[];
|
||||
|
|
@ -1296,7 +1251,6 @@ export interface DomainWebAppLandingConfig {
|
|||
img_text_config?: DomainImgTextConfig;
|
||||
metrics_config?: DomainMetricsConfig;
|
||||
node_ids?: string[];
|
||||
question_config?: DomainQuestionConfig;
|
||||
simple_doc_config?: DomainSimpleDocConfig;
|
||||
text_config?: DomainTextConfig;
|
||||
text_img_config?: DomainTextImgConfig;
|
||||
|
|
@ -1306,7 +1260,6 @@ export interface DomainWebAppLandingConfig {
|
|||
export interface DomainWebAppLandingConfigResp {
|
||||
banner_config?: DomainBannerConfig;
|
||||
basic_doc_config?: DomainBasicDocConfig;
|
||||
block_grid_config?: DomainBlockGridConfig;
|
||||
carousel_config?: DomainCarouselConfig;
|
||||
case_config?: DomainCaseConfig;
|
||||
com_config_order?: string[];
|
||||
|
|
@ -1318,7 +1271,6 @@ export interface DomainWebAppLandingConfigResp {
|
|||
metrics_config?: DomainMetricsConfig;
|
||||
node_ids?: string[];
|
||||
nodes?: DomainRecommendNodeListResp[];
|
||||
question_config?: DomainQuestionConfig;
|
||||
simple_doc_config?: DomainSimpleDocConfig;
|
||||
text_config?: DomainTextConfig;
|
||||
text_img_config?: DomainTextImgConfig;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Box, Drawer, IconButton, Stack } from '@mui/material';
|
||||
import {
|
||||
H1Icon,
|
||||
H2Icon,
|
||||
|
|
@ -8,15 +9,12 @@ import {
|
|||
TocList,
|
||||
} from '@ctzhian/tiptap';
|
||||
import { Ellipsis, Icon } from '@ctzhian/ui';
|
||||
import { Box, Drawer, IconButton, Stack } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface TocProps {
|
||||
headings: TocList;
|
||||
fixed: boolean;
|
||||
setFixed: (fixed: boolean) => void;
|
||||
isMarkdown: boolean;
|
||||
scrollToHeading?: (headingText: string) => void;
|
||||
}
|
||||
|
||||
const HeadingIcon = [
|
||||
|
|
@ -34,13 +32,7 @@ const HeadingSx = [
|
|||
{ fontSize: 14, fontWeight: 400, color: 'text.disabled' },
|
||||
];
|
||||
|
||||
const Toc = ({
|
||||
headings,
|
||||
fixed,
|
||||
setFixed,
|
||||
scrollToHeading,
|
||||
isMarkdown,
|
||||
}: TocProps) => {
|
||||
const Toc = ({ headings, fixed, setFixed }: TocProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const levels = Array.from(
|
||||
new Set(headings.map(it => it.level).sort((a, b) => a - b)),
|
||||
|
|
@ -172,40 +164,15 @@ const Toc = ({
|
|||
onClick={() => {
|
||||
const element = document.getElementById(it.id);
|
||||
if (element) {
|
||||
if (isMarkdown) {
|
||||
const container = document.getElementById(
|
||||
'markdown-preview-container',
|
||||
);
|
||||
if (container) {
|
||||
const containerRect =
|
||||
container.getBoundingClientRect();
|
||||
const elementRect = element.getBoundingClientRect();
|
||||
const offset = 20; // 顶部偏移
|
||||
const scrollTop =
|
||||
container.scrollTop +
|
||||
elementRect.top -
|
||||
containerRect.top -
|
||||
offset;
|
||||
container.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
// 同时滚动 AceEditor
|
||||
if (scrollToHeading) {
|
||||
scrollToHeading(it.textContent);
|
||||
}
|
||||
} else {
|
||||
const offset = 100;
|
||||
const elementPosition =
|
||||
element.getBoundingClientRect().top;
|
||||
const offsetPosition =
|
||||
elementPosition + window.pageYOffset - offset;
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
const offset = 100;
|
||||
const elementPosition =
|
||||
element.getBoundingClientRect().top;
|
||||
const offsetPosition =
|
||||
elementPosition + window.pageYOffset - offset;
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,20 +2,13 @@
|
|||
import Emoji from '@/components/emoji';
|
||||
import { postShareProV1FileUploadWithProgress } from '@/request/pro/otherCustomer';
|
||||
import { V1NodeDetailResp } from '@/request/types';
|
||||
import {
|
||||
Editor,
|
||||
EditorMarkdown,
|
||||
MarkdownEditorRef,
|
||||
TocList,
|
||||
useTiptap,
|
||||
UseTiptapReturn,
|
||||
} from '@ctzhian/tiptap';
|
||||
import { Editor, TocList, useTiptap, UseTiptapReturn } from '@ctzhian/tiptap';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { Box, Stack, TextField } from '@mui/material';
|
||||
import { IconAShijian2, IconZiti } from '@panda-wiki/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { useParams, useSearchParams } from 'next/navigation';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useWrapContext } from '..';
|
||||
import AIGenerate from './AIGenerate';
|
||||
import ConfirmModal from './ConfirmModal';
|
||||
|
|
@ -28,12 +21,9 @@ interface WrapProps {
|
|||
}
|
||||
|
||||
const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const contentType = searchParams.get('contentType') || 'html';
|
||||
const { nodeDetail, setNodeDetail, onSave } = useWrapContext();
|
||||
const { catalogOpen, nodeDetail, setNodeDetail, onSave } = useWrapContext();
|
||||
const { id } = useParams();
|
||||
|
||||
const markdownEditorRef = useRef<MarkdownEditorRef>(null);
|
||||
const [characterCount, setCharacterCount] = useState(0);
|
||||
const [headings, setHeadings] = useState<TocList>([]);
|
||||
const [fixedToc, setFixedToc] = useState(false);
|
||||
|
|
@ -41,14 +31,6 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
const [aiGenerateOpen, setAiGenerateOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
||||
|
||||
const isMarkdown = useMemo(() => {
|
||||
if (!id && contentType === 'md') {
|
||||
return true;
|
||||
}
|
||||
return defaultDetail.meta?.content_type === 'md';
|
||||
}, [defaultDetail.meta?.content_type, contentType]);
|
||||
|
||||
const updateDetail = (value: V1NodeDetailResp) => {
|
||||
setNodeDetail({
|
||||
...nodeDetail,
|
||||
|
|
@ -91,9 +73,8 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
};
|
||||
|
||||
const editorRef = useTiptap({
|
||||
editable: true,
|
||||
immediatelyRender: false,
|
||||
editable: !isMarkdown,
|
||||
contentType: isMarkdown ? 'markdown' : 'html',
|
||||
content: defaultDetail?.content || '',
|
||||
exclude: ['invisibleCharacters', 'youtube', 'mention'],
|
||||
onCreate: ({ editor: tiptapEditor }) => {
|
||||
|
|
@ -125,33 +106,17 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
event.preventDefault();
|
||||
if (editorRef && editorRef.editor) {
|
||||
const value = editorRef.getContent();
|
||||
const html = editorRef.getHTML();
|
||||
updateDetail({
|
||||
content: value,
|
||||
content: html,
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (checkRequiredFields()) {
|
||||
setConfirmModalOpen(true);
|
||||
}
|
||||
}, 10);
|
||||
setConfirmModalOpen(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[editorRef],
|
||||
[editorRef, onSave],
|
||||
);
|
||||
|
||||
const checkRequiredFields = useCallback(() => {
|
||||
if (!nodeDetail?.name?.trim()) {
|
||||
message.error('请先输入文档名称');
|
||||
return false;
|
||||
}
|
||||
if (!nodeDetail?.content?.trim()) {
|
||||
message.error('请先输入文档内容');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, [nodeDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleGlobalSave);
|
||||
return () => {
|
||||
|
|
@ -183,14 +148,10 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
detail={nodeDetail!}
|
||||
updateDetail={updateDetail}
|
||||
handleSave={async () => {
|
||||
if (checkRequiredFields()) {
|
||||
setConfirmModalOpen(true);
|
||||
}
|
||||
setConfirmModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
{!isMarkdown && (
|
||||
<Toolbar editorRef={editorRef} handleAiGenerate={handleAiGenerate} />
|
||||
)}
|
||||
<Toolbar editorRef={editorRef} handleAiGenerate={handleAiGenerate} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -202,8 +163,8 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
<Box
|
||||
sx={{
|
||||
width: `calc(100vw - 160px - ${fixedToc ? 292 : 0}px)`,
|
||||
p: isMarkdown ? '72px 80px' : '72px 80px 150px',
|
||||
mt: isMarkdown ? '56px' : '102px',
|
||||
p: '72px 80px 150px',
|
||||
mt: '102px',
|
||||
mx: 'auto',
|
||||
}}
|
||||
>
|
||||
|
|
@ -292,52 +253,24 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
{characterCount} 字
|
||||
</Stack>
|
||||
</Stack>
|
||||
{editorRef.editor && (
|
||||
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
|
||||
{isMarkdown ? (
|
||||
<EditorMarkdown
|
||||
ref={markdownEditorRef}
|
||||
editor={editorRef.editor}
|
||||
value={nodeDetail?.content || defaultDetail?.content || ''}
|
||||
onAceChange={value => {
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
}}
|
||||
height='calc(100vh - 340px)'
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
wordBreak: 'break-all',
|
||||
'.tiptap.ProseMirror': {
|
||||
overflowX: 'hidden',
|
||||
minHeight: 'calc(100vh - 102px - 48px)',
|
||||
},
|
||||
'.tableWrapper': {
|
||||
width: '100%',
|
||||
overflowX: 'auto',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Editor editor={editorRef.editor} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
wordBreak: 'break-all',
|
||||
'.tiptap.ProseMirror': {
|
||||
overflowX: 'hidden',
|
||||
minHeight: 'calc(100vh - 102px - 48px)',
|
||||
},
|
||||
'.tableWrapper': {
|
||||
width: '100%',
|
||||
overflowX: 'auto',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{editorRef.editor && <Editor editor={editorRef.editor} />}
|
||||
</Box>
|
||||
</Box>
|
||||
<Toc
|
||||
headings={headings}
|
||||
fixed={fixedToc}
|
||||
setFixed={setFixedToc}
|
||||
isMarkdown={isMarkdown}
|
||||
scrollToHeading={
|
||||
isMarkdown
|
||||
? headingText =>
|
||||
markdownEditorRef.current?.scrollToHeading(headingText)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Toc headings={headings} fixed={fixedToc} setFixed={setFixedToc} />
|
||||
</Box>
|
||||
|
||||
<AIGenerate
|
||||
|
|
@ -351,11 +284,11 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
open={confirmModalOpen}
|
||||
onCancel={() => setConfirmModalOpen(false)}
|
||||
onOk={async (reason: string, token: string) => {
|
||||
const value = editorRef.getContent();
|
||||
const value = editorRef.getHTML();
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
await onSave(value, reason, token, isMarkdown ? 'md' : 'html');
|
||||
await onSave(value, reason, token);
|
||||
setConfirmModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
'use client';
|
||||
import { createContext, useContext } from 'react';
|
||||
import { postShareProV1ContributeSubmit } from '@/request/pro/ShareContribute';
|
||||
import { V1NodeDetailResp } from '@/request/types';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { Box, Stack, useMediaQuery } from '@mui/material';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Edit from './edit';
|
||||
|
||||
export interface WrapContext {
|
||||
|
|
@ -12,12 +13,7 @@ export interface WrapContext {
|
|||
setCatalogOpen: (open: boolean) => void;
|
||||
nodeDetail: V1NodeDetailResp | null;
|
||||
setNodeDetail: (detail: V1NodeDetailResp) => void;
|
||||
onSave: (
|
||||
content: string,
|
||||
reason: string,
|
||||
token: string,
|
||||
contentType?: 'html' | 'md',
|
||||
) => void;
|
||||
onSave: (content: string, reason: string, token: string) => void;
|
||||
saveLoading: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -45,12 +41,7 @@ const DocEditor = () => {
|
|||
);
|
||||
const [catalogOpen, setCatalogOpen] = useState(true);
|
||||
|
||||
const onSave = (
|
||||
content: string,
|
||||
reason: string,
|
||||
token: string,
|
||||
contentType?: 'html' | 'md',
|
||||
) => {
|
||||
const onSave = (content: string, reason: string, token: string) => {
|
||||
setSaveLoading(true);
|
||||
return postShareProV1ContributeSubmit({
|
||||
node_id: id ? id[0] : undefined,
|
||||
|
|
@ -60,7 +51,6 @@ const DocEditor = () => {
|
|||
reason,
|
||||
emoji: nodeDetail?.meta?.emoji,
|
||||
captcha_token: token,
|
||||
content_type: contentType || 'html',
|
||||
}).then(() => {
|
||||
message.success('保存成功, 即将关闭页面');
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.12.1",
|
||||
"dependencies": {
|
||||
"@ctzhian/tiptap": "^1.11.4",
|
||||
"@ctzhian/tiptap": "^1.10.3",
|
||||
"@ctzhian/ui": "^7.0.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,9 @@ const Carousel = ({ title, items }: CarouselProps) => {
|
|||
|
||||
document.body.removeChild(measureContainer);
|
||||
|
||||
// 获取目标背景色(从计算样式获取)
|
||||
const targetBgColor = alpha(theme.palette.text.primary, 0.5).toString();
|
||||
|
||||
// 设置容器初始状态(只有 padding,背景色透明)
|
||||
gsap.set(descElement, {
|
||||
width: padding,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue