提交 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, import iconGallery from '@/assets/images/editor/icon_gallery_editor.png'
SlateEditor, import iconClose from '@/assets/images/icon_chapter_close.png'
SlateElement, import { findNodeWithParent } from '../utils/setting'
SlateNode,
} from '@wangeditor/editor';
import { h } from 'snabbdom';
import $ from 'jquery';
import iconGallery from '@/assets/images/editor/icon_gallery_editor.png';
import iconClose from '@/assets/images/icon_chapter_close.png';
import { findNodeWithParent } from '../utils/setting';
const withGalleryNode = (editor) => { const withGalleryNode = (editor) => {
const { isInline, isVoid, normalizeNode } = editor; const { 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);
...@@ -22,25 +15,25 @@ const withGalleryNode = (editor) => { ...@@ -22,25 +15,25 @@ const withGalleryNode = (editor) => {
// }; // };
newEditor.isVoid = (elem) => { newEditor.isVoid = (elem) => {
const type = DomEditor.getNodeType(elem); const type = DomEditor.getNodeType(elem)
if (type === 'chapterGallery') return true; // 设置为 void if (type === 'chapterGallery') 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 !== 'chapterGallery') { if (type !== 'chapterGallery') {
// 未命中 chapterGallery ,执行默认的 normalizeNode // 未命中 chapterGallery ,执行默认的 normalizeNode
return normalizeNode([node, path]); return normalizeNode([node, path])
} }
// editor 顶级 node // editor 顶级 node
const topLevelNodes = newEditor.children || []; const topLevelNodes = newEditor.children || []
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字) // 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const nextNode = topLevelNodes[path[0] + 1] || {}; const nextNode = topLevelNodes[path[0] + 1] || {}
const nextNodeType = DomEditor.getNodeType(nextNode); const nextNodeType = DomEditor.getNodeType(nextNode)
if ( if (
nextNodeType !== 'paragraph' && nextNodeType !== 'paragraph' &&
nextNodeType !== 'blockquote' && nextNodeType !== 'blockquote' &&
...@@ -53,45 +46,45 @@ const withGalleryNode = (editor) => { ...@@ -53,45 +46,45 @@ const withGalleryNode = (editor) => {
!nextNodeType.startsWith('header') !nextNodeType.startsWith('header')
) { ) {
// link-card node 后面不是 p 或 header ,则插入一个空 p // link-card node 后面不是 p 或 header ,则插入一个空 p
const p = { type: 'paragraph', children: [{ text: '' }] }; const p = { type: 'paragraph', children: [{ text: '' }] }
const insertPath = [path[0] + 1]; const insertPath = [path[0] + 1]
SlateTransforms.insertNodes(newEditor, p, { SlateTransforms.insertNodes(newEditor, p, {
at: insertPath, // 在 link-card 后面插入 at: insertPath // 在 link-card 后面插入
}); })
} }
}; }
return newEditor; // 返回 newEditor ,重要!!! return newEditor // 返回 newEditor ,重要!!!
}; }
// 在编辑器中渲染新元素 // 在编辑器中渲染新元素
// 定义 renderElem 函数 // 定义 renderElem 函数
const renderGallery = (elem, children, editor) => { const renderGallery = (elem, children, editor) => {
// 获取“附件”的数据,参考上文 myResume 数据结构 // 获取“附件”的数据,参考上文 myResume 数据结构
const { title = '', galleryList = '', random = '', theme = '', flex = 1, callback } = elem; const { title = '', galleryList = '', random = '', theme = '', flex = 1, callback } = elem
const galleryArr = JSON.parse(decodeURI(galleryList)); const galleryArr = JSON.parse(decodeURI(galleryList))
// console.log('renderGallery', galleryArr); // console.log('renderGallery', galleryArr);
const titleNode = h( const titleNode = h(
'h6', 'h6',
{ {
props: { props: {
className: 'chapter-gallery-title', className: 'chapter-gallery-title'
}, },
style: { style: {
textAlign: 'center', textAlign: 'center',
color: '#333', color: '#333',
fontSize: '14px', fontSize: '14px',
margin: '0px', margin: '0px',
lineHeight: '20px', lineHeight: '20px'
}, }
}, },
[title], [title]
); )
const element = []; const element = []
let galleryListNode = []; let galleryListNode = []
if (parseInt(flex) !== 2) { if (parseInt(flex) !== 2) {
for (let i = 0; i < galleryArr.length; i++) { for (let i = 0; i < galleryArr.length; i++) {
if (parseInt(flex) === 1) { if (parseInt(flex) === 1) {
...@@ -100,25 +93,25 @@ const renderGallery = (elem, children, editor) => { ...@@ -100,25 +93,25 @@ const renderGallery = (elem, children, editor) => {
'div', 'div',
{ {
props: { props: {
className: 'chapter-gallery-item', className: 'chapter-gallery-item'
}, },
style: { style: {
flex: `0 0 50%`, flex: `0 0 50%`,
padding: '10px', padding: '10px',
boxSizing: 'border-box', boxSizing: 'border-box'
}, }
}, },
[ [
h('img', { h('img', {
props: { src: galleryArr[i].url }, props: { src: galleryArr[i].url },
style: { height: 'auto', width: '100%' }, style: { height: 'auto', width: '100%' }
}), }),
h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title]), h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title])
], ]
); )
galleryListNode.push(titleNode); galleryListNode.push(titleNode)
} else { } else {
break; break
} }
} else if (parseInt(flex) === 3) { } else if (parseInt(flex) === 3) {
if (i === 0) { if (i === 0) {
...@@ -126,48 +119,48 @@ const renderGallery = (elem, children, editor) => { ...@@ -126,48 +119,48 @@ const renderGallery = (elem, children, editor) => {
'div', 'div',
{ {
props: { props: {
className: 'chapter-gallery-item one', className: 'chapter-gallery-item one'
}, },
style: { style: {
flex: `1`, flex: `1`,
padding: '10px', padding: '10px',
boxSizing: 'border-box', boxSizing: 'border-box'
}, }
}, },
[ [
h('img', { h('img', {
props: { src: galleryArr[i].url }, props: { src: galleryArr[i].url },
style: { height: 'auto', width: '100%' }, style: { height: 'auto', width: '100%' }
}), }),
h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title]), h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title])
], ]
); )
galleryListNode.push(titleNode); galleryListNode.push(titleNode)
} else { } else {
break; break
} }
} else { } else {
const titleNode = h( const titleNode = h(
'div', 'div',
{ {
props: { props: {
className: 'chapter-gallery-item', className: 'chapter-gallery-item'
}, },
style: { style: {
flex: `0 0 50%`, flex: `0 0 50%`,
padding: '10px', padding: '10px',
boxSizing: 'border-box', boxSizing: 'border-box'
}, }
}, },
[ [
h('img', { h('img', {
props: { src: galleryArr[i].url }, props: { src: galleryArr[i].url },
style: { height: 'auto', width: '100%' }, style: { height: 'auto', width: '100%' }
}), }),
h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title]), h('p', { props: {}, style: { textAlign: 'center' } }, [galleryArr[i].title])
], ]
); )
galleryListNode.push(titleNode); galleryListNode.push(titleNode)
} }
} }
...@@ -176,16 +169,16 @@ const renderGallery = (elem, children, editor) => { ...@@ -176,16 +169,16 @@ const renderGallery = (elem, children, editor) => {
'div', 'div',
{ {
props: { props: {
className: 'chapter-gallery-box', className: 'chapter-gallery-box'
}, },
style: { display: 'flex', justifyContent: 'flex-start', flexFlow: 'row wrap' }, style: { display: 'flex', justifyContent: 'flex-start', flexFlow: 'row wrap' }
}, },
[...galleryListNode], [...galleryListNode]
); )
element.push(galleryNode); element.push(galleryNode)
} }
if (title) { if (title) {
element.push(titleNode); element.push(titleNode)
} }
// 附件元素 vnode // 附件元素 vnode
...@@ -197,21 +190,20 @@ const renderGallery = (elem, children, editor) => { ...@@ -197,21 +190,20 @@ const renderGallery = (elem, children, editor) => {
props: { props: {
// HTML 属性,驼峰式写法 // HTML 属性,驼峰式写法
contentEditable: false, contentEditable: false,
className: 'chapter-gallery-container', className: 'chapter-gallery-container'
}, },
style: { padding: '10px 0px', style: { padding: '10px 0px', position: 'relative' }, // style ,驼峰式写法
position: 'relative' }, // style ,驼峰式写法
dataset: { dataset: {
galleryList: galleryList, galleryList: galleryList,
random: random, random: random,
flex: flex, flex: flex,
theme, theme,
title, title
}, },
on: { on: {
click(ev) { click(ev) {
ev.stopPropagation(); ev.stopPropagation()
ev.preventDefault(); ev.preventDefault()
localStorage.setItem( localStorage.setItem(
'galleryNum', 'galleryNum',
...@@ -220,55 +212,58 @@ const renderGallery = (elem, children, editor) => { ...@@ -220,55 +212,58 @@ const renderGallery = (elem, children, editor) => {
random: random, random: random,
i: Math.random(), i: Math.random(),
galleryList: galleryList, galleryList: galleryList,
flex: flex, flex: flex
}), })
); )
callback && callback(title, random, galleryList, flex,); callback && callback(title, random, galleryList, flex)
}, }
}, }
}, },
// 子节点 // 子节点
[...element, h( [
'span', ...element,
{ h(
props: { 'span',
contentEditable: false, {
className: `chapter-close`, props: {
}, contentEditable: false,
style: { className: `chapter-close`
position: 'absolute', },
top: '10px', style: {
right: '15px', position: 'absolute',
display: 'inline', top: '10px',
width: '18px', right: '15px',
height: '18px', display: 'inline',
}, width: '18px',
on: { height: '18px'
async click(ev) { },
ev.stopPropagation(); on: {
ev.preventDefault(); async click(ev) {
ev.stopPropagation()
ev.preventDefault()
try { try {
const path = findNodeWithParent(editor.children, 'chapterGallery', 'random', random); const path = findNodeWithParent(editor.children, 'chapterGallery', 'random', random)
console.log(editor.children, path); console.log(editor.children, path)
SlateTransforms.removeNodes(editor, { at: path.reverse() }); SlateTransforms.removeNodes(editor, { at: path.reverse() })
} catch (e) { } catch (e) {
console.log(e); console.log(e)
}
} }
}, }
}, },
}, [
[ h('img', {
h('img', { props: { src: iconClose, width: 18, height: 18 },
props: { src: iconClose, width: 18, height: 18 }, style: { cursor: 'pointer' }
style: { cursor: 'pointer' }, })
}), ]
], )
),], ]
); )
if (parseInt(flex) !== 2) { if (parseInt(flex) !== 2) {
return attachVnode; return attachVnode
} else { } else {
const span = h( const span = h(
'div', 'div',
...@@ -277,12 +272,12 @@ const renderGallery = (elem, children, editor) => { ...@@ -277,12 +272,12 @@ const renderGallery = (elem, children, editor) => {
dataset: { dataset: {
galleryList: galleryList, galleryList: galleryList,
random: random, random: random,
flex: flex, flex: flex
}, },
on: { on: {
click(ev) { click(ev) {
ev.stopPropagation(); ev.stopPropagation()
ev.preventDefault(); ev.preventDefault()
localStorage.setItem( localStorage.setItem(
'galleryNum', 'galleryNum',
...@@ -291,47 +286,47 @@ const renderGallery = (elem, children, editor) => { ...@@ -291,47 +286,47 @@ const renderGallery = (elem, children, editor) => {
random: random, random: random,
i: Math.random(), i: Math.random(),
galleryList: galleryList, galleryList: galleryList,
flex: flex, flex: flex
}), })
); )
callback && callback(title, random, galleryList, flex); callback && callback(title, random, galleryList, flex)
}, }
}, }
}, },
[h('img', { props: { src: iconGallery }, style: { width: '18px', height: '18px' } })], [h('img', { props: { src: iconGallery }, style: { width: '18px', height: '18px' } })]
); )
return span; return span
} }
}; }
const renderElemConf = { const renderElemConf = {
type: 'chapterGallery', type: 'chapterGallery',
renderElem: renderGallery, renderElem: renderGallery
}; }
// 把新元素转换为 HTML // 把新元素转换为 HTML
const chapterGalleryToHtml = (elem, childrenHtml) => { const chapterGalleryToHtml = (elem) => {
// console.log('chapterGalleryToHtml', elem); // console.log('chapterGalleryToHtml', elem);
// 获取附件元素的数据 // 获取附件元素的数据
const { title = '', galleryList = [], random = '', theme = '', flex = 1 } = elem; const { title = '', galleryList = [], random = '', theme = '', flex = 1 } = elem
const galleryArr = JSON.parse(decodeURI(galleryList)); const galleryArr = JSON.parse(decodeURI(galleryList))
let str = ''; let str = ''
if (galleryArr.length > 0) { if (galleryArr.length > 0) {
for (let i = 0; i < galleryArr.length; i++) { for (let i = 0; i < galleryArr.length; i++) {
if (parseInt(flex) === 3) { if (parseInt(flex) === 3) {
if (i > 0) { if (i > 0) {
break; break
} else { } else {
str = `<div class="chapter-gallery-item one" style="flex: 1; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`; str = `<div class="chapter-gallery-item one" style="flex: 1; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`
} }
} else if (parseInt(flex) === 1) { } else if (parseInt(flex) === 1) {
if (i > 1) { if (i > 1) {
break; break
} else { } else {
str += `<div class="chapter-gallery-item" style="flex: 0 0 50%; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`; str += `<div class="chapter-gallery-item" style="flex: 0 0 50%; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`
} }
} else if (parseInt(flex) === 4) { } else if (parseInt(flex) === 4) {
str += `<div class="chapter-gallery-item" style="flex: 0 0 50%; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`; str += `<div class="chapter-gallery-item" style="flex: 0 0 50%; padding: 10px; box-sizing: border-box; align-items: center;"><img src="${galleryArr[i].url}" alt="${galleryArr[i].title}" style="height: auto; width: 100%;" /><p>${galleryArr[i].title}</p></div>`
} }
} }
} }
...@@ -349,28 +344,28 @@ const chapterGalleryToHtml = (elem, childrenHtml) => { ...@@ -349,28 +344,28 @@ const chapterGalleryToHtml = (elem, childrenHtml) => {
> >
<div class="chapter-gallery-box" style="display: flex; justify-content: flex-start; flex-flow: row wrap">${str}</div> <div class="chapter-gallery-box" style="display: flex; justify-content: flex-start; flex-flow: row wrap">${str}</div>
<h6 class="chapter-gallery-title" style="text-align: center; color: #333; font-size: 14px; margin: 0px; line-height: 20px">${title}</h6> <h6 class="chapter-gallery-title" style="text-align: center; color: #333; font-size: 14px; margin: 0px; line-height: 20px">${title}</h6>
</div>`; </div>`
if (parseInt(flex) === 2) { if (parseInt(flex) === 2) {
return `<div class="chapter-gallery-container chapter-gallery-inline" data-w-e-type="chapterGallery" data-galleryList="${galleryList}" data-random="${random}" data-title="${title}" data-flex="${flex}"><img src="${iconGallery}" style="width: 14px; height: 14px; " /></div>`; return `<div class="chapter-gallery-container chapter-gallery-inline" data-w-e-type="chapterGallery" data-galleryList="${galleryList}" data-random="${random}" data-title="${title}" data-flex="${flex}"><img src="${iconGallery}" style="width: 14px; height: 14px; " /></div>`
} else { } else {
return html; return html
} }
}; }
const chapterGalleryElemToHtmlConf = { const chapterGalleryElemToHtmlConf = {
type: 'chapterGallery', // 新元素的 type ,重要!!! type: 'chapterGallery', // 新元素的 type ,重要!!!
elemToHtml: chapterGalleryToHtml, elemToHtml: chapterGalleryToHtml
}; }
// 解析新元素 HTML 到编辑器 // 解析新元素 HTML 到编辑器
const parseGalleryHtml = (domElem, children, editor) => { const parseGalleryHtml = (domElem) => {
// console.log('parseGalleryHtml', domElem); // console.log('parseGalleryHtml', domElem);
// 从 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') || ''; const theme = domElem.getAttribute('data-theme') || ''
// 生成“附件”元素(按照此前约定的数据结构) // 生成“附件”元素(按照此前约定的数据结构)
const myResume = { const myResume = {
...@@ -380,22 +375,22 @@ const parseGalleryHtml = (domElem, children, editor) => { ...@@ -380,22 +375,22 @@ const parseGalleryHtml = (domElem, children, editor) => {
flex, flex,
theme, theme,
galleryList: galleryList, galleryList: galleryList,
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!! children: [{ text: '' }] // void node 必须有 children ,其中有一个空字符串,重要!!!
}; }
return myResume; return myResume
}; }
const parseGalleryConf = { const parseGalleryConf = {
selector: 'div[data-w-e-type="chapterGallery"]', // CSS 选择器,匹配特定的 HTML 标签 selector: 'div[data-w-e-type="chapterGallery"]', // CSS 选择器,匹配特定的 HTML 标签
parseElemHtml: parseGalleryHtml, parseElemHtml: parseGalleryHtml
}; }
const chapterGalleryModule = { const chapterGalleryModule = {
editorPlugin: withGalleryNode, editorPlugin: withGalleryNode,
renderElems: [renderElemConf], renderElems: [renderElemConf],
elemsToHtml: [chapterGalleryElemToHtmlConf], elemsToHtml: [chapterGalleryElemToHtmlConf],
parseElemsHtml: [parseGalleryConf], parseElemsHtml: [parseGalleryConf]
}; }
export default chapterGalleryModule; export default chapterGalleryModule
export { withGalleryNode, renderElemConf, chapterGalleryElemToHtmlConf, parseGalleryConf }; export { withGalleryNode, renderElemConf, chapterGalleryElemToHtmlConf, parseGalleryConf }
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,
};
import { DomEditor, SlateTransforms } from '@wangeditor/editor'; import { DomEditor, SlateTransforms } from '@wangeditor/editor'
import { h } from 'snabbdom'; import { h } from 'snabbdom'
import { throttle } from 'lodash-es'; import { throttle } from 'lodash-es'
import $ from 'jquery'; import $ from 'jquery'
import { getStyleValue } from '@/utils/common'; import { getStyleValue } from '@/utils/common'
import ImageAutoConf, { import ImageAutoConf, { imageWidth100MenuChapterConf, imageWidth50MenuChapterConf, imageWidth30MenuChapterConf } from '../customer/Image'
imageWidth100MenuChapterConf,
imageWidth50MenuChapterConf,
imageWidth30MenuChapterConf,
} from '../customer/Image';
const withImageNode = (editor) => { const withImageNode = (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 === 'chapterImage') return true; // 设置为 inline if (type === 'chapterImage') 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 === 'chapterImage') return true; // 设置为 void if (type === 'chapterImage') 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 !== 'chapterImage') { if (type !== 'chapterImage') {
// 未命中 chapterImage ,执行默认的 normalizeNode // 未命中 chapterImage ,执行默认的 normalizeNode
return normalizeNode([node, path]); return normalizeNode([node, path])
} }
// editor 顶级 node // editor 顶级 node
const topLevelNodes = newEditor.children || []; const topLevelNodes = newEditor.children || []
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字) // 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const nextNode = topLevelNodes[path[0] + 1] || {}; const nextNode = topLevelNodes[path[0] + 1] || {}
const nextNodeType = DomEditor.getNodeType(nextNode); const nextNodeType = DomEditor.getNodeType(nextNode)
if ( if (nextNodeType !== 'paragraph' && nextNodeType !== 'blockquote' && !nextNodeType.startsWith('header')) {
nextNodeType !== 'paragraph' &&
nextNodeType !== 'blockquote' &&
!nextNodeType.startsWith('header')
) {
// link-card node 后面不是 p 或 header ,则插入一个空 p // link-card node 后面不是 p 或 header ,则插入一个空 p
const p = { type: 'paragraph', children: [{ text: '' }] }; const p = { type: 'paragraph', children: [{ text: '' }] }
const insertPath = [path[0] + 1]; const insertPath = [path[0] + 1]
SlateTransforms.insertNodes(newEditor, p, { SlateTransforms.insertNodes(newEditor, p, {
at: insertPath, // 在 link-card 后面插入 at: insertPath // 在 link-card 后面插入
}); })
} }
}; }
return newEditor; // 返回 newEditor ,重要!!! return newEditor // 返回 newEditor ,重要!!!
}; }
function genContainerId(editor, elemNode) { function genContainerId(editor, elemNode) {
const { id } = DomEditor.findKey(editor, elemNode); // node 唯一 id const { id } = DomEditor.findKey(editor, elemNode) // node 唯一 id
return `w-e-image-container-${id}`; return `w-e-image-container-${id}`
} }
const renderImageContainer = (editor, elemNode, imageVnode, imageInfo) => { const renderImageContainer = (editor, elemNode, imageVnode, imageInfo) => {
const { width, height } = imageInfo; const { width, height } = imageInfo
const style = {}; const style = {}
if (width) style.width = width; if (width) style.width = width
if (height) style.height = height; if (height) style.height = height
const containerId = genContainerId(editor, elemNode); const containerId = genContainerId(editor, elemNode)
return h('div', { return h('div', {
props: { id: containerId, className: 'w-e-image-container', innerHTML: imageVnode }, props: { id: containerId, className: 'w-e-image-container', innerHTML: imageVnode },
style: style, style: style
}); })
}; }
/** /**
* 选中状态下,渲染 image container(渲染拖拽容器,修改图片尺寸) * 选中状态下,渲染 image container(渲染拖拽容器,修改图片尺寸)
*/ */
function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) { function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
const $body = $('body'); const $body = $('body')
const containerId = genContainerId(editor, elemNode); const containerId = genContainerId(editor, elemNode)
const { width, height } = imageInfo; const { width, height } = imageInfo
let originalX = 0; let originalX = 0
let originalWith = 0; let originalWith = 0
let originalHeight = 0; let originalHeight = 0
let revers = false; // 是否反转。如向右拖拽 right-top 需增加宽度(非反转),但向右拖拽 left-top 则需要减少宽度(反转) let revers = false // 是否反转。如向右拖拽 right-top 需增加宽度(非反转),但向右拖拽 left-top 则需要减少宽度(反转)
let $container = null; let $container = null
function getContainerElem() { function getContainerElem() {
const $container = $(`#${containerId}`); const $container = $(`#${containerId}`)
if ($container.length === 0) throw new Error('Cannot find image container elem'); if ($container.length === 0) throw new Error('Cannot find image container elem')
return $container; return $container
} }
/** /**
* 初始化。监听事件,记录原始数据 * 初始化。监听事件,记录原始数据
*/ */
function init(clientX) { function init(clientX) {
$container = getContainerElem(); $container = getContainerElem()
// 记录当前 x 坐标值 // 记录当前 x 坐标值
originalX = clientX; originalX = clientX
// 记录 img 原始宽高 // 记录 img 原始宽高
const $img = $container.find('img'); const $img = $container.find('img')
if ($img.length === 0) throw new Error('Cannot find image elem'); if ($img.length === 0) throw new Error('Cannot find image elem')
originalWith = $img.width(); originalWith = $img.width()
originalHeight = $img.height(); originalHeight = $img.height()
// 监听 mousemove // 监听 mousemove
$body.on('mousemove', onMousemove); $body.on('mousemove', onMousemove)
// 监听 mouseup // 监听 mouseup
$body.on('mouseup', onMouseup); $body.on('mouseup', onMouseup)
// 隐藏 hoverbar // 隐藏 hoverbar
const hoverbar = DomEditor.getHoverbar(editor); const hoverbar = DomEditor.getHoverbar(editor)
if (hoverbar) hoverbar.hideAndClean(); if (hoverbar) hoverbar.hideAndClean()
} }
// mouseover callback (节流) // mouseover callback (节流)
const onMousemove = throttle((e) => { const onMousemove = throttle((e) => {
e.preventDefault(); e.preventDefault()
const { clientX } = e; const { clientX } = e
const gap = revers ? originalX - clientX : clientX - originalX; // 考虑是否反转 const gap = revers ? originalX - clientX : clientX - originalX // 考虑是否反转
const newWidth = originalWith + gap; const newWidth = originalWith + gap
const newHeight = originalHeight * (newWidth / originalWith); // 根据 width ,按比例计算 height const newHeight = originalHeight * (newWidth / originalWith) // 根据 width ,按比例计算 height
// 实时修改 img 宽高 -【注意】这里只修改 DOM ,mouseup 时再统一不修改 node // 实时修改 img 宽高 -【注意】这里只修改 DOM ,mouseup 时再统一不修改 node
if ($container === null) return; if ($container === null) return
if (newWidth <= 15 || newHeight <= 15) return; // 最小就是 15px if (newWidth <= 15 || newHeight <= 15) return // 最小就是 15px
$container.css('width', `${newWidth}px`); $container.css('width', `${newWidth}px`)
$container.css('height', `${newHeight}px`); $container.css('height', `${newHeight}px`)
}, 100); }, 100)
function onMouseup(e) { function onMouseup(e) {
// 取消监听 mousemove // 取消监听 mousemove
$body.off('mousemove', onMousemove); $body.off('mousemove', onMousemove)
if ($container === null) return; if ($container === null) return
const newWidth = $container.width().toFixed(2); const newWidth = $container.width().toFixed(2)
const newHeight = $container.height().toFixed(2); const newHeight = $container.height().toFixed(2)
// 修改 node // 修改 node
const props = { const props = {
style: { style: {
...elemNode.style, ...elemNode.style,
width: `${newWidth}px`, width: `${newWidth}px`,
height: `${newHeight}px`, height: `${newHeight}px`
}, }
}; }
Transforms.setNodes(editor, props, { at: DomEditor.findPath(editor, elemNode) }); Transforms.setNodes(editor, props, { at: DomEditor.findPath(editor, elemNode) })
// 取消监听 mouseup // 取消监听 mouseup
$body.off('mouseup', onMouseup); $body.off('mouseup', onMouseup)
} }
const style = {}; const style = {}
if (width) style.width = width; if (width) style.width = width
if (height) style.height = height; if (height) style.height = height
// style.boxShadow = '0 0 0 1px #B4D5FF' // 自定义 selected 样式,因为有拖拽触手 // style.boxShadow = '0 0 0 1px #B4D5FF' // 自定义 selected 样式,因为有拖拽触手
const normalStyle = const normalStyle = 'width: 7px; height: 7px; background-color: #4290f7; position: absolute; cursor: nwse-resize; z-index: 12;'
'width: 7px; height: 7px; background-color: #4290f7; position: absolute; cursor: nwse-resize; z-index: 12;';
const strDom = `${imageVnode}<div className='w-e-image-dragger left-top' style="${normalStyle} left: 0; top: 0;"></div> const strDom = `${imageVnode}<div className='w-e-image-dragger left-top' style="${normalStyle} left: 0; top: 0;"></div>
<div className='w-e-image-dragger right-top' style="${normalStyle} right: 0; top: 0;"></div> <div className='w-e-image-dragger right-top' style="${normalStyle} right: 0; top: 0;"></div>
<div className='w-e-image-dragger left-bottom' style="${normalStyle} left: 0; bottom: 0;"></div> <div className='w-e-image-dragger left-bottom' style="${normalStyle} left: 0; bottom: 0;"></div>
<div className='w-e-image-dragger right-bottom' style="${normalStyle} right: 0; bottom: 0;"></div>`; <div className='w-e-image-dragger right-bottom' style="${normalStyle} right: 0; bottom: 0;"></div>`
return h( return h(
'div', 'div',
{ {
props: { props: {
id: containerId, id: containerId,
className: 'w-e-image-container w-e-selected-image-container', className: 'w-e-image-container w-e-selected-image-container'
// innerHTML: strDom, // innerHTML: strDom,
}, },
style: style, style: style
}, },
[ [
imageVnode, imageVnode,
...@@ -194,23 +185,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) { ...@@ -194,23 +185,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
position: 'absolute', position: 'absolute',
cursor: 'nwse-resize', cursor: 'nwse-resize',
left: 0, left: 0,
top: 0, top: 0
}, },
on: { on: {
mousedown(e) { mousedown(e) {
const $target = $(e.target); const $target = $(e.target)
if (!$target.hasClass('w-e-image-dragger')) { if (!$target.hasClass('w-e-image-dragger')) {
// target 不是 .w-e-image-dragger 拖拽触手,则忽略 // target 不是 .w-e-image-dragger 拖拽触手,则忽略
return; return
} }
e.preventDefault(); e.preventDefault()
if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) { if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) {
revers = true; // 反转。向右拖拽,减少宽度 revers = true // 反转。向右拖拽,减少宽度
} }
init(e.clientX); // 初始化 init(e.clientX) // 初始化
}, }
}, }
}), }),
h('div', { h('div', {
props: { className: 'w-e-image-dragger right-top' }, props: { className: 'w-e-image-dragger right-top' },
...@@ -221,23 +212,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) { ...@@ -221,23 +212,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
position: 'absolute', position: 'absolute',
cursor: 'nwse-resize', cursor: 'nwse-resize',
right: 0, right: 0,
top: 0, top: 0
}, },
on: { on: {
mousedown(e) { mousedown(e) {
const $target = $(e.target); const $target = $(e.target)
if (!$target.hasClass('w-e-image-dragger')) { if (!$target.hasClass('w-e-image-dragger')) {
// target 不是 .w-e-image-dragger 拖拽触手,则忽略 // target 不是 .w-e-image-dragger 拖拽触手,则忽略
return; return
} }
e.preventDefault(); e.preventDefault()
if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) { if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) {
revers = true; // 反转。向右拖拽,减少宽度 revers = true // 反转。向右拖拽,减少宽度
} }
init(e.clientX); // 初始化 init(e.clientX) // 初始化
}, }
}, }
}), }),
h('div', { h('div', {
props: { className: 'w-e-image-dragger left-bottom' }, props: { className: 'w-e-image-dragger left-bottom' },
...@@ -248,23 +239,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) { ...@@ -248,23 +239,23 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
position: 'absolute', position: 'absolute',
cursor: 'nwse-resize', cursor: 'nwse-resize',
left: 0, left: 0,
bottom: 0, bottom: 0
}, },
on: { on: {
mousedown(e) { mousedown(e) {
const $target = $(e.target); const $target = $(e.target)
if (!$target.hasClass('w-e-image-dragger')) { if (!$target.hasClass('w-e-image-dragger')) {
// target 不是 .w-e-image-dragger 拖拽触手,则忽略 // target 不是 .w-e-image-dragger 拖拽触手,则忽略
return; return
} }
e.preventDefault(); e.preventDefault()
if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) { if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) {
revers = true; // 反转。向右拖拽,减少宽度 revers = true // 反转。向右拖拽,减少宽度
} }
init(e.clientX); // 初始化 init(e.clientX) // 初始化
}, }
}, }
}), }),
h('div', { h('div', {
props: { className: 'w-e-image-dragger right-bottom' }, props: { className: 'w-e-image-dragger right-bottom' },
...@@ -275,85 +266,85 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) { ...@@ -275,85 +266,85 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
position: 'absolute', position: 'absolute',
cursor: 'nwse-resize', cursor: 'nwse-resize',
right: 0, right: 0,
bottom: 0, bottom: 0
}, },
on: { on: {
mousedown(e) { mousedown(e) {
const $target = $(e.target); const $target = $(e.target)
if (!$target.hasClass('w-e-image-dragger')) { if (!$target.hasClass('w-e-image-dragger')) {
// target 不是 .w-e-image-dragger 拖拽触手,则忽略 // target 不是 .w-e-image-dragger 拖拽触手,则忽略
return; return
} }
e.preventDefault(); e.preventDefault()
if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) { if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) {
revers = true; // 反转。向右拖拽,减少宽度 revers = true // 反转。向右拖拽,减少宽度
} }
init(e.clientX); // 初始化 init(e.clientX) // 初始化
}, }
}, }
}), })
], ]
); )
} }
// 在编辑器中渲染新元素 // 在编辑器中渲染新元素
// 定义 renderElem 函数 // 定义 renderElem 函数
const renderImage = (elem, children, editor) => { const renderImage = (elem, children, editor) => {
// 获取“附件”的数据,参考上文 myResume 数据结构 // 获取“附件”的数据,参考上文 myResume 数据结构
const { title = '', imgUrl = '', imgDescript = '', style = {} } = elem; const { title = '', imgUrl = '', imgDescript = '', style = {} } = elem
const { width = '', height = '' } = style; const { width = '', height = '' } = style
let imageStyle = ''; let imageStyle = ''
if (width) imageStyle += 'width: 100%; '; if (width) imageStyle += 'width: 100%; '
if (height) imageStyle += 'height: 100%; '; if (height) imageStyle += 'height: 100%; '
const isDisabled = editor.isDisabled(); const isDisabled = editor.isDisabled()
const selected = DomEditor.isNodeSelected(editor, elem); // 图片是否选中 const selected = DomEditor.isNodeSelected(editor, elem) // 图片是否选中
const vnode = `<img style="${imageStyle}" src=${imgUrl} alt="${imgDescript}" data-href="${imgUrl}" />`; const vnode = `<img style="${imageStyle}" src=${imgUrl} alt="${imgDescript}" data-href="${imgUrl}" />`
let str = ''; let str = ''
if (selected && !isDisabled) { if (selected && !isDisabled) {
str = renderResizeContainer(editor, elem, vnode, style); str = renderResizeContainer(editor, elem, vnode, style)
} else { } else {
str = renderImageContainer(editor, elem, vnode, style); str = renderImageContainer(editor, elem, vnode, style)
} }
const imgNode = h( const imgNode = h(
'div', 'div',
{ {
props: { props: {
className: 'chapter-image-pic', className: 'chapter-image-pic'
}, },
style: { style: {
position: 'relative', position: 'relative',
fontSize: '0', fontSize: '0'
}, }
}, },
[str], [str]
); )
const titleNode = h( const titleNode = h(
'p', 'p',
{ {
props: { props: {
className: 'chapter-image-title', className: 'chapter-image-title'
}, },
style: { color: '#666', fontSize: '14px', margin: '0', lineHeight: '20px' }, style: { color: '#666', fontSize: '14px', margin: '0', lineHeight: '20px' }
}, },
[title], [title]
); )
const titleDescriptNode = h( const titleDescriptNode = h(
'p', 'p',
{ {
props: { props: {
className: 'chapter-image-descript', className: 'chapter-image-descript'
}, },
style: { color: '#999', fontSize: '14px', margin: '0', lineHeight: '20px' }, style: { color: '#999', fontSize: '14px', margin: '0', lineHeight: '20px' }
}, },
[imgDescript], [imgDescript]
); )
const element = [imgNode]; const element = [imgNode]
if (title) { if (title) {
// element.push(titleNode); // element.push(titleNode);
} }
...@@ -370,35 +361,35 @@ const renderImage = (elem, children, editor) => { ...@@ -370,35 +361,35 @@ const renderImage = (elem, children, editor) => {
props: { props: {
// HTML 属性,驼峰式写法 // HTML 属性,驼峰式写法
contentEditable: false, contentEditable: false,
className: 'chapter-image-container', className: 'chapter-image-container'
}, },
style: { padding: '10px 0px' }, // style ,驼峰式写法 style: { padding: '10px 0px' }, // style ,驼峰式写法
on: { on: {
click() { click() {
console.log('clicked'); console.log('clicked')
} /* 其他... */, } /* 其他... */
}, }
}, },
// 子节点 // 子节点
[...element], [...element]
); )
return attachVnode; return attachVnode
}; }
const renderElemConf = { const renderElemConf = {
type: 'chapterImage', type: 'chapterImage',
renderElem: renderImage, renderElem: renderImage
}; }
// 把新元素转换为 HTML // 把新元素转换为 HTML
const chapterImageToHtml = (elem, childrenHtml) => { const chapterImageToHtml = (elem, childrenHtml) => {
// 获取附件元素的数据 // 获取附件元素的数据
const { title = '', imgUrl = '', imgDescript, style = {} } = elem; const { title = '', imgUrl = '', imgDescript, style = {} } = elem
const { width = '', height = '' } = style; const { width = '', height = '' } = style
let styleStr = ''; let styleStr = ''
if (width) styleStr += `width: ${width};`; if (width) styleStr += `width: ${width};`
if (height) styleStr += `height: ${height};`; if (height) styleStr += `height: ${height};`
// 生成 HTML 代码 // 生成 HTML 代码
const html = `<span const html = `<span
...@@ -412,24 +403,24 @@ const chapterImageToHtml = (elem, childrenHtml) => { ...@@ -412,24 +403,24 @@ const chapterImageToHtml = (elem, childrenHtml) => {
<div class="chapter-image-pic" style="position: relative; font-size: 0px;"> <div class="chapter-image-pic" style="position: relative; font-size: 0px;">
<img src="${imgUrl}" alt="${imgDescript}" style="${styleStr}" /> <img src="${imgUrl}" alt="${imgDescript}" style="${styleStr}" />
</div> </div>
</span>`; </span>`
// `<p class="chapter-image-title">${title}</p><p class="chapter-image-descript">${imgDescript}</p>` // `<p class="chapter-image-title">${title}</p><p class="chapter-image-descript">${imgDescript}</p>`
return html; return html
}; }
const chapterImageElemToHtmlConf = { const chapterImageElemToHtmlConf = {
type: 'chapterImage', // 新元素的 type ,重要!!! type: 'chapterImage', // 新元素的 type ,重要!!!
elemToHtml: chapterImageToHtml, elemToHtml: chapterImageToHtml
}; }
// 解析新元素 HTML 到编辑器 // 解析新元素 HTML 到编辑器
const parseImageHtml = (domElem, children, editor) => { const parseImageHtml = (domElem, children, editor) => {
// 从 DOM element 中获取“附件”的信息 // 从 DOM element 中获取“附件”的信息
const imgUrl = domElem.getAttribute('data-imgUrl') || ''; const imgUrl = domElem.getAttribute('data-imgUrl') || ''
const imgDescript = domElem.getAttribute('data-imgDescript') || ''; const imgDescript = domElem.getAttribute('data-imgDescript') || ''
const title = domElem.getAttribute('data-title') || ''; const title = domElem.getAttribute('data-title') || ''
const $elem = $(domElem); const $elem = $(domElem)
// 生成“附件”元素(按照此前约定的数据结构) // 生成“附件”元素(按照此前约定的数据结构)
const myResume = { const myResume = {
...@@ -439,30 +430,25 @@ const parseImageHtml = (domElem, children, editor) => { ...@@ -439,30 +430,25 @@ const parseImageHtml = (domElem, children, editor) => {
imgDescript, imgDescript,
style: { style: {
width: getStyleValue($elem, 'width'), width: getStyleValue($elem, 'width'),
height: getStyleValue($elem, 'height'), height: getStyleValue($elem, 'height')
}, },
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!! children: [{ text: '' }] // void node 必须有 children ,其中有一个空字符串,重要!!!
}; }
return myResume; return myResume
}; }
const parseImageConf = { const parseImageConf = {
selector: 'span[data-w-e-type="chapterImage"]', // CSS 选择器,匹配特定的 HTML 标签 selector: 'span[data-w-e-type="chapterImage"]', // CSS 选择器,匹配特定的 HTML 标签
parseElemHtml: parseImageHtml, parseElemHtml: parseImageHtml
}; }
const chapterImageModule = { const chapterImageModule = {
editorPlugin: withImageNode, editorPlugin: withImageNode,
renderElems: [renderElemConf], renderElems: [renderElemConf],
elemsToHtml: [chapterImageElemToHtmlConf], elemsToHtml: [chapterImageElemToHtmlConf],
parseElemsHtml: [parseImageConf], parseElemsHtml: [parseImageConf],
menus: [ menus: [ImageAutoConf, imageWidth100MenuChapterConf, imageWidth50MenuChapterConf, imageWidth30MenuChapterConf]
ImageAutoConf, }
imageWidth100MenuChapterConf,
imageWidth50MenuChapterConf, export default chapterImageModule
imageWidth30MenuChapterConf, export { withImageNode, renderElemConf, chapterImageElemToHtmlConf, parseImageConf }
],
};
export default chapterImageModule;
export { withImageNode, renderElemConf, chapterImageElemToHtmlConf, parseImageConf };
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论