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

chore: update

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