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