提交 be72dcaa authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 2be389ac
......@@ -15,7 +15,10 @@ import {
Radio,
} from 'antd'
import { SlateTransforms } from '@wangeditor/editor'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { TextArea } = Input
import '../ai-research/search.less'
import './image.less'
......@@ -147,7 +150,7 @@ export default function AIImageModal(props) {
children: [{ text: prompt || 'AI生成的图片' }],
}
SlateTransforms.insertNodes(editor, [oImg, oP])
insertBlockNodesAndFocus(editor, SlateTransforms, [oImg, oP])
message.success('图片已插入编辑器!')
setIsModalOpen(false)
} else {
......
......@@ -20,7 +20,10 @@ import {
InputNumber,
} from 'antd'
import { SlateTransforms } from '@wangeditor/editor'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import { useVideo } from '@ezijing/ai-react'
import { uploadFileByUrl } from '@/utils/oss'
import '../ai-research/search.less'
......@@ -128,7 +131,7 @@ export default function AIVideoModal(props) {
editor.restoreSelection()
removeEmptyParagraph(editor)
SlateTransforms.insertNodes(editor, {
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo',
videoUrl: ossUrl,
title: videoPrompt || 'AI生成的视频',
......
......@@ -26,7 +26,10 @@ import { saveAs } from 'file-saver'
import { uploadFileByUrl } from '@/utils/oss'
import useDigitalHuman from '@/components/editor/hooks/useDigitalHuman'
import './digital-human.less'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { TextArea } = Input
......@@ -100,7 +103,7 @@ export default function AIDigitalHumanModal(props) {
if (editor) {
editor.restoreSelection()
removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, {
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo',
videoUrl: ossUrl,
title: `${generatedVideo.figureName} - AI播报`,
......
......@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons'
import { SlateTransforms } from '@wangeditor/editor'
import '../../index.less'
import { uploadFile } from '@/utils/oss'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { Dragger } = Upload
......@@ -70,7 +73,7 @@ const AudioModal = ({ open, onClose, payload = {}, runtime }) => {
removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, {
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterAudio',
audioUrl: values.audioUrl,
title: values.audioTitle,
......
......@@ -12,7 +12,10 @@ import {
Col,
} from 'antd'
import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import { fontFamilyList, fontSizeList } from '../../shared/editor-settings'
import '../../index.less'
......@@ -104,7 +107,7 @@ const ChapterItemModal = (props, ref) => {
}
const random = Math.random().toString(10).substring(2, 10)
editor.insertNode({
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterSection',
title: values.title,
textColor: values.textColor,
......@@ -235,7 +238,6 @@ const ChapterItemModal = (props, ref) => {
controls={false}
placeholder=''
style={{ width: '100%', textAlign: 'left' }}
allowClear
/>
</Form.Item>
</Col>
......
......@@ -16,7 +16,10 @@ import {
} from 'antd'
import { CloudUploadOutlined } from '@ant-design/icons'
import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import { fontFamilyList, fontSizeList } from '../../shared/editor-settings'
import '../../index.less'
......@@ -161,7 +164,7 @@ const ImageModal = (props, ref) => {
const docBody = parser.parseFromString(html, 'text/html')
const section = docBody.body.querySelectorAll('.chapter-item-header') // 是否存在章头
editor.insertNode({
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterHeader',
title: values.title,
textColor: values.textColor,
......@@ -347,7 +350,6 @@ const ImageModal = (props, ref) => {
controls={false}
placeholder=''
style={{ width: '100%', textAlign: 'left' }}
allowClear
/>
</Form.Item>
</Col>
......
......@@ -21,6 +21,7 @@ import '../../index.less'
import { addExpandRead, expandReadInfo } from '../../shared/request'
import {
findElementPathByAttribute,
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
......@@ -148,7 +149,7 @@ const ExpandModal = (props, ref) => {
})
if (parseInt(values.flex) === 1) {
editor.insertNode({
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterExpandRead',
flex: parseInt(values.flex),
name: values.name,
......
......@@ -5,7 +5,10 @@ import { addGallery } from '../../shared/request'
import { SlateTransforms } from '@wangeditor/editor'
import { findNodeWithParent, fontSizeList } from '../../shared/editor-settings'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import GalleryFormItem from './GalleryItem'
const randomOne = Math.random().toString(16).substring(2, 10)
......@@ -274,7 +277,7 @@ const GalleryModal = (props, ref) => {
children: [{ text: '' }],
})
} else {
editor.insertNode({
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterGallery',
title: galleryTitle,
galleryList: encodeURI(JSON.stringify(gallery)),
......
......@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons'
import { DomEditor, SlateTransforms } from '@wangeditor/editor'
import '../../index.less'
import OnlineImageList from '../../shared/online-image/OnlineImageList'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import { uploadFile } from '@/utils/oss'
......@@ -122,7 +125,7 @@ const ImageModal = (props, ref) => {
nodes.push(oP)
}
SlateTransforms.insertNodes(editor, nodes)
insertBlockNodesAndFocus(editor, SlateTransforms, nodes)
clear()
}
......
......@@ -15,6 +15,7 @@ import image from './image/feature'
import link from './link/feature'
import practice from './practice/feature'
import preview from './preview/feature'
import tableActions from './table-actions/feature'
import tooltip from './tooltip/feature'
import video from './video/feature'
......@@ -37,6 +38,7 @@ export const editorFeatures = [
aiResearch,
preview,
history,
tableActions,
]
export const editorFeatureRegistry = createFeatureRegistry(editorFeatures)
......@@ -257,7 +257,6 @@ const LinkModal = (props, ref) => {
</Form.Item>
<Form.Item label={` `}>
<ColorPicker
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
......
......@@ -17,7 +17,10 @@ export default defineEditorFeature({
{
name: 'CustomerLinkClick',
handler: ({ editor }) =>
openEditorDialog(editor, 'link.insert', { selectionSize: getSelectionFontSize(editor) }),
openEditorDialog(editor, 'link.insert', {
selectionSize: getSelectionFontSize(editor),
theme: '#ab1941',
}),
},
],
})
import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react'
import {
Modal,
Row,
Col,
Divider,
Upload,
Input,
Space,
Button,
Form,
Spin,
message,
ColorPicker,
} from 'antd'
import { CloseOutlined } from '@ant-design/icons'
import { useEditorStore } from '@/stores/editorStore'
import { DomEditor, SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
import { useState, forwardRef, useImperativeHandle } from 'react'
import { Divider, Input, Space, Button, Form, ColorPicker } from 'antd'
import ListTopic from './components/list'
import CustomerTopic from './components/customer'
import InsertTopic from './components/insert'
import iconPractice from '@/assets/images/icon_practice.png'
import iconClose from '@/assets/images/icon_chapter_close.png'
import { getChapterTopic } from '../../shared/request'
import { bookNameList } from './question-api'
import { SlateTransforms, SlateEditor, SlateElement } from '@wangeditor/editor'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import './styles.less'
import { useEditorStore } from '@/stores/editorStore'
import '../../index.less'
const PracticeSettingModal = (props, ref) => {
const { editor, chapterId, bookId, closePanel } = props
const practiceRandom = useEditorStore((state) => state.practiceRandom)
const practiceTitle = useEditorStore((state) => state.practiceTitle)
const practiceTheme = useEditorStore((state) => state.practiceTheme)
const PracticeModal = (props, ref) => {
const { bookId, chapterId, setPracticeOpenVisible } = props
const setPracticeRandom = useEditorStore((state) => state.setPracticeRandom)
const [themeValue, setThemeValue] = useState(practiceTheme)
const listRef = useRef()
const customerTopicRef = useRef()
const [customerType, setCustomerType] = useState('')
const [practiceTitleVisible, setPracticeTitleVisible] = useState(false) // 是否显示可编辑
const [practiceTitleValue, setPracticeTitleValue] = useState(practiceTitle) // input 标题
const [practiceStatus, setPracticeStatus] = useState(false) // 手动还是自动
// 右边勾选的列表
const [chooseList, setChooseList] = useState([])
// 线上的题库
const [onlineList, setOnloneList] = useState([])
// 左边插入的列表
const [insertList, setInsertList] = useState([])
const [bookNameIdData, setbookNameIdData] = useState([])
const getbookNameList = async () => {
const res = await bookNameList({})
setbookNameIdData(res)
}
useEffect(() => {
getbookNameList()
}, [])
const { editor } = props
const [form] = Form.useForm()
const [themeValue, setThemeValue] = useState('#ab1941')
const [initValues, setInitValues] = useState({ practice: '', theme: '#ab1941' })
const [nodes, setNodes] = useState(null)
useImperativeHandle(ref, () => {
return {
nodes,
}
})
const textColorChange = (value) => {
const hex = typeof value === 'string' ? value : value?.toHexString()
setThemeValue(hex)
form.setFieldsValue({ theme: hex })
}
// 获取
const getTopicList = async () => {
const data = await getChapterTopic({
position: practiceRandom,
book_id: bookId,
chapter_id: chapterId,
})
if (data && data.length > 0) {
const dataSource = []
data.forEach((item, index) => {
dataSource.push({ ...item, choose_id: item.question_id })
})
setOnloneList(dataSource)
if (insertList.length === 0) {
setInsertList(dataSource)
} else {
const tempChoose = [...insertList]
const ids = tempChoose.map((item) => parseInt(item.choose_id))
dataSource.forEach((item) => {
if (!ids.includes(parseInt(item.choose_id))) {
tempChoose.push(item)
}
})
setInsertList(tempChoose)
}
if (chooseList.length === 0) {
setChooseList(dataSource)
} else {
const tempChoose = [...chooseList]
const ids = tempChoose.map((item) => parseInt(item.choose_id))
dataSource.forEach((item) => {
if (!ids.includes(parseInt(item.choose_id))) {
tempChoose.push(item)
}
})
setChooseList(tempChoose)
}
}
}
useEffect(() => {
if (bookId && chapterId && practiceRandom) {
getTopicList()
}
}, [bookId, chapterId, practiceRandom])
// 设置标题
const changePracticeTitle = () => {
setPracticeTitleValue(practiceTitle)
setPracticeTitleVisible(true)
}
const submitPracticeTitle = () => {
editor.restoreSelection()
const callback = (practiceNum, practiceTitle) => {
const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => {
// JS syntax
if (SlateElement.isElement(node)) {
if (node.type === 'chapterPractice') {
return true // 匹配 chapterHeader
return true
}
}
return false
......@@ -130,148 +45,88 @@ const PracticeSettingModal = (props, ref) => {
universal: true,
})
if (nodeEntries) {
for (const nodeEntry of nodeEntries) {
const [node, path] = nodeEntry
// console.log('选中了 chapterHeader 节点', node);
// console.log('节点 path 是', path);
if (node.practiceNum === practiceRandom) {
SlateTransforms.setNodes(
editor,
{ ...node, title: practiceTitleValue, theme: themeValue },
{ at: path },
)
setPracticeRandom({ practiceNum: node.practiceNum, practiceTitle: practiceTitleValue })
setPracticeTitleVisible(false)
return false
}
}
}
return
setNodes(nodeEntries)
setPracticeRandom({ practiceNum, practiceTitle })
}
// 添加题目到左边
const addTopicList = () => {
if (chooseList.length === 0) {
message.error('您还没有勾选题目!')
return
}
const onFinish = (values) => {
editor.restoreSelection()
const tempChoose = [...insertList]
const ids = tempChoose.map((item) => parseInt(item.choose_id))
chooseList.forEach((item) => {
if (!ids.includes(parseInt(item.choose_id))) {
tempChoose.push(item)
}
removeSelectedEmptyParagraph(editor, SlateTransforms)
const practiceNum = Math.random().toString(32).substring(2, 10)
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterPractice',
title: values.practice,
practiceNum: practiceNum,
bookId,
chapterId,
theme: values.theme,
callback: callback,
children: [{ text: '' }],
})
setInsertList(tempChoose)
}
const getTopList = () => {
listRef.current.setPage(1)
listRef.current.getTopicTypeList()
form.resetFields()
setPracticeOpenVisible(false)
editor.focus()
}
return (
<div>
<Row gutter='20' className='practice-topic-row'>
<Col span={12} className='common practice-left'>
<div className='practice-header'>
<div className='practice-l-img'>
{/* <img src={iconPractice} alt='' /> */}
<svg className='svg-icon' aria-hidden='true'>
<use xlinkHref='#icon-writing'></use>
</svg>
<Divider />
<div className='editor-content-form'>
<Form
layout='vertical'
name='validate_other'
form={form}
onFinish={onFinish}
initialValues={initValues}
>
<Form.Item
label='标题'
name='practice'
rules={[{ required: true, message: '请输入标题' }]}
extra='最多输入100字'
>
<Input maxLength={100} placeholder='' allowClear />
</Form.Item>
<Form.Item>
<div className='justcontent-color-inline'>
<Form.Item
label='练习主题色'
name='theme'
className='flex-max'
rules={[
{ required: true, message: '请选择颜色' },
{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' },
]}
>
<Input placeholder='' allowClear />
</Form.Item>
<Form.Item label={` `}>
<ColorPicker
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
/>
</Form.Item>
</div>
<div className='practice-l-title'>
{!practiceTitleVisible && (
<h2 onClick={changePracticeTitle} className='title'>
<ColorPicker value={themeValue} defaultValue={themeValue} size='small' disabled />
<span className='titles'>{practiceTitle}</span>
<span className='click'>(可点击编辑)</span>
</h2>
)}
{practiceTitleVisible && (
<div style={{ marginTop: 2 }}>
<Space>
<Input
value={practiceTitleValue}
onChange={(e) => setPracticeTitleValue(e.target.value)}
/>
<div style={{ marginTop: 2 }}>
<ColorPicker
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
showText
/>
</div>
<Button type='primary' onClick={submitPracticeTitle}>
确定
</Button>
<Button type='default' onClick={() => setPracticeTitleVisible(false)}>
取消
</Button>
</Space>
</div>
)}
</div>
</div>
<div className='practice-topic-list'>
<InsertTopic
insertList={insertList}
setInsertList={setInsertList}
chooseList={chooseList}
setChooseList={setChooseList}
practiceRandom={practiceRandom}
chapterId={chapterId}
bookId={bookId}
closePanel={closePanel}
/>
</div>
</Col>
<Col span={12} className='common practice-right'>
<div className='practice-header right'>
<div style={{ marginTop: 5 }}>
<Button type='default' onClick={() => setPracticeStatus(!practiceStatus)}>
{practiceStatus ? '自动添加' : '手动添加'}
</Form.Item>
<Form.Item className='editor-form-buttons'>
<Space>
<Button type='default' onClick={() => setPracticeOpenVisible(false)}>
取消
</Button>
</div>
<Button
type='link'
icon={<CloseOutlined style={{ color: '#b83956' }} />}
onClick={closePanel}
></Button>
</div>
<div className='practice-topic-list'>
{practiceStatus ? (
<CustomerTopic
ref={customerTopicRef}
setPracticeStatus={setPracticeStatus}
getTopList={getTopList}
bookNameIdData={bookNameIdData}
bookId={bookId}
setCustomerType={setCustomerType}
/>
) : (
<ListTopic
ref={listRef}
chooseList={chooseList}
setChooseList={setChooseList}
addTopicList={addTopicList}
practiceStatus={practiceStatus}
bookNameIdData={bookNameIdData}
bookId={bookId}
customerType={customerType}
/>
)}
</div>
</Col>
</Row>
<Button type='primary' htmlType='submit'>
插入
</Button>
</Space>
</Form.Item>
</Form>
</div>
</div>
)
}
export default forwardRef(PracticeSettingModal)
export default forwardRef(PracticeModal)
import { defineEditorFeature } from '../../core/define-editor-feature'
import { insertTableColAfterMenu, insertTableRowAfterMenu } from './insert-after-menu'
export default defineEditorFeature({
id: 'table-actions',
menus: [
{ order: 170, config: insertTableRowAfterMenu },
{ order: 180, config: insertTableColAfterMenu },
],
})
import { DomEditor, SlateRange, SlateTransforms } from '@wangeditor/editor'
const createCell = (isHeader = false) => ({
type: 'table-cell',
...(isHeader ? { isHeader: true } : {}),
children: [{ text: '' }],
})
class InsertTableAfterMenu {
constructor() {
this.tag = 'button'
}
getValue() {
return ''
}
isActive() {
return false
}
isDisabled(editor) {
return (
editor.selection === null ||
SlateRange.isExpanded(editor.selection) ||
DomEditor.getSelectedNodeByType(editor, 'table') === null
)
}
}
class InsertTableRowAfter extends InsertTableAfterMenu {
constructor() {
super()
this.title = '向前插入行'
this.iconSvg =
'<svg viewBox="0 0 1048 1024"><g transform="translate(0 917.504) scale(1 -1)"><path d="M707.7888 521.0112h-147.456v-147.456H488.2432v147.456h-147.456v68.8128h147.456v147.456h72.0896v-147.456h147.456zM0 917.504V0h1048.576v917.504H0zM327.68 65.536H65.536v196.608H327.68V65.536z m327.68 0H393.216v196.608h262.144V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 258.8672H65.536v462.0288H983.04V324.4032z"></path></g></svg>'
}
exec(editor) {
if (this.isDisabled(editor)) return
const row = DomEditor.getSelectedNodeByType(editor, 'table-row')
if (!row?.children?.length) return
const rowPath = DomEditor.findPath(editor, row)
SlateTransforms.insertNodes(
editor,
{
type: 'table-row',
children: row.children.map(() => createCell()),
},
{ at: rowPath },
)
}
}
class InsertTableColAfter extends InsertTableAfterMenu {
constructor() {
super()
this.title = '向后插入列'
this.iconSvg =
'<svg viewBox="0 0 1048 1024"><g transform="translate(1048 0) scale(-1 1)"><path d="M327.68 193.3312v186.7776H140.9024v91.7504H327.68v186.7776h88.4736V471.8592h190.0544V380.1088H416.1536V193.3312zM0 917.504V0h1048.576v917.504H0zM655.36 65.536H65.536v720.896H655.36V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 262.144h-262.144v196.608h262.144V327.68z m0 262.144h-262.144v196.608h262.144v-196.608z"></path></g></svg>'
}
exec(editor) {
if (this.isDisabled(editor)) return
const table = DomEditor.getSelectedNodeByType(editor, 'table')
const cell = DomEditor.getSelectedNodeByType(editor, 'table-cell')
if (!table || !cell) return
const cellPath = DomEditor.findPath(editor, cell)
const columnIndex = cellPath[2]
const firstRowIsHeader = table.children?.[0]?.children?.every((item) => item.isHeader)
table.children.forEach((_, rowIndex) => {
SlateTransforms.insertNodes(editor, createCell(rowIndex === 0 && firstRowIsHeader), {
at: [cellPath[0], rowIndex, columnIndex + 1],
})
})
}
}
export const insertTableRowAfterMenu = {
key: 'insertTableRowAfter',
factory() {
return new InsertTableRowAfter()
},
}
export const insertTableColAfterMenu = {
key: 'insertTableColAfter',
factory() {
return new InsertTableColAfter()
},
}
......@@ -5,7 +5,10 @@ import { SlateTransforms } from '@wangeditor/editor'
import '../../index.less'
import { uploadFile } from '@/utils/oss'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations'
import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { Dragger } = Upload
......@@ -70,7 +73,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, {
insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo',
videoUrl: values.videoUrl,
title: values.videoTitle,
......
......@@ -532,13 +532,13 @@
align-items: center;
justify-content: center;
width: 100%;
max-height: 320px;
max-height: 110px;
overflow: hidden;
img {
display: block;
max-width: 100%;
max-height: 320px;
max-height: 110px;
width: auto;
height: auto;
object-fit: contain;
......
......@@ -13,6 +13,30 @@ export const removeSelectedEmptyParagraph = (editor, transforms) => {
if (path) transforms.removeNodes(editor, { at: path })
}
export const insertBlockNodesAndFocus = (editor, transforms, nodes) => {
const insertedNodes = Array.isArray(nodes) ? nodes : [nodes]
const lastInsertedNode = insertedNodes[insertedNodes.length - 1]
transforms.insertNodes(editor, insertedNodes)
const insertedIndex = editor.children.findIndex((node) => node === lastInsertedNode)
if (insertedIndex < 0) return
const nextPath = [insertedIndex + 1]
const nextNode = editor.children[nextPath[0]]
const nextFirstChild = nextNode?.children?.[0]
if (!nextFirstChild || !('text' in nextFirstChild)) {
transforms.insertNodes(
editor,
{ type: 'paragraph', children: [{ text: '' }] },
{ at: nextPath },
)
}
transforms.select(editor, {
anchor: { path: [...nextPath, 0], offset: 0 },
focus: { path: [...nextPath, 0], offset: 0 },
})
}
export const findElementPathByAttribute = (children, attribute, value) => {
const index = children?.findIndex((node) => String(node?.[attribute]) === String(value))
return index >= 0 ? [index] : null
......
......@@ -99,6 +99,20 @@ export const createEditorConfig = ({ onFocus, onContentChange }) => ({
image: {
menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'ImageEditor', 'deleteImage'],
},
table: {
menuKeys: [
'enter',
'tableHeader',
'tableFullWidth',
'insertTableRow',
'insertTableRowAfter',
'deleteTableRow',
'insertTableCol',
'insertTableColAfter',
'deleteTableCol',
'deleteTable',
],
},
ImageAuto: {
menuKeys: [
'imageWidthChpater100',
......
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert'
// https://vitejs.dev/config/
export default defineConfig(() => {
export default defineConfig(() => {
return {
base: '/admin/',
plugins: [react(), mkcert()],
......@@ -13,30 +13,30 @@ export default defineConfig(() => {
open: '/admin/',
host: 'dev.ezijing.com',
port: 5174,
strictPort: true,
proxy: {
'/api/admin': {
target: 'http://127.0.0.1:7419',
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/api\/admin/, ''),
},
'/api/qwen': {
target: 'https://dashscope.aliyuncs.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qwen/, ''),
},
'/api/qianfan': {
target: 'https://aip.baidubce.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qianfan/, ''),
},
'/api/deepseek': {
target: 'https://api.deepseek.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/deepseek/, ''),
},
},
strictPort: true,
proxy: {
'/api/admin': {
target: 'http://127.0.0.1:7419',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api\/admin/, ''),
},
'/api/qwen': {
target: 'https://dashscope.aliyuncs.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/qwen/, ''),
},
'/api/qianfan': {
target: 'https://aip.baidubce.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/qianfan/, ''),
},
'/api/deepseek': {
target: 'https://api.deepseek.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/deepseek/, ''),
},
},
},
build: {
outDir: '../../dist/admin',
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论