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

chore: update

上级 2be389ac
...@@ -15,7 +15,10 @@ import { ...@@ -15,7 +15,10 @@ import {
Radio, Radio,
} from 'antd' } from 'antd'
import { SlateTransforms } from '@wangeditor/editor' import { SlateTransforms } from '@wangeditor/editor'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations' import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { TextArea } = Input const { TextArea } = Input
import '../ai-research/search.less' import '../ai-research/search.less'
import './image.less' import './image.less'
...@@ -147,7 +150,7 @@ export default function AIImageModal(props) { ...@@ -147,7 +150,7 @@ export default function AIImageModal(props) {
children: [{ text: prompt || 'AI生成的图片' }], children: [{ text: prompt || 'AI生成的图片' }],
} }
SlateTransforms.insertNodes(editor, [oImg, oP]) insertBlockNodesAndFocus(editor, SlateTransforms, [oImg, oP])
message.success('图片已插入编辑器!') message.success('图片已插入编辑器!')
setIsModalOpen(false) setIsModalOpen(false)
} else { } else {
......
...@@ -20,7 +20,10 @@ import { ...@@ -20,7 +20,10 @@ import {
InputNumber, InputNumber,
} from 'antd' } from 'antd'
import { SlateTransforms } from '@wangeditor/editor' 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 { useVideo } from '@ezijing/ai-react'
import { uploadFileByUrl } from '@/utils/oss' import { uploadFileByUrl } from '@/utils/oss'
import '../ai-research/search.less' import '../ai-research/search.less'
...@@ -128,7 +131,7 @@ export default function AIVideoModal(props) { ...@@ -128,7 +131,7 @@ export default function AIVideoModal(props) {
editor.restoreSelection() editor.restoreSelection()
removeEmptyParagraph(editor) removeEmptyParagraph(editor)
SlateTransforms.insertNodes(editor, { insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo', type: 'chapterVideo',
videoUrl: ossUrl, videoUrl: ossUrl,
title: videoPrompt || 'AI生成的视频', title: videoPrompt || 'AI生成的视频',
......
...@@ -26,7 +26,10 @@ import { saveAs } from 'file-saver' ...@@ -26,7 +26,10 @@ import { saveAs } from 'file-saver'
import { uploadFileByUrl } from '@/utils/oss' import { uploadFileByUrl } from '@/utils/oss'
import useDigitalHuman from '@/components/editor/hooks/useDigitalHuman' import useDigitalHuman from '@/components/editor/hooks/useDigitalHuman'
import './digital-human.less' import './digital-human.less'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations' import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { TextArea } = Input const { TextArea } = Input
...@@ -100,7 +103,7 @@ export default function AIDigitalHumanModal(props) { ...@@ -100,7 +103,7 @@ export default function AIDigitalHumanModal(props) {
if (editor) { if (editor) {
editor.restoreSelection() editor.restoreSelection()
removeSelectedEmptyParagraph(editor, SlateTransforms) removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, { insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo', type: 'chapterVideo',
videoUrl: ossUrl, videoUrl: ossUrl,
title: `${generatedVideo.figureName} - AI播报`, title: `${generatedVideo.figureName} - AI播报`,
......
...@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons' ...@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons'
import { SlateTransforms } from '@wangeditor/editor' import { SlateTransforms } from '@wangeditor/editor'
import '../../index.less' import '../../index.less'
import { uploadFile } from '@/utils/oss' import { uploadFile } from '@/utils/oss'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations' import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { Dragger } = Upload const { Dragger } = Upload
...@@ -70,7 +73,7 @@ const AudioModal = ({ open, onClose, payload = {}, runtime }) => { ...@@ -70,7 +73,7 @@ const AudioModal = ({ open, onClose, payload = {}, runtime }) => {
removeSelectedEmptyParagraph(editor, SlateTransforms) removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, { insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterAudio', type: 'chapterAudio',
audioUrl: values.audioUrl, audioUrl: values.audioUrl,
title: values.audioTitle, title: values.audioTitle,
......
...@@ -12,7 +12,10 @@ import { ...@@ -12,7 +12,10 @@ import {
Col, Col,
} from 'antd' } from 'antd'
import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor' 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 { fontFamilyList, fontSizeList } from '../../shared/editor-settings'
import '../../index.less' import '../../index.less'
...@@ -104,7 +107,7 @@ const ChapterItemModal = (props, ref) => { ...@@ -104,7 +107,7 @@ const ChapterItemModal = (props, ref) => {
} }
const random = Math.random().toString(10).substring(2, 10) const random = Math.random().toString(10).substring(2, 10)
editor.insertNode({ insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterSection', type: 'chapterSection',
title: values.title, title: values.title,
textColor: values.textColor, textColor: values.textColor,
...@@ -235,7 +238,6 @@ const ChapterItemModal = (props, ref) => { ...@@ -235,7 +238,6 @@ const ChapterItemModal = (props, ref) => {
controls={false} controls={false}
placeholder='' placeholder=''
style={{ width: '100%', textAlign: 'left' }} style={{ width: '100%', textAlign: 'left' }}
allowClear
/> />
</Form.Item> </Form.Item>
</Col> </Col>
......
...@@ -16,7 +16,10 @@ import { ...@@ -16,7 +16,10 @@ import {
} from 'antd' } from 'antd'
import { CloudUploadOutlined } from '@ant-design/icons' import { CloudUploadOutlined } from '@ant-design/icons'
import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor' 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 { fontFamilyList, fontSizeList } from '../../shared/editor-settings'
import '../../index.less' import '../../index.less'
...@@ -161,7 +164,7 @@ const ImageModal = (props, ref) => { ...@@ -161,7 +164,7 @@ const ImageModal = (props, ref) => {
const docBody = parser.parseFromString(html, 'text/html') const docBody = parser.parseFromString(html, 'text/html')
const section = docBody.body.querySelectorAll('.chapter-item-header') // 是否存在章头 const section = docBody.body.querySelectorAll('.chapter-item-header') // 是否存在章头
editor.insertNode({ insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterHeader', type: 'chapterHeader',
title: values.title, title: values.title,
textColor: values.textColor, textColor: values.textColor,
...@@ -347,7 +350,6 @@ const ImageModal = (props, ref) => { ...@@ -347,7 +350,6 @@ const ImageModal = (props, ref) => {
controls={false} controls={false}
placeholder='' placeholder=''
style={{ width: '100%', textAlign: 'left' }} style={{ width: '100%', textAlign: 'left' }}
allowClear
/> />
</Form.Item> </Form.Item>
</Col> </Col>
......
...@@ -21,6 +21,7 @@ import '../../index.less' ...@@ -21,6 +21,7 @@ import '../../index.less'
import { addExpandRead, expandReadInfo } from '../../shared/request' import { addExpandRead, expandReadInfo } from '../../shared/request'
import { import {
findElementPathByAttribute, findElementPathByAttribute,
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph, removeSelectedEmptyParagraph,
} from '../../shared/editor-operations' } from '../../shared/editor-operations'
...@@ -148,7 +149,7 @@ const ExpandModal = (props, ref) => { ...@@ -148,7 +149,7 @@ const ExpandModal = (props, ref) => {
}) })
if (parseInt(values.flex) === 1) { if (parseInt(values.flex) === 1) {
editor.insertNode({ insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterExpandRead', type: 'chapterExpandRead',
flex: parseInt(values.flex), flex: parseInt(values.flex),
name: values.name, name: values.name,
......
...@@ -5,7 +5,10 @@ import { addGallery } from '../../shared/request' ...@@ -5,7 +5,10 @@ import { addGallery } from '../../shared/request'
import { SlateTransforms } from '@wangeditor/editor' import { SlateTransforms } from '@wangeditor/editor'
import { findNodeWithParent, fontSizeList } from '../../shared/editor-settings' import { findNodeWithParent, fontSizeList } from '../../shared/editor-settings'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations' import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
import GalleryFormItem from './GalleryItem' import GalleryFormItem from './GalleryItem'
const randomOne = Math.random().toString(16).substring(2, 10) const randomOne = Math.random().toString(16).substring(2, 10)
...@@ -274,7 +277,7 @@ const GalleryModal = (props, ref) => { ...@@ -274,7 +277,7 @@ const GalleryModal = (props, ref) => {
children: [{ text: '' }], children: [{ text: '' }],
}) })
} else { } else {
editor.insertNode({ insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterGallery', type: 'chapterGallery',
title: galleryTitle, title: galleryTitle,
galleryList: encodeURI(JSON.stringify(gallery)), galleryList: encodeURI(JSON.stringify(gallery)),
......
...@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons' ...@@ -4,7 +4,10 @@ import { CloudUploadOutlined } from '@ant-design/icons'
import { DomEditor, SlateTransforms } from '@wangeditor/editor' import { DomEditor, SlateTransforms } from '@wangeditor/editor'
import '../../index.less' import '../../index.less'
import OnlineImageList from '../../shared/online-image/OnlineImageList' 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' import { uploadFile } from '@/utils/oss'
...@@ -122,7 +125,7 @@ const ImageModal = (props, ref) => { ...@@ -122,7 +125,7 @@ const ImageModal = (props, ref) => {
nodes.push(oP) nodes.push(oP)
} }
SlateTransforms.insertNodes(editor, nodes) insertBlockNodesAndFocus(editor, SlateTransforms, nodes)
clear() clear()
} }
......
...@@ -15,6 +15,7 @@ import image from './image/feature' ...@@ -15,6 +15,7 @@ import image from './image/feature'
import link from './link/feature' import link from './link/feature'
import practice from './practice/feature' import practice from './practice/feature'
import preview from './preview/feature' import preview from './preview/feature'
import tableActions from './table-actions/feature'
import tooltip from './tooltip/feature' import tooltip from './tooltip/feature'
import video from './video/feature' import video from './video/feature'
...@@ -37,6 +38,7 @@ export const editorFeatures = [ ...@@ -37,6 +38,7 @@ export const editorFeatures = [
aiResearch, aiResearch,
preview, preview,
history, history,
tableActions,
] ]
export const editorFeatureRegistry = createFeatureRegistry(editorFeatures) export const editorFeatureRegistry = createFeatureRegistry(editorFeatures)
...@@ -257,7 +257,6 @@ const LinkModal = (props, ref) => { ...@@ -257,7 +257,6 @@ const LinkModal = (props, ref) => {
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker
disabledAlpha
value={themeValue} value={themeValue}
defaultValue={themeValue} defaultValue={themeValue}
format='hex' format='hex'
......
...@@ -17,7 +17,10 @@ export default defineEditorFeature({ ...@@ -17,7 +17,10 @@ export default defineEditorFeature({
{ {
name: 'CustomerLinkClick', name: 'CustomerLinkClick',
handler: ({ editor }) => 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 { useState, forwardRef, useImperativeHandle } from 'react'
import { import { Divider, Input, Space, Button, Form, ColorPicker } from 'antd'
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 ListTopic from './components/list' import { SlateTransforms, SlateEditor, SlateElement } from '@wangeditor/editor'
import CustomerTopic from './components/customer' import {
import InsertTopic from './components/insert' insertBlockNodesAndFocus,
import iconPractice from '@/assets/images/icon_practice.png' removeSelectedEmptyParagraph,
import iconClose from '@/assets/images/icon_chapter_close.png' } from '../../shared/editor-operations'
import { getChapterTopic } from '../../shared/request'
import { bookNameList } from './question-api'
import './styles.less' import { useEditorStore } from '@/stores/editorStore'
import '../../index.less'
const PracticeSettingModal = (props, ref) => { const PracticeModal = (props, ref) => {
const { editor, chapterId, bookId, closePanel } = props const { bookId, chapterId, setPracticeOpenVisible } = props
const practiceRandom = useEditorStore((state) => state.practiceRandom)
const practiceTitle = useEditorStore((state) => state.practiceTitle)
const practiceTheme = useEditorStore((state) => state.practiceTheme)
const setPracticeRandom = useEditorStore((state) => state.setPracticeRandom) const setPracticeRandom = useEditorStore((state) => state.setPracticeRandom)
const [themeValue, setThemeValue] = useState(practiceTheme) const { editor } = props
const [form] = Form.useForm()
const listRef = useRef() const [themeValue, setThemeValue] = useState('#ab1941')
const customerTopicRef = useRef() const [initValues, setInitValues] = useState({ practice: '', theme: '#ab1941' })
const [nodes, setNodes] = useState(null)
const [customerType, setCustomerType] = useState('')
const [practiceTitleVisible, setPracticeTitleVisible] = useState(false) // 是否显示可编辑 useImperativeHandle(ref, () => {
const [practiceTitleValue, setPracticeTitleValue] = useState(practiceTitle) // input 标题 return {
const [practiceStatus, setPracticeStatus] = useState(false) // 手动还是自动 nodes,
}
// 右边勾选的列表 })
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 textColorChange = (value) => { const textColorChange = (value) => {
const hex = typeof value === 'string' ? value : value?.toHexString() const hex = typeof value === 'string' ? value : value?.toHexString()
setThemeValue(hex) 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, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: (node) => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterPractice') { if (node.type === 'chapterPractice') {
return true // 匹配 chapterHeader return true
} }
} }
return false return false
...@@ -130,148 +45,88 @@ const PracticeSettingModal = (props, ref) => { ...@@ -130,148 +45,88 @@ const PracticeSettingModal = (props, ref) => {
universal: true, universal: true,
}) })
if (nodeEntries) { setNodes(nodeEntries)
for (const nodeEntry of nodeEntries) { setPracticeRandom({ practiceNum, practiceTitle })
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
} }
// 添加题目到左边 const onFinish = (values) => {
const addTopicList = () => { editor.restoreSelection()
if (chooseList.length === 0) {
message.error('您还没有勾选题目!')
return
}
const tempChoose = [...insertList] removeSelectedEmptyParagraph(editor, SlateTransforms)
const ids = tempChoose.map((item) => parseInt(item.choose_id))
chooseList.forEach((item) => { const practiceNum = Math.random().toString(32).substring(2, 10)
if (!ids.includes(parseInt(item.choose_id))) { insertBlockNodesAndFocus(editor, SlateTransforms, {
tempChoose.push(item) type: 'chapterPractice',
} title: values.practice,
practiceNum: practiceNum,
bookId,
chapterId,
theme: values.theme,
callback: callback,
children: [{ text: '' }],
}) })
setInsertList(tempChoose) form.resetFields()
} setPracticeOpenVisible(false)
editor.focus()
const getTopList = () => {
listRef.current.setPage(1)
listRef.current.getTopicTypeList()
} }
return ( return (
<div> <div>
<Row gutter='20' className='practice-topic-row'> <Divider />
<Col span={12} className='common practice-left'> <div className='editor-content-form'>
<div className='practice-header'> <Form
<div className='practice-l-img'> layout='vertical'
{/* <img src={iconPractice} alt='' /> */} name='validate_other'
<svg className='svg-icon' aria-hidden='true'> form={form}
<use xlinkHref='#icon-writing'></use> onFinish={onFinish}
</svg> 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>
<div className='practice-l-title'> </Form.Item>
{!practiceTitleVisible && ( <Form.Item className='editor-form-buttons'>
<h2 onClick={changePracticeTitle} className='title'> <Space>
<ColorPicker value={themeValue} defaultValue={themeValue} size='small' disabled /> <Button type='default' onClick={() => setPracticeOpenVisible(false)}>
<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 ? '自动添加' : '手动添加'}
</Button> </Button>
</div> <Button type='primary' htmlType='submit'>
<Button 插入
type='link' </Button>
icon={<CloseOutlined style={{ color: '#b83956' }} />} </Space>
onClick={closePanel} </Form.Item>
></Button> </Form>
</div> </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>
</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' ...@@ -5,7 +5,10 @@ import { SlateTransforms } from '@wangeditor/editor'
import '../../index.less' import '../../index.less'
import { uploadFile } from '@/utils/oss' import { uploadFile } from '@/utils/oss'
import { removeSelectedEmptyParagraph } from '../../shared/editor-operations' import {
insertBlockNodesAndFocus,
removeSelectedEmptyParagraph,
} from '../../shared/editor-operations'
const { Dragger } = Upload const { Dragger } = Upload
...@@ -70,7 +73,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => { ...@@ -70,7 +73,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
removeSelectedEmptyParagraph(editor, SlateTransforms) removeSelectedEmptyParagraph(editor, SlateTransforms)
SlateTransforms.insertNodes(editor, { insertBlockNodesAndFocus(editor, SlateTransforms, {
type: 'chapterVideo', type: 'chapterVideo',
videoUrl: values.videoUrl, videoUrl: values.videoUrl,
title: values.videoTitle, title: values.videoTitle,
......
...@@ -532,13 +532,13 @@ ...@@ -532,13 +532,13 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; width: 100%;
max-height: 320px; max-height: 110px;
overflow: hidden; overflow: hidden;
img { img {
display: block; display: block;
max-width: 100%; max-width: 100%;
max-height: 320px; max-height: 110px;
width: auto; width: auto;
height: auto; height: auto;
object-fit: contain; object-fit: contain;
......
...@@ -13,6 +13,30 @@ export const removeSelectedEmptyParagraph = (editor, transforms) => { ...@@ -13,6 +13,30 @@ export const removeSelectedEmptyParagraph = (editor, transforms) => {
if (path) transforms.removeNodes(editor, { at: path }) 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) => { export const findElementPathByAttribute = (children, attribute, value) => {
const index = children?.findIndex((node) => String(node?.[attribute]) === String(value)) const index = children?.findIndex((node) => String(node?.[attribute]) === String(value))
return index >= 0 ? [index] : null return index >= 0 ? [index] : null
......
...@@ -99,6 +99,20 @@ export const createEditorConfig = ({ onFocus, onContentChange }) => ({ ...@@ -99,6 +99,20 @@ export const createEditorConfig = ({ onFocus, onContentChange }) => ({
image: { image: {
menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'ImageEditor', 'deleteImage'], menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'ImageEditor', 'deleteImage'],
}, },
table: {
menuKeys: [
'enter',
'tableHeader',
'tableFullWidth',
'insertTableRow',
'insertTableRowAfter',
'deleteTableRow',
'insertTableCol',
'insertTableColAfter',
'deleteTableCol',
'deleteTable',
],
},
ImageAuto: { ImageAuto: {
menuKeys: [ menuKeys: [
'imageWidthChpater100', 'imageWidthChpater100',
......
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert' import mkcert from 'vite-plugin-mkcert'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(() => { export default defineConfig(() => {
return { return {
base: '/admin/', base: '/admin/',
plugins: [react(), mkcert()], plugins: [react(), mkcert()],
...@@ -13,30 +13,30 @@ export default defineConfig(() => { ...@@ -13,30 +13,30 @@ export default defineConfig(() => {
open: '/admin/', open: '/admin/',
host: 'dev.ezijing.com', host: 'dev.ezijing.com',
port: 5174, port: 5174,
strictPort: true, strictPort: true,
proxy: { proxy: {
'/api/admin': { '/api/admin': {
target: 'http://127.0.0.1:7419', target: 'http://127.0.0.1:7419',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
rewrite: path => path.replace(/^\/api\/admin/, ''), rewrite: (path) => path.replace(/^\/api\/admin/, ''),
}, },
'/api/qwen': { '/api/qwen': {
target: 'https://dashscope.aliyuncs.com', target: 'https://dashscope.aliyuncs.com',
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qwen/, ''), rewrite: (path) => path.replace(/^\/api\/qwen/, ''),
}, },
'/api/qianfan': { '/api/qianfan': {
target: 'https://aip.baidubce.com', target: 'https://aip.baidubce.com',
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qianfan/, ''), rewrite: (path) => path.replace(/^\/api\/qianfan/, ''),
}, },
'/api/deepseek': { '/api/deepseek': {
target: 'https://api.deepseek.com', target: 'https://api.deepseek.com',
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api\/deepseek/, ''), rewrite: (path) => path.replace(/^\/api\/deepseek/, ''),
}, },
}, },
}, },
build: { build: {
outDir: '../../dist/admin', outDir: '../../dist/admin',
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论