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

chore: update

上级 253a8d2e
import axios from '@/utils/axios'
// 查询我的设计
export function getChuangKitDesigns(data) {
return axios.post('/api/ai/chuangKit/designs', data)
}
import React, { useState, useEffect, useCallback } from 'react' import { useState, useEffect } from 'react'
import { SendOutlined } from '@ant-design/icons' import { SendOutlined } from '@ant-design/icons'
import { Input, Space, Button, message } from 'antd' import { Input, Button, message } from 'antd'
import md5 from 'js-md5' import md5 from 'js-md5'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { sleep } from '@/utils/common' import { sleep } from '@/utils/common'
...@@ -14,11 +14,11 @@ const UUID = 'f3846153ba784b6d86bdcd5533259c88' ...@@ -14,11 +14,11 @@ const UUID = 'f3846153ba784b6d86bdcd5533259c88'
const auth_key = 'f3846153ba784b6d86bdcd5533259c88' const auth_key = 'f3846153ba784b6d86bdcd5533259c88'
const auth_secret = 'HO4IyLEwEOHpeOXBxaLQUOqWslJRGs1M' const auth_secret = 'HO4IyLEwEOHpeOXBxaLQUOqWslJRGs1M'
const AIDrawerComponent = props => { const AIDrawerComponent = (props) => {
const { selectText } = props const { selectText } = props
const dispatch = useDispatch() const dispatch = useDispatch()
const { userInfo } = useSelector(state => state.user) // 用户状态信息 const { userInfo } = useSelector((state) => state.user) // 用户状态信息
const { editorAi } = useSelector(state => state.editor) // ai信息设置 const { editorAi } = useSelector((state) => state.editor) // ai信息设置
const [value, setValue] = useState(selectText) // 输入值 const [value, setValue] = useState(selectText) // 输入值
const [loading, setLoading] = useState(false) // loading const [loading, setLoading] = useState(false) // loading
...@@ -31,7 +31,7 @@ const AIDrawerComponent = props => { ...@@ -31,7 +31,7 @@ const AIDrawerComponent = props => {
if (editorAi && Object.entries(editorAi).length > 0) { if (editorAi && Object.entries(editorAi).length > 0) {
let tempJSON = JSON.parse(JSON.stringify(chatContentList)) let tempJSON = JSON.parse(JSON.stringify(chatContentList))
let conversationId = editorAi.conversationId let conversationId = editorAi.conversationId
const item = tempJSON.filter(item => item.conversationId === conversationId) const item = tempJSON.filter((item) => item.conversationId === conversationId)
if (item && item.length > 0) { if (item && item.length > 0) {
tempJSON[tempJSON.length - 1] = editorAi tempJSON[tempJSON.length - 1] = editorAi
} else { } else {
...@@ -57,7 +57,6 @@ const AIDrawerComponent = props => { ...@@ -57,7 +57,6 @@ const AIDrawerComponent = props => {
} }
}, []) }, [])
let cData = []
let buffer = '' let buffer = ''
const readBuffer = (reader, decoder) => { const readBuffer = (reader, decoder) => {
...@@ -85,7 +84,7 @@ const AIDrawerComponent = props => { ...@@ -85,7 +84,7 @@ const AIDrawerComponent = props => {
}) })
} }
const processBuffer = text => { const processBuffer = (text) => {
let aiContent = {} let aiContent = {}
try { try {
const content = text.replace(/\n+/g, '').split('data:') const content = text.replace(/\n+/g, '').split('data:')
...@@ -129,7 +128,7 @@ const AIDrawerComponent = props => { ...@@ -129,7 +128,7 @@ const AIDrawerComponent = props => {
}), }),
mode: 'cors' mode: 'cors'
}) })
.then(response => { .then((response) => {
// response.body 是一个 ReadableStream 对象 // response.body 是一个 ReadableStream 对象
const stream = response.body const stream = response.body
// 使用流式数据 // 使用流式数据
...@@ -147,7 +146,7 @@ const AIDrawerComponent = props => { ...@@ -147,7 +146,7 @@ const AIDrawerComponent = props => {
// 开始读取流数据 // 开始读取流数据
readBuffer(reader, decoder) readBuffer(reader, decoder)
}) })
.catch(error => { .catch((error) => {
// 处理请求错误 // 处理请求错误
console.error('Request failed:', error) console.error('Request failed:', error)
}) })
...@@ -200,7 +199,7 @@ const AIDrawerComponent = props => { ...@@ -200,7 +199,7 @@ const AIDrawerComponent = props => {
defaultValue={value} defaultValue={value}
allowClear allowClear
disabled={loading} disabled={loading}
onChange={e => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
palceholder="请输入内容" palceholder="请输入内容"
style={{ height: '80px', resize: 'none' }}></Input.TextArea> style={{ height: '80px', resize: 'none' }}></Input.TextArea>
</div> </div>
......
...@@ -7,14 +7,9 @@ import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor' ...@@ -7,14 +7,9 @@ import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
import { findNodeWithParent, fontFizeList } from '../utils/setting' import { findNodeWithParent, fontFizeList } from '../utils/setting'
import GalleryFormItem from './galleryItem' import GalleryFormItem from './galleryItem'
const temp = [
'http://zxts-book-file.zijingebook.com/2024/02/27/gallery-1709002611091-nrxnqw595pc.png',
'http://zxts-book-file.zijingebook.com/2024/02/27/gallery-1709002615115-ngpulfnmspr.jpg'
]
const randomOne = Math.random().toString(16).substring(2, 10) const randomOne = Math.random().toString(16).substring(2, 10)
const GalleryModal = props => { const GalleryModal = (props) => {
const { editor, ossClient, galleryInfo, bookId, chapterId, setGalleryVisible, setGalleryInfo, selectionSize = 18 } = props const { editor, ossClient, galleryInfo, bookId, chapterId, setGalleryVisible, setGalleryInfo, selectionSize = 18, isOnline = false } = props
const [form] = Form.useForm() const [form] = Form.useForm()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
...@@ -52,7 +47,9 @@ const GalleryModal = props => { ...@@ -52,7 +47,9 @@ const GalleryModal = props => {
const initialItems = [ const initialItems = [
{ {
label: '图 1', label: '图 1',
children: <GalleryFormItem ossClient={ossClient} form={form} galleryInfo={galleryInfo} activeKey={randomOne} setPicList={setPicList} />, children: (
<GalleryFormItem ossClient={ossClient} form={form} galleryInfo={galleryInfo} activeKey={randomOne} setPicList={setPicList} isOnline={isOnline} />
),
key: randomOne, key: randomOne,
forceRender: true forceRender: true
} }
...@@ -60,7 +57,7 @@ const GalleryModal = props => { ...@@ -60,7 +57,7 @@ const GalleryModal = props => {
const [activeKey, setActiveKey] = useState(initialItems[0].key) const [activeKey, setActiveKey] = useState(initialItems[0].key)
const [items, setItems] = useState(initialItems) const [items, setItems] = useState(initialItems)
const onChange = newActiveKey => { const onChange = (newActiveKey) => {
setActiveKey(newActiveKey) setActiveKey(newActiveKey)
} }
const add = () => { const add = () => {
...@@ -71,7 +68,9 @@ const GalleryModal = props => { ...@@ -71,7 +68,9 @@ const GalleryModal = props => {
const newActiveKey = `${random}` const newActiveKey = `${random}`
newPanes.push({ newPanes.push({
label: `图 ${index + 1}`, label: `图 ${index + 1}`,
children: <GalleryFormItem ossClient={ossClient} form={form} galleryInfo={galleryInfo} activeKey={newActiveKey} setPicList={setPicList} />, children: (
<GalleryFormItem ossClient={ossClient} form={form} galleryInfo={galleryInfo} activeKey={newActiveKey} setPicList={setPicList} isOnline={isOnline} />
),
key: newActiveKey, key: newActiveKey,
forceRender: true forceRender: true
}) })
...@@ -82,8 +81,8 @@ const GalleryModal = props => { ...@@ -82,8 +81,8 @@ const GalleryModal = props => {
setItems(newPanes) setItems(newPanes)
setActiveKey(newActiveKey) setActiveKey(newActiveKey)
} }
const remove = async targetKey => { const remove = async (targetKey) => {
const tempGallery = picList.filter(item => item.key !== targetKey) const tempGallery = picList.filter((item) => item.key !== targetKey)
await setPicList(tempGallery) await setPicList(tempGallery)
console.log(tempGallery) console.log(tempGallery)
form.setFieldsValue({ gallery: tempGallery }) form.setFieldsValue({ gallery: tempGallery })
...@@ -95,7 +94,7 @@ const GalleryModal = props => { ...@@ -95,7 +94,7 @@ const GalleryModal = props => {
lastIndex = i - 1 lastIndex = i - 1
} }
}) })
let newPanes = items.filter(item => item.key !== targetKey) let newPanes = items.filter((item) => item.key !== targetKey)
if (newPanes.length && newActiveKey === targetKey) { if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) { if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key newActiveKey = newPanes[lastIndex].key
...@@ -169,6 +168,7 @@ const GalleryModal = props => { ...@@ -169,6 +168,7 @@ const GalleryModal = props => {
form={form} form={form}
setPicList={setPicList} setPicList={setPicList}
activeKey={newActiveKey} activeKey={newActiveKey}
isOnline={isOnline}
/> />
), ),
key: newActiveKey, key: newActiveKey,
...@@ -184,7 +184,7 @@ const GalleryModal = props => { ...@@ -184,7 +184,7 @@ const GalleryModal = props => {
} }
}, [galleryInfo]) }, [galleryInfo])
const onFinish = async values => { const onFinish = async (values) => {
editor.restoreSelection() editor.restoreSelection()
// setLoading(true); // setLoading(true);
const { galleryTitle, flex, gallery, fontSize, theme = '' } = values const { galleryTitle, flex, gallery, fontSize, theme = '' } = values
...@@ -193,7 +193,7 @@ const GalleryModal = props => { ...@@ -193,7 +193,7 @@ const GalleryModal = props => {
if (parseInt(flex) !== 2) { if (parseInt(flex) !== 2) {
// 删除空白的p标签 // 删除空白的p标签
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 === 'paragraph') { if (node.type === 'paragraph') {
......
...@@ -2,12 +2,13 @@ import { useState, useEffect, useImperativeHandle, forwardRef } from 'react' ...@@ -2,12 +2,13 @@ import { useState, useEffect, useImperativeHandle, forwardRef } from 'react'
import { CloudUploadOutlined } from '@ant-design/icons' import { CloudUploadOutlined } from '@ant-design/icons'
import { Form, Input, Spin, Upload, Button } from 'antd' import { Form, Input, Spin, Upload, Button } from 'antd'
import { partSize, normalUploader, multipartUploader } from '../utils/upload' import { partSize, normalUploader, multipartUploader } from '../utils/upload'
import OnlineImageList from './onlineImageList'
const fileAccept = ['.png', '.jpg', '.jpeg', '.svg'] const fileAccept = ['.png', '.jpg', '.jpeg', '.svg']
const randomOne = Math.random().toString(16).substring(2, 10) const randomOne = Math.random().toString(16).substring(2, 10)
const GalleryFormItem = (props, ref) => { const GalleryFormItem = (props, ref) => {
const { activeKey, ossClient, form, setPicList, galleryInfo } = props const { activeKey, ossClient, form, setPicList, galleryInfo, isOnline = false } = props
const [uploading, setUploading] = useState(false) // 文件上传状态 const [uploading, setUploading] = useState(false) // 文件上传状态
const [progress, setProgress] = useState(0) const [progress, setProgress] = useState(0)
...@@ -35,7 +36,7 @@ const GalleryFormItem = (props, ref) => { ...@@ -35,7 +36,7 @@ const GalleryFormItem = (props, ref) => {
if (galleryArr.length > 0) { if (galleryArr.length > 0) {
const values = { ['content']: {} } const values = { ['content']: {} }
galleryArr.forEach(item => { galleryArr.forEach((item) => {
values['content'][item.key] = { values['content'][item.key] = {
url: item.url, url: item.url,
title: item.title, title: item.title,
...@@ -44,7 +45,7 @@ const GalleryFormItem = (props, ref) => { ...@@ -44,7 +45,7 @@ const GalleryFormItem = (props, ref) => {
}) })
form.setFieldsValue({ ...values, gallery: galleryArr }) form.setFieldsValue({ ...values, gallery: galleryArr })
const item = galleryArr.filter(item => item.key === activeKey) const item = galleryArr.filter((item) => item.key === activeKey)
if (item.length > 0) { if (item.length > 0) {
const itemGallery = item[0] const itemGallery = item[0]
setImgUrl(itemGallery.url) setImgUrl(itemGallery.url)
...@@ -55,7 +56,7 @@ const GalleryFormItem = (props, ref) => { ...@@ -55,7 +56,7 @@ const GalleryFormItem = (props, ref) => {
useEffect(() => {}, []) useEffect(() => {}, [])
const normFile = e => { const normFile = (e) => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
return e return e
} }
...@@ -117,7 +118,7 @@ const GalleryFormItem = (props, ref) => { ...@@ -117,7 +118,7 @@ const GalleryFormItem = (props, ref) => {
} }
} }
const changeTitleValue = e => { const changeTitleValue = (e) => {
const allContent = form.getFieldValue('content') const allContent = form.getFieldValue('content')
const newGalleryList = [] const newGalleryList = []
// eslint-disable-next-line guard-for-in // eslint-disable-next-line guard-for-in
...@@ -127,7 +128,30 @@ const GalleryFormItem = (props, ref) => { ...@@ -127,7 +128,30 @@ const GalleryFormItem = (props, ref) => {
setPicList(newGalleryList) setPicList(newGalleryList)
form.setFieldsValue({ gallery: newGalleryList }) form.setFieldsValue({ gallery: newGalleryList })
} }
const changeDescValue = e => { const changeDescValue = (e) => {
const allContent = form.getFieldValue('content')
const newGalleryList = []
// eslint-disable-next-line guard-for-in
for (let key in allContent) {
newGalleryList.push({ ...allContent[key], key: key })
}
setPicList(newGalleryList)
form.setFieldsValue({ gallery: newGalleryList })
}
const onOnlineImageChange = (url) => {
setImgUrl(url)
const values = {
['content']: {
[activeKey]: {
url: url,
title: '',
desc: ''
}
}
}
setImgUrl(url)
form.setFieldsValue({ ...values })
const allContent = form.getFieldValue('content') const allContent = form.getFieldValue('content')
const newGalleryList = [] const newGalleryList = []
// eslint-disable-next-line guard-for-in // eslint-disable-next-line guard-for-in
...@@ -140,42 +164,55 @@ const GalleryFormItem = (props, ref) => { ...@@ -140,42 +164,55 @@ const GalleryFormItem = (props, ref) => {
return ( return (
<> <>
<Form.Item {isOnline ? (
label="上传图片" <Form.Item
valuePropName="fileList" label="在线图片"
getValueFromEvent={normFile} valuePropName="fileList"
extra="最多可上传10张" getValueFromEvent={normFile}
name={['content', activeKey, 'url']} extra="最多可上传10张"
rules={[{ required: true, message: '请上传画廊图片' }]}> name={['content', activeKey, 'url']}
<Spin spinning={uploading} tip="Loading"> rules={[{ required: true, message: '请选择图片' }]}>
<div className="editor-dragger"> <OnlineImageList imgUrl={imgUrl} onChange={onOnlineImageChange}></OnlineImageList>
<Upload.Dragger {...uploadProps} showUploadList={false}> </Form.Item>
{!imgUrl ? ( ) : (
<> <Form.Item
<div className="editor-uploader-process"> label="上传图片"
<p className="ant-upload-drag-icon"> valuePropName="fileList"
<CloudUploadOutlined style={{ fontSize: 40 }} /> getValueFromEvent={normFile}
</p> extra="最多可上传10张"
<p className="ant-upload-text">点击上传或拖拽到此处上传</p> name={['content', activeKey, 'url']}
<p className="ant-upload-hint">支持上传 .png、.jpg、.jpeg、.svg格式的图片</p> rules={[{ required: true, message: '请上传画廊图片' }]}>
<Spin spinning={uploading} tip="Loading">
<div className="editor-dragger">
<Upload.Dragger {...uploadProps} showUploadList={false}>
{!imgUrl ? (
<>
<div className="editor-uploader-process">
<p className="ant-upload-drag-icon">
<CloudUploadOutlined style={{ fontSize: 40 }} />
</p>
<p className="ant-upload-text">点击上传或拖拽到此处上传</p>
<p className="ant-upload-hint">支持上传 .png、.jpg、.jpeg、.svg格式的图片</p>
</div>
</>
) : (
<div className="editor-uploader-result">
<div className="editor-uploader-result-img">
<img src={imgUrl} alt="" />
</div>
<div className="editor-uploader-result-tips">
<Button size="small" type="primary" ghost onClick={() => setImgUrl(null)}>
替换
</Button>
</div>
</div> </div>
</> )}
) : ( </Upload.Dragger>
<div className="editor-uploader-result"> </div>
<div className="editor-uploader-result-img"> </Spin>
<img src={imgUrl} alt="" /> </Form.Item>
</div> )}
<div className="editor-uploader-result-tips">
<Button size="small" type="primary" ghost onClick={() => setImgUrl(null)}>
替换
</Button>
</div>
</div>
)}
</Upload.Dragger>
</div>
</Spin>
</Form.Item>
<Form.Item label="标题" rules={[{ required: false, message: '请输入图片标题' }]} name={['content', activeKey, 'title']} extra="最多输入100字"> <Form.Item label="标题" rules={[{ required: false, message: '请输入图片标题' }]} name={['content', activeKey, 'title']} extra="最多输入100字">
<Input maxLength={100} placeholder="" allowClear onChange={changeTitleValue} /> <Input maxLength={100} placeholder="" allowClear onChange={changeTitleValue} />
</Form.Item> </Form.Item>
......
...@@ -3,13 +3,14 @@ import { Divider, Upload, Input, Space, Button, Form, Spin, message } from 'antd ...@@ -3,13 +3,14 @@ import { Divider, Upload, Input, Space, Button, Form, Spin, message } from 'antd
import { CloudUploadOutlined } from '@ant-design/icons' import { CloudUploadOutlined } from '@ant-design/icons'
import { SlateTransforms, SlateEditor, SlateElement } from '@wangeditor/editor' import { SlateTransforms, SlateEditor, SlateElement } from '@wangeditor/editor'
import './index.less' import './index.less'
import OnlineImageList from './onlineImageList'
import { partSize, normalUploader, multipartUploader } from '../utils/upload' import { partSize, normalUploader, multipartUploader } from '../utils/upload'
const { Dragger } = Upload const { Dragger } = Upload
const ImageModal = (props, ref) => { const ImageModal = (props, ref) => {
const { editor, ossClient, setImageVisible, imageInfo, setImageInfo } = props const { editor, ossClient, setImageVisible, imageInfo, setImageInfo, isOnline = false } = props
const [imgUrl, setImgUrl] = useState('') const [imgUrl, setImgUrl] = useState('')
const fileAccept = ['.png', '.jpg', '.jpeg', '.svg'] const fileAccept = ['.png', '.jpg', '.jpeg', '.svg']
...@@ -34,7 +35,7 @@ const ImageModal = (props, ref) => { ...@@ -34,7 +35,7 @@ const ImageModal = (props, ref) => {
} }
}, [imageInfo]) }, [imageInfo])
const normFile = e => { const normFile = (e) => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
return e return e
} }
...@@ -87,7 +88,8 @@ const ImageModal = (props, ref) => { ...@@ -87,7 +88,8 @@ const ImageModal = (props, ref) => {
setImageVisible(false) setImageVisible(false)
} }
const onFinish = values => { const onFinish = (values) => {
console.log(values)
editor.restoreSelection() editor.restoreSelection()
// editor.insertNode({ // editor.insertNode({
// type: 'chapterImage', // type: 'chapterImage',
...@@ -98,7 +100,7 @@ const ImageModal = (props, ref) => { ...@@ -98,7 +100,7 @@ const ImageModal = (props, ref) => {
// }); // });
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 === 'paragraph') { if (node.type === 'paragraph') {
...@@ -179,42 +181,52 @@ const ImageModal = (props, ref) => { ...@@ -179,42 +181,52 @@ const ImageModal = (props, ref) => {
clear() clear()
} }
const onOnlineImageChange = (url) => {
setImgUrl(url)
form.setFieldsValue({ imgUrl: url })
}
return ( return (
<div> <div>
<Divider /> <Divider />
<div className="editor-content-form"> <div className="editor-content-form">
<Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initValues}> <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initValues}>
<Form.Item label="上传图片" valuePropName="fileList" name="imgUrl" getValueFromEvent={normFile} rules={[{ required: true, message: '请上传图片' }]}> {isOnline ? (
<Spin spinning={uploading} tip={`${progress}%`}> <Form.Item label="在线图片" name="imgUrl" rules={[{ required: true, message: '请选择图片' }]}>
<div className="editor-dragger"> <OnlineImageList imgUrl={imgUrl} onChange={onOnlineImageChange}></OnlineImageList>
<Dragger {...uploadProps} showUploadList={false}> </Form.Item>
{!imgUrl && ( ) : (
<div className="editor-uploader-process"> <Form.Item label="上传图片" valuePropName="fileList" name="imgUrl" getValueFromEvent={normFile} rules={[{ required: true, message: '请上传图片' }]}>
<p className="ant-upload-drag-icon"> <Spin spinning={uploading} tip={`${progress}%`}>
<CloudUploadOutlined style={{ fontSize: 40 }} /> <div className="editor-dragger">
</p> <Dragger {...uploadProps} showUploadList={false}>
<p className="ant-upload-text">点击上传或拖拽到此处上传</p> {!imgUrl && (
<p className="ant-upload-hint">支持上传 .png、.jpg、.jpeg、.svg格式的图片</p> <div className="editor-uploader-process">
</div> <p className="ant-upload-drag-icon">
)} <CloudUploadOutlined style={{ fontSize: 40 }} />
{imgUrl && ( </p>
<> <p className="ant-upload-text">点击上传或拖拽到此处上传</p>
<div className="editor-uploader-result"> <p className="ant-upload-hint">支持上传 .png、.jpg、.jpeg、.svg格式的图片</p>
<div className="editor-uploader-result-img">
<img src={imgUrl} alt="" />
</div>
<div className="editor-uploader-result-tips">
<Button size="small" type="primary" ghost onClick={() => setImgUrl(null)}>
替换
</Button>
</div>
</div> </div>
</> )}
)} {imgUrl && (
</Dragger> <>
</div> <div className="editor-uploader-result">
</Spin> <div className="editor-uploader-result-img">
</Form.Item> <img src={imgUrl} alt="" />
</div>
<div className="editor-uploader-result-tips">
<Button size="small" type="primary" ghost onClick={() => setImgUrl(null)}>
替换
</Button>
</div>
</div>
</>
)}
</Dragger>
</div>
</Spin>
</Form.Item>
)}
<Form.Item label="图片标题" name="imgTitle" extra="最多输入100字"> <Form.Item label="图片标题" name="imgTitle" extra="最多输入100字">
<Input maxLength={100} placeholder="" allowClear /> <Input maxLength={100} placeholder="" allowClear />
</Form.Item> </Form.Item>
......
...@@ -125,4 +125,32 @@ ...@@ -125,4 +125,32 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
} }
\ No newline at end of file
.online-image-list {
display: flex;
background: #ffffff;
border-width: 1px;
border-style: solid;
border-color: #d9d9d9;
border-radius: 6px;
padding: 10px;
gap: 10px;
flex-wrap: wrap;
.online-image-list-item {
width: 200px;
height: 100px;
border-radius: 10px;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
&.is-active {
border: 4px solid #ab1941;
}
}
}
import { getChuangKitDesigns } from '@/api/chuangkit'
import { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
export default function OnlineImageList({ imgUrl, onChange }) {
const [list, setList] = useState([])
const { userInfo } = useSelector((state) => state.user)
useEffect(() => {
getChuangKitDesigns({ params: { time_order: 1, user_flag: userInfo.id } }).then((res) => {
setList(res.data.list)
})
}, [userInfo])
return (
<div className="online-image-list">
{list.map((item) => {
return (
<div
className={'online-image-list-item ' + (imgUrl === item.thumbUrl ? 'is-active' : '')}
key={item.designId}
onClick={() => onChange(item.thumbUrl)}>
<img src={item.thumbUrl} />
</div>
)
})}
</div>
)
}
...@@ -2,7 +2,7 @@ import { DomEditor, SlateTransforms, SlateRange } from '@wangeditor/editor'; ...@@ -2,7 +2,7 @@ import { DomEditor, SlateTransforms, SlateRange } from '@wangeditor/editor';
class GalleryAuto { class GalleryAuto {
constructor() { constructor() {
this.title = '画廊' this.title = '离线画廊'
this.iconSvg = `<svg width="24px" height="19px" viewBox="0 0 24 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> this.iconSvg = `<svg width="24px" height="19px" viewBox="0 0 24 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title> <title>图标</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
......
import { DomEditor, SlateRange } from '@wangeditor/editor' import { DomEditor, SlateRange } from '@wangeditor/editor'
class GalleryAuto { class GalleryAutoOnline {
constructor() { constructor() {
this.title = '画廊' this.title = '在线画廊'
this.iconSvg = `<svg width="24px" height="19px" viewBox="0 0 24 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> this.iconSvg = `<svg width="24px" height="19px" viewBox="0 0 24 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title> <title>图标</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
...@@ -39,7 +39,7 @@ class GalleryAuto { ...@@ -39,7 +39,7 @@ class GalleryAuto {
// if (hasVoidElem) return true // 选中了 void 元素,禁用 // if (hasVoidElem) return true // 选中了 void 元素,禁用
// eslint-disable-next-line array-callback-return // eslint-disable-next-line array-callback-return
const hasPreElem = selectedElems.some(elem => { const hasPreElem = selectedElems.some((elem) => {
const type = DomEditor.getNodeType(elem) const type = DomEditor.getNodeType(elem)
// 代码块 引用 表格 禁用 // 代码块 引用 表格 禁用
if (type === 'pre' || type === 'blockquote' || type === 'table' || type === 'table-row' || type === 'table-cell') return true if (type === 'pre' || type === 'blockquote' || type === 'table' || type === 'table-row' || type === 'table-cell') return true
...@@ -53,15 +53,15 @@ class GalleryAuto { ...@@ -53,15 +53,15 @@ class GalleryAuto {
if (this.isDisabled(editor)) { if (this.isDisabled(editor)) {
return return
} }
editor.emit('GalleryMenuClick') editor.emit('GalleryOnlineMenuClick')
} }
} }
export default { export default {
key: 'GalleryAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'GalleryAutoOnline', // 定义 menu key :要保证唯一、不重复(重要)
factory() { factory() {
return new GalleryAuto() // 把 `YourMenuClass` 替换为你菜单的 class return new GalleryAutoOnline() // 把 `YourMenuClass` 替换为你菜单的 class
} }
} }
export { GalleryAuto } export { GalleryAutoOnline }
...@@ -3,7 +3,7 @@ import { DomEditor, SlateTransforms as Transforms, SlateRange } from '@wangedito ...@@ -3,7 +3,7 @@ import { DomEditor, SlateTransforms as Transforms, SlateRange } from '@wangedito
// Extend menu // Extend menu
class ImageAuto { class ImageAuto {
constructor() { constructor() {
this.title = '图片' this.title = '离线图片'
this.iconSvg = `<svg width="23px" height="21px" viewBox="0 0 23 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> this.iconSvg = `<svg width="23px" height="21px" viewBox="0 0 23 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编辑器图片样式</title> <title>编辑器图片样式</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
......
import { DomEditor, SlateTransforms as Transforms, SlateRange } from '@wangeditor/editor' import { DomEditor, SlateTransforms as Transforms, SlateRange } from '@wangeditor/editor'
// Extend menu // Extend menu
class ImageAuto { class ImageAutoOnline {
constructor() { constructor() {
this.title = '图片' this.title = '在线图片'
this.iconSvg = `<svg width="23px" height="21px" viewBox="0 0 23 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> this.iconSvg = `<svg width="23px" height="21px" viewBox="0 0 23 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编辑器图片样式</title> <title>编辑器图片样式</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
...@@ -29,11 +29,11 @@ class ImageAuto { ...@@ -29,11 +29,11 @@ class ImageAuto {
const selectedElems = DomEditor.getSelectedElems(editor) const selectedElems = DomEditor.getSelectedElems(editor)
const hasVoidElem = selectedElems.some(elem => editor.isVoid(elem)) const hasVoidElem = selectedElems.some((elem) => editor.isVoid(elem))
if (hasVoidElem) return true // 选中了 void 元素,禁用 if (hasVoidElem) return true // 选中了 void 元素,禁用
// eslint-disable-next-line array-callback-return // eslint-disable-next-line array-callback-return
const hasPreElem = selectedElems.some(elem => { const hasPreElem = selectedElems.some((elem) => {
const type = DomEditor.getNodeType(elem) const type = DomEditor.getNodeType(elem)
// 代码块 引用 表格 禁用 // 代码块 引用 表格 禁用
if (type === 'pre' || type === 'blockquote') return true if (type === 'pre' || type === 'blockquote') return true
...@@ -48,7 +48,7 @@ class ImageAuto { ...@@ -48,7 +48,7 @@ class ImageAuto {
exec(editor, value) { exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值 // editor.insertText(value) // value 即 this.getValue(editor) 的返回值
editor.emit('ImageMenuClick') editor.emit('ImageOnlineMenuClick')
if (this.isDisabled(editor)) { if (this.isDisabled(editor)) {
return return
...@@ -71,30 +71,30 @@ class ImageAuto { ...@@ -71,30 +71,30 @@ class ImageAuto {
} }
Transforms.setNodes(editor, props, { Transforms.setNodes(editor, props, {
match: n => DomEditor.checkNodeType(n, 'chapterImage') match: (n) => DomEditor.checkNodeType(n, 'chapterImage')
}) })
} }
} }
class ImageWidthCustomer100 extends ImageAuto { class ImageWidthCustomer100 extends ImageAutoOnline {
title = '100%' // 菜单标题 title = '100%' // 菜单标题
value = '100%' // css width 的值 value = '100%' // css width 的值
} }
class ImageWidthCustomer50 extends ImageAuto { class ImageWidthCustomer50 extends ImageAutoOnline {
title = '50%' // 菜单标题 title = '50%' // 菜单标题
value = '50%' // css width 的值 value = '50%' // css width 的值
} }
class ImageWidthCustomer30 extends ImageAuto { class ImageWidthCustomer30 extends ImageAutoOnline {
title = '30%' // 菜单标题 title = '30%' // 菜单标题
value = '30%' // css width 的值 value = '30%' // css width 的值
} }
export default { export default {
key: 'ImageAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'ImageAutoOnline', // 定义 menu key :要保证唯一、不重复(重要)
factory() { factory() {
return new ImageAuto() // 把 `YourMenuClass` 替换为你菜单的 class return new ImageAutoOnline() // 把 `YourMenuClass` 替换为你菜单的 class
} }
} }
...@@ -119,4 +119,4 @@ const imageWidth30MenuChapterConf = { ...@@ -119,4 +119,4 @@ const imageWidth30MenuChapterConf = {
} }
} }
export { ImageAuto, imageWidth100MenuChapterConf, imageWidth50MenuChapterConf, imageWidth30MenuChapterConf } export { ImageAutoOnline, imageWidth100MenuChapterConf, imageWidth50MenuChapterConf, imageWidth30MenuChapterConf }
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react' import { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import { Input, Spin, message, Button, Divider, Space, Modal, Row, Drawer } from 'antd' import { Button, Divider, Space, Modal, Drawer } from 'antd'
import { EyeOutlined, SaveOutlined, CloseOutlined, HistoryOutlined } from '@ant-design/icons' import { EyeOutlined, SaveOutlined, CloseOutlined, HistoryOutlined } from '@ant-design/icons'
import AliOSS from 'ali-oss' import AliOSS from 'ali-oss'
import dayjs from 'dayjs' import dayjs from 'dayjs'
...@@ -7,7 +7,7 @@ import './utils/iconfont' ...@@ -7,7 +7,7 @@ import './utils/iconfont'
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { setPracticeRandom } from '@/store/modules/editor' import { setPracticeRandom } from '@/store/modules/editor'
import formulaEditor from 'easy-formula-editor' // import formulaEditor from 'easy-formula-editor'
import './utils/jax.less' import './utils/jax.less'
import { Boot } from '@wangeditor/editor' import { Boot } from '@wangeditor/editor'
...@@ -24,7 +24,9 @@ import { storageChange } from '@/utils/storage.js' ...@@ -24,7 +24,9 @@ import { storageChange } from '@/utils/storage.js'
// import PaddingSpace from './customer/padding'; // import PaddingSpace from './customer/padding';
import ImageAutoOnlineConf from './customer/ImageOnline'
import GalleryAutoConf from './customer/Gallery' import GalleryAutoConf from './customer/Gallery'
import GalleryAutoOnlineConf from './customer/GalleryOnline'
import VideoAutoConf from './customer/Video' import VideoAutoConf from './customer/Video'
import AudioAutoConf from './customer/Audio' import AudioAutoConf from './customer/Audio'
import ChapterTitleAutoConf from './customer/ChapterTitle' import ChapterTitleAutoConf from './customer/ChapterTitle'
...@@ -75,21 +77,12 @@ import PreviewModal from './components/preview' ...@@ -75,21 +77,12 @@ import PreviewModal from './components/preview'
import HistoryModal from './history/' import HistoryModal from './history/'
import $ from 'jquery' import $ from 'jquery'
import { getInfoByChapterId } from '@/pages/books/section/request' import { getAliOSSSTSToken } from './utils/request'
import { getAliOSSSTSToken, getRecordInfo } from './utils/request'
import './index.less' import './index.less'
const jsonContent = [
{
type: 'paragraph',
lineHeight: '1.5',
children: [{ text: '', fontFamily: '黑体', fontSize: '16px' }]
}
]
const menuArr0 = ['重做', '撤销'] const menuArr0 = ['重做', '撤销']
const menuArr1 = ['字体样式', '字号', '行高'] const menuArr1 = ['字体样式', '字号', '行高']
const menuArr2 = ['图片', '画廊', '视频', '音频', '表格'] const menuArr2 = ['离线图片', '在线图片', '离线画廊', '在线画廊', '视频', '音频', '表格']
const menuArr3 = ['代码块', '引用', '链接', '公式', '章头', '节头', '交互练习', '气泡', '扩展阅读'] const menuArr3 = ['代码块', '引用', '链接', '公式', '章头', '节头', '交互练习', '气泡', '扩展阅读']
const menuArr4 = ['改写', '扩写', '缩写', '总结'] const menuArr4 = ['改写', '扩写', '缩写', '总结']
const colorList = ['#ab1941', '#2970f6', '#2ad882', '#eb3351'] const colorList = ['#ab1941', '#2970f6', '#2ad882', '#eb3351']
...@@ -98,7 +91,9 @@ const bookBucketName = 'zxts-book-file' ...@@ -98,7 +91,9 @@ const bookBucketName = 'zxts-book-file'
const module = { const module = {
menus: [ menus: [
// ImageAutoConf, // ImageAutoConf,
ImageAutoOnlineConf,
GalleryAutoConf, GalleryAutoConf,
GalleryAutoOnlineConf,
VideoAutoConf, VideoAutoConf,
AudioAutoConf, AudioAutoConf,
FormulaAutoConf, FormulaAutoConf,
...@@ -150,15 +145,13 @@ const tabsMenu = [ ...@@ -150,15 +145,13 @@ const tabsMenu = [
storageChange() storageChange()
const WangEditorCustomer = (props, ref) => { const WangEditorCustomer = (props, ref) => {
const { chapterId, bookId, contentId, html, setHtml, saveContent, gData, nowTitle, recordList } = props const { chapterId, bookId, contentId, html, setHtml, saveContent, gData, nowTitle } = props
const dispatch = useDispatch() const dispatch = useDispatch()
// 自动保存时间 // 自动保存时间
const { autosaveTime } = useSelector(state => state.editor) const { autosaveTime } = useSelector((state) => state.editor)
const toolbarRef = useRef() const toolbarRef = useRef()
const paddingSpaceRef = useRef()
const [cId, setCId] = useState(contentId)
const [ossClient, setOssClient] = useState(null) // oss 操作 const [ossClient, setOssClient] = useState(null) // oss 操作
const [STSToken, setSTSToken] = useState(null) // oss 过期设置 const [STSToken, setSTSToken] = useState(null) // oss 过期设置
...@@ -167,7 +160,6 @@ const WangEditorCustomer = (props, ref) => { ...@@ -167,7 +160,6 @@ const WangEditorCustomer = (props, ref) => {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// editor 实例 // editor 实例
const [editor, setEditor] = useState(null) const [editor, setEditor] = useState(null)
const [editorNodes, setEditorNodes] = useState(null)
const [content, setContent] = useState(html) const [content, setContent] = useState(html)
const saveRef = useRef() const saveRef = useRef()
...@@ -193,11 +185,11 @@ const WangEditorCustomer = (props, ref) => { ...@@ -193,11 +185,11 @@ const WangEditorCustomer = (props, ref) => {
const [priviewVisible, setPriviewVisible] = useState(false) const [priviewVisible, setPriviewVisible] = useState(false)
const [historyVisible, setHistoryVisible] = useState(false) //点击历史 const [historyVisible, setHistoryVisible] = useState(false) //点击历史
const [delMoadal, setDelModal] = useState(false)
const [selectedRecordName, setSelectedRecordName] = useState('')
const [selectionSize, setSelectionSize] = useState(16) // 当前字号大小 const [selectionSize, setSelectionSize] = useState(16) // 当前字号大小
const [isOnline, setIsOnline] = useState(false)
window.addEventListener('setItemEvent', function (e) { window.addEventListener('setItemEvent', function (e) {
if (e.key === 'chapterTitleNum') { if (e.key === 'chapterTitleNum') {
setTitleInfo(JSON.parse(e.newValue)) setTitleInfo(JSON.parse(e.newValue))
...@@ -256,7 +248,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -256,7 +248,7 @@ const WangEditorCustomer = (props, ref) => {
} }
}) })
const listenNodeStyle = path => { const listenNodeStyle = (path) => {
const children = editor.children const children = editor.children
let node = null let node = null
if (path[1] === 0) { if (path[1] === 0) {
...@@ -384,7 +376,9 @@ const WangEditorCustomer = (props, ref) => { ...@@ -384,7 +376,9 @@ const WangEditorCustomer = (props, ref) => {
index: 30, index: 30,
keys: [ keys: [
'ImageAuto', 'ImageAuto',
'ImageAutoOnline',
'GalleryAuto', 'GalleryAuto',
'GalleryAutoOnline',
'VideoAuto', 'VideoAuto',
'AudioAuto', 'AudioAuto',
'CustomerLink', 'CustomerLink',
...@@ -537,7 +531,9 @@ const WangEditorCustomer = (props, ref) => { ...@@ -537,7 +531,9 @@ const WangEditorCustomer = (props, ref) => {
const itemBox4 = document.createElement('div') const itemBox4 = document.createElement('div')
itemBox4.setAttribute('class', 'custom-bar-box media') itemBox4.setAttribute('class', 'custom-bar-box media')
toolbarElement.insertBefore(itemBox4, allChildren[8]) toolbarElement.insertBefore(itemBox4, allChildren[8])
console.log($(allChildren[8]), $(allChildren[13]), $(allChildren[9]))
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach()) $(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach()) $(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach()) $(allChildren[8]).append($(allChildren[13]).detach())
...@@ -609,18 +605,18 @@ const WangEditorCustomer = (props, ref) => { ...@@ -609,18 +605,18 @@ const WangEditorCustomer = (props, ref) => {
setLoading(false) setLoading(false)
}, 350) }, 350)
} }
editorConfig.onCreated = editor => { editorConfig.onCreated = (editor) => {
setLoading(true) setLoading(true)
} }
editorConfig.onFocus = editor => { editorConfig.onFocus = (editor) => {
clearTimeout(saveRef.current) clearTimeout(saveRef.current)
} }
editorConfig.onBlur = editor => { editorConfig.onBlur = (editor) => {
// 失焦保存 // 失焦保存
setHtml(editor.getHtml()) setHtml(editor.getHtml())
setContent(editor.getHtml()) setContent(editor.getHtml())
} }
editorConfig.onChange = editor => { editorConfig.onChange = (editor) => {
setHtml(editor.getHtml()) setHtml(editor.getHtml())
setContent(editor.getHtml()) setContent(editor.getHtml())
} }
...@@ -635,6 +631,13 @@ const WangEditorCustomer = (props, ref) => { ...@@ -635,6 +631,13 @@ const WangEditorCustomer = (props, ref) => {
// 图片上传 // 图片上传
editor.on('ImageMenuClick', () => { editor.on('ImageMenuClick', () => {
console.log('ImageMenuClick', '----') console.log('ImageMenuClick', '----')
setIsOnline(false)
setImageVisible(true)
})
// 在线图片
editor.on('ImageOnlineMenuClick', () => {
console.log('ImageOnlineMenuClick', '----')
setIsOnline(true)
setImageVisible(true) setImageVisible(true)
}) })
// 画廊上传 // 画廊上传
...@@ -644,6 +647,17 @@ const WangEditorCustomer = (props, ref) => { ...@@ -644,6 +647,17 @@ const WangEditorCustomer = (props, ref) => {
} }
console.log('GalleryMenuClick', '----') console.log('GalleryMenuClick', '----')
// galleryRef.current.setVisible(true); // galleryRef.current.setVisible(true);
setIsOnline(false)
setGalleryVisible(true)
})
// 在线画廊
editor.on('GalleryOnlineMenuClick', () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path)
}
console.log('GalleryOnlineMenuClick', '----')
// galleryRef.current.setVisible(true);
setIsOnline(true)
setGalleryVisible(true) setGalleryVisible(true)
}) })
// 视频上传 // 视频上传
...@@ -681,7 +695,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -681,7 +695,7 @@ const WangEditorCustomer = (props, ref) => {
console.log('ImageEditorClick', '----') console.log('ImageEditorClick', '----')
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 === 'paragraph') { if (node.type === 'paragraph') {
...@@ -707,6 +721,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -707,6 +721,7 @@ const WangEditorCustomer = (props, ref) => {
info.path = path info.path = path
} }
setImageInfo(info) setImageInfo(info)
setIsOnline(false)
setImageVisible(true) setImageVisible(true)
}) })
// 气泡 // 气泡
...@@ -799,7 +814,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -799,7 +814,7 @@ const WangEditorCustomer = (props, ref) => {
} }
}, [gData, editor]) }, [gData, editor])
const tabKeyChange = key => { const tabKeyChange = (key) => {
if (key === 'text') { if (key === 'text') {
toolSetttingReplace() toolSetttingReplace()
} }
...@@ -883,15 +898,15 @@ const WangEditorCustomer = (props, ref) => { ...@@ -883,15 +898,15 @@ const WangEditorCustomer = (props, ref) => {
})() })()
}, []) }, [])
const setColor = type => { const setColor = (type) => {
const headers = document.querySelectorAll(`.w-e-scroll .chapter-item-header`) const headers = document.querySelectorAll(`.w-e-scroll .chapter-item-header`)
const sections = document.querySelectorAll(`.w-e-scroll .chapter-item-section`) const sections = document.querySelectorAll(`.w-e-scroll .chapter-item-section`)
headers.forEach(item => { headers.forEach((item) => {
const node = DomEditor.toSlateNode(editor, item) const node = DomEditor.toSlateNode(editor, item)
const path = DomEditor.findPath(editor, node) const path = DomEditor.findPath(editor, node)
SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path }) SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path })
}) })
sections.forEach(item => { sections.forEach((item) => {
const node = DomEditor.toSlateNode(editor, item) const node = DomEditor.toSlateNode(editor, item)
const path = DomEditor.findPath(editor, node) const path = DomEditor.findPath(editor, node)
SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path }) SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path })
...@@ -948,7 +963,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -948,7 +963,7 @@ const WangEditorCustomer = (props, ref) => {
<div className="tabs"> <div className="tabs">
{tabsMenu && {tabsMenu &&
tabsMenu.length && tabsMenu.length &&
tabsMenu.map(item => { tabsMenu.map((item) => {
return ( return (
<div className={`tabs-item ${item.key === tabKey ? 'active' : ''}`} key={item.key} onClick={() => tabKeyChange(item.key)}> <div className={`tabs-item ${item.key === tabKey ? 'active' : ''}`} key={item.key} onClick={() => tabKeyChange(item.key)}>
{item.title} {item.title}
...@@ -1028,6 +1043,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -1028,6 +1043,7 @@ const WangEditorCustomer = (props, ref) => {
onCancel={() => setImageVisible(false)}> onCancel={() => setImageVisible(false)}>
<ImageModal <ImageModal
ref={imageRef} ref={imageRef}
isOnline={isOnline}
editor={editor} editor={editor}
ossClient={ossClient} ossClient={ossClient}
STSToken={STSToken} STSToken={STSToken}
...@@ -1053,6 +1069,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -1053,6 +1069,7 @@ const WangEditorCustomer = (props, ref) => {
width="800px"> width="800px">
<GalleryModal <GalleryModal
ref={galleryRef} ref={galleryRef}
isOnline={isOnline}
editor={editor} editor={editor}
ossClient={ossClient} ossClient={ossClient}
STSToken={STSToken} STSToken={STSToken}
......
import { import { DomEditor, SlateTransforms } from '@wangeditor/editor'
DomEditor, import { h } from 'snabbdom'
SlateTransforms,
SlateEditor,
SlateElement,
SlateNode,
} from '@wangeditor/editor';
import { h } from 'snabbdom';
import $ from 'jquery';
import iconGalleryInline from '@/assets/images/editor/icon_gallery_editor.png';
const withGalleryInlineNode = (editor) => { const withGalleryInlineNode = (editor) => {
const { isInline, isVoid, normalizeNode } = editor; const { isInline, isVoid, normalizeNode } = editor
const newEditor = editor; const newEditor = editor
newEditor.isInline = (elem) => { newEditor.isInline = (elem) => {
const type = DomEditor.getNodeType(elem); const type = DomEditor.getNodeType(elem)
if (type === 'chapterGalleryInline') return true; // 设置为 inline if (type === 'chapterGalleryInline') return true // 设置为 inline
return isInline(elem); return isInline(elem)
}; }
newEditor.isVoid = (elem) => { newEditor.isVoid = (elem) => {
const type = DomEditor.getNodeType(elem); const type = DomEditor.getNodeType(elem)
if (type === 'chapterGalleryInline') return true; // 设置为 void if (type === 'chapterGalleryInline') return true // 设置为 void
return isVoid(elem); return isVoid(elem)
}; }
// 重新 normalize // 重新 normalize
newEditor.normalizeNode = ([node, path]) => { newEditor.normalizeNode = ([node, path]) => {
const type = DomEditor.getNodeType(node); const type = DomEditor.getNodeType(node)
if (type !== 'chapterGalleryInline') { if (type !== 'chapterGalleryInline') {
// 未命中 chapterGalleryInline ,执行默认的 normalizeNode // 未命中 chapterGalleryInline ,执行默认的 normalizeNode
return normalizeNode([node, path]); return normalizeNode([node, path])
} }
const topLevelNodes = newEditor.children || []; const topLevelNodes = newEditor.children || []
const nextNode = topLevelNodes[path[0]] || {}; const nextNode = topLevelNodes[path[0]] || {}
if (nextNode.type === 'paragraph') { if (nextNode.type === 'paragraph') {
const lastChild = nextNode.children[nextNode.children.length -1]; const lastChild = nextNode.children[nextNode.children.length - 1]
if (lastChild.type === 'chapterExpandReadSimple') { if (lastChild.type === 'chapterExpandReadSimple') {
SlateTransforms.insertNodes(newEditor, { type: 'span', children: [{ text: '' }] }, { SlateTransforms.insertNodes(
at: path, // 在 link-card 后面插入 newEditor,
}); { type: 'span', children: [{ text: '' }] },
{
at: path // 在 link-card 后面插入
}
)
} }
} }
}; }
return newEditor; // 返回 newEditor ,重要!!! return newEditor // 返回 newEditor ,重要!!!
}; }
// 在编辑器中渲染新元素 // 在编辑器中渲染新元素
// 定义 renderElem 函数 // 定义 renderElem 函数
const renderGalleryInline = (elem, children, editor) => { const renderGalleryInline = (elem, children, editor) => {
// 获取“附件”的数据,参考上文 myResume 数据结构 // 获取“附件”的数据,参考上文 myResume 数据结构
const { const { title = '', galleryList = '', random = '', flex = 1, theme = '#ab1941', fontsize = 18, callback } = elem
title = '',
galleryList = '', const str = `<svg class="svg-icon" style="color: ${theme}; fill: currentColor; overflow: hidden; width: ${fontsize}px; height: ${fontsize}px;" aria-hidden="true"><use xlink:href="#icon-hualangshangchuan"></use></svg>`
random = '',
flex = 1,
theme = '#ab1941',
fontsize = 18,
callback,
} = elem;
const str = `<svg class="svg-icon" style="color: ${theme}; fill: currentColor; overflow: hidden; width: ${fontsize}px; height: ${fontsize}px;" aria-hidden="true"><use xlink:href="#icon-hualangshangchuan"></use></svg>`;
const span = h( const span = h(
'span', 'span',
...@@ -72,7 +60,7 @@ const renderGalleryInline = (elem, children, editor) => { ...@@ -72,7 +60,7 @@ const renderGalleryInline = (elem, children, editor) => {
width: `${fontsize}px`, width: `${fontsize}px`,
height: `${fontsize}px`, height: `${fontsize}px`,
alignItems: 'center', alignItems: 'center',
display: 'inline-flex', display: 'inline-flex'
}, },
dataset: { dataset: {
galleryList: galleryList, galleryList: galleryList,
...@@ -80,12 +68,12 @@ const renderGalleryInline = (elem, children, editor) => { ...@@ -80,12 +68,12 @@ const renderGalleryInline = (elem, children, editor) => {
flex: flex, flex: flex,
theme, theme,
title, title,
fontsize, fontsize
}, },
on: { on: {
click(ev) { click(ev) {
ev.stopPropagation(); ev.stopPropagation()
ev.preventDefault(); ev.preventDefault()
localStorage.setItem( localStorage.setItem(
'galleryNum', 'galleryNum',
...@@ -96,11 +84,11 @@ const renderGalleryInline = (elem, children, editor) => { ...@@ -96,11 +84,11 @@ const renderGalleryInline = (elem, children, editor) => {
galleryList: galleryList, galleryList: galleryList,
flex: flex, flex: flex,
theme, theme,
fontsize, fontsize
}), })
); )
}, }
}, }
}, },
[ [
h('span', { h('span', {
...@@ -108,49 +96,42 @@ const renderGalleryInline = (elem, children, editor) => { ...@@ -108,49 +96,42 @@ const renderGalleryInline = (elem, children, editor) => {
style: { style: {
width: `${fontsize}px`, width: `${fontsize}px`,
height: `${fontsize}px`, height: `${fontsize}px`,
marginLeft: '2px', marginLeft: '2px'
}, }
}), }),
h('span', {}, ['']) h('span', {}, [''])
], ]
); )
return span; return span
}; }
const renderElemConf = { const renderElemConf = {
type: 'chapterGalleryInline', type: 'chapterGalleryInline',
renderElem: renderGalleryInline, renderElem: renderGalleryInline
}; }
// 把新元素转换为 HTML // 把新元素转换为 HTML
const chapterGalleryInlineToHtml = (elem, childrenHtml) => { const chapterGalleryInlineToHtml = (elem, childrenHtml) => {
// console.log('chapterGalleryInlineToHtml', elem); // console.log('chapterGalleryInlineToHtml', elem);
// 获取附件元素的数据 // 获取附件元素的数据
const { const { title = '', galleryList = [], random = '', theme = '#ab1941', flex = 1, fontsize = 18 } = elem
title = '', const str = `<svg class="svg-icon" style="color: ${theme}; fill: currentColor; overflow: hidden; width: ${fontsize}px; height: ${fontsize}px;" aria-hidden="true"><use xlink:href="#icon-hualangshangchuan"></use></svg>`
galleryList = [],
random = '', return `<span class="chapter-gallery-container chapter-gallery-inline" data-w-e-type="chapterGalleryInline" data-galleryList="${galleryList}" data-random="${random}" data-title="${title}" data-theme="${theme}" data-fontsize="${fontsize}" data-flex="${flex}" style="cursor: pointer; display: inline-flex; align-items: center; width: ${fontsize}px; height: ${fontsize}px; margin: 0 2px;">${str}</span>`
theme = '#ab1941', }
flex = 1,
fontsize = 18,
} = elem;
const str = `<svg class="svg-icon" style="color: ${theme}; fill: currentColor; overflow: hidden; width: ${fontsize}px; height: ${fontsize}px;" aria-hidden="true"><use xlink:href="#icon-hualangshangchuan"></use></svg>`;
return `<span class="chapter-gallery-container chapter-gallery-inline" data-w-e-type="chapterGalleryInline" data-galleryList="${galleryList}" data-random="${random}" data-title="${title}" data-theme="${theme}" data-fontsize="${fontsize}" data-flex="${flex}" style="cursor: pointer; display: inline-flex; align-items: center; width: ${fontsize}px; height: ${fontsize}px; margin: 0 2px;">${str}</span>`;
};
const chapterGalleryInlineElemToHtmlConf = { const chapterGalleryInlineElemToHtmlConf = {
type: 'chapterGalleryInline', // 新元素的 type ,重要!!! type: 'chapterGalleryInline', // 新元素的 type ,重要!!!
elemToHtml: chapterGalleryInlineToHtml, elemToHtml: chapterGalleryInlineToHtml
}; }
// 解析新元素 HTML 到编辑器 // 解析新元素 HTML 到编辑器
const parseGalleryInlineHtml = (domElem, children, editor) => { const parseGalleryInlineHtml = (domElem, children, editor) => {
// 从 DOM element 中获取“附件”的信息 // 从 DOM element 中获取“附件”的信息
const galleryList = domElem.getAttribute('data-galleryList') || ''; const galleryList = domElem.getAttribute('data-galleryList') || ''
const title = domElem.getAttribute('data-title') || ''; const title = domElem.getAttribute('data-title') || ''
const random = domElem.getAttribute('data-random') || ''; const random = domElem.getAttribute('data-random') || ''
const flex = domElem.getAttribute('data-flex') || 1; const flex = domElem.getAttribute('data-flex') || 1
const theme = domElem.getAttribute('data-theme') || '#ab1941'; const theme = domElem.getAttribute('data-theme') || '#ab1941'
const fontsize = domElem.getAttribute('data-fontsize') || 18; const fontsize = domElem.getAttribute('data-fontsize') || 18
// 生成“附件”元素(按照此前约定的数据结构) // 生成“附件”元素(按照此前约定的数据结构)
const myResume = { const myResume = {
...@@ -161,27 +142,22 @@ const parseGalleryInlineHtml = (domElem, children, editor) => { ...@@ -161,27 +142,22 @@ const parseGalleryInlineHtml = (domElem, children, editor) => {
theme, theme,
fontsize, fontsize,
galleryList: galleryList, galleryList: galleryList,
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!! children: [{ text: '' }] // void node 必须有 children ,其中有一个空字符串,重要!!!
}; }
return myResume; return myResume
}; }
const parseGalleryInlineConf = { const parseGalleryInlineConf = {
selector: 'span[data-w-e-type="chapterGalleryInline"]', // CSS 选择器,匹配特定的 HTML 标签 selector: 'span[data-w-e-type="chapterGalleryInline"]', // CSS 选择器,匹配特定的 HTML 标签
parseElemHtml: parseGalleryInlineHtml, parseElemHtml: parseGalleryInlineHtml
}; }
const chapterGalleryInlineModule = { const chapterGalleryInlineModule = {
editorPlugin: withGalleryInlineNode, editorPlugin: withGalleryInlineNode,
renderElems: [renderElemConf], renderElems: [renderElemConf],
elemsToHtml: [chapterGalleryInlineElemToHtmlConf], elemsToHtml: [chapterGalleryInlineElemToHtmlConf],
parseElemsHtml: [parseGalleryInlineConf], parseElemsHtml: [parseGalleryInlineConf]
}; }
export default chapterGalleryInlineModule; export default chapterGalleryInlineModule
export { export { withGalleryInlineNode, renderElemConf, chapterGalleryInlineElemToHtmlConf, parseGalleryInlineConf }
withGalleryInlineNode,
renderElemConf,
chapterGalleryInlineElemToHtmlConf,
parseGalleryInlineConf,
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论