Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
3da7f60b
提交
3da7f60b
authored
8月 24, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
a9fbd6f2
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
117 个修改的文件
包含
945 行增加
和
1427 行删除
+945
-1427
AppList.tsx
apps/admin/src/components/AppList/AppList.tsx
+0
-1
editor-feature-events.test.tsx
...geditor-customer/__tests__/editor-feature-events.test.tsx
+0
-27
feature-registry.test.ts
...or/wangeditor-customer/__tests__/feature-registry.test.ts
+4
-10
image-menu.test.ts
...s/editor/wangeditor-customer/__tests__/image-menu.test.ts
+43
-0
normalize-inline-void.test.ts
...ngeditor-customer/__tests__/normalize-inline-void.test.ts
+44
-0
remove-spaces-menu.test.ts
.../wangeditor-customer/__tests__/remove-spaces-menu.test.ts
+32
-0
source-boundary.test.ts
...tor/wangeditor-customer/__tests__/source-boundary.test.ts
+6
-9
with-trailing-paragraph.test.ts
...editor-customer/__tests__/with-trailing-paragraph.test.ts
+51
-0
DialogMenu.js
.../components/editor/wangeditor-customer/core/DialogMenu.js
+21
-0
LegacyDialogMenu.js
...nents/editor/wangeditor-customer/core/LegacyDialogMenu.js
+0
-41
create-feature-registry.js
...ditor/wangeditor-customer/core/create-feature-registry.js
+0
-7
define-editor-feature.js
.../editor/wangeditor-customer/core/define-editor-feature.js
+5
-6
register-editor-features.js
...itor/wangeditor-customer/core/register-editor-features.js
+1
-10
useEditorFeatureEvents.js
...editor/wangeditor-customer/core/useEditorFeatureEvents.js
+0
-18
AiImageDialog.jsx
...r/wangeditor-customer/features/ai-media/AiImageDialog.jsx
+3
-11
AiVideoDialog.jsx
...r/wangeditor-customer/features/ai-media/AiVideoDialog.jsx
+3
-11
DigitalHumanDialog.jsx
...geditor-customer/features/ai-media/DigitalHumanDialog.jsx
+2
-3
digital-human-menu.jsx
...geditor-customer/features/ai-media/digital-human-menu.jsx
+3
-7
feature.js
...s/editor/wangeditor-customer/features/ai-media/feature.js
+6
-1
image-menu.jsx
...itor/wangeditor-customer/features/ai-media/image-menu.jsx
+3
-8
video-menu.jsx
...itor/wangeditor-customer/features/ai-media/video-menu.jsx
+3
-8
AiQuestionDialog.jsx
...editor-customer/features/ai-question/AiQuestionDialog.jsx
+5
-6
feature.js
...ditor/wangeditor-customer/features/ai-question/feature.js
+6
-1
judge-menu.jsx
...r/wangeditor-customer/features/ai-question/judge-menu.jsx
+3
-7
multiple-menu.jsx
...angeditor-customer/features/ai-question/multiple-menu.jsx
+3
-7
single-menu.jsx
.../wangeditor-customer/features/ai-question/single-menu.jsx
+3
-7
AiWriteDialog.jsx
...angeditor-customer/features/ai-research/AiWriteDialog.jsx
+3
-10
BaiduSearchDialog.jsx
...ditor-customer/features/ai-research/BaiduSearchDialog.jsx
+0
-160
SearchDialog.jsx
...wangeditor-customer/features/ai-research/SearchDialog.jsx
+3
-10
baidu-search-menu.jsx
...ditor-customer/features/ai-research/baidu-search-menu.jsx
+0
-21
feature.js
...ditor/wangeditor-customer/features/ai-research/feature.js
+7
-10
search-menu.jsx
.../wangeditor-customer/features/ai-research/search-menu.jsx
+3
-7
write-menu.jsx
...r/wangeditor-customer/features/ai-research/write-menu.jsx
+3
-7
AiChatDialog.jsx
...tor/wangeditor-customer/features/ai-text/AiChatDialog.jsx
+3
-4
chat-menu.jsx
...editor/wangeditor-customer/features/ai-text/chat-menu.jsx
+3
-7
content-inspect-menu.jsx
...editor-customer/features/ai-text/content-inspect-menu.jsx
+3
-7
expand-menu.jsx
...itor/wangeditor-customer/features/ai-text/expand-menu.jsx
+3
-7
polishing-menu.jsx
...r/wangeditor-customer/features/ai-text/polishing-menu.jsx
+3
-7
punctuation-menu.jsx
...wangeditor-customer/features/ai-text/punctuation-menu.jsx
+3
-7
rewrite-menu.jsx
...tor/wangeditor-customer/features/ai-text/rewrite-menu.jsx
+3
-7
summary-menu.jsx
...tor/wangeditor-customer/features/ai-text/summary-menu.jsx
+3
-7
translate-menu.jsx
...r/wangeditor-customer/features/ai-text/translate-menu.jsx
+3
-7
feature.js
...ents/editor/wangeditor-customer/features/audio/feature.js
+0
-8
menu.js
...ponents/editor/wangeditor-customer/features/audio/menu.js
+4
-3
node.js
...ponents/editor/wangeditor-customer/features/audio/node.js
+4
-31
feature.js
...wangeditor-customer/features/chapter-structure/feature.js
+0
-11
section-menu.js
...ditor-customer/features/chapter-structure/section-menu.js
+3
-2
section-node.js
...ditor-customer/features/chapter-structure/section-node.js
+3
-36
title-menu.js
...geditor-customer/features/chapter-structure/title-menu.js
+3
-2
title-node.js
...geditor-customer/features/chapter-structure/title-node.js
+3
-35
remove-spaces-menu.js
...tor-customer/features/content-tools/remove-spaces-menu.js
+22
-27
feature.js
...ditor/wangeditor-customer/features/expand-read/feature.js
+0
-11
menu.js
...s/editor/wangeditor-customer/features/expand-read/menu.js
+6
-2
node.js
...s/editor/wangeditor-customer/features/expand-read/node.js
+4
-43
simple-node.js
...r/wangeditor-customer/features/expand-read/simple-node.js
+10
-46
feature.js
...ts/editor/wangeditor-customer/features/formula/feature.js
+0
-7
menu.js
...nents/editor/wangeditor-customer/features/formula/menu.js
+3
-2
node.js
...nents/editor/wangeditor-customer/features/formula/node.js
+9
-21
feature.js
...ts/editor/wangeditor-customer/features/gallery/feature.js
+0
-20
menu.js
...nents/editor/wangeditor-customer/features/gallery/menu.js
+7
-2
node.js
...nents/editor/wangeditor-customer/features/gallery/node.js
+3
-36
online-menu.js
...ditor/wangeditor-customer/features/gallery/online-menu.js
+6
-1
FeatureDialog.jsx
...or/wangeditor-customer/features/history/FeatureDialog.jsx
+1
-2
HistoryDialog.jsx
...or/wangeditor-customer/features/history/HistoryDialog.jsx
+10
-13
menu.jsx
...ponents/editor/wangeditor-customer/features/icon/menu.jsx
+3
-8
edit-menu.js
...ts/editor/wangeditor-customer/features/image/edit-menu.js
+14
-2
feature.js
...ents/editor/wangeditor-customer/features/image/feature.js
+0
-36
menu.js
...ponents/editor/wangeditor-customer/features/image/menu.js
+49
-43
node.jsx
...onents/editor/wangeditor-customer/features/image/node.jsx
+10
-76
online-menu.js
.../editor/wangeditor-customer/features/image/online-menu.js
+11
-84
feature.js
...nents/editor/wangeditor-customer/features/link/feature.js
+0
-12
menu.js
...mponents/editor/wangeditor-customer/features/link/menu.js
+7
-2
node.js
...mponents/editor/wangeditor-customer/features/link/node.js
+9
-22
PracticeSettingsDialog.jsx
...tor-customer/features/practice/PracticeSettingsDialog.jsx
+2
-2
feature.js
...s/editor/wangeditor-customer/features/practice/feature.js
+0
-7
menu.js
...ents/editor/wangeditor-customer/features/practice/menu.js
+3
-2
node.js
...ents/editor/wangeditor-customer/features/practice/node.js
+4
-37
FeatureDialog.jsx
...or/wangeditor-customer/features/preview/FeatureDialog.jsx
+0
-1
PracticePreview.jsx
.../wangeditor-customer/features/preview/PracticePreview.jsx
+87
-0
PracticePreview.test.tsx
...editor-customer/features/preview/PracticePreview.test.tsx
+36
-0
PreviewDialog.jsx
...or/wangeditor-customer/features/preview/PreviewDialog.jsx
+0
-0
PreviewDialog.test.tsx
...ngeditor-customer/features/preview/PreviewDialog.test.tsx
+60
-0
feature.js
...ts/editor/wangeditor-customer/features/tooltip/feature.js
+0
-9
menu.js
...nents/editor/wangeditor-customer/features/tooltip/menu.js
+6
-2
node.js
...nents/editor/wangeditor-customer/features/tooltip/node.js
+9
-27
feature.js
...ents/editor/wangeditor-customer/features/video/feature.js
+0
-8
menu.js
...ponents/editor/wangeditor-customer/features/video/menu.js
+4
-3
node.js
...ponents/editor/wangeditor-customer/features/video/node.js
+4
-37
index.less
...dmin/src/components/editor/wangeditor-customer/index.less
+1
-15
normalize-inline-void.js
...ditor/wangeditor-customer/shared/normalize-inline-void.js
+17
-0
with-trailing-paragraph.js
...tor/wangeditor-customer/shared/with-trailing-paragraph.js
+36
-0
EditorHeader.jsx
...ponents/editor/wangeditor-customer/shell/EditorHeader.jsx
+11
-39
EditorSidebar.jsx
...onents/editor/wangeditor-customer/shell/EditorSidebar.jsx
+64
-54
EditorSidebar.test.tsx
...s/editor/wangeditor-customer/shell/EditorSidebar.test.tsx
+48
-0
WangEditorCustomer.jsx
...s/editor/wangeditor-customer/shell/WangEditorCustomer.jsx
+49
-35
message.jsx
apps/admin/src/components/layout/components/message.jsx
+1
-1
index.jsx
apps/admin/src/components/layout/index.jsx
+2
-6
index.less
apps/admin/src/components/layout/index.less
+0
-4
DeleteAdminModal.tsx
.../src/modules/access/admin/components/DeleteAdminModal.tsx
+1
-1
DeleteRoleModal.tsx
...in/src/modules/access/role/components/DeleteRoleModal.tsx
+0
-0
AccountView.jsx
apps/admin/src/modules/account/views/AccountView.jsx
+0
-0
DeleteAdvertModal.tsx
...es/advertisements/advert/components/DeleteAdvertModal.tsx
+0
-0
index.jsx
apps/admin/src/modules/books/management/chapters/index.jsx
+0
-0
DeleteBookModal.tsx
...c/modules/books/management/components/DeleteBookModal.tsx
+0
-0
ExportBookModal.tsx
...c/modules/books/management/components/ExportBookModal.tsx
+0
-0
ImportBookDrawer.tsx
.../modules/books/management/components/ImportBookDrawer.tsx
+0
-0
ManagementAddEdit.jsx
...modules/books/management/components/ManagementAddEdit.jsx
+0
-0
EditorReviewComments.tsx
...ules/books/review-comments/views/EditorReviewComments.tsx
+0
-0
SaleDiscussionDetail.jsx
...rc/modules/books/sale/components/SaleDiscussionDetail.jsx
+0
-0
SaleEdit.jsx
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
+0
-0
DeleteMemberLevelModal.tsx
...dules/members/level/components/DeleteMemberLevelModal.tsx
+0
-0
AppVersionListView.jsx
...modules/settings/app-version/views/AppVersionListView.jsx
+0
-0
AppliedListView.jsx
...in/src/modules/settings/applied/views/AppliedListView.jsx
+0
-0
HelpListView.jsx
apps/admin/src/modules/settings/help/views/HelpListView.jsx
+0
-0
StudentBookView.jsx
.../admin/src/modules/student-book/views/StudentBookView.jsx
+0
-0
TeacherListView.jsx
...admin/src/modules/teachers/list/views/TeacherListView.jsx
+0
-0
editorStore.ts
apps/admin/src/stores/editorStore.ts
+0
-0
没有找到文件。
apps/admin/src/components/AppList/AppList.tsx
浏览文件 @
3da7f60b
...
@@ -82,7 +82,6 @@ export function AppList<Row extends object, Filters extends object = Record<stri
...
@@ -82,7 +82,6 @@ export function AppList<Row extends object, Filters extends object = Record<stri
{
hasRefreshError
?
(
{
hasRefreshError
?
(
<
Alert
<
Alert
className=
'app-list__error'
type=
'error'
type=
'error'
showIcon
showIcon
title=
{
data
.
error
?.
message
}
title=
{
data
.
error
?.
message
}
...
...
apps/admin/src/components/editor/wangeditor-customer/__tests__/editor-feature-events.test.tsx
deleted
100644 → 0
浏览文件 @
a9fbd6f2
import
{
render
}
from
'@testing-library/react'
import
{
createElement
}
from
'react'
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
useEditorFeatureEvents
}
from
'@/components/editor/wangeditor-customer/core/useEditorFeatureEvents'
describe
(
'useEditorFeatureEvents'
,
()
=>
{
it
(
'subscribes registry menu events to the current editor and cleans them up'
,
()
=>
{
const
editor
=
{
on
:
vi
.
fn
(),
off
:
vi
.
fn
()
}
const
handler
=
vi
.
fn
()
const
registry
=
{
events
:
[{
name
:
'ImageMenuClick'
,
handler
}]
}
const
Subscriber
=
()
=>
{
useEditorFeatureEvents
({
editor
,
registry
,
runtime
:
{}
})
return
null
}
const
view
=
render
(
createElement
(
Subscriber
))
expect
(
editor
.
on
).
toHaveBeenCalledWith
(
'ImageMenuClick'
,
expect
.
any
(
Function
))
const
listener
=
editor
.
on
.
mock
.
calls
[
0
][
1
]
listener
({
source
:
'menu'
})
expect
(
handler
).
toHaveBeenCalledWith
({
editor
,
payload
:
{
source
:
'menu'
},
runtime
:
{}
})
view
.
unmount
()
expect
(
editor
.
off
).
toHaveBeenCalledWith
(
'ImageMenuClick'
,
listener
)
})
})
apps/admin/src/components/editor/wangeditor-customer/__tests__/feature-registry.test.ts
浏览文件 @
3da7f60b
...
@@ -44,7 +44,6 @@ const expectedMenuKeys = [
...
@@ -44,7 +44,6 @@ const expectedMenuKeys = [
'AIWrite'
,
'AIWrite'
,
'AIImage'
,
'AIImage'
,
'AIVideo'
,
'AIVideo'
,
'AIBaiduSearch'
,
'Icon'
,
'Icon'
,
]
]
...
@@ -90,7 +89,6 @@ describe('createFeatureRegistry', () => {
...
@@ -90,7 +89,6 @@ describe('createFeatureRegistry', () => {
menus
:
[{
order
:
10
,
config
:
menu
(
'menu-a'
)
}],
menus
:
[{
order
:
10
,
config
:
menu
(
'menu-a'
)
}],
nodeModules
:
[{
order
:
10
,
types
:
[
'node-a'
],
module
:
{}
}],
nodeModules
:
[{
order
:
10
,
types
:
[
'node-a'
],
module
:
{}
}],
dialogs
:
[{
id
:
'dialog-a'
,
component
:
dialog
}],
dialogs
:
[{
id
:
'dialog-a'
,
component
:
dialog
}],
events
:
[{
name
:
'event-a'
,
handler
:
()
=>
{}
}],
...
overrides
,
...
overrides
,
})
})
...
@@ -104,12 +102,6 @@ describe('createFeatureRegistry', () => {
...
@@ -104,12 +102,6 @@ describe('createFeatureRegistry', () => {
expect
(()
=>
expect
(()
=>
createFeatureRegistry
([
make
(),
make
({
id
:
'feature-b'
,
menus
:
[],
nodeModules
:
[]
})]),
createFeatureRegistry
([
make
(),
make
({
id
:
'feature-b'
,
menus
:
[],
nodeModules
:
[]
})]),
).
toThrow
(
'Duplicate dialog id: dialog-a'
)
).
toThrow
(
'Duplicate dialog id: dialog-a'
)
expect
(()
=>
createFeatureRegistry
([
make
(),
make
({
id
:
'feature-b'
,
menus
:
[],
nodeModules
:
[],
dialogs
:
[]
}),
]),
).
toThrow
(
'Duplicate editor event: event-a'
)
})
})
it
(
'keeps the legacy menu and node registration orders'
,
()
=>
{
it
(
'keeps the legacy menu and node registration orders'
,
()
=>
{
...
@@ -130,13 +122,15 @@ describe('createFeatureRegistry', () => {
...
@@ -130,13 +122,15 @@ describe('createFeatureRegistry', () => {
expect
(
calls
[
0
].
menus
.
map
((
menu
)
=>
menu
.
key
)).
toEqual
(
expectedMenuKeys
)
expect
(
calls
[
0
].
menus
.
map
((
menu
)
=>
menu
.
key
)).
toEqual
(
expectedMenuKeys
)
})
})
it
(
'opens
legacy dialogs by the stable menu key
'
,
()
=>
{
it
(
'opens
the dialog configured by the menu
'
,
()
=>
{
const
calls
=
[]
const
calls
=
[]
const
Boot
=
{
registerModule
:
(
module
)
=>
calls
.
push
(
module
)
}
const
Boot
=
{
registerModule
:
(
module
)
=>
calls
.
push
(
module
)
}
const
emit
=
vi
.
fn
()
const
emit
=
vi
.
fn
()
registerEditorFeatures
(
Boot
)
registerEditorFeatures
(
Boot
)
calls
[
0
].
menus
.
find
((
menu
)
=>
menu
.
key
===
'AIRewrite'
).
factory
().
exec
({
emit
})
const
menu
=
calls
[
0
].
menus
.
find
((
menu
)
=>
menu
.
key
===
'AIRewrite'
).
factory
()
expect
(
menu
.
getValue
()).
toBe
(
''
)
menu
.
exec
({
emit
})
expect
(
emit
).
toHaveBeenCalledWith
(
'wangeditor-customer:dialog'
,
{
expect
(
emit
).
toHaveBeenCalledWith
(
'wangeditor-customer:dialog'
,
{
id
:
'ai-text.action'
,
id
:
'ai-text.action'
,
...
...
apps/admin/src/components/editor/wangeditor-customer/__tests__/image-menu.test.ts
0 → 100644
浏览文件 @
3da7f60b
import
{
afterEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
ImageMenu
,
{
imageWidth50MenuChapterConf
,
}
from
'@/components/editor/wangeditor-customer/features/image/menu'
afterEach
(()
=>
vi
.
restoreAllMocks
())
describe
(
'image menus'
,
()
=>
{
it
(
'opens the image dialog without trying to resize an image'
,
()
=>
{
const
editor
=
{
emit
:
vi
.
fn
()
}
const
menu
=
ImageMenu
.
factory
()
vi
.
spyOn
(
menu
,
'isDisabled'
).
mockReturnValue
(
false
)
const
resize
=
vi
.
spyOn
(
SlateTransforms
,
'setNodes'
)
menu
.
exec
(
editor
)
expect
(
editor
.
emit
).
toHaveBeenCalledWith
(
'wangeditor-customer:dialog'
,
{
id
:
'image.insert'
,
payload
:
{
isOnline
:
false
},
})
expect
(
resize
).
not
.
toHaveBeenCalled
()
})
it
(
'resizes only the selected chapter image without opening the image dialog'
,
()
=>
{
const
image
=
{
type
:
'chapterImage'
,
style
:
{
margin
:
'auto'
}
}
const
editor
=
{
emit
:
vi
.
fn
()
}
vi
.
spyOn
(
DomEditor
,
'getSelectedNodeByType'
).
mockReturnValue
(
image
)
vi
.
spyOn
(
DomEditor
,
'getHoverbar'
).
mockReturnValue
(
null
)
vi
.
spyOn
(
DomEditor
,
'findPath'
).
mockReturnValue
([
2
])
const
resize
=
vi
.
spyOn
(
SlateTransforms
,
'setNodes'
).
mockImplementation
(()
=>
{})
imageWidth50MenuChapterConf
.
factory
().
exec
(
editor
)
expect
(
editor
.
emit
).
not
.
toHaveBeenCalled
()
expect
(
resize
).
toHaveBeenCalledWith
(
editor
,
{
style
:
{
margin
:
'auto'
,
width
:
'50%'
,
height
:
''
}
},
{
at
:
[
2
]
},
)
})
})
apps/admin/src/components/editor/wangeditor-customer/__tests__/normalize-inline-void.test.ts
0 → 100644
浏览文件 @
3da7f60b
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
normalizeInlineVoidNode
}
from
'../shared/normalize-inline-void'
describe
(
'normalizeInlineVoidNode'
,
()
=>
{
it
(
'adds one trailing text node after an inline void at the end of a paragraph'
,
()
=>
{
const
inlineNode
=
{
type
:
'chapterExpandReadSimple'
,
children
:
[{
text
:
''
}]
}
const
paragraph
=
{
type
:
'paragraph'
,
children
:
[{
text
:
'正文'
},
inlineNode
]
}
const
editor
=
{
children
:
[
paragraph
]
}
const
insertNodes
=
vi
.
fn
((
_editor
,
node
,
options
)
=>
{
paragraph
.
children
.
splice
(
options
.
at
[
1
],
0
,
node
)
})
const
input
=
{
editor
,
node
:
inlineNode
,
path
:
[
0
,
1
],
type
:
'chapterExpandReadSimple'
,
normalizeNode
:
vi
.
fn
(),
transforms
:
{
insertNodes
},
}
normalizeInlineVoidNode
(
input
)
normalizeInlineVoidNode
(
input
)
expect
(
insertNodes
).
toHaveBeenCalledOnce
()
expect
(
paragraph
.
children
).
toEqual
([{
text
:
'正文'
},
inlineNode
,
{
text
:
''
}])
})
it
(
'delegates nodes owned by another normalizer'
,
()
=>
{
const
normalizeNode
=
vi
.
fn
()
const
node
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
normalizeInlineVoidNode
({
editor
:
{
children
:
[
node
]
},
node
,
path
:
[
0
],
type
:
'chapterFormula'
,
normalizeNode
,
transforms
:
{
insertNodes
:
vi
.
fn
()
},
})
expect
(
normalizeNode
).
toHaveBeenCalledWith
([
node
,
[
0
]])
})
})
apps/admin/src/components/editor/wangeditor-customer/__tests__/remove-spaces-menu.test.ts
0 → 100644
浏览文件 @
3da7f60b
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
RemoveSpacesMenu
,
{
removeSpacesFromChineseParagraphs
,
}
from
'@/components/editor/wangeditor-customer/features/content-tools/remove-spaces-menu'
describe
(
'removeSpacesFromChineseParagraphs'
,
()
=>
{
it
(
'removes spaces and non-breaking spaces from Chinese paragraphs'
,
()
=>
{
const
html
=
'<p>这 是 <strong>中文 段落</strong>。</p><p>English text stays.</p>'
expect
(
removeSpacesFromChineseParagraphs
(
html
)).
toBe
(
'<p>这是<strong>中文段落</strong>。</p><p>English text stays.</p>'
,
)
})
it
(
'does not change content outside paragraphs'
,
()
=>
{
const
html
=
'<pre><code>const value = 1</code></pre><div>中文 内容</div>'
expect
(
removeSpacesFromChineseParagraphs
(
html
)).
toBe
(
html
)
})
it
(
'updates the editor only when content changes'
,
()
=>
{
const
editor
=
{
getHtml
:
vi
.
fn
(()
=>
'<p>中文 段落</p>'
),
setHtml
:
vi
.
fn
(),
}
RemoveSpacesMenu
.
factory
().
exec
(
editor
)
expect
(
editor
.
setHtml
).
toHaveBeenCalledWith
(
'<p>中文段落</p>'
)
})
})
apps/admin/src/components/editor/wangeditor-customer/__tests__/source-boundary.test.ts
浏览文件 @
3da7f60b
...
@@ -13,13 +13,10 @@ describe('WangEditorCustomer shell boundary', () => {
...
@@ -13,13 +13,10 @@ describe('WangEditorCustomer shell boundary', () => {
expect
(
shellSource
).
not
.
toMatch
(
/from
\s
+
[
'"
][^
'"
]
*features
\/
/
)
expect
(
shellSource
).
not
.
toMatch
(
/from
\s
+
[
'"
][^
'"
]
*features
\/
/
)
})
})
it
.
each
([
it
.
each
([
'window.dispatchEvent'
,
'document.querySelector('
])(
'ImageMenuClick'
,
'does not contain %s'
,
'GalleryMenuClick'
,
(
forbidden
)
=>
{
'PracticeMenuClick'
,
expect
(
shellSource
).
not
.
toContain
(
forbidden
)
'window.dispatchEvent'
,
},
'document.querySelector('
,
)
])(
'does not contain %s'
,
(
forbidden
)
=>
{
expect
(
shellSource
).
not
.
toContain
(
forbidden
)
})
})
})
apps/admin/src/components/editor/wangeditor-customer/__tests__/with-trailing-paragraph.test.ts
0 → 100644
浏览文件 @
3da7f60b
import
{
afterEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
SlateTransforms
}
from
'@wangeditor/editor'
import
{
withTrailingParagraph
}
from
'@/components/editor/wangeditor-customer/shared/with-trailing-paragraph'
afterEach
(()
=>
vi
.
restoreAllMocks
())
describe
(
'withTrailingParagraph'
,
()
=>
{
it
(
'adds an editable paragraph after a custom block at the document end'
,
()
=>
{
const
node
=
{
type
:
'chapterVideo'
,
children
:
[{
text
:
''
}]
}
const
editor
=
{
children
:
[
node
],
normalizeNode
:
vi
.
fn
()
}
const
insert
=
vi
.
spyOn
(
SlateTransforms
,
'insertNodes'
).
mockImplementation
(()
=>
{})
withTrailingParagraph
(
editor
,
'chapterVideo'
).
normalizeNode
([
node
,
[
0
]])
expect
(
insert
).
toHaveBeenCalledWith
(
editor
,
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
},
{
at
:
[
1
]
},
)
})
it
(
'keeps allowed following blocks and delegates unrelated nodes'
,
()
=>
{
const
node
=
{
type
:
'chapterVideo'
,
children
:
[{
text
:
''
}]
}
const
next
=
{
type
:
'chapterGallery'
,
children
:
[{
text
:
''
}]
}
const
normalizeNode
=
vi
.
fn
()
const
editor
=
{
children
:
[
node
,
next
],
normalizeNode
}
const
insert
=
vi
.
spyOn
(
SlateTransforms
,
'insertNodes'
).
mockImplementation
(()
=>
{})
withTrailingParagraph
(
editor
,
'chapterVideo'
)
editor
.
normalizeNode
([
node
,
[
0
]])
editor
.
normalizeNode
([
next
,
[
1
]])
expect
(
insert
).
not
.
toHaveBeenCalled
()
expect
(
normalizeNode
).
toHaveBeenCalledWith
([
next
,
[
1
]])
})
it
(
'supports the narrower image and audio rule without duplicating the normalizer'
,
()
=>
{
const
node
=
{
type
:
'chapterAudio'
,
children
:
[{
text
:
''
}]
}
const
next
=
{
type
:
'chapterGallery'
,
children
:
[{
text
:
''
}]
}
const
editor
=
{
children
:
[
node
,
next
],
normalizeNode
:
vi
.
fn
()
}
const
insert
=
vi
.
spyOn
(
SlateTransforms
,
'insertNodes'
).
mockImplementation
(()
=>
{})
withTrailingParagraph
(
editor
,
'chapterAudio'
,
[
'paragraph'
,
'blockquote'
]).
normalizeNode
([
node
,
[
0
],
])
expect
(
insert
).
toHaveBeenCalledOnce
()
})
})
apps/admin/src/components/editor/wangeditor-customer/core/DialogMenu.js
0 → 100644
浏览文件 @
3da7f60b
import
{
openEditorDialog
}
from
'./editor-dialog'
export
default
class
DialogMenu
{
constructor
(
dialogId
,
payload
=
{})
{
this
.
dialogId
=
dialogId
this
.
payload
=
payload
this
.
tag
=
'button'
}
getValue
()
{
return
''
}
isActive
()
{
return
false
}
isDisabled
()
{
return
false
}
exec
(
editor
)
{
if
(
!
this
.
isDisabled
(
editor
))
openEditorDialog
(
editor
,
this
.
dialogId
,
this
.
payload
)
}
}
apps/admin/src/components/editor/wangeditor-customer/core/LegacyDialogMenu.js
deleted
100644 → 0
浏览文件 @
a9fbd6f2
import
{
openEditorDialog
}
from
'./editor-dialog'
const
dialogByMenu
=
{
AIChat
:
[
'ai-text.chat'
,
{}],
AIRewrite
:
[
'ai-text.action'
,
{
action
:
'rewrite'
}],
AIExpand
:
[
'ai-text.action'
,
{
action
:
'expand'
}],
AISummary
:
[
'ai-text.action'
,
{
action
:
'summary'
}],
AIPolishing
:
[
'ai-text.action'
,
{
action
:
'abbreviate'
}],
AIPunctuation
:
[
'ai-text.action'
,
{
action
:
'punctuation'
}],
AIContentInspect
:
[
'ai-text.action'
,
{
action
:
'contentInspect'
}],
AITranslate
:
[
'ai-text.action'
,
{
action
:
'translate'
}],
AIQuestionSingle
:
[
'ai-question.generate'
,
{
action
:
'1'
}],
AIQuestionMultiple
:
[
'ai-question.generate'
,
{
action
:
'2'
}],
AIQuestionJudge
:
[
'ai-question.generate'
,
{
action
:
'3'
}],
AIQuestionGapFill
:
[
'ai-question.generate'
,
{
action
:
'4'
}],
AIQuestionOpenEnded
:
[
'ai-question.generate'
,
{
action
:
'5'
}],
AIDigitalHuman
:
[
'ai-media.digital-human'
,
{}],
AIImage
:
[
'ai-media.image'
,
{}],
AIVideo
:
[
'ai-media.video'
,
{}],
AISearch
:
[
'ai-research.search'
,
{
provider
:
'search'
}],
AIBaiduSearch
:
[
'ai-research.search'
,
{
provider
:
'baidu'
}],
AIWrite
:
[
'ai-research.write'
,
{}],
Icon
:
[
'icon.insert'
,
{}],
}
export
default
class
BaseModalMenu
{
constructor
()
{
this
.
tag
=
'button'
}
isActive
()
{
return
false
// or true
}
isDisabled
()
{
return
false
// or true
}
exec
(
editor
)
{
const
request
=
dialogByMenu
[
this
.
menuKey
]
if
(
!
request
)
return
openEditorDialog
(
editor
,
request
[
0
],
request
[
1
])
}
}
apps/admin/src/components/editor/wangeditor-customer/core/create-feature-registry.js
浏览文件 @
3da7f60b
...
@@ -18,7 +18,6 @@ export const createFeatureRegistry = (features) => {
...
@@ -18,7 +18,6 @@ export const createFeatureRegistry = (features) => {
const
menus
=
features
.
flatMap
((
feature
)
=>
feature
.
menus
).
sort
(
byOrder
)
const
menus
=
features
.
flatMap
((
feature
)
=>
feature
.
menus
).
sort
(
byOrder
)
const
nodeModules
=
features
.
flatMap
((
feature
)
=>
feature
.
nodeModules
).
sort
(
byOrder
)
const
nodeModules
=
features
.
flatMap
((
feature
)
=>
feature
.
nodeModules
).
sort
(
byOrder
)
const
dialogEntries
=
features
.
flatMap
((
feature
)
=>
feature
.
dialogs
)
const
dialogEntries
=
features
.
flatMap
((
feature
)
=>
feature
.
dialogs
)
const
events
=
features
.
flatMap
((
feature
)
=>
feature
.
events
)
assertUnique
(
assertUnique
(
menus
.
map
((
entry
)
=>
entry
.
config
.
key
),
menus
.
map
((
entry
)
=>
entry
.
config
.
key
),
...
@@ -32,16 +31,10 @@ export const createFeatureRegistry = (features) => {
...
@@ -32,16 +31,10 @@ export const createFeatureRegistry = (features) => {
dialogEntries
.
map
((
entry
)
=>
entry
.
id
),
dialogEntries
.
map
((
entry
)
=>
entry
.
id
),
'dialog id'
,
'dialog id'
,
)
)
assertUnique
(
events
.
map
((
entry
)
=>
entry
.
name
),
'editor event'
,
)
return
{
return
{
features
:
[...
features
],
features
:
[...
features
],
menus
,
menus
,
nodeModules
,
nodeModules
,
events
,
dialogs
:
new
Map
(
dialogEntries
.
map
((
entry
)
=>
[
entry
.
id
,
entry
.
component
])),
dialogs
:
new
Map
(
dialogEntries
.
map
((
entry
)
=>
[
entry
.
id
,
entry
.
component
])),
}
}
}
}
apps/admin/src/components/editor/wangeditor-customer/core/define-editor-feature.js
浏览文件 @
3da7f60b
export
const
defineEditorFeature
=
({
export
const
defineEditorFeature
=
({
id
,
menus
=
[],
nodeModules
=
[],
dialogs
=
[]
})
=>
({
id
,
id
,
menus
=
[],
menus
,
nodeModules
=
[],
nodeModules
,
dialogs
=
[],
dialogs
,
events
=
[],
})
})
=>
({
id
,
menus
,
nodeModules
,
dialogs
,
events
})
apps/admin/src/components/editor/wangeditor-customer/core/register-editor-features.js
浏览文件 @
3da7f60b
...
@@ -2,20 +2,11 @@ import { editorFeatureRegistry } from '../features'
...
@@ -2,20 +2,11 @@ import { editorFeatureRegistry } from '../features'
const
registrationKey
=
Symbol
.
for
(
'ebook.admin.wangeditor-customer.registered'
)
const
registrationKey
=
Symbol
.
for
(
'ebook.admin.wangeditor-customer.registered'
)
const
bindMenuKey
=
(
config
)
=>
({
...
config
,
factory
(...
args
)
{
const
menu
=
config
.
factory
.
apply
(
config
,
args
)
menu
.
menuKey
=
config
.
key
return
menu
},
})
export
const
registerEditorFeatures
=
(
Boot
)
=>
{
export
const
registerEditorFeatures
=
(
Boot
)
=>
{
if
(
Boot
[
registrationKey
])
return
if
(
Boot
[
registrationKey
])
return
Boot
.
registerModule
({
Boot
.
registerModule
({
menus
:
editorFeatureRegistry
.
menus
.
map
((
entry
)
=>
bindMenuKey
(
entry
.
config
)
),
menus
:
editorFeatureRegistry
.
menus
.
map
((
entry
)
=>
entry
.
config
),
})
})
editorFeatureRegistry
.
nodeModules
.
forEach
((
entry
)
=>
Boot
.
registerModule
(
entry
.
module
))
editorFeatureRegistry
.
nodeModules
.
forEach
((
entry
)
=>
Boot
.
registerModule
(
entry
.
module
))
...
...
apps/admin/src/components/editor/wangeditor-customer/core/useEditorFeatureEvents.js
deleted
100644 → 0
浏览文件 @
a9fbd6f2
import
{
useEffect
,
useRef
}
from
'react'
export
const
useEditorFeatureEvents
=
({
editor
,
registry
,
runtime
})
=>
{
const
runtimeRef
=
useRef
(
runtime
)
runtimeRef
.
current
=
runtime
useEffect
(()
=>
{
if
(
!
editor
)
return
const
listeners
=
registry
.
events
.
map
(({
name
,
handler
})
=>
({
name
,
listener
:
(
payload
)
=>
handler
({
editor
,
payload
,
runtime
:
runtimeRef
.
current
}),
}))
listeners
.
forEach
(({
name
,
listener
})
=>
editor
.
on
(
name
,
listener
))
return
()
=>
listeners
.
forEach
(({
name
,
listener
})
=>
editor
.
off
(
name
,
listener
))
},
[
editor
,
registry
])
}
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/AiImageDialog.jsx
浏览文件 @
3da7f60b
...
@@ -25,9 +25,7 @@ import './image.less'
...
@@ -25,9 +25,7 @@ import './image.less'
import
{
useImage
}
from
'@ezijing/ai-react'
import
{
useImage
}
from
'@ezijing/ai-react'
import
{
uploadFileByUrl
}
from
'@/utils/oss'
import
{
uploadFileByUrl
}
from
'@/utils/oss'
export
default
function
AIImageModal
(
props
)
{
export
default
function
AIImageModal
({
editor
,
open
,
onClose
})
{
const
{
editor
}
=
props
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
prompt
,
setPrompt
]
=
useState
(
''
)
const
[
prompt
,
setPrompt
]
=
useState
(
''
)
const
[
size
,
setSize
]
=
useState
(
'2048x2048'
)
const
[
size
,
setSize
]
=
useState
(
'2048x2048'
)
const
[
style
,
setStyle
]
=
useState
(
'default'
)
const
[
style
,
setStyle
]
=
useState
(
'default'
)
...
@@ -152,7 +150,7 @@ export default function AIImageModal(props) {
...
@@ -152,7 +150,7 @@ export default function AIImageModal(props) {
insertBlockNodesAndFocus
(
editor
,
SlateTransforms
,
[
oImg
,
oP
])
insertBlockNodesAndFocus
(
editor
,
SlateTransforms
,
[
oImg
,
oP
])
message
.
success
(
'图片已插入编辑器!'
)
message
.
success
(
'图片已插入编辑器!'
)
setIsModalOpen
(
false
)
onClose
(
)
}
else
{
}
else
{
message
.
error
(
'编辑器未找到'
)
message
.
error
(
'编辑器未找到'
)
}
}
...
@@ -208,13 +206,7 @@ export default function AIImageModal(props) {
...
@@ -208,13 +206,7 @@ export default function AIImageModal(props) {
<
ConfigProvider
<
ConfigProvider
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
>
>
<
Modal
<
Modal
title=
'AI图片生成'
open=
{
open
}
footer=
{
null
}
onCancel=
{
onClose
}
width=
{
1200
}
>
title=
'AI图片生成'
open=
{
isModalOpen
}
footer=
{
null
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
width=
{
1200
}
>
<
div
className=
'ai-image-generator'
>
<
div
className=
'ai-image-generator'
>
{
/* 输入区域 */
}
{
/* 输入区域 */
}
<
div
className=
'input-container'
>
<
div
className=
'input-container'
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/AiVideoDialog.jsx
浏览文件 @
3da7f60b
...
@@ -31,9 +31,7 @@ import './video.less'
...
@@ -31,9 +31,7 @@ import './video.less'
const
{
TextArea
}
=
Input
const
{
TextArea
}
=
Input
export
default
function
AIVideoModal
(
props
)
{
export
default
function
AIVideoModal
({
editor
,
open
,
onClose
})
{
const
{
editor
}
=
props
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
prompt
,
setPrompt
]
=
useState
(
''
)
const
[
prompt
,
setPrompt
]
=
useState
(
''
)
const
[
ratio
,
setRatio
]
=
useState
(
'16:9'
)
const
[
ratio
,
setRatio
]
=
useState
(
'16:9'
)
const
[
duration
,
setDuration
]
=
useState
(
5
)
const
[
duration
,
setDuration
]
=
useState
(
5
)
...
@@ -138,7 +136,7 @@ export default function AIVideoModal(props) {
...
@@ -138,7 +136,7 @@ export default function AIVideoModal(props) {
children
:
[{
text
:
''
}],
children
:
[{
text
:
''
}],
})
})
message
.
success
(
'视频已插入编辑器!'
)
message
.
success
(
'视频已插入编辑器!'
)
setIsModalOpen
(
false
)
onClose
(
)
}
else
{
}
else
{
message
.
error
(
'编辑器未找到'
)
message
.
error
(
'编辑器未找到'
)
}
}
...
@@ -192,13 +190,7 @@ export default function AIVideoModal(props) {
...
@@ -192,13 +190,7 @@ export default function AIVideoModal(props) {
<
ConfigProvider
<
ConfigProvider
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
>
>
<
Modal
<
Modal
title=
'AI视频生成'
open=
{
open
}
footer=
{
null
}
onCancel=
{
onClose
}
width=
{
1200
}
>
title=
'AI视频生成'
open=
{
isModalOpen
}
footer=
{
null
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
width=
{
1200
}
>
<
div
className=
'ai-video-generator'
>
<
div
className=
'ai-video-generator'
>
<
div
className=
'input-container'
>
<
div
className=
'input-container'
>
<
div
className=
'input-box'
>
<
div
className=
'input-box'
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/DigitalHumanDialog.jsx
浏览文件 @
3da7f60b
...
@@ -13,12 +13,11 @@ import './digital-human.less'
...
@@ -13,12 +13,11 @@ import './digital-human.less'
const
CUSTOMIZE_WORKSPACE
=
'customize'
const
CUSTOMIZE_WORKSPACE
=
'customize'
export
default
function
DigitalHumanDialog
({
editor
})
{
export
default
function
DigitalHumanDialog
({
editor
,
open
,
onClose
})
{
const
[
open
,
setOpen
]
=
useState
(
true
)
const
[
activeWorkspace
,
setActiveWorkspace
]
=
useState
(
getConfiguredDigitalHumanType
)
const
[
activeWorkspace
,
setActiveWorkspace
]
=
useState
(
getConfiguredDigitalHumanType
)
const
[
historySelection
,
setHistorySelection
]
=
useState
(
null
)
const
[
historySelection
,
setHistorySelection
]
=
useState
(
null
)
const
digitalHuman
=
useDigitalHuman
()
const
digitalHuman
=
useDigitalHuman
()
const
close
=
()
=>
setOpen
(
false
)
const
close
=
onClose
const
clearTaskSelection
=
useCallback
(()
=>
setHistorySelection
(
null
),
[])
const
clearTaskSelection
=
useCallback
(()
=>
setHistorySelection
(
null
),
[])
const
sharedProps
=
{
const
sharedProps
=
{
editor
,
editor
,
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/digital-human-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIDigitalHumanModal
from
'./DigitalHumanDialog'
class
AIDigitalHuman
extends
BaseModal
Menu
{
class
AIDigitalHuman
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-media.digital-human'
)
this
.
title
=
'数字人'
this
.
title
=
'数字人'
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 28 28"><path fill="currentColor" d="M12 5.5a2 2 0 0 0 1.491 1.935c.337.053.68.053 1.018 0A2 2 0 1 0 12 5.5m-1.337 1.058a3.5 3.5 0 1 1 6.675 0l4.419-1.436a2.477 2.477 0 1 1 1.53 4.712L18 11.552v3.822c0 .16.03.32.091.468l2.728 6.752a2.477 2.477 0 0 1-4.594 1.856l-2.243-5.553l-2.232 5.56a2.46 2.46 0 0 1-3.21 1.362a2.477 2.477 0 0 1-1.364-3.215l2.734-6.812q.09-.224.09-.466v-3.774L4.712 9.834a2.477 2.477 0 0 1 1.531-4.712zm2.518 2.346a5 5 0 0 1-.649-.162L5.78 6.548a.977.977 0 0 0-.604 1.859l5.46 1.774c.515.168.864.648.864 1.189v3.957c0 .35-.067.698-.198 1.024l-2.734 6.811a.977.977 0 0 0 .538 1.267a.96.96 0 0 0 1.252-.531l2.463-6.136c.42-1.045 1.897-1.047 2.319-.003l2.476 6.129a.977.977 0 1 0 1.812-.732L16.7 16.404a2.8 2.8 0 0 1-.2-1.03V11.37c0-.541.349-1.021.864-1.189l5.46-1.774a.977.977 0 1 0-.604-1.859l-6.752 2.194q-.32.104-.649.162a3.5 3.5 0 0 1-1.639 0"/></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 28 28"><path fill="currentColor" d="M12 5.5a2 2 0 0 0 1.491 1.935c.337.053.68.053 1.018 0A2 2 0 1 0 12 5.5m-1.337 1.058a3.5 3.5 0 1 1 6.675 0l4.419-1.436a2.477 2.477 0 1 1 1.53 4.712L18 11.552v3.822c0 .16.03.32.091.468l2.728 6.752a2.477 2.477 0 0 1-4.594 1.856l-2.243-5.553l-2.232 5.56a2.46 2.46 0 0 1-3.21 1.362a2.477 2.477 0 0 1-1.364-3.215l2.734-6.812q.09-.224.09-.466v-3.774L4.712 9.834a2.477 2.477 0 0 1 1.531-4.712zm2.518 2.346a5 5 0 0 1-.649-.162L5.78 6.548a.977.977 0 0 0-.604 1.859l5.46 1.774c.515.168.864.648.864 1.189v3.957c0 .35-.067.698-.198 1.024l-2.734 6.811a.977.977 0 0 0 .538 1.267a.96.96 0 0 0 1.252-.531l2.463-6.136c.42-1.045 1.897-1.047 2.319-.003l2.476 6.129a.977.977 0 1 0 1.812-.732L16.7 16.404a2.8 2.8 0 0 1-.2-1.03V11.37c0-.541.349-1.021.864-1.189l5.46-1.774a.977.977 0 1 0-.604-1.859l-6.752 2.194q-.32.104-.649.162a3.5 3.5 0 0 1-1.639 0"/></svg>`
}
}
getValue
(
editor
)
{
return
<
AIDigitalHumanModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AIDigitalHumanModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/feature.js
浏览文件 @
3da7f60b
...
@@ -7,7 +7,12 @@ import DigitalHumanDialog from './DigitalHumanDialog'
...
@@ -7,7 +7,12 @@ import DigitalHumanDialog from './DigitalHumanDialog'
import
AiImageDialog
from
'./AiImageDialog'
import
AiImageDialog
from
'./AiImageDialog'
import
AiVideoDialog
from
'./AiVideoDialog'
import
AiVideoDialog
from
'./AiVideoDialog'
const
dialog
=
(
Component
)
=>
(
props
)
=>
createElement
(
Component
,
{
editor
:
props
.
runtime
.
editor
})
const
dialog
=
(
Component
)
=>
(
props
)
=>
createElement
(
Component
,
{
editor
:
props
.
runtime
.
editor
,
open
:
props
.
open
,
onClose
:
props
.
onClose
,
})
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
id
:
'ai-media'
,
id
:
'ai-media'
,
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/image-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/Legacy
DialogMenu'
import
DialogMenu
from
'../../core/
DialogMenu'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
AIImageModal
from
'./AiImageDialog'
class
AIImage
extends
BaseModal
Menu
{
class
AIImage
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-media.image'
)
this
.
title
=
'AI图片'
this
.
title
=
'AI图片'
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.7134 8.12811L20.4668 8.69379C20.2864 9.10792 19.7136 9.10792 19.5331 8.69379L19.2866 8.12811C18.8471 7.11947 18.0555 6.31641 17.0677 5.87708L16.308 5.53922C15.8973 5.35653 15.8973 4.75881 16.308 4.57612L17.0252 4.25714C18.0384 3.80651 18.8442 2.97373 19.2761 1.93083L19.5293 1.31953C19.7058 0.893489 20.2942 0.893489 20.4706 1.31953L20.7238 1.93083C21.1558 2.97373 21.9616 3.80651 22.9748 4.25714L23.6919 4.57612C24.1027 4.75881 24.1027 5.35653 23.6919 5.53922L22.9323 5.87708C21.9445 6.31641 21.1529 7.11947 20.7134 8.12811ZM2.9918 3H14V5H4V19L14 9L20 15V11H22V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3ZM20 17.8284L14 11.8284L6.82843 19H20V17.8284ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z"></path></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.7134 8.12811L20.4668 8.69379C20.2864 9.10792 19.7136 9.10792 19.5331 8.69379L19.2866 8.12811C18.8471 7.11947 18.0555 6.31641 17.0677 5.87708L16.308 5.53922C15.8973 5.35653 15.8973 4.75881 16.308 4.57612L17.0252 4.25714C18.0384 3.80651 18.8442 2.97373 19.2761 1.93083L19.5293 1.31953C19.7058 0.893489 20.2942 0.893489 20.4706 1.31953L20.7238 1.93083C21.1558 2.97373 21.9616 3.80651 22.9748 4.25714L23.6919 4.57612C24.1027 4.75881 24.1027 5.35653 23.6919 5.53922L22.9323 5.87708C21.9445 6.31641 21.1529 7.11947 20.7134 8.12811ZM2.9918 3H14V5H4V19L14 9L20 15V11H22V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3ZM20 17.8284L14 11.8284L6.82843 19H20V17.8284ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z"></path></svg>`
}
}
getValue
(
editor
)
{
return
<
AIImageModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AIImageModal
>
}
isDisabled
(
editor
)
{
isDisabled
(
editor
)
{
const
{
selection
}
=
editor
||
{}
const
{
selection
}
=
editor
||
{}
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-media/video-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/Legacy
DialogMenu'
import
DialogMenu
from
'../../core/
DialogMenu'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
AIVideoModal
from
'./AiVideoDialog'
class
AIVideo
extends
BaseModal
Menu
{
class
AIVideo
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-media.video'
)
this
.
title
=
'AI视频'
this
.
title
=
'AI视频'
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.7134 8.12811L19.4668 8.69379C19.2864 9.10792 18.7136 9.10792 18.5331 8.69379L18.2866 8.12811C17.8471 7.11947 17.0555 6.31641 16.0677 5.87708L15.308 5.53922C14.8973 5.35653 14.8973 4.75881 15.308 4.57612L16.0252 4.25714C17.0384 3.80651 17.8442 2.97373 18.2761 1.93083L18.5293 1.31953C18.7058 0.893489 19.2942 0.893489 19.4706 1.31953L19.7238 1.93083C20.1558 2.97373 20.9616 3.80651 21.9748 4.25714L22.6919 4.57612C23.1027 4.75881 23.1027 5.35653 22.6919 5.53922L21.9323 5.87708C20.9445 6.31641 20.1529 7.11947 19.7134 8.12811ZM3.9934 3H13V5H5V19H19V11H21V20.0066C21 20.5552 20.5551 21 20.0066 21H3.9934C3.44476 21 3 20.5551 3 20.0066V3.9934C3 3.44476 3.44495 3 3.9934 3ZM10.6219 8.41459L15.5008 11.6672C15.6846 11.7897 15.7343 12.0381 15.6117 12.2219C15.5824 12.2658 15.5447 12.3035 15.5008 12.3328L10.6219 15.5854C10.4381 15.708 10.1897 15.6583 10.0672 15.4745C10.0234 15.4088 10 15.3316 10 15.2526V8.74741C10 8.52649 10.1791 8.34741 10.4 8.34741C10.479 8.34741 10.5562 8.37078 10.6219 8.41459Z"></path></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.7134 8.12811L19.4668 8.69379C19.2864 9.10792 18.7136 9.10792 18.5331 8.69379L18.2866 8.12811C17.8471 7.11947 17.0555 6.31641 16.0677 5.87708L15.308 5.53922C14.8973 5.35653 14.8973 4.75881 15.308 4.57612L16.0252 4.25714C17.0384 3.80651 17.8442 2.97373 18.2761 1.93083L18.5293 1.31953C18.7058 0.893489 19.2942 0.893489 19.4706 1.31953L19.7238 1.93083C20.1558 2.97373 20.9616 3.80651 21.9748 4.25714L22.6919 4.57612C23.1027 4.75881 23.1027 5.35653 22.6919 5.53922L21.9323 5.87708C20.9445 6.31641 20.1529 7.11947 19.7134 8.12811ZM3.9934 3H13V5H5V19H19V11H21V20.0066C21 20.5552 20.5551 21 20.0066 21H3.9934C3.44476 21 3 20.5551 3 20.0066V3.9934C3 3.44476 3.44495 3 3.9934 3ZM10.6219 8.41459L15.5008 11.6672C15.6846 11.7897 15.7343 12.0381 15.6117 12.2219C15.5824 12.2658 15.5447 12.3035 15.5008 12.3328L10.6219 15.5854C10.4381 15.708 10.1897 15.6583 10.0672 15.4745C10.0234 15.4088 10 15.3316 10 15.2526V8.74741C10 8.52649 10.1791 8.34741 10.4 8.34741C10.479 8.34741 10.5562 8.37078 10.6219 8.41459Z"></path></svg>`
}
}
getValue
(
editor
)
{
return
<
AIVideoModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AIVideoModal
>
}
isDisabled
(
editor
)
{
isDisabled
(
editor
)
{
const
{
selection
}
=
editor
||
{}
const
{
selection
}
=
editor
||
{}
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-question/AiQuestionDialog.jsx
浏览文件 @
3da7f60b
...
@@ -65,9 +65,8 @@ const actionMap = {
...
@@ -65,9 +65,8 @@ const actionMap = {
},
},
}
}
export
default
function
AIQuestionModal
({
editor
,
action
})
{
export
default
function
AIQuestionModal
({
editor
,
action
,
open
,
onClose
})
{
const
{
message
}
=
App
.
useApp
()
const
{
message
}
=
App
.
useApp
()
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
{
text
,
fetch
,
isLoading
}
=
useAIEdit
()
const
{
text
,
fetch
,
isLoading
}
=
useAIEdit
()
const
actionText
=
actionMap
[
action
]?.
name
const
actionText
=
actionMap
[
action
]?.
name
...
@@ -156,17 +155,17 @@ export default function AIQuestionModal({ editor, action }) {
...
@@ -156,17 +155,17 @@ export default function AIQuestionModal({ editor, action }) {
question_list
:
JSON
.
stringify
(
question
.
question_list
),
question_list
:
JSON
.
stringify
(
question
.
question_list
),
})
})
message
.
success
(
res
.
message
)
message
.
success
(
res
.
message
)
setIsModalOpen
(
false
)
onClose
(
)
}
}
return
(
return
(
<
Modal
<
Modal
title=
'AI创建试题'
title=
'AI创建试题'
open=
{
isModalO
pen
}
open=
{
o
pen
}
footer=
{
null
}
footer=
{
null
}
width=
{
800
}
width=
{
800
}
onOk=
{
handlePrimary
}
onOk=
{
handlePrimary
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
onCancel=
{
onClose
}
>
>
<
Spin
spinning=
{
isLoading
}
>
<
Spin
spinning=
{
isLoading
}
>
<
Form
labelCol=
{
{
span
:
2
}
}
>
<
Form
labelCol=
{
{
span
:
2
}
}
>
...
@@ -198,7 +197,7 @@ export default function AIQuestionModal({ editor, action }) {
...
@@ -198,7 +197,7 @@ export default function AIQuestionModal({ editor, action }) {
</
Spin
>
</
Spin
>
<
br
/>
<
br
/>
<
Flex
gap=
'small'
justify=
'end'
>
<
Flex
gap=
'small'
justify=
'end'
>
<
Button
onClick=
{
()
=>
setIsModalOpen
(
false
)
}
>
取消
</
Button
>
<
Button
onClick=
{
onClose
}
>
取消
</
Button
>
<
Button
type=
'primary'
onClick=
{
handleFetch
}
>
<
Button
type=
'primary'
onClick=
{
handleFetch
}
>
重新出题
重新出题
</
Button
>
</
Button
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-question/feature.js
浏览文件 @
3da7f60b
...
@@ -8,7 +8,12 @@ import AIQuestionSingle from './single-menu'
...
@@ -8,7 +8,12 @@ import AIQuestionSingle from './single-menu'
import
AiQuestionDialog
from
'./AiQuestionDialog'
import
AiQuestionDialog
from
'./AiQuestionDialog'
const
dialog
=
(
props
)
=>
const
dialog
=
(
props
)
=>
createElement
(
AiQuestionDialog
,
{
editor
:
props
.
runtime
.
editor
,
action
:
props
.
payload
.
action
})
createElement
(
AiQuestionDialog
,
{
editor
:
props
.
runtime
.
editor
,
action
:
props
.
payload
.
action
,
open
:
props
.
open
,
onClose
:
props
.
onClose
,
})
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
id
:
'ai-question'
,
id
:
'ai-question'
,
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-question/judge-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIQuestionModal
from
'./AiQuestionDialog'
class
AIQuestionJudge
extends
BaseModal
Menu
{
class
AIQuestionJudge
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-question.generate'
,
{
action
:
'3'
}
)
this
.
title
=
'判断题'
this
.
title
=
'判断题'
this
.
iconSvg
=
`<svg t="1729144923785" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="713" width="200" height="200"><path d="M804.672 27.968H219.328A182.528 182.528 0 0 0 36.672 210.624v585.408a182.528 182.528 0 0 0 182.656 182.592h585.408a182.528 182.528 0 0 0 182.656-182.656V210.624A182.144 182.144 0 0 0 804.672 27.968z m110.08 768a109.952 109.952 0 0 1-110.08 110.08H219.328a109.952 109.952 0 0 1-110.08-110.08V210.624a109.952 109.952 0 0 1 110.08-110.08h585.408a109.952 109.952 0 0 1 110.08 110.08z m-172.096-573.632l-511.04 511.04-4.096 5.248a37.248 37.248 0 0 0 4.096 46.848 36.736 36.736 0 0 0 51.2 0l511.04-511.04 4.096-5.248a37.248 37.248 0 0 0-4.096-46.848 35.712 35.712 0 0 0-51.2 0z m-4.672 468.288l56.192-56.192 4.096-4.672a30.336 30.336 0 0 0-46.848-38.08l-56.192 56.192-56.192-56.192-4.672-4.096a30.336 30.336 0 0 0-38.08 46.848l56.192 56.192-56.192 56.192-4.096 4.672a30.336 30.336 0 0 0 46.848 38.08l56.192-56.192 56.192 56.192 4.672 4.096a30.336 30.336 0 0 0 38.08-46.848z m-459.52-255.36a32.704 32.704 0 0 0 22.272 11.136h1.152a34.496 34.496 0 0 0 22.848-9.344l148.032-148.608 2.368-2.944a35.968 35.968 0 0 0 0-43.328 28.544 28.544 0 0 0-22.272-11.136 36.288 36.288 0 0 0-24 9.344L309.568 359.296l-38.4-62.656-2.368-2.944a30.4 30.4 0 0 0-42.432-4.032 35.392 35.392 0 0 0-8.768 46.272l58.56 96.576 2.368 2.944z" fill="#999999" p-id="714"></path></svg>`
this
.
iconSvg
=
`<svg t="1729144923785" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="713" width="200" height="200"><path d="M804.672 27.968H219.328A182.528 182.528 0 0 0 36.672 210.624v585.408a182.528 182.528 0 0 0 182.656 182.592h585.408a182.528 182.528 0 0 0 182.656-182.656V210.624A182.144 182.144 0 0 0 804.672 27.968z m110.08 768a109.952 109.952 0 0 1-110.08 110.08H219.328a109.952 109.952 0 0 1-110.08-110.08V210.624a109.952 109.952 0 0 1 110.08-110.08h585.408a109.952 109.952 0 0 1 110.08 110.08z m-172.096-573.632l-511.04 511.04-4.096 5.248a37.248 37.248 0 0 0 4.096 46.848 36.736 36.736 0 0 0 51.2 0l511.04-511.04 4.096-5.248a37.248 37.248 0 0 0-4.096-46.848 35.712 35.712 0 0 0-51.2 0z m-4.672 468.288l56.192-56.192 4.096-4.672a30.336 30.336 0 0 0-46.848-38.08l-56.192 56.192-56.192-56.192-4.672-4.096a30.336 30.336 0 0 0-38.08 46.848l56.192 56.192-56.192 56.192-4.096 4.672a30.336 30.336 0 0 0 46.848 38.08l56.192-56.192 56.192 56.192 4.672 4.096a30.336 30.336 0 0 0 38.08-46.848z m-459.52-255.36a32.704 32.704 0 0 0 22.272 11.136h1.152a34.496 34.496 0 0 0 22.848-9.344l148.032-148.608 2.368-2.944a35.968 35.968 0 0 0 0-43.328 28.544 28.544 0 0 0-22.272-11.136 36.288 36.288 0 0 0-24 9.344L309.568 359.296l-38.4-62.656-2.368-2.944a30.4 30.4 0 0 0-42.432-4.032 35.392 35.392 0 0 0-8.768 46.272l58.56 96.576 2.368 2.944z" fill="#999999" p-id="714"></path></svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
return
<
AIQuestionModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'3'
></
AIQuestionModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-question/multiple-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIQuestionModal
from
'./AiQuestionDialog'
class
AIQuestionMultiple
extends
BaseModal
Menu
{
class
AIQuestionMultiple
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-question.generate'
,
{
action
:
'2'
}
)
this
.
title
=
'多选题'
this
.
title
=
'多选题'
this
.
iconSvg
=
`<svg t="1729144537633" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4530" width="200" height="200"><path d="M358.4 256h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 1 1 0-102.4zM204.8 358.4a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m0 204.8a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m153.6-102.4h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z m0 204.8h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z" fill="#4C4C4C" p-id="4531"></path><path d="M204.8 51.2a153.6 153.6 0 0 0-153.6 153.6v614.4a153.6 153.6 0 0 0 153.6 153.6h614.4a153.6 153.6 0 0 0 153.6-153.6V204.8a153.6 153.6 0 0 0-153.6-153.6H204.8z m0-51.2h614.4a204.8 204.8 0 0 1 204.8 204.8v614.4a204.8 204.8 0 0 1-204.8 204.8H204.8a204.8 204.8 0 0 1-204.8-204.8V204.8a204.8 204.8 0 0 1 204.8-204.8z" fill="#4C4C4C" p-id="4532"></path></svg>`
this
.
iconSvg
=
`<svg t="1729144537633" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4530" width="200" height="200"><path d="M358.4 256h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 1 1 0-102.4zM204.8 358.4a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m0 204.8a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m153.6-102.4h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z m0 204.8h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z" fill="#4C4C4C" p-id="4531"></path><path d="M204.8 51.2a153.6 153.6 0 0 0-153.6 153.6v614.4a153.6 153.6 0 0 0 153.6 153.6h614.4a153.6 153.6 0 0 0 153.6-153.6V204.8a153.6 153.6 0 0 0-153.6-153.6H204.8z m0-51.2h614.4a204.8 204.8 0 0 1 204.8 204.8v614.4a204.8 204.8 0 0 1-204.8 204.8H204.8a204.8 204.8 0 0 1-204.8-204.8V204.8a204.8 204.8 0 0 1 204.8-204.8z" fill="#4C4C4C" p-id="4532"></path></svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
return
<
AIQuestionModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'2'
></
AIQuestionModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-question/single-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIQuestionModal
from
'./AiQuestionDialog'
class
AIQuestionSingle
extends
BaseModal
Menu
{
class
AIQuestionSingle
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-question.generate'
,
{
action
:
'1'
}
)
this
.
title
=
'单选题'
this
.
title
=
'单选题'
this
.
iconSvg
=
`<svg t="1729144945895" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="860" width="200" height="200"><path d="M358.4 256h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 1 1 0-102.4zM204.8 358.4a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m153.6 102.4h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z m0 204.8h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z" fill="#4C4C4C" p-id="861"></path><path d="M204.8 51.2a153.6 153.6 0 0 0-153.6 153.6v614.4a153.6 153.6 0 0 0 153.6 153.6h614.4a153.6 153.6 0 0 0 153.6-153.6V204.8a153.6 153.6 0 0 0-153.6-153.6H204.8z m0-51.2h614.4a204.8 204.8 0 0 1 204.8 204.8v614.4a204.8 204.8 0 0 1-204.8 204.8H204.8a204.8 204.8 0 0 1-204.8-204.8V204.8a204.8 204.8 0 0 1 204.8-204.8z" fill="#4C4C4C" p-id="862"></path></svg>`
this
.
iconSvg
=
`<svg t="1729144945895" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="860" width="200" height="200"><path d="M358.4 256h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 1 1 0-102.4zM204.8 358.4a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4z m153.6 102.4h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z m0 204.8h460.8a51.2 51.2 0 0 1 0 102.4H358.4a51.2 51.2 0 0 1 0-102.4z" fill="#4C4C4C" p-id="861"></path><path d="M204.8 51.2a153.6 153.6 0 0 0-153.6 153.6v614.4a153.6 153.6 0 0 0 153.6 153.6h614.4a153.6 153.6 0 0 0 153.6-153.6V204.8a153.6 153.6 0 0 0-153.6-153.6H204.8z m0-51.2h614.4a204.8 204.8 0 0 1 204.8 204.8v614.4a204.8 204.8 0 0 1-204.8 204.8H204.8a204.8 204.8 0 0 1-204.8-204.8V204.8a204.8 204.8 0 0 1 204.8-204.8z" fill="#4C4C4C" p-id="862"></path></svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
return
<
AIQuestionModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'1'
></
AIQuestionModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/AiWriteDialog.jsx
浏览文件 @
3da7f60b
...
@@ -17,8 +17,7 @@ import { usePaper } from '@/components/editor/hooks/useWenku'
...
@@ -17,8 +17,7 @@ import { usePaper } from '@/components/editor/hooks/useWenku'
import
{
useCopyToClipboard
}
from
'react-use'
import
{
useCopyToClipboard
}
from
'react-use'
import
{
RiMore2Line
}
from
'@remixicon/react'
import
{
RiMore2Line
}
from
'@remixicon/react'
export
default
function
AIModal
()
{
export
default
function
AIModal
({
open
,
onClose
})
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
textIndent
,
setTextIndent
]
=
useState
(
0
)
const
[
textIndent
,
setTextIndent
]
=
useState
(
0
)
const
prePromptRef
=
useRef
(
null
)
const
prePromptRef
=
useRef
(
null
)
...
@@ -40,7 +39,7 @@ export default function AIModal() {
...
@@ -40,7 +39,7 @@ export default function AIModal() {
const
width
=
prePromptRef
.
current
.
offsetWidth
+
10
const
width
=
prePromptRef
.
current
.
offsetWidth
+
10
setTextIndent
(
width
)
setTextIndent
(
width
)
}
}
},
[
isModalO
pen
])
},
[
o
pen
])
const
[
isEditMessage
,
setIsEditMessage
]
=
useState
(
false
)
const
[
isEditMessage
,
setIsEditMessage
]
=
useState
(
false
)
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
messageScrollRef
.
current
&&
!
isEditMessage
)
{
if
(
messageScrollRef
.
current
&&
!
isEditMessage
)
{
...
@@ -237,13 +236,7 @@ export default function AIModal() {
...
@@ -237,13 +236,7 @@ export default function AIModal() {
<
ConfigProvider
<
ConfigProvider
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
>
>
<
Modal
<
Modal
title=
'长文写作/课程大纲:'
open=
{
open
}
footer=
{
null
}
onCancel=
{
onClose
}
width=
{
1000
}
>
title=
'长文写作/课程大纲:'
open=
{
isModalOpen
}
footer=
{
null
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
width=
{
1000
}
>
<
div
className=
'message-scroll'
ref=
{
messageScrollRef
}
>
<
div
className=
'message-scroll'
ref=
{
messageScrollRef
}
>
{
messages
.
map
((
msg
)
=>
{
{
messages
.
map
((
msg
)
=>
{
return
<
MessageRender
msg=
{
msg
}
key=
{
msg
.
id
}
></
MessageRender
>
return
<
MessageRender
msg=
{
msg
}
key=
{
msg
.
id
}
></
MessageRender
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/BaiduSearchDialog.jsx
deleted
100644 → 0
浏览文件 @
a9fbd6f2
import
{
useState
,
useEffect
,
useRef
}
from
'react'
import
{
SendOutlined
,
UndoOutlined
,
CopyOutlined
,
LinkOutlined
}
from
'@ant-design/icons'
import
{
ConfigProvider
,
Modal
,
Input
,
Button
}
from
'antd'
const
{
TextArea
}
=
Input
import
'./search.less'
import
{
useSearch
}
from
'@/components/editor/hooks/useWenku'
import
{
useCopyToClipboard
}
from
'react-use'
export
default
function
AIModal
()
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
textIndent
,
setTextIndent
]
=
useState
(
0
)
const
prePromptRef
=
useRef
(
null
)
const
messageScrollRef
=
useRef
(
null
)
const
{
messages
,
isLoading
,
query
}
=
useSearch
()
useEffect
(()
=>
{
if
(
prePromptRef
.
current
)
{
const
width
=
prePromptRef
.
current
.
offsetWidth
+
10
setTextIndent
(
width
)
}
},
[
isModalOpen
])
useEffect
(()
=>
{
if
(
messageScrollRef
.
current
)
{
const
scrollContainer
=
messageScrollRef
.
current
scrollContainer
.
scrollTop
=
scrollContainer
.
scrollHeight
}
},
[
messages
])
const
prePrompt
=
'帮我找一些文献资料,主题是:'
const
handleEnterSearch
=
(
e
)
=>
{
if
(
e
.
key
===
'Enter'
&&
!
e
.
shiftKey
)
{
e
.
preventDefault
()
!
isLoading
&&
handleSearch
()
}
}
const
handleSearch
=
()
=>
{
query
(
prePrompt
+
content
)
setContent
(
''
)
}
const
[,
copyToClipboard
]
=
useCopyToClipboard
()
const
handleCopy
=
(
content
)
=>
{
copyToClipboard
(
content
)
}
const
MessageRender
=
({
msg
})
=>
{
if
(
msg
.
role
===
'ai'
)
{
const
copyMessageContent
=
msg
.
searchReferList
?.
map
((
refer
,
index
)
=>
`
${
index
+
1
}
.《
${
refer
.
title
}
》\n
${
refer
.
abstract
}
\n`
)
.
join
(
'
\
n'
)
return
(
msg
.
searchReferList
?.
length
>
0
&&
(
<
div
className=
{
`message-item ${msg.role}`
}
>
<
div
className=
'message-box'
>
<
div
className=
'message-content'
>
{
msg
.
searchReferList
.
map
((
refer
,
index
)
=>
{
return
(
<
div
key=
{
index
}
className=
'result-item'
>
<
p
>
<
strong
>
{
index
+
1
}
.《
{
refer
.
title
}
》
</
strong
>
</
p
>
<
p
>
{
refer
.
abstract
}
<
a
href=
{
refer
.
orgUrl
}
target=
'_blank'
rel=
'noreferrer'
>
<
LinkOutlined
/>
</
a
>
</
p
>
</
div
>
)
})
}
</
div
>
<
div
className=
'message-tools'
>
<
Button
type=
'text'
size=
'small'
icon=
{
<
UndoOutlined
/>
}
disabled=
{
isLoading
}
onClick=
{
()
=>
query
(
msg
.
userQuery
)
}
>
重新生成
</
Button
>
<
Button
type=
'text'
size=
'small'
icon=
{
<
CopyOutlined
/>
}
onClick=
{
()
=>
handleCopy
(
copyMessageContent
)
}
>
复制内容
</
Button
>
</
div
>
</
div
>
</
div
>
)
)
}
else
{
return
(
<
div
className=
{
`message-item ${msg.role}`
}
>
<
div
className=
'message-box'
>
<
div
className=
'message-content'
>
{
msg
.
content
}
</
div
>
</
div
>
</
div
>
)
}
}
return
(
<
ConfigProvider
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
>
<
Modal
title=
'学术/文献搜索:'
open=
{
isModalOpen
}
footer=
{
null
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
width=
{
1000
}
>
<
div
className=
'message-scroll'
ref=
{
messageScrollRef
}
>
{
messages
.
map
((
msg
,
index
)
=>
{
return
<
MessageRender
msg=
{
msg
}
key=
{
index
}
></
MessageRender
>
})
}
</
div
>
<
div
className=
'input-container'
>
<
div
className=
'input-box'
>
<
div
className=
'edit-area'
>
{
prePrompt
&&
(
<
span
className=
'pre-prompt'
ref=
{
prePromptRef
}
>
{
prePrompt
}
</
span
>
)
}
<
TextArea
className=
'content'
autoSize
value=
{
content
}
placeholder=
'请输入关键词'
onChange=
{
(
e
)
=>
setContent
(
e
.
target
.
value
)
}
onKeyDown=
{
handleEnterSearch
}
style=
{
{
textIndent
}
}
/>
</
div
>
<
div
className=
'input-tools'
>
<
Button
type=
'primary'
size=
'large'
icon=
{
<
SendOutlined
/>
}
onClick=
{
handleSearch
}
loading=
{
isLoading
}
/>
</
div
>
</
div
>
</
div
>
</
Modal
>
</
ConfigProvider
>
)
}
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/SearchDialog.jsx
浏览文件 @
3da7f60b
...
@@ -7,8 +7,7 @@ import { useSearch } from '@/components/editor/hooks/useWenku'
...
@@ -7,8 +7,7 @@ import { useSearch } from '@/components/editor/hooks/useWenku'
import
{
useCopyToClipboard
}
from
'react-use'
import
{
useCopyToClipboard
}
from
'react-use'
export
default
function
AIModal
()
{
export
default
function
AIModal
({
open
,
onClose
})
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
textIndent
,
setTextIndent
]
=
useState
(
0
)
const
[
textIndent
,
setTextIndent
]
=
useState
(
0
)
const
prePromptRef
=
useRef
(
null
)
const
prePromptRef
=
useRef
(
null
)
...
@@ -20,7 +19,7 @@ export default function AIModal() {
...
@@ -20,7 +19,7 @@ export default function AIModal() {
const
width
=
prePromptRef
.
current
.
offsetWidth
+
10
const
width
=
prePromptRef
.
current
.
offsetWidth
+
10
setTextIndent
(
width
)
setTextIndent
(
width
)
}
}
},
[
isModalO
pen
])
},
[
o
pen
])
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
messageScrollRef
.
current
)
{
if
(
messageScrollRef
.
current
)
{
...
@@ -113,13 +112,7 @@ export default function AIModal() {
...
@@ -113,13 +112,7 @@ export default function AIModal() {
<
ConfigProvider
<
ConfigProvider
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
theme=
{
{
components
:
{
Modal
:
{
headerBg
:
'#f7f8fa'
,
contentBg
:
'#f7f8fa'
}
}
}
}
>
>
<
Modal
<
Modal
title=
'学术/文献搜索:'
open=
{
open
}
footer=
{
null
}
onCancel=
{
onClose
}
width=
{
1000
}
>
title=
'学术/文献搜索:'
open=
{
isModalOpen
}
footer=
{
null
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
width=
{
1000
}
>
<
div
className=
'message-scroll'
ref=
{
messageScrollRef
}
>
<
div
className=
'message-scroll'
ref=
{
messageScrollRef
}
>
{
messages
.
map
((
msg
,
index
)
=>
{
{
messages
.
map
((
msg
,
index
)
=>
{
return
<
MessageRender
msg=
{
msg
}
key=
{
index
}
></
MessageRender
>
return
<
MessageRender
msg=
{
msg
}
key=
{
index
}
></
MessageRender
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/baidu-search-menu.jsx
deleted
100644 → 0
浏览文件 @
a9fbd6f2
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
AISearchModal
from
'./BaiduSearchDialog'
class
AIBaiduSearch
extends
BaseModalMenu
{
constructor
()
{
super
()
this
.
title
=
'学术搜索'
this
.
iconSvg
=
`<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-file-search"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M12 21h-5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v4.5" /><path d="M16.5 17.5m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0" /><path d="M18.5 19.5l2.5 2.5" /></svg>`
}
getValue
(
editor
)
{
return
<
AISearchModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AISearchModal
>
}
}
export
default
{
key
:
'AIBaiduSearch'
,
factory
()
{
return
new
AIBaiduSearch
()
},
}
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
createElement
}
from
'react'
import
{
createElement
}
from
'react'
import
AIBaiduSearch
from
'./baidu-search-menu'
import
AISearch
from
'./search-menu'
import
AISearch
from
'./search-menu'
import
AIWrite
from
'./write-menu'
import
AIWrite
from
'./write-menu'
import
SearchDialog
from
'./SearchDialog'
import
SearchDialog
from
'./SearchDialog'
import
BaiduSearchDialog
from
'./BaiduSearchDialog'
import
AiWriteDialog
from
'./AiWriteDialog'
import
AiWriteDialog
from
'./AiWriteDialog'
const
searchDialog
=
(
props
)
=>
{
const
dialog
=
(
Component
)
=>
(
props
)
=>
c
onst
Component
=
props
.
payload
.
provider
===
'baidu'
?
BaiduSearchDialog
:
SearchDialog
c
reateElement
(
Component
,
{
return
createElement
(
Component
,
{
editor
:
props
.
runtime
.
editor
})
open
:
props
.
open
,
}
onClose
:
props
.
onClose
,
const
writeDialog
=
(
props
)
=>
createElement
(
AiWriteDialog
,
{
editor
:
props
.
runtime
.
editor
})
})
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
id
:
'ai-research'
,
id
:
'ai-research'
,
menus
:
[
menus
:
[
{
order
:
300
,
config
:
AISearch
},
{
order
:
300
,
config
:
AISearch
},
{
order
:
310
,
config
:
AIWrite
},
{
order
:
310
,
config
:
AIWrite
},
{
order
:
340
,
config
:
AIBaiduSearch
},
],
],
dialogs
:
[
dialogs
:
[
{
id
:
'ai-research.search'
,
component
:
searchDialog
},
{
id
:
'ai-research.search'
,
component
:
dialog
(
SearchDialog
)
},
{
id
:
'ai-research.write'
,
component
:
writeDialog
},
{
id
:
'ai-research.write'
,
component
:
dialog
(
AiWriteDialog
)
},
],
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/search-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AISearchModal
from
'./SearchDialog'
class
AISearch
extends
BaseModal
Menu
{
class
AISearch
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-research.search'
,
{
provider
:
'search'
}
)
this
.
title
=
'学术搜索'
this
.
title
=
'学术搜索'
this
.
iconSvg
=
`<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-file-search"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M12 21h-5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v4.5" /><path d="M16.5 17.5m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0" /><path d="M18.5 19.5l2.5 2.5" /></svg>`
this
.
iconSvg
=
`<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-file-search"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M12 21h-5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v4.5" /><path d="M16.5 17.5m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0" /><path d="M18.5 19.5l2.5 2.5" /></svg>`
}
}
getValue
(
editor
)
{
return
<
AISearchModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AISearchModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-research/write-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIWriteModal
from
'./AiWriteDialog'
class
AIWrite
extends
BaseModal
Menu
{
class
AIWrite
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-research.write'
)
this
.
title
=
'写作/大纲'
this
.
title
=
'写作/大纲'
this
.
iconSvg
=
`<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pen"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg>`
this
.
iconSvg
=
`<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pen"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg>`
}
}
getValue
(
editor
)
{
return
<
AIWriteModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AIWriteModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/AiChatDialog.jsx
浏览文件 @
3da7f60b
...
@@ -8,9 +8,8 @@ import { useAI } from '@/components/editor/hooks/useAI'
...
@@ -8,9 +8,8 @@ import { useAI } from '@/components/editor/hooks/useAI'
import
'./styles.less'
import
'./styles.less'
const
AIChatDrawer
=
(
props
)
=>
{
const
AIChatDrawer
=
({
editor
,
open
,
onClose
})
=>
{
const
selectText
=
props
.
editor
.
getSelectionText
()
const
selectText
=
editor
.
getSelectionText
()
const
[
open
,
setOpen
]
=
useState
(
true
)
const
{
ai
,
setAI
,
options
,
messages
,
post
,
isLoading
}
=
useAI
()
const
{
ai
,
setAI
,
options
,
messages
,
post
,
isLoading
}
=
useAI
()
...
@@ -48,7 +47,7 @@ const AIChatDrawer = (props) => {
...
@@ -48,7 +47,7 @@ const AIChatDrawer = (props) => {
size=
{
600
}
size=
{
600
}
title=
'AI对话'
title=
'AI对话'
destroyOnHidden
destroyOnHidden
onClose=
{
()
=>
setOpen
(
false
)
}
onClose=
{
onClose
}
rootClassName=
'ai-drawer-wrapper'
rootClassName=
'ai-drawer-wrapper'
className=
'ai-drawer-container'
className=
'ai-drawer-container'
>
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/chat-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIChatDrawer
from
'./AiChatDialog'
class
AIChat
extends
BaseModal
Menu
{
class
AIChat
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.chat'
)
this
.
title
=
'AI'
this
.
title
=
'AI'
this
.
iconSvg
=
`<svg t="1713246032755" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12352" width="16" height="16"><path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#cdcdcd" p-id="12353"></path></svg>`
this
.
iconSvg
=
`<svg t="1713246032755" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12352" width="16" height="16"><path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#cdcdcd" p-id="12353"></path></svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
return
<
AIChatDrawer
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
AIChatDrawer
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/content-inspect-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AIContentInspect
extends
BaseModal
Menu
{
class
AIContentInspect
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'contentInspect'
}
)
this
.
title
=
'内容检查'
this
.
title
=
'内容检查'
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.5 13.5q.525 0 .988-.137t.912-.413l1.525 1.575q.2.225.513.213t.537-.213q.225-.225.238-.537T16 13.45l-1.55-1.55q.275-.425.413-.9T15 10q0-1.475-1.038-2.488T11.5 6.5T9.037 7.513T8 10t1.038 2.488T11.5 13.5m0-1.5q-.825 0-1.412-.587T9.5 10t.588-1.412T11.5 8q.8 0 1.4.588T13.5 10t-.587 1.413T11.5 12M2 21q-.425 0-.712-.288T1 20t.288-.712T2 19h20q.425 0 .713.288T23 20t-.288.713T22 21zm2-3q-.825 0-1.412-.587T2 16V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v11q0 .825-.587 1.413T20 18zm0-2h16V5H4zm0 0V5z"/></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.5 13.5q.525 0 .988-.137t.912-.413l1.525 1.575q.2.225.513.213t.537-.213q.225-.225.238-.537T16 13.45l-1.55-1.55q.275-.425.413-.9T15 10q0-1.475-1.038-2.488T11.5 6.5T9.037 7.513T8 10t1.038 2.488T11.5 13.5m0-1.5q-.825 0-1.412-.587T9.5 10t.588-1.412T11.5 8q.8 0 1.4.588T13.5 10t-.587 1.413T11.5 12M2 21q-.425 0-.712-.288T1 20t.288-.712T2 19h20q.425 0 .713.288T23 20t-.288.713T22 21zm2-3q-.825 0-1.412-.587T2 16V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v11q0 .825-.587 1.413T20 18zm0-2h16V5H4zm0 0V5z"/></svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'contentInspect'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/expand-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AIExpand
extends
DialogMenu
{
class
AIExpand
extends
BaseModalMenu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'expand'
}
)
this
.
title
=
'扩写'
this
.
title
=
'扩写'
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<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">
...
@@ -14,9 +13,6 @@ class AIExpand extends BaseModalMenu {
...
@@ -14,9 +13,6 @@ class AIExpand extends BaseModalMenu {
</g>
</g>
</svg>`
</svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'expand'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/polishing-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AIPolishing
extends
BaseModal
Menu
{
class
AIPolishing
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'abbreviate'
}
)
this
.
title
=
'润色'
this
.
title
=
'润色'
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
...
@@ -19,9 +18,6 @@ class AIPolishing extends BaseModalMenu {
...
@@ -19,9 +18,6 @@ class AIPolishing extends BaseModalMenu {
</g>
</g>
</svg>`
</svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'abbreviate'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/punctuation-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AIPunctuation
extends
BaseModal
Menu
{
class
AIPunctuation
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'punctuation'
}
)
this
.
title
=
'标点校对'
this
.
title
=
'标点校对'
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M920 416H616c-4.4 0-8 3.6-8 8v112c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-56h60v320h-46c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h164c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-46V480h60v56c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V424c0-4.4-3.6-8-8-8M656 296V168c0-4.4-3.6-8-8-8H104c-4.4 0-8 3.6-8 8v128c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-64h168v560h-92c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-92V232h168v64c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8"/></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M920 416H616c-4.4 0-8 3.6-8 8v112c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-56h60v320h-46c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h164c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-46V480h60v56c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V424c0-4.4-3.6-8-8-8M656 296V168c0-4.4-3.6-8-8-8H104c-4.4 0-8 3.6-8 8v128c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-64h168v560h-92c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-92V232h168v64c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8"/></svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'punctuation'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/rewrite-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AIRewrite
extends
BaseModal
Menu
{
class
AIRewrite
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'rewrite'
}
)
this
.
title
=
'改写'
this
.
title
=
'改写'
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
...
@@ -18,9 +17,6 @@ class AIRewrite extends BaseModalMenu {
...
@@ -18,9 +17,6 @@ class AIRewrite extends BaseModalMenu {
</svg>`
</svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'rewrite'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/summary-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AISummary
extends
BaseModal
Menu
{
class
AISummary
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'summary'
}
)
this
.
title
=
'总结'
this
.
title
=
'总结'
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
this
.
iconSvg
=
`<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
...
@@ -18,9 +17,6 @@ class AISummary extends BaseModalMenu {
...
@@ -18,9 +17,6 @@ class AISummary extends BaseModalMenu {
</g>
</g>
</svg>`
</svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'summary'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/ai-text/translate-menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/LegacyDialogMenu'
import
DialogMenu
from
'../../core/DialogMenu'
import
AIModal
from
'./AiTextDialog'
class
AITranslate
extends
BaseModal
Menu
{
class
AITranslate
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'ai-text.action'
,
{
action
:
'translate'
}
)
this
.
title
=
'翻译'
this
.
title
=
'翻译'
this
.
iconSvg
=
`<svg fill="#000000" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><defs><style>.cls-1{fill:none;}</style></defs><title>translate</title><path d="M27.85,29H30L24,14H21.65l-6,15H17.8l1.6-4h6.85ZM20.2,23l2.62-6.56L25.45,23Z"></path><path d="M18,7V5H11V2H9V5H2V7H12.74a14.71,14.71,0,0,1-3.19,6.18A13.5,13.5,0,0,1,7.26,9H5.16a16.47,16.47,0,0,0,3,5.58A16.84,16.84,0,0,1,3,18l.75,1.86A18.47,18.47,0,0,0,9.53,16a16.92,16.92,0,0,0,5.76,3.84L16,18a14.48,14.48,0,0,1-5.12-3.37A17.64,17.64,0,0,0,14.8,7Z"></path><rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"></rect></g></svg>`
this
.
iconSvg
=
`<svg fill="#000000" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><defs><style>.cls-1{fill:none;}</style></defs><title>translate</title><path d="M27.85,29H30L24,14H21.65l-6,15H17.8l1.6-4h6.85ZM20.2,23l2.62-6.56L25.45,23Z"></path><path d="M18,7V5H11V2H9V5H2V7H12.74a14.71,14.71,0,0,1-3.19,6.18A13.5,13.5,0,0,1,7.26,9H5.16a16.47,16.47,0,0,0,3,5.58A16.84,16.84,0,0,1,3,18l.75,1.86A18.47,18.47,0,0,0,9.53,16a16.92,16.92,0,0,0,5.76,3.84L16,18a14.48,14.48,0,0,1-5.12-3.37A17.64,17.64,0,0,0,14.8,7Z"></path><rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"></rect></g></svg>`
}
}
getValue
(
editor
)
{
return
<
AIModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
action=
'translate'
></
AIModal
>
}
}
}
export
default
{
export
default
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/audio/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
AudioDialog
from
'./AudioDialog'
import
AudioDialog
from
'./AudioDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -12,11 +11,4 @@ export default defineEditorFeature({
...
@@ -12,11 +11,4 @@ export default defineEditorFeature({
{
id
:
'audio.insert'
,
component
:
AudioDialog
},
{
id
:
'audio.insert'
,
component
:
AudioDialog
},
{
id
:
'audio.edit'
,
component
:
AudioDialog
},
{
id
:
'audio.edit'
,
component
:
AudioDialog
},
],
],
events
:
[
{
name
:
'AudioMenuClick'
,
handler
:
({
editor
,
payload
})
=>
openEditorDialog
(
editor
,
payload
?.
path
?
'audio.edit'
:
'audio.insert'
,
payload
),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/audio/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
AudioAuto
{
class
AudioAuto
{
constructor
()
{
constructor
()
{
...
@@ -61,14 +62,14 @@ class AudioAuto {
...
@@ -61,14 +62,14 @@ class AudioAuto {
const
chapterAudio
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterAudio'
)
const
chapterAudio
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterAudio'
)
if
(
chapterAudio
)
{
if
(
chapterAudio
)
{
const
path
=
DomEditor
.
findPath
(
editor
,
chapterAudio
)
const
path
=
DomEditor
.
findPath
(
editor
,
chapterAudio
)
editor
.
emit
(
'AudioMenuClick
'
,
{
openEditorDialog
(
editor
,
'audio.edit
'
,
{
path
,
path
,
audioUrl
:
chapterAudio
.
audioUrl
||
''
,
audioUrl
:
chapterAudio
.
audioUrl
||
''
,
title
:
chapterAudio
.
title
||
''
,
title
:
chapterAudio
.
title
||
''
,
})
})
return
return
}
}
editor
.
emit
(
'AudioMenuClick
'
)
openEditorDialog
(
editor
,
'audio.insert
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/audio/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
}
from
'@wangeditor/editor'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withAudioNode
=
(
editor
)
=>
{
const
withAudioNode
=
(
editor
)
=>
{
const
{
isVoid
,
normalizeNode
}
=
editor
const
{
isVoid
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
newEditor
.
isVoid
=
(
elem
)
=>
{
newEditor
.
isVoid
=
(
elem
)
=>
{
...
@@ -12,35 +13,7 @@ const withAudioNode = (editor) => {
...
@@ -12,35 +13,7 @@ const withAudioNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterAudio'
,
[
'paragraph'
,
'blockquote'
])
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterAudio'
)
{
// 未命中 chapterAudio ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
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'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/chapter-structure/feature.js
浏览文件 @
3da7f60b
...
@@ -3,7 +3,6 @@ import itemMenu from './section-menu'
...
@@ -3,7 +3,6 @@ import itemMenu from './section-menu'
import
titleMenu
from
'./title-menu'
import
titleMenu
from
'./title-menu'
import
sectionNodeModule
from
'./section-node'
import
sectionNodeModule
from
'./section-node'
import
headerNodeModule
from
'./title-node'
import
headerNodeModule
from
'./title-node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
ChapterTitleFeatureDialog
,
ChapterSectionFeatureDialog
}
from
'./FeatureDialogs'
import
{
ChapterTitleFeatureDialog
,
ChapterSectionFeatureDialog
}
from
'./FeatureDialogs'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -20,14 +19,4 @@ export default defineEditorFeature({
...
@@ -20,14 +19,4 @@ export default defineEditorFeature({
{
id
:
'chapter-structure.title'
,
component
:
ChapterTitleFeatureDialog
},
{
id
:
'chapter-structure.title'
,
component
:
ChapterTitleFeatureDialog
},
{
id
:
'chapter-structure.section'
,
component
:
ChapterSectionFeatureDialog
},
{
id
:
'chapter-structure.section'
,
component
:
ChapterSectionFeatureDialog
},
],
],
events
:
[
{
name
:
'ChapterItemMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'chapter-structure.section'
),
},
{
name
:
'ChapterTitleClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'chapter-structure.title'
),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/chapter-structure/section-menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
ChapterItem
{
class
ChapterItem
{
constructor
()
{
constructor
()
{
...
@@ -55,7 +56,7 @@ class ChapterItem {
...
@@ -55,7 +56,7 @@ class ChapterItem {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'ChapterItemMenuClick
'
)
openEditorDialog
(
editor
,
'chapter-structure.section
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/chapter-structure/section-node.js
浏览文件 @
3da7f60b
...
@@ -3,9 +3,10 @@ import iconClose from '@/assets/images/icon_chapter_close.png'
...
@@ -3,9 +3,10 @@ import iconClose from '@/assets/images/icon_chapter_close.png'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withChapterSectionNode
=
(
editor
)
=>
{
const
withChapterSectionNode
=
(
editor
)
=>
{
const
{
is
Inline
,
isVoid
,
normalizeNode
}
=
editor
const
{
is
Void
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
// newEditor.isInline = (elem) => {
// newEditor.isInline = (elem) => {
...
@@ -20,41 +21,7 @@ const withChapterSectionNode = (editor) => {
...
@@ -20,41 +21,7 @@ const withChapterSectionNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterSection'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterSection'
)
{
// 未命中 chapterSection ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/chapter-structure/title-menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
ChapterTitle
{
class
ChapterTitle
{
constructor
()
{
constructor
()
{
...
@@ -55,7 +56,7 @@ class ChapterTitle {
...
@@ -55,7 +56,7 @@ class ChapterTitle {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'ChapterTitleClick
'
)
openEditorDialog
(
editor
,
'chapter-structure.title
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/chapter-structure/title-node.js
浏览文件 @
3da7f60b
...
@@ -3,9 +3,10 @@ import iconClose from '@/assets/images/icon_chapter_close.png'
...
@@ -3,9 +3,10 @@ import iconClose from '@/assets/images/icon_chapter_close.png'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withChapterHeaderNode
=
(
editor
)
=>
{
const
withChapterHeaderNode
=
(
editor
)
=>
{
const
{
is
Inline
,
isVoid
,
normalizeNode
}
=
editor
const
{
is
Void
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
// newEditor.isInline = (elem) => {
// newEditor.isInline = (elem) => {
...
@@ -20,40 +21,7 @@ const withChapterHeaderNode = (editor) => {
...
@@ -20,40 +21,7 @@ const withChapterHeaderNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterHeader'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterHeader'
)
{
// 未命中 chapterHeader ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote (否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/content-tools/remove-spaces-menu.js
浏览文件 @
3da7f60b
const
CHINESE_TEXT
=
/
[\u
3400-
\u
4dbf
\u
4e00-
\u
9fff
\u
f900-
\u
faff
]
/
const
PARAGRAPH
=
/<p
\b[^
>
]
*>
[\s\S]
*
?
<
\/
p>/gi
const
TEXT_BETWEEN_TAGS
=
/>
([^
<
]
*
)
</g
const
SPACES
=
/
(?:
| |�*a0;|
[
\u
00a0
])
+/gi
export
const
removeSpacesFromChineseParagraphs
=
(
html
)
=>
html
.
replace
(
PARAGRAPH
,
(
paragraph
)
=>
{
const
text
=
paragraph
.
replace
(
/<
[^
>
]
+>/g
,
''
)
if
(
!
CHINESE_TEXT
.
test
(
text
))
return
paragraph
return
paragraph
.
replace
(
TEXT_BETWEEN_TAGS
,
(
_
,
content
)
=>
{
return
`>
${
content
.
replace
(
SPACES
,
''
)}
<`
})
})
class
RemoveSpaces
{
class
RemoveSpaces
{
constructor
()
{
constructor
()
{
this
.
title
=
'去空格'
this
.
title
=
'去空格'
...
@@ -5,49 +20,29 @@ class RemoveSpaces {
...
@@ -5,49 +20,29 @@ class RemoveSpaces {
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
// eslint-disable-next-line no-unused-vars
getValue
()
{
getValue
(
editor
)
{
return
''
return
''
}
}
// eslint-disable-next-line no-unused-vars
isActive
()
{
isActive
(
editor
)
{
return
false
return
false
}
}
// eslint-disable-next-line no-unused-vars
isDisabled
()
{
isDisabled
(
editor
)
{
return
false
return
false
}
}
exec
(
editor
)
{
exec
(
editor
)
{
// 获取当前 HTML
const
html
=
editor
.
getHtml
()
const
html
=
editor
.
getHtml
()
if
(
!
html
)
return
if
(
!
html
)
{
const
nextHtml
=
removeSpacesFromChineseParagraphs
(
html
)
return
if
(
nextHtml
!==
html
)
editor
.
setHtml
(
nextHtml
)
}
// 使用正则表达式去除 HTML 标签之间文本的空格
// 匹配 >文本< 之间的内容,去除空格字符(包括多个相邻的空格)和 ,保留其他字符和标签结构
const
htmlWithoutSpaces
=
html
.
replace
(
/>
([^
<
]
*
?)
</g
,
(
match
,
text
)
=>
{
// 先去除所有的 HTML 实体
let
textWithoutSpaces
=
text
.
replace
(
/ +/gi
,
''
)
// 然后去除所有空格字符(包括多个相邻的空格),保留其他字符
textWithoutSpaces
=
textWithoutSpaces
.
replace
(
/ +/g
,
''
)
return
`>
${
textWithoutSpaces
}
<`
})
// 如果内容有变化,设置回去
if
(
htmlWithoutSpaces
!==
html
)
{
// 设置处理后的 HTML
editor
.
setHtml
(
htmlWithoutSpaces
)
}
}
}
}
}
export
default
{
export
default
{
key
:
'RemoveSpaces'
,
// 定义 menu key :要保证唯一、不重复(重要)
key
:
'RemoveSpaces'
,
factory
()
{
factory
()
{
return
new
RemoveSpaces
()
return
new
RemoveSpaces
()
},
},
...
...
apps/admin/src/components/editor/wangeditor-customer/features/expand-read/feature.js
浏览文件 @
3da7f60b
...
@@ -2,8 +2,6 @@ import { defineEditorFeature } from '../../core/define-editor-feature'
...
@@ -2,8 +2,6 @@ import { defineEditorFeature } from '../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
simpleNodeModule
from
'./simple-node'
import
simpleNodeModule
from
'./simple-node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
import
ExpandReadFeatureDialog
from
'./FeatureDialog'
import
ExpandReadFeatureDialog
from
'./FeatureDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -17,13 +15,4 @@ export default defineEditorFeature({
...
@@ -17,13 +15,4 @@ export default defineEditorFeature({
{
id
:
'expand-read.insert'
,
component
:
ExpandReadFeatureDialog
},
{
id
:
'expand-read.insert'
,
component
:
ExpandReadFeatureDialog
},
{
id
:
'expand-read.edit'
,
component
:
ExpandReadFeatureDialog
},
{
id
:
'expand-read.edit'
,
component
:
ExpandReadFeatureDialog
},
],
],
events
:
[
{
name
:
'ExpandReadClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'expand-read.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
),
}),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/expand-read/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
class
ExpandRead
{
class
ExpandRead
{
constructor
()
{
constructor
()
{
...
@@ -54,7 +56,9 @@ class ExpandRead {
...
@@ -54,7 +56,9 @@ class ExpandRead {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'ExpandReadClick'
)
openEditorDialog
(
editor
,
'expand-read.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
),
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/expand-read/node.js
浏览文件 @
3da7f60b
import
{
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
DomEditor
,
SlateTransforms
,
SlateEditor
,
SlateElement
,
SlateNode
,
}
from
'@wangeditor/editor'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
iconExpand
from
'@/assets/images/editor/icon_expand_read.png'
import
iconExpand
from
'@/assets/images/editor/icon_expand_read.png'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withExpandReadNode
=
(
editor
)
=>
{
const
withExpandReadNode
=
(
editor
)
=>
{
const
{
is
Inline
,
isVoid
,
normalizeNode
}
=
editor
const
{
is
Void
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
// newEditor.isInline = (elem) => {
// newEditor.isInline = (elem) => {
...
@@ -27,41 +22,7 @@ const withExpandReadNode = (editor) => {
...
@@ -27,41 +22,7 @@ const withExpandReadNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterExpandRead'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterExpandRead'
)
{
// 未命中 chapterExpandRead ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/expand-read/simple-node.js
浏览文件 @
3da7f60b
import
{
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
DomEditor
,
SlateTransforms
,
SlateEditor
,
SlateElement
,
SlateNode
,
}
from
'@wangeditor/editor'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
iconExpand
from
'@/assets/images/editor/icon_expand_tips.png'
import
{
normalizeInlineVoidNode
}
from
'../../shared/normalize-inline-void'
import
iconExpandT
from
'@/assets/images/editor/icon_expand_editor.png'
const
withExpandReadNode
=
(
editor
)
=>
{
const
withExpandReadNode
=
(
editor
)
=>
{
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
...
@@ -28,43 +21,14 @@ const withExpandReadNode = (editor) => {
...
@@ -28,43 +21,14 @@ const withExpandReadNode = (editor) => {
// 重新 normalize
// 重新 normalize
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
return
normalizeInlineVoidNode
({
if
(
type
!==
'chapterExpandReadSimple'
)
{
editor
:
newEditor
,
// 未命中 chapterExpandReadSimple ,执行默认的 normalizeNode
node
,
return
normalizeNode
([
node
,
path
])
path
,
}
type
:
'chapterExpandReadSimple'
,
normalizeNode
,
// editor 顶级 node
transforms
:
SlateTransforms
,
const
topLevelNodes
=
newEditor
.
children
||
[]
})
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]]
||
{}
if
(
nextNode
.
type
===
'paragraph'
)
{
const
lastChild
=
nextNode
.
children
[
nextNode
.
children
.
length
-
1
]
if
(
lastChild
.
type
===
'chapterExpandReadSimple'
)
{
SlateTransforms
.
insertNodes
(
newEditor
,
{
type
:
'span'
,
children
:
[{
text
:
' '
}]
},
{
at
:
path
,
// 在 link-card 后面插入
},
)
}
}
// const nextNodeType = DomEditor.getNodeType(nextNode);
// console.log(nextNode, nextNodeType);
// 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];
// SlateTransforms.insertNodes(newEditor, p, {
// at: insertPath, // 在 link-card 后面插入
// });
// }
}
}
return
newEditor
// 返回 newEditor ,重要!!!
return
newEditor
// 返回 newEditor ,重要!!!
...
...
apps/admin/src/components/editor/wangeditor-customer/features/formula/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
FormulaDialog
from
'./FormulaDialog'
import
FormulaDialog
from
'./FormulaDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -9,10 +8,4 @@ export default defineEditorFeature({
...
@@ -9,10 +8,4 @@ export default defineEditorFeature({
menus
:
[{
order
:
60
,
config
:
menu
}],
menus
:
[{
order
:
60
,
config
:
menu
}],
nodeModules
:
[{
order
:
80
,
types
:
[
'chapterFormula'
],
module
:
nodeModule
}],
nodeModules
:
[{
order
:
80
,
types
:
[
'chapterFormula'
],
module
:
nodeModule
}],
dialogs
:
[{
id
:
'formula.insert'
,
component
:
FormulaDialog
}],
dialogs
:
[{
id
:
'formula.insert'
,
component
:
FormulaDialog
}],
events
:
[
{
name
:
'FormulaMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'formula.insert'
),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/formula/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
FormulaAuto
{
class
FormulaAuto
{
constructor
()
{
constructor
()
{
...
@@ -58,7 +59,7 @@ class FormulaAuto {
...
@@ -58,7 +59,7 @@ class FormulaAuto {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'FormulaMenuClick
'
)
openEditorDialog
(
editor
,
'formula.insert
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/formula/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
icon_tooltip_editor
from
'@/assets/images/editor/icon_tooltip_editor.png'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
normalizeInlineVoidNode
}
from
'../../shared/normalize-inline-void'
const
withChapterFormulaNode
=
(
editor
)
=>
{
const
withChapterFormulaNode
=
(
editor
)
=>
{
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
...
@@ -20,26 +20,14 @@ const withChapterFormulaNode = (editor) => {
...
@@ -20,26 +20,14 @@ const withChapterFormulaNode = (editor) => {
// 重新 normalize
// 重新 normalize
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
return
normalizeInlineVoidNode
({
if
(
type
!==
'chapterFormula'
)
{
editor
:
newEditor
,
// 未命中 chapterHeader ,执行默认的 normalizeNode
node
,
return
normalizeNode
([
node
,
path
])
path
,
}
type
:
'chapterFormula'
,
normalizeNode
,
const
topLevelNodes
=
newEditor
.
children
||
[]
transforms
:
SlateTransforms
,
const
nextNode
=
topLevelNodes
[
path
[
0
]]
||
{}
})
if
(
nextNode
.
type
===
'paragraph'
)
{
const
lastChild
=
nextNode
.
children
[
nextNode
.
children
.
length
-
1
]
if
(
lastChild
.
type
===
'chapterExpandReadSimple'
)
{
SlateTransforms
.
insertNodes
(
newEditor
,
{
type
:
'span'
,
children
:
[{
text
:
''
}]
},
{
at
:
path
,
// 在 link-card 后面插入
},
)
}
}
}
}
return
newEditor
// 返回 newEditor ,重要!!!
return
newEditor
// 返回 newEditor ,重要!!!
...
...
apps/admin/src/components/editor/wangeditor-customer/features/gallery/feature.js
浏览文件 @
3da7f60b
...
@@ -3,8 +3,6 @@ import menu from './menu'
...
@@ -3,8 +3,6 @@ import menu from './menu'
import
onlineMenu
from
'./online-menu'
import
onlineMenu
from
'./online-menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
inlineNodeModule
from
'./inline-node'
import
inlineNodeModule
from
'./inline-node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
import
GalleryFeatureDialog
from
'./FeatureDialog'
import
GalleryFeatureDialog
from
'./FeatureDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -21,22 +19,4 @@ export default defineEditorFeature({
...
@@ -21,22 +19,4 @@ export default defineEditorFeature({
{
id
:
'gallery.insert'
,
component
:
GalleryFeatureDialog
},
{
id
:
'gallery.insert'
,
component
:
GalleryFeatureDialog
},
{
id
:
'gallery.edit'
,
component
:
GalleryFeatureDialog
},
{
id
:
'gallery.edit'
,
component
:
GalleryFeatureDialog
},
],
],
events
:
[
{
name
:
'GalleryMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'gallery.insert'
,
{
isOnline
:
false
,
selectionSize
:
getSelectionFontSize
(
editor
),
}),
},
{
name
:
'GalleryOnlineMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'gallery.insert'
,
{
isOnline
:
true
,
selectionSize
:
getSelectionFontSize
(
editor
),
}),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/gallery/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
class
GalleryAuto
{
class
GalleryAuto
{
constructor
()
{
constructor
()
{
...
@@ -60,7 +62,10 @@ class GalleryAuto {
...
@@ -60,7 +62,10 @@ class GalleryAuto {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'GalleryMenuClick'
)
openEditorDialog
(
editor
,
'gallery.insert'
,
{
isOnline
:
false
,
selectionSize
:
getSelectionFontSize
(
editor
),
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/gallery/node.js
浏览文件 @
3da7f60b
...
@@ -4,9 +4,10 @@ import iconGallery from '@/assets/images/editor/icon_gallery_editor.png'
...
@@ -4,9 +4,10 @@ import iconGallery from '@/assets/images/editor/icon_gallery_editor.png'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withGalleryNode
=
(
editor
)
=>
{
const
withGalleryNode
=
(
editor
)
=>
{
const
{
isVoid
,
normalizeNode
}
=
editor
const
{
isVoid
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
// newEditor.isInline = (elem) => {
// newEditor.isInline = (elem) => {
...
@@ -21,41 +22,7 @@ const withGalleryNode = (editor) => {
...
@@ -21,41 +22,7 @@ const withGalleryNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterGallery'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterGallery'
)
{
// 未命中 chapterGallery ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/gallery/online-menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
class
GalleryAutoOnline
{
class
GalleryAutoOnline
{
constructor
()
{
constructor
()
{
...
@@ -65,7 +67,10 @@ class GalleryAutoOnline {
...
@@ -65,7 +67,10 @@ class GalleryAutoOnline {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'GalleryOnlineMenuClick'
)
openEditorDialog
(
editor
,
'gallery.insert'
,
{
isOnline
:
true
,
selectionSize
:
getSelectionFontSize
(
editor
),
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/history/FeatureDialog.jsx
浏览文件 @
3da7f60b
...
@@ -21,8 +21,7 @@ export default function HistoryFeatureDialog({ open, onClose, runtime }) {
...
@@ -21,8 +21,7 @@ export default function HistoryFeatureDialog({ open, onClose, runtime }) {
<
HistoryDialog
<
HistoryDialog
chapterId=
{
runtime
.
chapterId
}
chapterId=
{
runtime
.
chapterId
}
bookId=
{
runtime
.
bookId
}
bookId=
{
runtime
.
bookId
}
saveContent=
{
runtime
.
saveContent
}
onRestore=
{
runtime
.
restoreContent
}
setContent=
{
runtime
.
setContent
}
setHistoryVisible=
{
(
visible
)
=>
!
visible
&&
onClose
()
}
setHistoryVisible=
{
(
visible
)
=>
!
visible
&&
onClose
()
}
/>
/>
</
Modal
>
</
Modal
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/history/HistoryDialog.jsx
浏览文件 @
3da7f60b
import
React
,
{
useState
}
from
'react'
import
{
useEffect
,
useState
}
from
'react'
import
{
Divider
,
Button
,
Spin
,
Modal
,
Row
,
Col
,
Space
}
from
'antd'
import
{
Button
,
Spin
,
Modal
,
Row
,
Space
}
from
'antd'
import
{
CloseOutlined
}
from
'@ant-design/icons'
import
{
CloseOutlined
}
from
'@ant-design/icons'
import
{
Editor
,
Toolbar
}
from
'@wangeditor/editor-for-react'
import
{
getRecordList
,
getRecordInfo
}
from
'../../shared/request'
import
{
getRecordList
,
getRecordInfo
}
from
'../../shared/request'
import
'./styles.less'
import
'./styles.less'
import
{
useEffect
}
from
'react'
const
HistoryEditor
=
(
props
)
=>
{
const
HistoryEditor
=
(
props
)
=>
{
const
{
setHistoryVisible
,
chapterId
,
bookId
,
setContent
,
saveContent
}
=
props
const
{
setHistoryVisible
,
chapterId
,
bookId
,
onRestore
}
=
props
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
[
delModal
,
setDelModal
]
=
useState
(
false
)
const
[
delModal
,
setDelModal
]
=
useState
(
false
)
...
@@ -16,9 +14,8 @@ const HistoryEditor = (props) => {
...
@@ -16,9 +14,8 @@ const HistoryEditor = (props) => {
const
[
recordId
,
setRecordId
]
=
useState
(
null
)
// 当前的记录id
const
[
recordId
,
setRecordId
]
=
useState
(
null
)
// 当前的记录id
// 确认还原
// 确认还原
const
handleSuccess
=
()
=>
{
const
handleSuccess
=
async
()
=>
{
setContent
(
recordContent
)
await
onRestore
(
recordContent
)
saveContent
()
setDelModal
(
false
)
setDelModal
(
false
)
setHistoryVisible
(
false
)
setHistoryVisible
(
false
)
}
}
...
@@ -34,7 +31,7 @@ const HistoryEditor = (props) => {
...
@@ -34,7 +31,7 @@ const HistoryEditor = (props) => {
})
})
if
(
data
.
length
>
0
)
{
if
(
data
.
length
>
0
)
{
setRecordList
(
data
)
setRecordList
(
data
)
await
getRecordInfoVal
(
data
[
0
].
name
)
setRecordId
(
data
[
0
].
name
)
}
}
setLoading
(
false
)
setLoading
(
false
)
}
}
...
@@ -48,7 +45,7 @@ const HistoryEditor = (props) => {
...
@@ -48,7 +45,7 @@ const HistoryEditor = (props) => {
name
:
name
||
recordId
,
name
:
name
||
recordId
,
})
})
if
(
name
)
{
if
(
name
)
{
setRecordId
[
name
]
setRecordId
(
name
)
}
}
setRecordContent
(
data
.
content
)
setRecordContent
(
data
.
content
)
setLoading
(
false
)
setLoading
(
false
)
...
@@ -140,10 +137,10 @@ const HistoryEditor = (props) => {
...
@@ -140,10 +137,10 @@ const HistoryEditor = (props) => {
onCancel=
{
()
=>
setDelModal
(
false
)
}
onCancel=
{
()
=>
setDelModal
(
false
)
}
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
{
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
{
return
(
return
(
<
Row
justify=
{
'end'
}
className=
'delModal'
style=
{
{
marginBottom
:
'30px'
}
}
>
<
Row
justify=
{
'end'
}
style=
{
{
marginBottom
:
'30px'
}
}
>
<
Space
size=
{
20
}
>
<
Space
size=
{
20
}
>
<
CancelBtn
className=
'cancel'
/>
<
CancelBtn
/>
<
OkBtn
className=
'true'
/>
<
OkBtn
/>
</
Space
>
</
Space
>
</
Row
>
</
Row
>
)
)
...
...
apps/admin/src/components/editor/wangeditor-customer/features/icon/menu.jsx
浏览文件 @
3da7f60b
import
BaseModalMenu
from
'../../core/Legacy
DialogMenu'
import
DialogMenu
from
'../../core/
DialogMenu'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
{
SlateRange
}
from
'@wangeditor/editor'
import
IconModal
from
'./IconDialog'
class
Icon
extends
BaseModal
Menu
{
class
Icon
extends
Dialog
Menu
{
constructor
()
{
constructor
()
{
super
()
super
(
'icon.insert'
)
this
.
title
=
'插入图标'
this
.
title
=
'插入图标'
// Using a smiley/icon-like SVG
// Using a smiley/icon-like SVG
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.5199 19.8634C10.5955 18.6615 10.8833 17.5172 11.3463 16.4676C9.81124 16.3252 8.41864 15.6867 7.33309 14.7151L8.66691 13.2248C9.55217 14.0172 10.7188 14.4978 12 14.4978C12.1763 14.4978 12.3501 14.4887 12.5211 14.471C14.227 12.2169 16.8661 10.7083 19.8634 10.5199C19.1692 6.80877 15.9126 4 12 4C7.58172 4 4 7.58172 4 12C4 15.9126 6.80877 19.1692 10.5199 19.8634ZM19.0233 12.636C15.7891 13.2396 13.2396 15.7891 12.636 19.0233L19.0233 12.636ZM22 12C22 12.1677 21.9959 12.3344 21.9877 12.5L12.5 21.9877C12.3344 21.9959 12.1677 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM10 10C10 10.8284 9.32843 11.5 8.5 11.5C7.67157 11.5 7 10.8284 7 10C7 9.17157 7.67157 8.5 8.5 8.5C9.32843 8.5 10 9.17157 10 10ZM17 10C17 10.8284 16.3284 11.5 15.5 11.5C14.6716 11.5 14 10.8284 14 10C14 9.17157 14.6716 8.5 15.5 8.5C16.3284 8.5 17 9.17157 17 10Z"></path></svg>`
this
.
iconSvg
=
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.5199 19.8634C10.5955 18.6615 10.8833 17.5172 11.3463 16.4676C9.81124 16.3252 8.41864 15.6867 7.33309 14.7151L8.66691 13.2248C9.55217 14.0172 10.7188 14.4978 12 14.4978C12.1763 14.4978 12.3501 14.4887 12.5211 14.471C14.227 12.2169 16.8661 10.7083 19.8634 10.5199C19.1692 6.80877 15.9126 4 12 4C7.58172 4 4 7.58172 4 12C4 15.9126 6.80877 19.1692 10.5199 19.8634ZM19.0233 12.636C15.7891 13.2396 13.2396 15.7891 12.636 19.0233L19.0233 12.636ZM22 12C22 12.1677 21.9959 12.3344 21.9877 12.5L12.5 21.9877C12.3344 21.9959 12.1677 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM10 10C10 10.8284 9.32843 11.5 8.5 11.5C7.67157 11.5 7 10.8284 7 10C7 9.17157 7.67157 8.5 8.5 8.5C9.32843 8.5 10 9.17157 10 10ZM17 10C17 10.8284 16.3284 11.5 15.5 11.5C14.6716 11.5 14 10.8284 14 10C14 9.17157 14.6716 8.5 15.5 8.5C16.3284 8.5 17 9.17157 17 10Z"></path></svg>`
}
}
getValue
(
editor
)
{
return
<
IconModal
key=
{
Date
.
now
()
}
editor=
{
editor
}
></
IconModal
>
}
isDisabled
(
editor
)
{
isDisabled
(
editor
)
{
const
{
selection
}
=
editor
||
{}
const
{
selection
}
=
editor
||
{}
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
return
!
selection
||
SlateRange
.
isExpanded
(
selection
)
...
...
apps/admin/src/components/editor/wangeditor-customer/features/image/edit-menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
const
getSelectedImageInfo
=
(
editor
)
=>
{
const
image
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'image'
)
if
(
!
image
)
return
{}
const
[
node
,
path
]
=
SlateEditor
.
parent
(
editor
,
DomEditor
.
findPath
(
editor
,
image
))
return
{
image
,
node
,
path
}
}
class
ImageEditor
{
class
ImageEditor
{
constructor
()
{
constructor
()
{
...
@@ -37,7 +46,10 @@ class ImageEditor {
...
@@ -37,7 +46,10 @@ class ImageEditor {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'ImageEditorClick'
)
openEditorDialog
(
editor
,
'image.edit'
,
{
isOnline
:
false
,
imageInfo
:
getSelectedImageInfo
(
editor
),
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/image/feature.js
浏览文件 @
3da7f60b
...
@@ -2,26 +2,8 @@ import { defineEditorFeature } from '../../core/define-editor-feature'
...
@@ -2,26 +2,8 @@ import { defineEditorFeature } from '../../core/define-editor-feature'
import
editMenu
from
'./edit-menu'
import
editMenu
from
'./edit-menu'
import
onlineMenu
from
'./online-menu'
import
onlineMenu
from
'./online-menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
SlateEditor
,
SlateElement
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
ImageFeatureDialog
from
'./FeatureDialog'
import
ImageFeatureDialog
from
'./FeatureDialog'
const
getSelectedImageInfo
=
(
editor
)
=>
{
const
entries
=
SlateEditor
.
nodes
(
editor
,
{
match
:
(
node
)
=>
SlateElement
.
isElement
(
node
)
&&
node
.
type
===
'paragraph'
,
universal
:
true
,
})
const
info
=
{}
for
(
const
[
node
,
path
]
of
entries
)
{
node
.
children
.
forEach
((
item
)
=>
{
if
(
item
.
type
===
'image'
)
info
.
image
=
item
})
info
.
node
=
node
info
.
path
=
path
}
return
info
}
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
id
:
'image'
,
id
:
'image'
,
menus
:
[
menus
:
[
...
@@ -33,22 +15,4 @@ export default defineEditorFeature({
...
@@ -33,22 +15,4 @@ export default defineEditorFeature({
{
id
:
'image.insert'
,
component
:
ImageFeatureDialog
},
{
id
:
'image.insert'
,
component
:
ImageFeatureDialog
},
{
id
:
'image.edit'
,
component
:
ImageFeatureDialog
},
{
id
:
'image.edit'
,
component
:
ImageFeatureDialog
},
],
],
events
:
[
{
name
:
'ImageMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'image.insert'
,
{
isOnline
:
false
}),
},
{
name
:
'ImageOnlineMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'image.insert'
,
{
isOnline
:
true
}),
},
{
name
:
'ImageEditorClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'image.edit'
,
{
isOnline
:
false
,
imageInfo
:
getSelectedImageInfo
(
editor
),
}),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/image/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
as
Transforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
as
Transforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
// Extend menu
// Extend menu
class
ImageAuto
{
class
ImageAuto
{
...
@@ -16,11 +17,11 @@ class ImageAuto {
...
@@ -16,11 +17,11 @@ class ImageAuto {
</svg>`
</svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
getValue
()
{
return
'
hello, 图片, , , 图片
'
return
''
}
}
isActive
(
editor
)
{
isActive
()
{
return
false
// or true
return
false
}
}
isDisabled
(
editor
)
{
isDisabled
(
editor
)
{
const
{
selection
}
=
editor
const
{
selection
}
=
editor
...
@@ -32,69 +33,74 @@ class ImageAuto {
...
@@ -32,69 +33,74 @@ class ImageAuto {
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
const
hasPreElem
=
selectedElems
.
some
((
elem
)
=>
{
const
hasPreElem
=
selectedElems
.
some
((
elem
)
=>
{
const
type
=
DomEditor
.
getNodeType
(
elem
)
const
type
=
DomEditor
.
getNodeType
(
elem
)
// 代码块 引用 表格 禁用
return
type
===
'pre'
||
type
===
'blockquote'
if
(
type
===
'pre'
||
type
===
'blockquote'
)
return
true
})
})
if
(
hasPreElem
)
return
true
// 选中了 pre 原则,禁用
return
hasPreElem
}
exec
(
editor
)
{
if
(
!
this
.
isDisabled
(
editor
))
openEditorDialog
(
editor
,
'image.insert'
,
{
isOnline
:
false
})
}
}
class
ImageWidthMenu
{
constructor
(
title
,
width
)
{
this
.
title
=
title
this
.
width
=
width
this
.
tag
=
'button'
}
getValue
()
{
return
''
}
isActive
()
{
return
false
return
false
}
}
getSelectedNode
(
editor
)
{
isDisabled
(
editor
)
{
return
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterImage'
)
return
!
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterImage'
)
}
}
exec
(
editor
,
value
)
{
exec
(
editor
)
{
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
const
imageNode
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterImage'
)
if
(
!
imageNode
)
return
editor
.
emit
(
'ImageMenuClick'
)
if
(
this
.
isDisabled
(
editor
))
{
return
}
const
imageNode
=
this
.
getSelectedNode
(
editor
)
if
(
imageNode
===
null
)
return
// 隐藏 hoverbar
const
hoverbar
=
DomEditor
.
getHoverbar
(
editor
)
const
hoverbar
=
DomEditor
.
getHoverbar
(
editor
)
if
(
hoverbar
)
hoverbar
.
hideAndClean
()
if
(
hoverbar
)
hoverbar
.
hideAndClean
()
const
{
style
=
{}
}
=
imageNode
const
{
style
=
{}
}
=
imageNode
const
props
=
{
Transforms
.
setNodes
(
style
:
{
editor
,
...
style
,
{
width
:
this
.
value
,
// 修改 width
style
:
{
...
style
,
width
:
this
.
width
,
height
:
''
},
height
:
''
,
// 清空 height
},
},
}
{
at
:
DomEditor
.
findPath
(
editor
,
imageNode
),
Transforms
.
setNodes
(
editor
,
props
,
{
},
match
:
(
n
)
=>
DomEditor
.
checkNodeType
(
n
,
'chapterImage'
),
)
})
}
}
}
}
class
ImageWidthCustomer100
extends
ImageAuto
{
class
ImageWidthCustomer100
extends
ImageWidthMenu
{
title
=
'100%'
// 菜单标题
constructor
()
{
value
=
'100%'
// css width 的值
super
(
'100%'
,
'100%'
)
}
}
}
class
ImageWidthCustomer50
extends
ImageAuto
{
class
ImageWidthCustomer50
extends
ImageWidthMenu
{
title
=
'50%'
// 菜单标题
constructor
()
{
value
=
'50%'
// css width 的值
super
(
'50%'
,
'50%'
)
}
}
}
class
ImageWidthCustomer30
extends
ImageAuto
{
class
ImageWidthCustomer30
extends
ImageWidthMenu
{
title
=
'30%'
// 菜单标题
constructor
()
{
value
=
'30%'
// css width 的值
super
(
'30%'
,
'30%'
)
}
}
}
export
default
{
export
default
{
key
:
'ImageAuto'
,
// 定义 menu key :要保证唯一、不重复(重要)
key
:
'ImageAuto'
,
factory
()
{
factory
()
{
return
new
ImageAuto
()
// 把 `YourMenuClass` 替换为你菜单的 class
return
new
ImageAuto
()
},
},
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/image/node.jsx
浏览文件 @
3da7f60b
...
@@ -3,6 +3,7 @@ import { h } from 'snabbdom'
...
@@ -3,6 +3,7 @@ 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
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
import
ImageAutoConf
,
{
import
ImageAutoConf
,
{
imageWidth100MenuChapterConf
,
imageWidth100MenuChapterConf
,
...
@@ -11,7 +12,7 @@ import ImageAutoConf, {
...
@@ -11,7 +12,7 @@ import ImageAutoConf, {
}
from
'./menu'
}
from
'./menu'
const
withImageNode
=
(
editor
)
=>
{
const
withImageNode
=
(
editor
)
=>
{
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
const
{
isInline
,
isVoid
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
newEditor
.
isInline
=
(
elem
)
=>
{
newEditor
.
isInline
=
(
elem
)
=>
{
...
@@ -26,35 +27,7 @@ const withImageNode = (editor) => {
...
@@ -26,35 +27,7 @@ const withImageNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterImage'
,
[
'paragraph'
,
'blockquote'
])
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterImage'
)
{
// 未命中 chapterImage ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
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'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
function
genContainerId
(
editor
,
elemNode
)
{
function
genContainerId
(
editor
,
elemNode
)
{
...
@@ -100,6 +73,8 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
...
@@ -100,6 +73,8 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
*/
*/
function
init
(
clientX
)
{
function
init
(
clientX
)
{
$container
=
getContainerElem
()
$container
=
getContainerElem
()
$body
.
off
(
'mousemove'
,
onMousemove
)
$body
.
off
(
'mouseup'
,
onMouseup
)
// 记录当前 x 坐标值
// 记录当前 x 坐标值
originalX
=
clientX
originalX
=
clientX
...
@@ -138,9 +113,11 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
...
@@ -138,9 +113,11 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
$container
.
css
(
'height'
,
`
${
newHeight
}
px`
)
$container
.
css
(
'height'
,
`
${
newHeight
}
px`
)
},
100
)
},
100
)
function
onMouseup
(
e
)
{
function
onMouseup
()
{
// 取消监听 mousemove
// 取消监听 mousemove
$body
.
off
(
'mousemove'
,
onMousemove
)
$body
.
off
(
'mousemove'
,
onMousemove
)
$body
.
off
(
'mouseup'
,
onMouseup
)
onMousemove
.
cancel
()
if
(
$container
===
null
)
return
if
(
$container
===
null
)
return
const
newWidth
=
$container
.
width
().
toFixed
(
2
)
const
newWidth
=
$container
.
width
().
toFixed
(
2
)
...
@@ -154,10 +131,7 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
...
@@ -154,10 +131,7 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
height
:
`
${
newHeight
}
px`
,
height
:
`
${
newHeight
}
px`
,
},
},
}
}
Transforms
.
setNodes
(
editor
,
props
,
{
at
:
DomEditor
.
findPath
(
editor
,
elemNode
)
})
SlateTransforms
.
setNodes
(
editor
,
props
,
{
at
:
DomEditor
.
findPath
(
editor
,
elemNode
)
})
// 取消监听 mouseup
$body
.
off
(
'mouseup'
,
onMouseup
)
}
}
const
style
=
{}
const
style
=
{}
...
@@ -165,14 +139,6 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
...
@@ -165,14 +139,6 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
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
=
'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>`
return
h
(
return
h
(
'div'
,
'div'
,
{
{
...
@@ -301,7 +267,7 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
...
@@ -301,7 +267,7 @@ function renderResizeContainer(editor, elemNode, imageVnode, imageInfo) {
// 定义 renderElem 函数
// 定义 renderElem 函数
const
renderImage
=
(
elem
,
children
,
editor
)
=>
{
const
renderImage
=
(
elem
,
children
,
editor
)
=>
{
// 获取“附件”的数据,参考上文 myResume 数据结构
// 获取“附件”的数据,参考上文 myResume 数据结构
const
{
title
=
''
,
imgUrl
=
''
,
imgDescript
=
''
,
style
=
{}
}
=
elem
const
{
imgUrl
=
''
,
imgDescript
=
''
,
style
=
{}
}
=
elem
const
{
width
=
''
,
height
=
''
}
=
style
const
{
width
=
''
,
height
=
''
}
=
style
let
imageStyle
=
''
let
imageStyle
=
''
...
@@ -332,34 +298,7 @@ const renderImage = (elem, children, editor) => {
...
@@ -332,34 +298,7 @@ const renderImage = (elem, children, editor) => {
},
},
[
str
],
[
str
],
)
)
const
titleNode
=
h
(
'p'
,
{
props
:
{
className
:
'chapter-image-title'
,
},
style
:
{
color
:
'#666'
,
fontSize
:
'14px'
,
margin
:
'0'
,
lineHeight
:
'20px'
},
},
[
title
],
)
const
titleDescriptNode
=
h
(
'p'
,
{
props
:
{
className
:
'chapter-image-descript'
,
},
style
:
{
color
:
'#999'
,
fontSize
:
'14px'
,
margin
:
'0'
,
lineHeight
:
'20px'
},
},
[
imgDescript
],
)
const
element
=
[
imgNode
]
const
element
=
[
imgNode
]
if
(
title
)
{
// element.push(titleNode);
}
if
(
imgDescript
)
{
// element.push(titleDescriptNode);
}
// 附件元素 vnode
// 附件元素 vnode
const
attachVnode
=
h
(
const
attachVnode
=
h
(
...
@@ -373,11 +312,6 @@ const renderImage = (elem, children, editor) => {
...
@@ -373,11 +312,6 @@ const renderImage = (elem, children, editor) => {
className
:
'chapter-image-container'
,
className
:
'chapter-image-container'
,
},
},
style
:
{
padding
:
'10px 0px'
},
// style ,驼峰式写法
style
:
{
padding
:
'10px 0px'
},
// style ,驼峰式写法
on
:
{
click
()
{
console
.
log
(
'clicked'
)
}
/* 其他... */
,
},
},
},
// 子节点
// 子节点
[...
element
],
[...
element
],
...
...
apps/admin/src/components/editor/wangeditor-customer/features/image/online-menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
as
Transforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
// Extend menu
// Extend menu
class
ImageAutoOnline
{
class
ImageAutoOnline
{
...
@@ -30,11 +31,11 @@ class ImageAutoOnline {
...
@@ -30,11 +31,11 @@ class ImageAutoOnline {
</svg>`
</svg>`
this
.
tag
=
'button'
this
.
tag
=
'button'
}
}
getValue
(
editor
)
{
getValue
()
{
return
'
hello, 图片, , , 图片
'
return
''
}
}
isActive
(
editor
)
{
isActive
()
{
return
false
// or true
return
false
}
}
isDisabled
(
editor
)
{
isDisabled
(
editor
)
{
const
{
selection
}
=
editor
const
{
selection
}
=
editor
...
@@ -46,63 +47,15 @@ class ImageAutoOnline {
...
@@ -46,63 +47,15 @@ class ImageAutoOnline {
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
const
hasPreElem
=
selectedElems
.
some
((
elem
)
=>
{
const
hasPreElem
=
selectedElems
.
some
((
elem
)
=>
{
const
type
=
DomEditor
.
getNodeType
(
elem
)
const
type
=
DomEditor
.
getNodeType
(
elem
)
// 代码块 引用 表格 禁用
return
type
===
'pre'
||
type
===
'blockquote'
if
(
type
===
'pre'
||
type
===
'blockquote'
)
return
true
})
})
if
(
hasPreElem
)
return
true
// 选中了 pre 原则,禁用
return
hasPreElem
return
false
}
}
getSelectedNode
(
editor
)
{
exec
(
editor
)
{
return
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterImage'
)
if
(
!
this
.
isDisabled
(
editor
))
openEditorDialog
(
editor
,
'image.insert'
,
{
isOnline
:
true
}
)
}
}
exec
(
editor
,
value
)
{
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
editor
.
emit
(
'ImageOnlineMenuClick'
)
if
(
this
.
isDisabled
(
editor
))
{
return
}
const
imageNode
=
this
.
getSelectedNode
(
editor
)
if
(
imageNode
===
null
)
return
// 隐藏 hoverbar
const
hoverbar
=
DomEditor
.
getHoverbar
(
editor
)
if
(
hoverbar
)
hoverbar
.
hideAndClean
()
const
{
style
=
{}
}
=
imageNode
const
props
=
{
style
:
{
...
style
,
width
:
this
.
value
,
// 修改 width
height
:
''
,
// 清空 height
},
}
Transforms
.
setNodes
(
editor
,
props
,
{
match
:
(
n
)
=>
DomEditor
.
checkNodeType
(
n
,
'chapterImage'
),
})
}
}
class
ImageWidthCustomer100
extends
ImageAutoOnline
{
title
=
'100%'
// 菜单标题
value
=
'100%'
// css width 的值
}
class
ImageWidthCustomer50
extends
ImageAutoOnline
{
title
=
'50%'
// 菜单标题
value
=
'50%'
// css width 的值
}
class
ImageWidthCustomer30
extends
ImageAutoOnline
{
title
=
'30%'
// 菜单标题
value
=
'30%'
// css width 的值
}
}
export
default
{
export
default
{
...
@@ -112,30 +65,4 @@ export default {
...
@@ -112,30 +65,4 @@ export default {
},
},
}
}
const
imageWidth100MenuChapterConf
=
{
export
{
ImageAutoOnline
}
key
:
'imageWidthChpater100'
,
factory
()
{
return
new
ImageWidthCustomer100
()
},
}
const
imageWidth50MenuChapterConf
=
{
key
:
'imageWidthChpater50'
,
factory
()
{
return
new
ImageWidthCustomer50
()
},
}
const
imageWidth30MenuChapterConf
=
{
key
:
'imageWidthChpater30'
,
factory
()
{
return
new
ImageWidthCustomer30
()
},
}
export
{
ImageAutoOnline
,
imageWidth100MenuChapterConf
,
imageWidth50MenuChapterConf
,
imageWidth30MenuChapterConf
,
}
apps/admin/src/components/editor/wangeditor-customer/features/link/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
import
LinkFeatureDialog
from
'./FeatureDialog'
import
LinkFeatureDialog
from
'./FeatureDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -13,14 +11,4 @@ export default defineEditorFeature({
...
@@ -13,14 +11,4 @@ export default defineEditorFeature({
{
id
:
'link.insert'
,
component
:
LinkFeatureDialog
},
{
id
:
'link.insert'
,
component
:
LinkFeatureDialog
},
{
id
:
'link.edit'
,
component
:
LinkFeatureDialog
},
{
id
:
'link.edit'
,
component
:
LinkFeatureDialog
},
],
],
events
:
[
{
name
:
'CustomerLinkClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'link.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
),
theme
:
'#ab1941'
,
}),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/link/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
class
CustomerLink
{
class
CustomerLink
{
constructor
()
{
constructor
()
{
...
@@ -44,7 +46,10 @@ class CustomerLink {
...
@@ -44,7 +46,10 @@ class CustomerLink {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'CustomerLinkClick'
)
openEditorDialog
(
editor
,
'link.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
),
theme
:
'#ab1941'
,
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/link/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
icon_tooltip_editor
from
'@/assets/images/editor/icon_tooltip_editor.png'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
iconLink
from
'@/assets/images/editor/icon_link_editor.png'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
normalizeInlineVoidNode
}
from
'../../shared/normalize-inline-void'
const
withChapterLinkNode
=
(
editor
)
=>
{
const
withChapterLinkNode
=
(
editor
)
=>
{
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
...
@@ -23,26 +22,14 @@ const withChapterLinkNode = (editor) => {
...
@@ -23,26 +22,14 @@ const withChapterLinkNode = (editor) => {
// 重新 normalize
// 重新 normalize
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
return
normalizeInlineVoidNode
({
if
(
type
!==
'chapterLink'
)
{
editor
:
newEditor
,
// 未命中 chapterHeader ,执行默认的 normalizeNode
node
,
return
normalizeNode
([
node
,
path
])
path
,
}
type
:
'chapterLink'
,
normalizeNode
,
const
topLevelNodes
=
newEditor
.
children
||
[]
transforms
:
SlateTransforms
,
const
nextNode
=
topLevelNodes
[
path
[
0
]]
||
{}
})
if
(
nextNode
.
type
===
'paragraph'
)
{
const
lastChild
=
nextNode
.
children
[
nextNode
.
children
.
length
-
1
]
if
(
lastChild
.
type
===
'chapterExpandReadSimple'
)
{
SlateTransforms
.
insertNodes
(
newEditor
,
{
type
:
'span'
,
children
:
[{
text
:
''
}]
},
{
at
:
path
,
// 在 link-card 后面插入
},
)
}
}
}
}
return
newEditor
// 返回 newEditor ,重要!!!
return
newEditor
// 返回 newEditor ,重要!!!
...
...
apps/admin/src/components/editor/wangeditor-customer/features/practice/PracticeSettingsDialog.jsx
浏览文件 @
3da7f60b
...
@@ -85,7 +85,7 @@ const PracticeSettingsDialog = ({ editor, chapterId, bookId, closePanel }) => {
...
@@ -85,7 +85,7 @@ const PracticeSettingsDialog = ({ editor, chapterId, bookId, closePanel }) => {
}
}
return
(
return
(
<
Row
gutter=
{
20
}
className=
'practice-topic-row'
>
<
Row
gutter=
{
20
}
>
<
Col
span=
{
12
}
className=
'common practice-left'
>
<
Col
span=
{
12
}
className=
'common practice-left'
>
<
div
className=
'practice-header'
>
<
div
className=
'practice-header'
>
<
div
className=
'practice-l-img'
>
<
div
className=
'practice-l-img'
>
...
@@ -123,7 +123,7 @@ const PracticeSettingsDialog = ({ editor, chapterId, bookId, closePanel }) => {
...
@@ -123,7 +123,7 @@ const PracticeSettingsDialog = ({ editor, chapterId, bookId, closePanel }) => {
/>
/>
</
div
>
</
div
>
</
Col
>
</
Col
>
<
Col
span=
{
12
}
className=
'common
practice-right
'
>
<
Col
span=
{
12
}
className=
'common'
>
<
div
className=
'practice-header right'
>
<
div
className=
'practice-header right'
>
<
Button
onClick=
{
()
=>
setManualAdd
((
value
)
=>
!
value
)
}
>
<
Button
onClick=
{
()
=>
setManualAdd
((
value
)
=>
!
value
)
}
>
{
manualAdd
?
'从题库选择'
:
'新增试题'
}
{
manualAdd
?
'从题库选择'
:
'新增试题'
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/practice/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
PracticeInsertFeatureDialog
,
PracticeSettingsFeatureDialog
}
from
'./FeatureDialogs'
import
{
PracticeInsertFeatureDialog
,
PracticeSettingsFeatureDialog
}
from
'./FeatureDialogs'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -12,10 +11,4 @@ export default defineEditorFeature({
...
@@ -12,10 +11,4 @@ export default defineEditorFeature({
{
id
:
'practice.insert'
,
component
:
PracticeInsertFeatureDialog
},
{
id
:
'practice.insert'
,
component
:
PracticeInsertFeatureDialog
},
{
id
:
'practice.settings'
,
component
:
PracticeSettingsFeatureDialog
},
{
id
:
'practice.settings'
,
component
:
PracticeSettingsFeatureDialog
},
],
],
events
:
[
{
name
:
'PracticeMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'practice.insert'
),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/practice/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
Practice
{
class
Practice
{
constructor
()
{
constructor
()
{
...
@@ -55,7 +56,7 @@ class Practice {
...
@@ -55,7 +56,7 @@ class Practice {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'PracticeMenuClick
'
)
openEditorDialog
(
editor
,
'practice.insert
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/practice/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateEditor
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
import
iconClose
from
'@/assets/images/icon_chapter_close.png'
...
@@ -6,9 +6,10 @@ import { delChapterTopic } from '../../shared/request'
...
@@ -6,9 +6,10 @@ import { delChapterTopic } from '../../shared/request'
import
{
hexToRgb
}
from
'@/utils/common'
import
{
hexToRgb
}
from
'@/utils/common'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
findNodeWithParent
}
from
'../../shared/editor-settings'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withPracticeNode
=
(
editor
)
=>
{
const
withPracticeNode
=
(
editor
)
=>
{
const
{
is
Inline
,
isVoid
,
normalizeNode
}
=
editor
const
{
is
Void
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
// newEditor.isInline = (elem) => {
// newEditor.isInline = (elem) => {
...
@@ -23,41 +24,7 @@ const withPracticeNode = (editor) => {
...
@@ -23,41 +24,7 @@ const withPracticeNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterPractice'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterPractice'
)
{
// 未命中 chapterPractice ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/features/preview/FeatureDialog.jsx
浏览文件 @
3da7f60b
...
@@ -24,7 +24,6 @@ export default function PreviewFeatureDialog({ open, onClose, runtime }) {
...
@@ -24,7 +24,6 @@ export default function PreviewFeatureDialog({ open, onClose, runtime }) {
onCancel=
{
onClose
}
onCancel=
{
onClose
}
>
>
<
PreviewDialog
<
PreviewDialog
gData=
{
runtime
.
gData
}
editor=
{
runtime
.
editor
}
editor=
{
runtime
.
editor
}
chapterId=
{
runtime
.
chapterId
}
chapterId=
{
runtime
.
chapterId
}
bookId=
{
runtime
.
bookId
}
bookId=
{
runtime
.
bookId
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/preview/PracticePreview.jsx
0 → 100644
浏览文件 @
3da7f60b
import
{
Input
}
from
'antd'
const
QUESTION_TYPES
=
[
{
style
:
1
,
title
:
'单选题'
},
{
style
:
2
,
title
:
'多选题'
},
{
style
:
3
,
title
:
'判断题'
},
{
style
:
4
,
title
:
'填空题'
},
{
style
:
5
,
title
:
'简答题'
},
]
const
SECTION_NUMBERS
=
[
'一'
,
'二'
,
'三'
,
'四'
,
'五'
]
export
function
buildTopicGroups
(
topics
)
{
const
normalizedTopics
=
(
Array
.
isArray
(
topics
)
?
topics
:
[]).
map
((
topic
)
=>
{
const
questionStyle
=
Number
(
topic
.
question_style
)
let
questionList
=
[]
if
([
1
,
2
,
3
].
includes
(
questionStyle
))
{
try
{
questionList
=
Array
.
isArray
(
topic
.
question_list
)
?
topic
.
question_list
:
JSON
.
parse
(
topic
.
question_list
)
}
catch
{
questionList
=
[]
}
}
return
{
...
topic
,
question_style
:
questionStyle
,
question_list
:
questionList
}
})
return
QUESTION_TYPES
.
map
((
type
)
=>
({
...
type
,
topics
:
normalizedTopics
.
filter
((
topic
)
=>
topic
.
question_style
===
type
.
style
),
})).
filter
((
group
)
=>
group
.
topics
.
length
>
0
)
}
function
Question
({
topic
,
index
})
{
const
isChoice
=
[
1
,
2
,
3
].
includes
(
topic
.
question_style
)
return
(
<
div
className=
'practice-item'
>
<
div
className=
'topic-choose-title'
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${topic.titles}`
,
}
}
/>
<
div
className=
'topic_choose-list'
>
{
isChoice
&&
topic
.
question_list
.
map
((
option
,
optionIndex
)
=>
(
<
div
className=
'topic_choose-item'
key=
{
optionIndex
}
>
<
div
className=
'choose-ico'
>
<
i
className=
{
`ico ${topic.question_style === 2 ? 'checkbox' : 'radio'}`
}
/>
</
div
>
<
div
className=
'topic_content'
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${String.fromCharCode(65 + optionIndex)}.</span> ${option.option}`
,
}
}
/>
</
div
>
))
}
{
topic
.
question_style
===
5
&&
(
<
Input
.
TextArea
disabled
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
)
}
</
div
>
</
div
>
)
}
export
default
function
PracticePreview
({
groups
})
{
return
(
<
div
className=
'practice-insert-topic'
>
{
groups
.
map
((
group
,
groupIndex
)
=>
(
<
div
className=
'topic_style'
key=
{
group
.
style
}
>
<
h3
>
{
SECTION_NUMBERS
[
groupIndex
]
}
.
{
group
.
title
}
</
h3
>
<
div
className=
'practice-list'
>
{
group
.
topics
.
map
((
topic
,
index
)
=>
(
<
Question
topic=
{
topic
}
index=
{
index
}
key=
{
topic
.
id
??
index
}
/>
))
}
</
div
>
</
div
>
))
}
</
div
>
)
}
apps/admin/src/components/editor/wangeditor-customer/features/preview/PracticePreview.test.tsx
0 → 100644
浏览文件 @
3da7f60b
import
{
cleanup
,
render
,
screen
}
from
'@testing-library/react'
import
{
afterEach
,
describe
,
expect
,
it
}
from
'vitest'
import
PracticePreview
,
{
buildTopicGroups
}
from
'./PracticePreview'
describe
(
'PracticePreview'
,
()
=>
{
afterEach
(
cleanup
)
it
(
'normalizes and groups questions returned by the API'
,
()
=>
{
const
groups
=
buildTopicGroups
([
{
id
:
1
,
question_style
:
'2'
,
titles
:
'多选题'
,
question_list
:
'[{"option":"选项 A"}]'
,
},
{
id
:
2
,
question_style
:
5
,
titles
:
'简答题'
},
])
expect
(
groups
.
map
((
group
)
=>
group
.
style
)).
toEqual
([
2
,
5
])
expect
(
groups
[
0
].
topics
[
0
].
question_list
).
toEqual
([{
option
:
'选项 A'
}])
})
it
(
'numbers only the question groups that have content'
,
()
=>
{
const
groups
=
buildTopicGroups
([
{
id
:
1
,
question_style
:
3
,
titles
:
'判断题'
,
question_list
:
[]
},
{
id
:
2
,
question_style
:
5
,
titles
:
'简答题'
},
])
render
(<
PracticePreview
groups=
{
groups
}
/>)
expect
(
screen
.
getByRole
(
'heading'
,
{
name
:
'一. 判断题'
})).
toBeInTheDocument
()
expect
(
screen
.
getByRole
(
'heading'
,
{
name
:
'二. 简答题'
})).
toBeInTheDocument
()
expect
(
screen
.
getByRole
(
'textbox'
)).
toBeDisabled
()
})
})
apps/admin/src/components/editor/wangeditor-customer/features/preview/PreviewDialog.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/components/editor/wangeditor-customer/features/preview/PreviewDialog.test.tsx
0 → 100644
浏览文件 @
3da7f60b
import
{
cleanup
,
fireEvent
,
render
,
screen
,
waitFor
}
from
'@testing-library/react'
import
{
afterEach
,
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
PreviewDialog
from
'./PreviewDialog'
import
{
expandReadInfo
}
from
'../../shared/request'
import
hljs
from
'highlight.js'
vi
.
mock
(
'@/modules/books/management/chapters/api'
,
()
=>
({
getInfoByChapterId
:
vi
.
fn
(),
}))
vi
.
mock
(
'../../shared/request'
,
()
=>
({
expandReadInfo
:
vi
.
fn
(),
getChapterTopic
:
vi
.
fn
(),
}))
vi
.
mock
(
'highlight.js'
,
()
=>
({
default
:
{
highlightElement
:
vi
.
fn
()
},
}))
describe
(
'PreviewDialog'
,
()
=>
{
beforeEach
(()
=>
{
vi
.
clearAllMocks
()
})
afterEach
(
cleanup
)
it
(
'processes current chapter HTML after React renders it'
,
async
()
=>
{
const
editor
=
{
getHtml
:
()
=>
'<pre><code>const value = 1</code></pre><video></video>'
,
}
render
(<
PreviewDialog
editor=
{
editor
}
bookId=
{
86
}
chapterId=
{
10
}
nowTitle=
'当前章节'
/>)
await
waitFor
(()
=>
expect
(
hljs
.
highlightElement
).
toHaveBeenCalledOnce
())
const
video
=
document
.
querySelector
(
'.preview-content-html video'
)
expect
(
video
).
toHaveAttribute
(
'controls'
)
expect
(
video
).
toHaveAttribute
(
'controlslist'
,
'nodownload'
)
})
it
(
'handles images loaded later inside expand-read content'
,
async
()
=>
{
vi
.
mocked
(
expandReadInfo
).
mockResolvedValue
({
content
:
'<img src="/expand.png" alt="扩展图">'
,
})
const
editor
=
{
getHtml
:
()
=>
'<div class="chapter-expand" data-name="扩展阅读" data-random="position-1">查看扩展阅读</div>'
,
}
render
(<
PreviewDialog
editor=
{
editor
}
bookId=
{
86
}
chapterId=
{
10
}
nowTitle=
'当前章节'
/>)
fireEvent
.
click
(
await
screen
.
findByText
(
'查看扩展阅读'
))
fireEvent
.
click
(
await
screen
.
findByAltText
(
'扩展图'
))
expect
(
screen
.
getByText
(
'扩展图片'
)).
toBeInTheDocument
()
expect
(
screen
.
getByAltText
(
'预览图片'
)).
toHaveAttribute
(
'src'
,
'/expand.png'
)
fireEvent
.
click
(
screen
.
getByRole
(
'img'
,
{
name
:
'left'
}))
expect
(
screen
.
getByText
(
'扩展阅读'
)).
toBeInTheDocument
()
expect
(
screen
.
getByAltText
(
'扩展图'
)).
toBeInTheDocument
()
})
})
apps/admin/src/components/editor/wangeditor-customer/features/tooltip/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
import
TooltipFeatureDialog
from
'./FeatureDialog'
import
TooltipFeatureDialog
from
'./FeatureDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -13,11 +11,4 @@ export default defineEditorFeature({
...
@@ -13,11 +11,4 @@ export default defineEditorFeature({
{
id
:
'tooltip.insert'
,
component
:
TooltipFeatureDialog
},
{
id
:
'tooltip.insert'
,
component
:
TooltipFeatureDialog
},
{
id
:
'tooltip.edit'
,
component
:
TooltipFeatureDialog
},
{
id
:
'tooltip.edit'
,
component
:
TooltipFeatureDialog
},
],
],
events
:
[
{
name
:
'TooltipMenuClick'
,
handler
:
({
editor
})
=>
openEditorDialog
(
editor
,
'tooltip.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
)
}),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/tooltip/menu.js
浏览文件 @
3da7f60b
// Extend menu
// Extend menu
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
getSelectionFontSize
}
from
'../../core/selection'
class
TooltipAuto
{
class
TooltipAuto
{
constructor
()
{
constructor
()
{
...
@@ -58,7 +60,9 @@ class TooltipAuto {
...
@@ -58,7 +60,9 @@ class TooltipAuto {
if
(
this
.
isDisabled
(
editor
))
{
if
(
this
.
isDisabled
(
editor
))
{
return
return
}
}
editor
.
emit
(
'TooltipMenuClick'
)
openEditorDialog
(
editor
,
'tooltip.insert'
,
{
selectionSize
:
getSelectionFontSize
(
editor
),
})
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/tooltip/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
icon_tooltip_editor
from
'@/assets/images/editor/icon_tooltip_editor.png'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
escapeHtml
}
from
'../../shared/html'
import
{
escapeHtml
}
from
'../../shared/html'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
normalizeInlineVoidNode
}
from
'../../shared/normalize-inline-void'
const
withChapterTooltipNode
=
(
editor
)
=>
{
const
withChapterTooltipNode
=
(
editor
)
=>
{
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
const
{
isInline
,
isVoid
,
normalizeNode
}
=
editor
...
@@ -23,30 +23,14 @@ const withChapterTooltipNode = (editor) => {
...
@@ -23,30 +23,14 @@ const withChapterTooltipNode = (editor) => {
// 重新 normalize
// 重新 normalize
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
return
normalizeInlineVoidNode
({
if
(
type
!==
'chapterTooltip'
)
{
editor
:
newEditor
,
// 未命中 chapterHeader ,执行默认的 normalizeNode
node
,
return
normalizeNode
([
node
,
path
])
path
,
}
type
:
'chapterTooltip'
,
normalizeNode
,
// SlateTransforms.insertNodes(newEditor, {type: 'paragraph', children: [{ text: ' ' }]}, {
transforms
:
SlateTransforms
,
// at: path, // 在 link-card 后面插入
})
// });
const
topLevelNodes
=
newEditor
.
children
||
[]
const
nextNode
=
topLevelNodes
[
path
[
0
]]
||
{}
if
(
nextNode
.
type
===
'paragraph'
)
{
const
lastChild
=
nextNode
.
children
[
nextNode
.
children
.
length
-
1
]
if
(
lastChild
.
type
===
'chapterExpandReadSimple'
)
{
SlateTransforms
.
insertNodes
(
newEditor
,
{
type
:
'span'
,
children
:
[{
text
:
''
}]
},
{
at
:
path
,
// 在 link-card 后面插入
},
)
}
}
}
}
return
newEditor
// 返回 newEditor ,重要!!!
return
newEditor
// 返回 newEditor ,重要!!!
...
@@ -61,7 +45,6 @@ const renderChapterTooltip = (elem, children, editor) => {
...
@@ -61,7 +45,6 @@ const renderChapterTooltip = (elem, children, editor) => {
tooltipType
=
1
,
tooltipType
=
1
,
link
=
''
,
link
=
''
,
content
=
''
,
content
=
''
,
icon
=
''
,
random
=
''
,
random
=
''
,
theme
=
'#ab1941'
,
theme
=
'#ab1941'
,
fontsize
=
18
,
fontsize
=
18
,
...
@@ -150,7 +133,6 @@ const chapterTooltipToHtml = (elem, childrenHtml) => {
...
@@ -150,7 +133,6 @@ const chapterTooltipToHtml = (elem, childrenHtml) => {
tooltipType
=
1
,
tooltipType
=
1
,
link
=
''
,
link
=
''
,
content
=
''
,
content
=
''
,
icon
=
''
,
random
=
''
,
random
=
''
,
theme
=
'#ab1941'
,
theme
=
'#ab1941'
,
fontsize
=
18
,
fontsize
=
18
,
...
...
apps/admin/src/components/editor/wangeditor-customer/features/video/feature.js
浏览文件 @
3da7f60b
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
{
defineEditorFeature
}
from
'../../core/define-editor-feature'
import
menu
from
'./menu'
import
menu
from
'./menu'
import
nodeModule
from
'./node'
import
nodeModule
from
'./node'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
VideoDialog
from
'./VideoDialog'
import
VideoDialog
from
'./VideoDialog'
export
default
defineEditorFeature
({
export
default
defineEditorFeature
({
...
@@ -12,11 +11,4 @@ export default defineEditorFeature({
...
@@ -12,11 +11,4 @@ export default defineEditorFeature({
{
id
:
'video.insert'
,
component
:
VideoDialog
},
{
id
:
'video.insert'
,
component
:
VideoDialog
},
{
id
:
'video.edit'
,
component
:
VideoDialog
},
{
id
:
'video.edit'
,
component
:
VideoDialog
},
],
],
events
:
[
{
name
:
'VideoMenuClick'
,
handler
:
({
editor
,
payload
})
=>
openEditorDialog
(
editor
,
payload
?.
path
?
'video.edit'
:
'video.insert'
,
payload
),
},
],
})
})
apps/admin/src/components/editor/wangeditor-customer/features/video/menu.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
,
SlateRange
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateRange
}
from
'@wangeditor/editor'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
class
VideoAuto
{
class
VideoAuto
{
constructor
()
{
constructor
()
{
...
@@ -61,14 +62,14 @@ class VideoAuto {
...
@@ -61,14 +62,14 @@ class VideoAuto {
const
chapterVideo
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterVideo'
)
const
chapterVideo
=
DomEditor
.
getSelectedNodeByType
(
editor
,
'chapterVideo'
)
if
(
chapterVideo
)
{
if
(
chapterVideo
)
{
const
path
=
DomEditor
.
findPath
(
editor
,
chapterVideo
)
const
path
=
DomEditor
.
findPath
(
editor
,
chapterVideo
)
editor
.
emit
(
'VideoMenuClick
'
,
{
openEditorDialog
(
editor
,
'video.edit
'
,
{
path
,
path
,
videoUrl
:
chapterVideo
.
videoUrl
||
''
,
videoUrl
:
chapterVideo
.
videoUrl
||
''
,
title
:
chapterVideo
.
title
||
''
,
title
:
chapterVideo
.
title
||
''
,
})
})
return
return
}
}
editor
.
emit
(
'VideoMenuClick
'
)
openEditorDialog
(
editor
,
'video.insert
'
)
}
}
}
}
...
...
apps/admin/src/components/editor/wangeditor-customer/features/video/node.js
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
}
from
'@wangeditor/editor'
import
{
h
}
from
'snabbdom'
import
{
h
}
from
'snabbdom'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
openEditorDialog
}
from
'../../core/editor-dialog'
import
{
withTrailingParagraph
}
from
'../../shared/with-trailing-paragraph'
const
withVideoNode
=
(
editor
)
=>
{
const
withVideoNode
=
(
editor
)
=>
{
const
{
isVoid
,
normalizeNode
}
=
editor
const
{
isVoid
}
=
editor
const
newEditor
=
editor
const
newEditor
=
editor
newEditor
.
isVoid
=
(
elem
)
=>
{
newEditor
.
isVoid
=
(
elem
)
=>
{
...
@@ -12,41 +13,7 @@ const withVideoNode = (editor) => {
...
@@ -12,41 +13,7 @@ const withVideoNode = (editor) => {
return
isVoid
(
elem
)
return
isVoid
(
elem
)
}
}
// 重新 normalize
return
withTrailingParagraph
(
newEditor
,
'chapterVideo'
)
newEditor
.
normalizeNode
=
([
node
,
path
])
=>
{
const
type
=
DomEditor
.
getNodeType
(
node
)
if
(
type
!==
'chapterVideo'
)
{
// 未命中 chapterVideo ,执行默认的 normalizeNode
return
normalizeNode
([
node
,
path
])
}
// editor 顶级 node
const
topLevelNodes
=
newEditor
.
children
||
[]
// 后面必须跟一个 p header blockquote(否则后面无法继续输入文字)
const
nextNode
=
topLevelNodes
[
path
[
0
]
+
1
]
||
{}
const
nextNodeType
=
DomEditor
.
getNodeType
(
nextNode
)
if
(
nextNodeType
!==
'paragraph'
&&
nextNodeType
!==
'blockquote'
&&
nextNodeType
!==
'header'
&&
nextNodeType
!==
'chapterPractice'
&&
nextNodeType
!==
'chapterExpandRead'
&&
nextNodeType
!==
'chapterSection'
&&
nextNodeType
!==
'chapterHeader'
&&
nextNodeType
!==
'chapterGallery'
&&
!
nextNodeType
.
startsWith
(
'header'
)
)
{
// link-card node 后面不是 p 或 header ,则插入一个空 p
const
p
=
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
}
const
insertPath
=
[
path
[
0
]
+
1
]
SlateTransforms
.
insertNodes
(
newEditor
,
p
,
{
at
:
insertPath
,
// 在 link-card 后面插入
})
}
}
return
newEditor
// 返回 newEditor ,重要!!!
}
}
// 在编辑器中渲染新元素
// 在编辑器中渲染新元素
...
...
apps/admin/src/components/editor/wangeditor-customer/index.less
浏览文件 @
3da7f60b
...
@@ -265,19 +265,6 @@
...
@@ -265,19 +265,6 @@
line-height: 14px;
line-height: 14px;
}
}
}
}
.view {
&:disabled {
color: #666666 !important;
}
}
.history {
color: #666666;
&:hover {
.ant-btn-icon {
color: #ab1941;
}
}
}
}
}
}
}
}
}
...
@@ -341,9 +328,8 @@
...
@@ -341,9 +328,8 @@
height: 50px;
height: 50px;
display: flex;
display: flex;
width: 100%;
width: 100%;
justify-content: center;
.tabs-item {
.tabs-item {
flex: 1;
flex: 1
1 auto
;
text-align: center;
text-align: center;
border-bottom: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
color: #333;
color: #333;
...
...
apps/admin/src/components/editor/wangeditor-customer/shared/normalize-inline-void.js
0 → 100644
浏览文件 @
3da7f60b
export
const
normalizeInlineVoidNode
=
({
editor
,
node
,
path
,
type
,
normalizeNode
,
transforms
,
})
=>
{
if
(
node
.
type
!==
type
)
return
normalizeNode
([
node
,
path
])
const
[
parentIndex
,
childIndex
]
=
path
const
parent
=
editor
.
children
?.[
parentIndex
]
if
(
path
.
length
!==
2
||
parent
?.
type
!==
'paragraph'
)
return
if
(
childIndex
!==
parent
.
children
.
length
-
1
)
return
transforms
.
insertNodes
(
editor
,
{
text
:
''
},
{
at
:
[
parentIndex
,
childIndex
+
1
]
})
}
apps/admin/src/components/editor/wangeditor-customer/shared/with-trailing-paragraph.js
0 → 100644
浏览文件 @
3da7f60b
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
const
DEFAULT_ALLOWED_NEXT_TYPES
=
new
Set
([
'paragraph'
,
'blockquote'
,
'chapterPractice'
,
'chapterExpandRead'
,
'chapterSection'
,
'chapterHeader'
,
'chapterGallery'
,
])
export
const
withTrailingParagraph
=
(
editor
,
nodeType
,
allowedNextTypes
=
DEFAULT_ALLOWED_NEXT_TYPES
,
)
=>
{
const
normalizeNode
=
editor
.
normalizeNode
const
allowedTypes
=
allowedNextTypes
instanceof
Set
?
allowedNextTypes
:
new
Set
(
allowedNextTypes
)
editor
.
normalizeNode
=
([
node
,
path
])
=>
{
if
(
DomEditor
.
getNodeType
(
node
)
!==
nodeType
)
return
normalizeNode
([
node
,
path
])
const
nextType
=
DomEditor
.
getNodeType
(
editor
.
children
?.[
path
[
0
]
+
1
]
||
{})
if
(
allowedTypes
.
has
(
nextType
)
||
nextType
.
startsWith
(
'header'
))
return
SlateTransforms
.
insertNodes
(
editor
,
{
type
:
'paragraph'
,
children
:
[{
text
:
''
}]
},
{
at
:
[
path
[
0
]
+
1
]
},
)
}
return
editor
}
apps/admin/src/components/editor/wangeditor-customer/shell/EditorHeader.jsx
浏览文件 @
3da7f60b
...
@@ -4,61 +4,33 @@ import dayjs from 'dayjs'
...
@@ -4,61 +4,33 @@ import dayjs from 'dayjs'
import
timesave
from
'@/assets/images/timesave.png'
import
timesave
from
'@/assets/images/timesave.png'
export
const
EditorHeader
=
({
export
const
EditorHeader
=
({
title
,
autosaveTime
,
isView
,
disabled
,
contentId
,
onSave
,
onPreview
,
onHistory
})
=>
(
title
,
<
div
className=
"title-head"
>
autosaveTime
,
<
div
className=
"left"
>
isView
,
disabled
,
contentId
,
onSave
,
onPreview
,
onHistory
,
})
=>
(
<
div
className=
'title-head'
>
<
div
className=
'left'
>
<
h4
>
{
title
}
</
h4
>
<
h4
>
{
title
}
</
h4
>
</
div
>
</
div
>
<
div
className=
'right'
>
<
div
className=
"right"
>
<
Space
>
<
Space
>
{
!
isView
&&
(
{
!
isView
&&
(
<>
<>
<
div
className=
'save-time'
>
<
div
className=
"save-time"
>
<
div
className=
'img'
>
<
div
className=
"img"
>
<
img
src=
{
timesave
}
/>
<
span
>
自动保存
</
span
>
<
img
src=
{
timesave
}
/>
<
span
>
自动保存
</
span
>
</
div
>
</
div
>
<
span
className=
'time'
>
<
span
className=
"time"
>
{
autosaveTime
>
0
{
autosaveTime
>
0
?
`上次保存发生在 ${dayjs(autosaveTime).format('YYYY-MM-DD HH:mm:ss')}`
:
''
}
?
`上次保存发生在 ${dayjs(autosaveTime).format('YYYY-MM-DD HH:mm:ss')}`
:
''
}
</
span
>
</
span
>
</
div
>
</
div
>
<
Button
<
Button
type=
"primary"
icon=
{
<
SaveOutlined
/>
}
onClick=
{
onSave
}
disabled=
{
disabled
||
!
contentId
}
>
type=
'primary'
icon=
{
<
SaveOutlined
/>
}
className=
'view'
onClick=
{
onSave
}
style=
{
{
color
:
'#fff'
}
}
disabled=
{
disabled
||
!
contentId
}
>
保存
保存
</
Button
>
</
Button
>
</>
</>
)
}
)
}
<
Button
<
Button
icon=
{
<
EyeOutlined
/>
}
onClick=
{
onPreview
}
disabled=
{
!
contentId
}
>
icon=
{
<
EyeOutlined
/>
}
className=
'history'
onClick=
{
onPreview
}
disabled=
{
!
contentId
}
>
预览
预览
</
Button
>
</
Button
>
{
!
isView
&&
(
{
!
isView
&&
(
<
Button
<
Button
icon=
{
<
HistoryOutlined
/>
}
onClick=
{
onHistory
}
disabled=
{
disabled
||
!
contentId
}
>
icon=
{
<
HistoryOutlined
/>
}
className=
'history'
onClick=
{
onHistory
}
disabled=
{
disabled
||
!
contentId
}
>
历史
历史
</
Button
>
</
Button
>
)
}
)
}
...
...
apps/admin/src/components/editor/wangeditor-customer/shell/EditorSidebar.jsx
浏览文件 @
3da7f60b
import
{
cloneElement
,
isValidElement
,
useState
}
from
'react'
import
{
Button
}
from
'antd'
import
{
Button
}
from
'antd'
import
{
Toolbar
}
from
'@wangeditor/editor-for-react'
import
{
Toolbar
}
from
'@wangeditor/editor-for-react'
...
@@ -10,58 +11,67 @@ export const EditorSidebar = ({
...
@@ -10,58 +11,67 @@ export const EditorSidebar = ({
colorList
,
colorList
,
onApplyColor
,
onApplyColor
,
reviewCommentPanel
,
reviewCommentPanel
,
})
=>
(
})
=>
{
<
div
className=
'menu-tabs-key'
>
const
[
reviewCommentMounted
,
setReviewCommentMounted
]
=
useState
(
tabKey
===
'comment'
)
<
div
className=
'tabs'
>
const
handleTabChange
=
(
key
)
=>
{
{
[
if
(
key
===
'comment'
)
setReviewCommentMounted
(
true
)
{
key
:
'text'
,
title
:
'文本设置'
},
onTabChange
(
key
)
{
key
:
'style'
,
title
:
'样式模版'
},
}
...(
reviewCommentPanel
?
[{
key
:
'comment'
,
title
:
'评论'
}]
:
[]),
const
commentPanel
=
isValidElement
(
reviewCommentPanel
)
].
map
((
item
)
=>
(
?
cloneElement
(
reviewCommentPanel
,
{
active
:
tabKey
===
'comment'
})
<
div
:
reviewCommentPanel
className=
{
`tabs-item ${item.key === tabKey ? 'active' : ''}`
}
key=
{
item
.
key
}
return
(
onClick=
{
()
=>
onTabChange
(
item
.
key
)
}
<
div
className=
'menu-tabs-key'
>
>
<
div
className=
'tabs'
>
{
item
.
title
}
{
[
<
span
/>
{
key
:
'text'
,
title
:
'文本设置'
},
</
div
>
{
key
:
'style'
,
title
:
'样式模版'
},
))
}
...(
reviewCommentPanel
?
[{
key
:
'comment'
,
title
:
'评论'
}]
:
[]),
</
div
>
].
map
((
item
)
=>
(
<
div
className=
'menu-tabs-content'
>
<
div
{
tabKey
===
'text'
&&
(
className=
{
`tabs-item ${item.key === tabKey ? 'active' : ''}`
}
<
div
ref=
{
toolbarRef
}
className=
'toolbox-parent'
>
key=
{
item
.
key
}
<
Toolbar
onClick=
{
()
=>
handleTabChange
(
item
.
key
)
}
editor=
{
editor
}
>
defaultConfig=
{
toolbarConfig
}
{
item
.
title
}
mode=
'default'
<
span
/>
className=
'editor-toolbar-container'
</
div
>
/>
))
}
</
div
>
</
div
>
)
}
<
div
className=
'menu-tabs-content'
>
{
tabKey
===
'style'
&&
(
{
tabKey
===
'text'
&&
(
<
div
className=
'styletem'
>
<
div
ref=
{
toolbarRef
}
className=
'toolbox-parent'
>
<
p
>
样式模板
</
p
>
<
Toolbar
<
ul
>
editor=
{
editor
}
{
colorList
.
map
((
item
)
=>
(
defaultConfig=
{
toolbarConfig
}
<
li
key=
{
item
.
color
}
>
mode=
'default'
<
div
className=
'left'
>
className=
'editor-toolbar-container'
<
span
className=
'color'
style=
{
{
backgroundColor
:
item
.
color
}
}
/>
/>
<
b
className=
'type'
>
{
item
.
name
}
</
b
>
</
div
>
</
div
>
)
}
<
Button
type=
'link'
className=
'use'
onClick=
{
()
=>
onApplyColor
(
item
.
color
)
}
>
{
tabKey
===
'style'
&&
(
使用
<
div
className=
'styletem'
>
</
Button
>
<
p
>
样式模板
</
p
>
</
li
>
<
ul
>
))
}
{
colorList
.
map
((
item
)
=>
(
</
ul
>
<
li
key=
{
item
.
color
}
>
</
div
>
<
div
className=
'left'
>
)
}
<
span
className=
'color'
style=
{
{
backgroundColor
:
item
.
color
}
}
/>
{
reviewCommentPanel
&&
(
<
b
className=
'type'
>
{
item
.
name
}
</
b
>
<
div
className=
'editor-review-comment-tab'
hidden=
{
tabKey
!==
'comment'
}
>
</
div
>
{
reviewCommentPanel
}
<
Button
type=
'link'
className=
'use'
onClick=
{
()
=>
onApplyColor
(
item
.
color
)
}
>
</
div
>
使用
)
}
</
Button
>
</
li
>
))
}
</
ul
>
</
div
>
)
}
{
reviewCommentMounted
&&
reviewCommentPanel
&&
(
<
div
hidden=
{
tabKey
!==
'comment'
}
>
{
commentPanel
}
</
div
>
)
}
</
div
>
</
div
>
</
div
>
</
div
>
)
)
}
apps/admin/src/components/editor/wangeditor-customer/shell/EditorSidebar.test.tsx
0 → 100644
浏览文件 @
3da7f60b
import
{
fireEvent
,
render
,
screen
}
from
'@testing-library/react'
import
{
useEffect
,
useState
}
from
'react'
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
EditorSidebar
}
from
'./EditorSidebar'
vi
.
mock
(
'@wangeditor/editor-for-react'
,
()
=>
({
Toolbar
:
()
=>
<
div
>
编辑工具栏
</
div
>,
}))
describe
(
'EditorSidebar'
,
()
=>
{
it
(
'mounts the comment panel on first open and keeps it mounted when hidden'
,
()
=>
{
const
mounted
=
vi
.
fn
()
const
CommentPanel
=
({
active
})
=>
{
useEffect
(()
=>
mounted
(),
[])
return
<
div
>
评论面板:
{
active
?
'显示'
:
'隐藏'
}
</
div
>
}
const
Harness
=
()
=>
{
const
[
tabKey
,
setTabKey
]
=
useState
(
'text'
)
return
(
<
EditorSidebar
editor=
{
null
}
toolbarConfig=
{
{}
}
toolbarRef=
{
{
current
:
null
}
}
tabKey=
{
tabKey
}
onTabChange=
{
setTabKey
}
colorList=
{
[]
}
onApplyColor=
{
()
=>
{}
}
reviewCommentPanel=
{
<
CommentPanel
/>
}
/>
)
}
render
(<
Harness
/>)
expect
(
screen
.
queryByText
(
/评论面板/
)).
not
.
toBeInTheDocument
()
fireEvent
.
click
(
screen
.
getByText
(
'评论'
))
expect
(
screen
.
getByText
(
'评论面板:显示'
)).
toBeInTheDocument
()
fireEvent
.
click
(
screen
.
getByText
(
'文本设置'
))
expect
(
screen
.
getByText
(
'评论面板:隐藏'
)).
not
.
toBeVisible
()
fireEvent
.
click
(
screen
.
getByText
(
'评论'
))
expect
(
screen
.
getByText
(
'评论面板:显示'
)).
toBeInTheDocument
()
expect
(
mounted
).
toHaveBeenCalledOnce
()
})
})
apps/admin/src/components/editor/wangeditor-customer/shell/WangEditorCustomer.jsx
浏览文件 @
3da7f60b
import
{
useState
,
useEffect
,
useRef
,
forwardRef
,
useImperativeHandle
}
from
'react'
import
{
useState
,
useEffect
,
useRef
,
forwardRef
,
useImperativeHandle
}
from
'react'
import
{
Button
,
Divider
,
Space
,
Modal
}
from
'antd'
import
{
Divider
}
from
'antd'
import
{
EyeOutlined
,
SaveOutlined
,
HistoryOutlined
}
from
'@ant-design/icons'
import
dayjs
from
'dayjs'
import
{
useEditorStore
}
from
'@/stores/editorStore'
import
{
useEditorStore
}
from
'@/stores/editorStore'
// import formulaEditor from 'easy-formula-editor'
import
'../shared/jax.less'
import
'../shared/jax.less'
import
{
Boot
}
from
'@wangeditor/editor'
import
{
Boot
}
from
'@wangeditor/editor'
import
{
registerEditorFeatures
}
from
'../core/register-editor-features'
import
{
registerEditorFeatures
}
from
'../core/register-editor-features'
...
@@ -15,16 +12,12 @@ import { editorFeatureRegistry } from '../features'
...
@@ -15,16 +12,12 @@ import { editorFeatureRegistry } from '../features'
import
{
DomEditor
,
SlateEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
Editor
}
from
'@wangeditor/editor-for-react'
import
{
Editor
}
from
'@wangeditor/editor-for-react'
import
'@wangeditor/editor/dist/css/style.css'
// 引入 css
import
'@wangeditor/editor/dist/css/style.css'
// 引入 css
import
timesave
from
'@/assets/images/timesave.png'
import
relativeTime
from
'dayjs/plugin/relativeTime'
dayjs
.
extend
(
relativeTime
)
import
{
EditorHeader
}
from
'./EditorHeader'
import
{
EditorHeader
}
from
'./EditorHeader'
import
{
EditorSidebar
}
from
'./EditorSidebar'
import
{
EditorSidebar
}
from
'./EditorSidebar'
import
{
useToolbarPresentation
}
from
'./useToolbarPresentation'
import
{
useToolbarPresentation
}
from
'./useToolbarPresentation'
import
{
useEditorLifecycle
}
from
'./useEditorLifecycle'
import
{
createEditorConfig
,
defaultEditorContent
,
toolbarConfig
}
from
'./editor-config'
import
{
createEditorConfig
,
defaultEditorContent
,
toolbarConfig
}
from
'./editor-config'
import
{
useEditorFeatureEvents
}
from
'../core/useEditorFeatureEvents'
import
{
createAutosaveScheduler
}
from
'./autosave'
import
{
createAutosaveScheduler
}
from
'./autosave'
import
{
getEmptyParagraphPath
,
normalizeEmptyParagraph
}
from
'./paragraph-behavior'
import
{
getEmptyParagraphPath
,
normalizeEmptyParagraph
}
from
'./paragraph-behavior'
...
@@ -55,25 +48,28 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -55,25 +48,28 @@ const WangEditorCustomer = (props, ref) => {
const
[
tabKey
,
setTabKey
]
=
useState
(
'text'
)
const
[
tabKey
,
setTabKey
]
=
useState
(
'text'
)
// editor 实例
// editor 实例
const
[
editor
,
setEditor
]
=
useState
(
null
)
const
[
editor
,
setEditor
]
=
useState
(
null
)
const
[
content
,
setContent
]
=
useState
(
html
)
const
saveContentRef
=
useRef
(
saveContent
)
const
saveContentRef
=
useRef
(
saveContent
)
const
autosaveRef
=
useRef
(
)
const
latestHtmlRef
=
useRef
(
html
)
const
[
autosave
]
=
useState
(()
=>
createAutosaveScheduler
(()
=>
saveContentRef
.
current
?.(),
1500
))
if
(
!
autosaveRef
.
current
)
{
const
[
htmlSync
]
=
useState
(()
=>
autosaveRef
.
current
=
createAutosaveScheduler
(()
=>
saveContentRef
.
current
?.(),
1500
)
createAutosaveScheduler
(()
=>
setHtml
(
latestHtmlRef
.
current
),
500
),
}
)
useEffect
(()
=>
{
useEffect
(()
=>
{
saveContentRef
.
current
=
saveContent
saveContentRef
.
current
=
saveContent
},
[
saveContent
])
},
[
saveContent
])
useEffect
(()
=>
{
useEffect
(()
=>
{
return
()
=>
autosaveRef
.
current
?.
cancel
()
return
()
=>
{
},
[])
autosave
.
cancel
()
htmlSync
.
cancel
()
}
},
[
autosave
,
htmlSync
])
useEffect
(()
=>
{
useEffect
(()
=>
{
autosaveRef
.
current
?.
cancel
()
autosave
.
cancel
()
},
[
chapterId
,
contentId
,
disabled
,
isView
])
htmlSync
.
cancel
()
},
[
autosave
,
chapterId
,
contentId
,
disabled
,
htmlSync
,
isView
])
useImperativeHandle
(
ref
,
()
=>
{
useImperativeHandle
(
ref
,
()
=>
{
return
{
return
{
...
@@ -82,15 +78,23 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -82,15 +78,23 @@ const WangEditorCustomer = (props, ref) => {
})
})
const
updateContent
=
(
editor
)
=>
{
const
updateContent
=
(
editor
)
=>
{
for
(
const
[
node
]
of
SlateEditor
.
nodes
(
editor
,
{
const
shouldSyncVideoAlignment
=
(
editor
.
operations
||
[]).
some
(
match
:
(
node
)
=>
node
.
type
===
'video'
,
(
operation
)
=>
universal
:
true
,
(
operation
.
type
===
'insert_node'
&&
operation
.
node
?.
type
===
'video'
)
||
}))
{
(
operation
.
type
===
'set_node'
&&
'align'
in
(
operation
.
newProperties
||
{})),
const
dom
=
DomEditor
.
toDOMNode
(
editor
,
node
)
)
Object
.
assign
(
dom
.
style
,
{
textAlign
:
node
.
align
||
'center'
})
if
(
shouldSyncVideoAlignment
)
{
for
(
const
[
node
]
of
SlateEditor
.
nodes
(
editor
,
{
match
:
(
node
)
=>
node
.
type
===
'video'
,
universal
:
true
,
}))
{
const
dom
=
DomEditor
.
toDOMNode
(
editor
,
node
)
Object
.
assign
(
dom
.
style
,
{
textAlign
:
node
.
align
||
'center'
})
}
}
}
setHtml
(
editor
.
getHtml
())
setContent
(
editor
.
getHtml
())
latestHtmlRef
.
current
=
editor
.
getHtml
()
htmlSync
.
schedule
()
}
}
const
editorConfig
=
createEditorConfig
({
const
editorConfig
=
createEditorConfig
({
...
@@ -100,10 +104,10 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -100,10 +104,10 @@ const WangEditorCustomer = (props, ref) => {
editorConfig
.
onChange
=
(
currentEditor
)
=>
{
editorConfig
.
onChange
=
(
currentEditor
)
=>
{
updateContent
(
currentEditor
)
updateContent
(
currentEditor
)
if
(
!
disabled
&&
!
isView
&&
contentId
)
autosave
Ref
.
current
.
schedule
()
if
(
!
disabled
&&
!
isView
&&
contentId
)
autosave
.
schedule
()
}
}
useEditor
FeatureEvents
({
editor
,
registry
:
editorFeatureRegistry
,
runtime
:
{}
})
useEditor
Lifecycle
({
editor
,
gData
,
disabled
:
disabled
||
isView
})
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
editor
)
return
if
(
!
editor
)
return
...
@@ -149,7 +153,6 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -149,7 +153,6 @@ const WangEditorCustomer = (props, ref) => {
editor
.
addMark
(
'fontSize'
,
'18px'
)
editor
.
addMark
(
'fontSize'
,
'18px'
)
editor
.
addMark
(
'fontFamily'
,
'黑体'
)
editor
.
addMark
(
'fontFamily'
,
'黑体'
)
editor
.
addMark
(
'lineHeight'
,
1.5
)
editor
.
addMark
(
'lineHeight'
,
1.5
)
setContent
(
html
)
return
()
=>
{
return
()
=>
{
editor
.
destroy
()
editor
.
destroy
()
setEditor
(
null
)
setEditor
(
null
)
...
@@ -166,7 +169,7 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -166,7 +169,7 @@ const WangEditorCustomer = (props, ref) => {
// 预览
// 预览
const
previewIt
=
async
()
=>
{
const
previewIt
=
async
()
=>
{
if
(
!
isView
)
{
if
(
!
isView
)
{
autosave
Ref
.
current
.
cancel
()
autosave
.
cancel
()
await
saveContent
()
await
saveContent
()
}
}
openEditorDialog
(
editor
,
'preview.open'
)
openEditorDialog
(
editor
,
'preview.open'
)
...
@@ -178,10 +181,20 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -178,10 +181,20 @@ const WangEditorCustomer = (props, ref) => {
}
}
const
saveNow
=
()
=>
{
const
saveNow
=
()
=>
{
autosave
Ref
.
current
.
cancel
()
autosave
.
cancel
()
return
saveContent
()
return
saveContent
()
}
}
const
restoreContent
=
async
(
contentHtml
)
=>
{
if
(
!
editor
||
!
saveContent
)
return
editor
.
setHtml
(
contentHtml
)
autosave
.
cancel
()
htmlSync
.
cancel
()
setHtml
(
contentHtml
)
await
saveContent
(
contentHtml
)
}
const
colorList
=
[
const
colorList
=
[
{
color
:
'#ab1941'
,
name
:
'默认'
},
{
color
:
'#ab1941'
,
name
:
'默认'
},
{
color
:
'#2970f6'
,
name
:
'蓝色'
},
{
color
:
'#2970f6'
,
name
:
'蓝色'
},
...
@@ -229,8 +242,9 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -229,8 +242,9 @@ const WangEditorCustomer = (props, ref) => {
<
Divider
style=
{
{
margin
:
0
}
}
/>
<
Divider
style=
{
{
margin
:
0
}
}
/>
<
Editor
<
Editor
defaultConfig=
{
editorConfig
}
defaultConfig=
{
editorConfig
}
defaultContent=
{
defaultEditorContent
}
defaultContent=
{
html
?
[]
:
defaultEditorContent
}
value=
{
content
}
defaultHtml=
{
html
}
value=
{
html
}
onCreated=
{
setEditor
}
onCreated=
{
setEditor
}
mode=
'default'
mode=
'default'
style=
{
{
flex
:
1
,
minHeight
:
0
,
overflowY
:
'auto'
,
overflowX
:
'hidden'
}
}
style=
{
{
flex
:
1
,
minHeight
:
0
,
overflowY
:
'auto'
,
overflowX
:
'hidden'
}
}
...
@@ -251,7 +265,7 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -251,7 +265,7 @@ const WangEditorCustomer = (props, ref) => {
<
EditorDialogHost
<
EditorDialogHost
editor=
{
editor
}
editor=
{
editor
}
registry=
{
editorFeatureRegistry
}
registry=
{
editorFeatureRegistry
}
runtime=
{
{
editor
,
chapterId
,
bookId
,
gData
,
nowTitle
,
saveContent
,
set
Content
}
}
runtime=
{
{
editor
,
chapterId
,
bookId
,
gData
,
nowTitle
,
saveContent
,
restore
Content
}
}
/>
/>
</
div
>
</
div
>
)
)
...
...
apps/admin/src/components/layout/components/message.jsx
浏览文件 @
3da7f60b
...
@@ -108,7 +108,7 @@ function MessageListItem({ item, onRead }) {
...
@@ -108,7 +108,7 @@ function MessageListItem({ item, onRead }) {
<
BellOutlined
/>
<
BellOutlined
/>
</
Badge
>
</
Badge
>
</
Col
>
</
Col
>
<
Col
span=
{
22
}
className=
'process'
>
<
Col
span=
{
22
}
>
<
div
<
div
className=
'head'
className=
'head'
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
marginBottom
:
10
}
}
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
marginBottom
:
10
}
}
...
...
apps/admin/src/components/layout/index.jsx
浏览文件 @
3da7f60b
...
@@ -177,8 +177,6 @@ export default function AppLayout() {
...
@@ -177,8 +177,6 @@ export default function AppLayout() {
const
[
collapsed
,
setCollapsed
]
=
useState
(
const
[
collapsed
,
setCollapsed
]
=
useState
(
()
=>
localStorage
.
getItem
(
SIDER_COLLAPSED_STORAGE_KEY
)
!==
'false'
,
()
=>
localStorage
.
getItem
(
SIDER_COLLAPSED_STORAGE_KEY
)
!==
'false'
,
)
)
const
[
siderHovered
,
setSiderHovered
]
=
useState
(
false
)
const
siderCollapsed
=
collapsed
&&
!
siderHovered
const
headerOverride
=
pageHeaderOverrides
[
pathname
]
const
headerOverride
=
pageHeaderOverrides
[
pathname
]
const
pageTitle
=
headerOverride
?.
title
||
getCurrentMenuTitle
(
menuList
,
pathname
)
const
pageTitle
=
headerOverride
?.
title
||
getCurrentMenuTitle
(
menuList
,
pathname
)
...
@@ -232,11 +230,9 @@ export default function AppLayout() {
...
@@ -232,11 +230,9 @@ export default function AppLayout() {
className=
'layout-sider'
className=
'layout-sider'
width=
{
240
}
width=
{
240
}
collapsedWidth=
{
64
}
collapsedWidth=
{
64
}
collapsed=
{
siderC
ollapsed
}
collapsed=
{
c
ollapsed
}
theme=
'light'
theme=
'light'
trigger=
{
null
}
trigger=
{
null
}
onMouseEnter=
{
()
=>
setSiderHovered
(
true
)
}
onMouseLeave=
{
()
=>
setSiderHovered
(
false
)
}
>
>
<
div
className=
'layout-brand'
>
<
div
className=
'layout-brand'
>
<
Link
className=
'layout-brand__logo'
to=
'/'
aria
-
label=
'返回工作台首页'
>
<
Link
className=
'layout-brand__logo'
to=
'/'
aria
-
label=
'返回工作台首页'
>
...
@@ -244,7 +240,7 @@ export default function AppLayout() {
...
@@ -244,7 +240,7 @@ export default function AppLayout() {
</
Link
>
</
Link
>
</
div
>
</
div
>
<
div
className=
'layout-sider__body'
>
<
div
className=
'layout-sider__body'
>
<
Menu
menuList=
{
menuList
}
collapsed=
{
siderC
ollapsed
}
></
Menu
>
<
Menu
menuList=
{
menuList
}
collapsed=
{
c
ollapsed
}
></
Menu
>
</
div
>
</
div
>
<
Tooltip
title=
{
collapsed
?
'固定展开侧边菜单'
:
'收起侧边菜单'
}
placement=
'right'
>
<
Tooltip
title=
{
collapsed
?
'固定展开侧边菜单'
:
'收起侧边菜单'
}
placement=
'right'
>
<
Button
<
Button
...
...
apps/admin/src/components/layout/index.less
浏览文件 @
3da7f60b
...
@@ -14,10 +14,6 @@
...
@@ -14,10 +14,6 @@
.layout-sider-slot--collapsed {
.layout-sider-slot--collapsed {
flex-basis: 64px;
flex-basis: 64px;
width: 64px;
width: 64px;
.layout-sider:not(.ant-layout-sider-collapsed) {
z-index: 102;
}
}
}
.layout-sider {
.layout-sider {
...
...
apps/admin/src/modules/access/admin/components/DeleteAdminModal.tsx
浏览文件 @
3da7f60b
...
@@ -15,7 +15,7 @@ export default function DeleteAdminModal({ open, onCancel, onConfirm }: DeleteAd
...
@@ -15,7 +15,7 @@ export default function DeleteAdminModal({ open, onCancel, onConfirm }: DeleteAd
onOk=
{
onConfirm
}
onOk=
{
onConfirm
}
onCancel=
{
onCancel
}
onCancel=
{
onCancel
}
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
(
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
(
<
Row
justify=
'center'
className=
'delModal'
style=
{
{
marginBottom
:
'30px'
}
}
>
<
Row
justify=
'center'
style=
{
{
marginBottom
:
'30px'
}
}
>
<
Space
size=
{
20
}
>
<
Space
size=
{
20
}
>
<
CancelBtn
/>
<
CancelBtn
/>
<
OkBtn
/>
<
OkBtn
/>
...
...
apps/admin/src/modules/access/role/components/DeleteRoleModal.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/account/views/AccountView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/advertisements/advert/components/DeleteAdvertModal.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/management/chapters/index.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/management/components/DeleteBookModal.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/management/components/ExportBookModal.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/management/components/ImportBookDrawer.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/management/components/ManagementAddEdit.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/review-comments/views/EditorReviewComments.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/sale/components/SaleDiscussionDetail.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/members/level/components/DeleteMemberLevelModal.tsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/settings/app-version/views/AppVersionListView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/settings/applied/views/AppliedListView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/settings/help/views/HelpListView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/student-book/views/StudentBookView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/modules/teachers/list/views/TeacherListView.jsx
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
apps/admin/src/stores/editorStore.ts
浏览文件 @
3da7f60b
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论