增加侧边栏样式,优化配置文件,更新文档说明

master
joefalmko 2 weeks ago
parent cdfd97ab47
commit 784066340c

@ -1,5 +1,5 @@
### 运行 ### 运行
``` ```bash
npm install npm install
npm run serve npm run serve
``` ```
@ -8,7 +8,7 @@ npm run serve
见`App.vue`、`main.js` 见`App.vue`、`main.js`
### 添加插件 ### 添加插件
直接修改`App.vue`,见Export2Word插件 插件定义在`./components/plugins.js`中,添加方式见Export2Word插件
#### Export2Word插件 #### Export2Word插件
bug1图片导出为word会恢复原始大小而不是在编辑器中显示的大小。 bug1图片导出为word会恢复原始大小而不是在编辑器中显示的大小。
@ -20,6 +20,7 @@ bug2: 导出格式部分错误,字体颜色可以正常显示但背景颜色
#### 智能润色 #### 智能润色
选中需要处理的文本后点击对应按钮即可触发实现对应逻辑需要实现框架已搭建。见插件Translate 选中需要处理的文本后点击对应按钮即可触发实现对应逻辑需要实现框架已搭建。见插件Translate
具体的输出可以显示在侧边栏中,修改侧边栏中智能润色子菜单部分`App.vue`44-48行如果需要与大模型交互可以参考271-279行和`sendMessage, displayMessage`函数
#### 智能格式排版 #### 智能格式排版
· 样式库的管理和编辑 · 样式库的管理和编辑
@ -27,7 +28,7 @@ bug2: 导出格式部分错误,字体颜色可以正常显示但背景颜色
https://ckeditor.com/docs/ckeditor5/latest/features/style.html https://ckeditor.com/docs/ckeditor5/latest/features/style.html
编辑器初始化时 编辑器初始化时
``` ```javascript
// 配置文件定义样式 // 配置文件定义样式
import { ClassicEditor, Style } from 'ckeditor5'; import { ClassicEditor, Style } from 'ckeditor5';
@ -51,8 +52,11 @@ ClassicEditor
.then( /* ... */ ) .then( /* ... */ )
.catch( /* ... */ ); .catch( /* ... */ );
``` ```
``` 初始化配置,可以通过修改`./components/utils`中的`setConfig`函数和`getUserConfigFromBackend`函数和初始化时对应部分。
// 定义对应样式的css格式 应用样式需要修改`getAndApplyUserStyles`函数。需要和后端配合。
需要在目录页面(或其他位置)增加一个用户自定义配置的功能。
```css
/* 定义对应样式的css格式*/
.ck.ck-content h3.category { .ck.ck-content h3.category {
font-family: 'Bebas Neue'; font-family: 'Bebas Neue';
font-size: 20px; font-size: 20px;

@ -1,5 +1,5 @@
### 角色 ### 角色
你是一个设计和 CSS 专家,能够根据用户需求生成精确的 CSS 样式。 你是一个设计和 CSS 专家,能够根据用户需求生成精确的 CSS 样式。 如果用户提出与生成CSS样式无关的问题请回答“对不起我无法回答该问题”
### 任务 ### 任务
为以下元素生成 CSS 样式类: 为以下元素生成 CSS 样式类:
标题:<hi>i为标题级别如 h1, h2 等) 标题:<hi>i为标题级别如 h1, h2 等)
@ -9,8 +9,10 @@
代码块:<pre> 代码块:<pre>
所有样式都需在 .ck-content 中定义,格式为: 所有样式都需在 .ck-content 中定义,格式为:
``` css ``` css
.ck-content element.class { ... } .ck-content element.className { ... }
``` ```
### 输出
仅输出你编写的 CSS 内容,不要对其进行解释和输出其他内容。!important
### 示例 ### 示例
生成一个文本块样式,生成的样式如下所示。 生成一个文本块样式,生成的样式如下所示。
``` css ``` css
@ -25,5 +27,64 @@
box-shadow: 5px 5px 0 #ffe6ef; box-shadow: 5px 5px 0 #ffe6ef;
} }
``` ```
### 输出 生成一个代码框样式,生成的样式如下所示:
仅输出你编写的 CSS 内容,不要对其进行解释和输出其他内容。 ```css
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color: #fff;
box-shadow: 5px 5px 0 #0000001f;
}
```
生成一个块引用样式,生成的样式如下所示
```css
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
```

@ -0,0 +1,127 @@
/*侧边栏样式*/
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 350px;
height: 100%;
overflow-y: hidden;
transition: transform 0.3s ease;
transform: translateX(-100%);
background-color: #f9f9f9;
padding-left: 8px;
/* 调整内边距,使文字右移 */
padding-right: 8px;
/* 调整内边距,使文字右移 */
z-index: 9;
border-right: 5px solid #aec7e5;
resize: horizontal;
overflow: auto;
}
.sidebar.active {
transform: translateX(0);
}
.sidebar-menu {
flex-direction: row;
justify-content: space-between;
/* background-color: rgb(182, 229, 244); */
border-radius: 19px;
/* 添加圆角 */
display: flex;
}
.sidebar-menu .el-sub-menu {
display: flex;
flex-direction: column;
}
.sidebar-menu .el-menu-item {
flex: 1;
text-align: left;
background-color: #9cd6ce;
border-radius: 8px;
/* 添加圆角 */
margin: 2px 0;
/* 添加间距 */
}
.sidebar-menu .el-menu-item span {
display: block;
padding: 10px;
background-color: #ffffff;
border-radius: 4px;
}
.sidebar-menu .el-menu-item span:hover {
background-color: #e0e0e0;
}
.preview {
margin-top: 20px;
/* 调整预览区与表单之间的距离 */
border: 1px solid #ccc;
/* 添加边框 */
padding: 10px;
/* 添加内边距 */
border-radius: 4px;
/* 添加圆角 */
}
.form-item {
margin-bottom: 1px;
/* 减小表单项之间的距离 */
}
.chat-container {
display: flex;
flex-direction: column;
height: 90vh;
justify-content: space-between;
}
.messages {
flex: 1;
overflow-y: auto;
margin-bottom: 10px;
}
.message pre {
white-space: pre-wrap;
/* 保留空白符,但允许自动换行 */
word-wrap: break-word;
/* 允许长单词换行 */
}
.input-area {
display: flex;
}
.input-area input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
.input-area button {
padding: 10px 15px;
background-color: #2980b9;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 5px;
}
.input-area button:hover {
background-color: #3498db;
}
.message {
margin: 5px 0;
padding: 10px;
border-radius: 4px;
background-color: #ecf0f1;
}

@ -18,8 +18,8 @@
id="toggleSidebarButton">打开/关闭侧边栏</el-button> id="toggleSidebarButton">打开/关闭侧边栏</el-button>
<div class="sidebar" :class="{ 'active': isSidebarOpen }"> <div class="sidebar" :class="{ 'active': isSidebarOpen }">
<!-- 侧边栏内容 --> <!-- 侧边栏内容 -->
<el-menu class="sidebar-menu" :class="{ 'active': isNavbarOpen }"> <el-menu class="sidebar-menu" :class="{ 'active': isNavbarOpen }" :collapse="isNavbarOpen">
<el-sub-menu index="1"> <el-sub-menu index="1" class="horizontal-sub-menu">
<template #title>智能助手</template> <template #title>智能助手</template>
<el-menu-item index="1-1" @click="showContent('polish')"></el-menu-item> <el-menu-item index="1-1" @click="showContent('polish')"></el-menu-item>
<el-menu-item index="1-2" @click="showContent('rewrite')"></el-menu-item> <el-menu-item index="1-2" @click="showContent('rewrite')"></el-menu-item>
@ -27,12 +27,12 @@
<el-menu-item index="1-4" @click="showContent('edit')"></el-menu-item> <el-menu-item index="1-4" @click="showContent('edit')"></el-menu-item>
<el-menu-item index="1-5" @click="showContent('translate')"></el-menu-item> <el-menu-item index="1-5" @click="showContent('translate')"></el-menu-item>
</el-sub-menu> </el-sub-menu>
<el-sub-menu index="2"> <el-sub-menu index="2" class="horizontal-sub-menu">
<template #title>图文转换</template> <template #title>图文转换</template>
<el-menu-item index="2-1" @click="showContent('ocr')">OCR</el-menu-item> <el-menu-item index="2-1" @click="showContent('ocr')">OCR</el-menu-item>
<el-menu-item index="2-2" @click="showContent('mindmap')"></el-menu-item> <el-menu-item index="2-2" @click="showContent('mindmap')"></el-menu-item>
</el-sub-menu> </el-sub-menu>
<el-sub-menu index="3"> <el-sub-menu index="3" class="horizontal-sub-menu">
<template #title>样式生成</template> <template #title>样式生成</template>
<el-menu-item index="3-1" @click="showContent('manual')"></el-menu-item> <el-menu-item index="3-1" @click="showContent('manual')"></el-menu-item>
<el-menu-item index="3-2" @click="showContent('ai')"></el-menu-item> <el-menu-item index="3-2" @click="showContent('ai')"></el-menu-item>
@ -40,7 +40,6 @@
</el-menu> </el-menu>
<!-- Content Sections --> <!-- Content Sections -->
<div v-if="currentContent" class="content-section"> <div v-if="currentContent" class="content-section">
<el-button icon="el-icon-menu" @click="toggleNavBar()"></el-button>
<!-- Dynamic content based on navigation selection --> <!-- Dynamic content based on navigation selection -->
<div v-if="currentContent === 'polish'"></div> <div v-if="currentContent === 'polish'"></div>
<div v-if="currentContent === 'rewrite'"></div> <div v-if="currentContent === 'rewrite'"></div>
@ -49,7 +48,7 @@
<div v-if="currentContent === 'translate'"></div> <div v-if="currentContent === 'translate'"></div>
<div v-if="currentContent === 'ocr'">OCR</div> <div v-if="currentContent === 'ocr'">OCR</div>
<div v-if="currentContent === 'mindmap'"></div> <div v-if="currentContent === 'mindmap'"></div>
<div v-if="currentContent === 'manual'" class="p-3"> <div v-if="currentContent === 'manual'">
<!-- 手动输入表单 --> <!-- 手动输入表单 -->
<el-form @submit.prevent="submitForm"> <el-form @submit.prevent="submitForm">
<el-col :span="30"> <el-col :span="30">
@ -269,12 +268,14 @@
Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345
</div> </div>
</div> </div>
<div v-if="currentContent === 'ai'" class="p-3"> <div v-if="currentContent === 'ai'">
<!-- AI对话框 --> <!-- AI对话框 -->
<div class="chat-box border rounded p-3"> <div class="chat-container">
<p v-for="message in messages" :key="message.id" class="mb-1">{{ message.text }}</p> <div class="messages" id="messages"></div>
<el-input v-model="aiInput" @keyup.enter="sendMessageToAI" placeholder="请输入消息" <div class="input-area">
class="w-100"></el-input> <input type="text" id="userInput" placeholder="输入您的消息...">
<button @click="sendMessage()"></button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -284,158 +285,16 @@
</div> </div>
</div> </div>
</template> </template>
<style>
/*侧边栏样式*/
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 350px;
height: 100%;
overflow-y: hidden;
transition: transform 0.3s ease;
transform: translateX(-100%);
background-color: #f9f9f9;
padding-left: 8px;
/* 调整内边距,使文字右移 */
padding-right: 8px;
/* 调整内边距,使文字右移 */
z-index: 9;
}
.sidebar.active {
transform: translateX(0);
}
.sidebar-menu {
display: none;
flex-direction: column;
justify-content: space-between;
}
.sidebar-menu.active {
display: block;
}
.sidebar-menu .el-menu-item {
flex: 1;
text-align: left;
}
.sidebar-menu .el-menu-item span {
display: block;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
margin: 5px;
transition: background-color 0.3s;
}
.sidebar-menu .el-menu-item span:hover {
background-color: #e0e0e0;
}
.chat-box {
max-height: 350px;
overflow-y: auto;
}
.preview {
margin-top: 20px;
/* 调整预览区与表单之间的距离 */
border: 1px solid #ccc;
/* 添加边框 */
padding: 10px;
/* 添加内边距 */
border-radius: 4px;
/* 添加圆角 */
}
.form-item {
margin-bottom: 1px;
/* 减小表单项之间的距离 */
}
</style>
<script> <script>
import { import {
DecoupledEditor, DecoupledEditor,
// ClassicEditor, // ClassicEditor,
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
} from 'ckeditor5'; } from 'ckeditor5';
import translations from 'ckeditor5/translations/zh-cn.js';
import 'ckeditor5/ckeditor5.css'; import 'ckeditor5/ckeditor5.css';
import { ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu } from 'element-plus'; import '../public/sidebar.css';
import { ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu, } from 'element-plus';
// import {getUserConfigFromBackend,saveData,getPageContent,getAndApplyUserStyles} from './components/utils'; // import {getUserConfigFromBackend,saveData,getPageContent,getAndApplyUserStyles} from './components/utils';
import { getUserConfigFromBackend, saveData, getPageContent } from './components/utils'; import { setConfig } from './components/plugins'
import { Export2PDF, Export2Word, ToggleSideBar, Translation } from './components/plugins'
export default { export default {
name: 'app', name: 'app',
data() { data() {
@ -445,7 +304,6 @@ export default {
editor: DecoupledEditor, editor: DecoupledEditor,
// editor: ClassicEditor, // editor: ClassicEditor,
isSidebarOpen: false,// isSidebarOpen: false,//
isNavbarOpen: false,// isNavbarOpen: false,//
currentContent: '', // currentContent: '', //
@ -469,251 +327,7 @@ export default {
}; };
}, },
mounted() { mounted() {
// this.config = setConfig();
const userConfig = getUserConfigFromBackend();
//
// getAndApplyUserStyles();
this.config = {
toolbar: {
items: [
'undo',
'redo',
'|',
'heading',
'style',
'|',
'fontSize',
'fontFamily',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'|',
'link',
'insertImage',
'insertTable',
'highlight',
'codeBlock',
'blockquote',
'|',
'alignment',
'bulletedList',
'numberedList',
'outdent',
'indent',
'|', 'ExportToWord', 'ExportToPDF', 'translate', 'SideBar'
],
shouldNotGroupWhenFull: true
},
plugins: [
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
Export2Word, Translation, Export2PDF, ToggleSideBar
],
balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'],
//
fontFamily: {
//
options: userConfig.fontFamily.options,
//
supportAllValues: true,
},
fontSize: {
//
options: userConfig.fontSize.options,
supportAllValues: true
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage'
]
},
initialData:
'',
language: 'zh-cn',
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
mention: {
feeds: [
{
marker: '@',
feed: [
/* See: https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html */
]
}
]
},
menuBar: {
isVisible: true,
removeItems: ['help'],
},
placeholder: 'Type or paste your content here!',
//
style: {
definitions: userConfig.style.definitions
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
autosave: {
waitingTime: 180000, // (in ms) 3minutes
save() {
// TODO save
return saveData(getPageContent());
}
},
translations: [translations]
};
this.isLayoutReady = true; this.isLayoutReady = true;
}, },
methods: { methods: {
@ -735,13 +349,9 @@ export default {
this.isSidebarOpen = !this.isSidebarOpen; this.isSidebarOpen = !this.isSidebarOpen;
this.isNavbarOpen = this.isSidebarOpen; this.isNavbarOpen = this.isSidebarOpen;
}, },
toggleNavBar() {
this.isNavbarOpen = !this.isNavbarOpen;
},
// //
showContent(formType) { showContent(formType) {
this.currentContent = formType; this.currentContent = formType;
this.isNavbarOpen=false;
}, },
// //
submitForm() { submitForm() {
@ -760,8 +370,7 @@ export default {
cssClass += ` ${cssKey}: ${selectedStyles[key]};\n`; cssClass += ` ${cssKey}: ${selectedStyles[key]};\n`;
} }
cssClass += `}`; cssClass += `}`;
// TODO
console.log(cssClass); // cssClass
alert(cssClass); alert(cssClass);
} else { } else {
alert("类名不能为空!"); alert("类名不能为空!");
@ -776,20 +385,182 @@ export default {
} }
} }
}, },
// API // AI
sendMessageToAI() { displayMessage(text, sender) {
// APIAI const messagesDiv = document.getElementById('messages');
this.messages.push({ text: this.aiInput });
this.aiInput = ''; // // messages
// AI const previousButtonsMessage = messagesDiv.querySelectorAll('.preview-buttons');
previousButtonsMessage.forEach(buttons => buttons.remove());
const messageDiv = document.createElement('div');
messageDiv.className = 'message';
// 使 pre text
// css htmlpre set language
const preElement = document.createElement('pre');
preElement.textContent = text;
messageDiv.appendChild(preElement);
messagesDiv.appendChild(messageDiv);
if (sender === 'ai') {
// TODO css
preElement.textContent = `文心一言:\n` + preElement.textContent;
messageDiv.style.backgroundColor = '#bdc3c7';
// preview
const previewWrapper = document.createElement('div');
previewWrapper.style.border = '1px solid #ccc';
previewWrapper.style.display = 'flow-root';
// 'preview'
const previewLabel = document.createElement('div');
previewLabel.textContent = 'preview';
previewWrapper.appendChild(previewLabel);
//
const previewStyle = document.createElement('style');
//
// csscss,css,}
const cssRegex = /css([\s\S]*)\}/;
const cssMatch = cssRegex.exec(text);
if (cssMatch) {
previewStyle.textContent = cssMatch[0].replace('css', '');
}else{
previewStyle.textContent = text;
}
previewStyle.textContent = previewStyle.textContent.replace(/\.ck-content/g, '');
document.head.appendChild(previewStyle);
//
let previewElement;
// element tag class name
const styleRegex = /\.ck-content\s+([a-z]+)\.([a-z-]+)\s*\{/g;
let match;
const classNames = [];
while ((match = styleRegex.exec(text)) !== null) {
if (!previewElement) {
previewElement = document.createElement(match[1]);
previewElement.textContent = 'AaBbCcDdEeFf';
previewWrapper.appendChild(previewElement);
}
classNames.push(match[2]);
}
if (previewElement) {
previewElement.className = classNames.join(' ');
}
messagesDiv.appendChild(previewWrapper);
//
const buttonsMessageDiv = document.createElement('div');
buttonsMessageDiv.className = 'preview-buttons';
buttonsMessageDiv.style.display = 'flex';
buttonsMessageDiv.style.justifyContent = 'flex-end';
buttonsMessageDiv.style.marginTop = '10px';
// save
const saveButton = document.createElement('el-button');
saveButton.innerHTML = '<svg t="1731509644125" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6346" width="20" height="20"><path d="M512 1024C229.248 1024 0 794.752 0 512S229.248 0 512 0s512 229.248 512 512-229.248 512-512 512z m0-938.666667C276.352 85.333333 85.333333 276.352 85.333333 512s191.018667 426.666667 426.666667 426.666667 426.666667-191.018667 426.666667-426.666667S747.648 85.333333 512 85.333333z m-3.413333 611.541334a34.944 34.944 0 0 1-9.386667 16.682666 38.058667 38.058667 0 0 1-30.72 11.050667 38.954667 38.954667 0 0 1-40.106667-27.946667L308.053333 576.426667a38.4 38.4 0 0 1 54.186667-54.229334l93.013333 93.184 190.293334-337.365333a42.666667 42.666667 0 0 1 58.88-16.426667c20.608 12.714667 27.392 39.466667 15.36 60.458667l-211.2 374.826667z" fill="#000000" p-id="6347"></path></svg>';
saveButton.style.marginLeft = '10px';
saveButton.style.width = '40px';
saveButton.style.height = '40px';
saveButton.onclick = () => {
// save
// TODO
// 使style
// styleDefinition:[name: 'styleName',element: 'element',classes: [className]]
const styleName = prompt("请输入样式名称:");
// styleDefiniton
const styleDefinition = {
name: styleName,
element: previewElement.tagName,
classes: classNames
};
console.log(styleDefinition);
};
// clear
const clearButton = document.createElement('el-button');
clearButton.innerHTML = '<svg t="1731509926355" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8154" width="20" height="20"><path d="M1011.43552 981.92384l-68.4032-394.40384h23.10144c18.5856 0 33.54624-14.97088 33.54624-33.55648V306.16576c0-18.5856-14.97088-33.55648-33.54624-33.55648H648.6528V37.71392c0-18.5856-14.97088-33.55648-33.55648-33.55648H408.59648c-18.5856 0-33.55648 14.97088-33.55648 33.55648v234.88512H57.5488c-18.5856 0-33.54624 14.97088-33.54624 33.55648v247.79776c0 18.5856 14.97088 33.55648 33.54624 33.55648h23.10144L12.24704 981.9136c-0.38912 1.9456-0.512 3.87072-0.512 5.6832 0 18.5856 14.97088 33.54624 33.55648 33.54624h933.10976c1.93536 0 3.88096-0.12288 5.6832-0.512 18.31936-3.08224 30.57664-20.51072 27.35104-38.7072zM114.33984 362.94656h351.03744V94.50496h92.928v268.4416h351.03744v134.22592H114.33984V362.94656zM718.336 930.816V729.48736c0-5.6832-4.64896-10.33216-10.32192-10.33216h-61.952c-5.67296 0-10.32192 4.64896-10.32192 10.33216V930.816H387.9424V729.48736c0-5.6832-4.64896-10.33216-10.32192-10.33216h-61.952c-5.67296 0-10.32192 4.64896-10.32192 10.33216V930.816H112.78336l58.20416-335.55456h681.5744L910.76608 930.816H718.336z m0 0" fill="#2C2C2C" p-id="8155"></path></svg>';
clearButton.style.marginLeft = '10px';
clearButton.style.width = '40px';
clearButton.style.height = '40px';
clearButton.onclick = () => {
// clear message
const messagesDiv = document.getElementById('messages');
while (messagesDiv.firstChild) {
messagesDiv.removeChild(messagesDiv.firstChild);
}
// style
document.head.removeChild(previewStyle);
};
//
buttonsMessageDiv.appendChild(saveButton);
buttonsMessageDiv.appendChild(clearButton);
messagesDiv.appendChild(buttonsMessageDiv);
}
// messagesDiv.scrollTop = messagesDiv.scrollHeight;
},
sendMessage() {
const userInput = document.getElementById('userInput');
const messageText = userInput.value;
if (messageText.trim() === '') return;
// Display user's message
this.displayMessage(messageText, 'user');
// APIresponse
setTimeout(() => { setTimeout(() => {
this.messages.push({ text: '这是来自AI的回复' }); // Simulate AI response
let formatedResponse = `css
.ck-content p.marquee {
display: inline-block;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
padding: 0.5em;
background: linear-gradient(90deg, #ff7e5f, #feb47b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: marquee 10s linear infinite;
position: relative;
}
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
.ck-content p.marquee::selection {
background: rgba(255, 255, 255, 0.3); /* Ensure text is readable when selected */
}
/* Container to make sure the marquee stays within bounds */
.ck-content .marquee-container {
overflow: hidden;
white-space: nowrap;
width: 100%; /* Adjust as needed */
}
请注意由于使用了 inline-block文本框的长和宽会根据内容自动调整同时由于动画使用了 translateX 而不是 text-indent因此可以正确地处理不同长度的文本另外我添加了一个 .marquee-container 用于确保滚动文本保持在指定区域内例如不超出其父容器的边界您需要将 p.marquee 元素放置在一个具有 .marquee-container 类的元素内以确保动画效果正确
示例 HTML 结构
html
<div class="ck-content">
<div class="marquee-container">
<p class="marquee">这是一个带有流水灯效果的文本框它的宽度和高度会根据内容自动调整</p>
</div>
</div>
`;
this.displayMessage(formatedResponse, 'ai');
}, 1000); }, 1000);
userInput.value = '';
}, },
}, },
components: { components: {
// //
ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu,
} }
}; };
</script> </script>

@ -1,18 +1,101 @@
import { import {
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
Plugin, Plugin,
ButtonView, ButtonView,
createDropdown, createDropdown,
Collection, Collection,
addListToDropdown, addListToDropdown,
} from 'ckeditor5'; } from 'ckeditor5';
import translations from 'ckeditor5/translations/zh-cn.js';
import { asBlob } from 'html-docx-js-typescript' import { asBlob } from 'html-docx-js-typescript'
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { import {
getStyle, getStyle,
getPageContent getPageContent,
getUserConfigFromBackend,
saveData
} from './utils'; } from './utils';
// 导出为docx插件 // 导出为docx插件
function exportWord(){
const pageContent = getPageContent();
const style = getStyle();
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent + '</body></html>'
// console.log(page);
asBlob(page).then(data => {
saveAs(data, 'file.docx') // save as docx file
}); // asBlob() return Promise<Blob|Buffer>
}
class Export2Word extends Plugin { class Export2Word extends Plugin {
init() { init() {
const editor = this.editor; const editor = this.editor;
@ -27,22 +110,20 @@ class Export2Word extends Plugin {
tooltip: true, tooltip: true,
// 图标 直接插入svg文件 // 图标 直接插入svg文件
icon: '<svg t="1730216969869" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2612" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2613"></path><path d="M951.7 757.5c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7 0.1-1.1 0.1-2.1 0-3.2 0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4c0.4-0.6 0.6-0.9 0.8-1.1z" p-id="2614"></path><path d="M709.3 320.4c-17.4-2.9-33.9 8.9-36.8 26.3l-42.8 256.9-87.3-261.8c-4.4-13-16.6-21.8-30.4-21.8-13.8 0-26 8.8-30.4 21.9l-87.3 261.8-42.8-256.9c-2.9-17.4-19.4-29.2-36.8-26.3-17.4 2.9-29.2 19.4-26.3 36.8l64 384c2.4 14.5 14.4 25.5 29.1 26.6 14.7 1.1 28.2-7.8 32.9-21.8L512 453.2l66.4 199.1c3.9 11.8 15 19.8 27.4 19.8h52.9c14.1 0 26.2-10.2 28.5-24.1l48.4-290.6c2.9-17.6-8.9-34.1-26.3-37z" p-id="2615"></path></svg>' icon: '<svg t="1730216969869" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2612" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2613"></path><path d="M951.7 757.5c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7 0.1-1.1 0.1-2.1 0-3.2 0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4c0.4-0.6 0.6-0.9 0.8-1.1z" p-id="2614"></path><path d="M709.3 320.4c-17.4-2.9-33.9 8.9-36.8 26.3l-42.8 256.9-87.3-261.8c-4.4-13-16.6-21.8-30.4-21.8-13.8 0-26 8.8-30.4 21.9l-87.3 261.8-42.8-256.9c-2.9-17.4-19.4-29.2-36.8-26.3-17.4 2.9-29.2 19.4-26.3 36.8l64 384c2.4 14.5 14.4 25.5 29.1 26.6 14.7 1.1 28.2-7.8 32.9-21.8L512 453.2l66.4 199.1c3.9 11.8 15 19.8 27.4 19.8h52.9c14.1 0 26.2-10.2 28.5-24.1l48.4-290.6c2.9-17.6-8.9-34.1-26.3-37z" p-id="2615"></path></svg>',
keystroke: 'Ctrl+W'
}); });
// Execute a callback function when the button is clicked // Execute a callback function when the button is clicked
button.on('execute', () => { button.on('execute', () => {
const pageContent = getPageContent(); exportWord();
const style = getStyle();
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent + '</body></html>'
// console.log(page);
asBlob(page).then(data => {
saveAs(data, 'file.docx') // save as docx file
}); // asBlob() return Promise<Blob|Buffer>
}); });
// 添加快捷键 Ctrl+W 导出为docx
editor.keystrokes.set('Ctrl+W', (event, cancel) => {
exportWord();
cancel();
});
return button; return button;
}); });
@ -66,7 +147,22 @@ class Export2Word extends Plugin {
} }
} }
// 导出为PDF插件 // 导出为PDF插件
function printPDF() {
const pageContent = getPageContent();
console.log(pageContent);
const style = getStyle();
// 去掉element中的 ck-focused ck-weight_selected消除页面和图片的蓝边
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent.replaceAll('ck-focused', 'ck-blurred').replaceAll('ck-weight_selected', '') + '</body></html>'
const newWindow = window.open('', 'PrintDocument', 'height=600,width=700,top=50,left=50');
newWindow.document.write(page);
newWindow.document.close();
newWindow.print();
newWindow.onafterprint = function () {
newWindow.close();
}
}
class Export2PDF extends Plugin { class Export2PDF extends Plugin {
init() { init() {
const editor = this.editor; const editor = this.editor;
@ -81,30 +177,26 @@ class Export2PDF extends Plugin {
tooltip: true, tooltip: true,
// 图标 直接插入svg文件 // 图标 直接插入svg文件
icon: '<svg t="1730309942693" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2589" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2590"></path><path d="M960 734.4c0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4 0.7-0.7c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7-0.1-1.5-0.1-2.5-0.1-3.6zM352 320c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32s32-14.3 32-32V576h192c70.6 0 128-57.4 128-128s-57.4-128-128-128H352z m288 128c0 35.3-28.7 64-64 64H384V384h192c35.3 0 64 28.7 64 64z" p-id="2591"></path></svg>' icon: '<svg t="1730309942693" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2589" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2590"></path><path d="M960 734.4c0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4 0.7-0.7c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7-0.1-1.5-0.1-2.5-0.1-3.6zM352 320c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32s32-14.3 32-32V576h192c70.6 0 128-57.4 128-128s-57.4-128-128-128H352z m288 128c0 35.3-28.7 64-64 64H384V384h192c35.3 0 64 28.7 64 64z" p-id="2591"></path></svg>',
keystroke: 'Ctrl+P'
}); });
// Execute a callback function when the button is clicked // Execute a callback function when the button is clicked
button.on('execute', () => { button.on('execute', () => {
const pageContent = getPageContent(); printPDF();
console.log(pageContent); });
const style = getStyle();
// 去掉element中的 ck-focused ck-weight_selected消除页面和图片的蓝边 // 添加快捷键 Ctrl+P 导出为PDF
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent.replaceAll('ck-focused', 'ck-blurred').replaceAll('ck-weight_selected', '') + '</body></html>' editor.keystrokes.set('Ctrl+P', (event, cancel) => {
const newWindow = window.open('', 'PrintDocument', 'height=600,width=700,top=50,left=50'); printPDF();
newWindow.document.write(page); cancel();
newWindow.document.close();
newWindow.print();
newWindow.onafterprint = function () {
newWindow.close();
}
}); });
return button; return button;
}); });
} }
} }
// 智能润色插件 // 智能润色插件
class Translation extends Plugin { class Translation extends Plugin {
init() { init() {
@ -174,8 +266,9 @@ class Translation extends Plugin {
}); });
} }
} }
// 侧边栏
class ToggleSideBar extends Plugin{ // 侧边栏按钮
class ToggleSideBar extends Plugin {
// constructor(toggleSidebar) { // constructor(toggleSidebar) {
// super(); // super();
// this.toggleSidebar = toggleSidebar; // this.toggleSidebar = toggleSidebar;
@ -200,7 +293,6 @@ class ToggleSideBar extends Plugin{
button.on('execute', () => { button.on('execute', () => {
// 打开sidebar // 打开sidebar
const bt = document.getElementById("toggleSidebarButton"); const bt = document.getElementById("toggleSidebarButton");
console.log(bt);
bt.click(); bt.click();
}); });
@ -209,4 +301,288 @@ class ToggleSideBar extends Plugin{
} }
} }
export { Export2Word, Export2PDF, Translation, ToggleSideBar }; // 保存按钮
class SaveButton extends Plugin {
// constructor(toggleSidebar) {
// super();
// this.toggleSidebar = toggleSidebar;
// }
init() {
const editor = this.editor;
editor.ui.componentFactory.add('SaveButton', () => {
// The button will be an instance of ButtonView.
const button = new ButtonView();
button.set({
label: '保存',
// withText: true
tooltip: true,
// 图标 直接插入svg文件
icon: '<svg t="1731573509489" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5448" width="200" height="200"><path d="M933.376 977.066667H89.429333A45.952 45.952 0 0 1 42.666667 930.261333V89.472C42.666667 62.933333 62.933333 42.666667 89.472 42.666667H618.24c26.538667 0 46.805333 20.266667 46.805333 46.805333 0 26.496-20.266667 46.805333-46.805333 46.805333H136.277333v747.178667h750.293334V404.565333c0-26.496 20.309333-46.805333 46.805333-46.805333 26.538667 0 46.805333 20.309333 46.805333 46.805333v525.696a46.933333 46.933333 0 0 1-46.805333 46.805334z" fill="#172B4D" p-id="5449"></path><path d="M933.376 451.370667c-12.458667 0-23.381333-4.693333-32.768-14.08l-315.093333-315.050667c-18.730667-18.773333-18.730667-48.384 0-65.536 18.730667-18.730667 48.384-18.730667 65.536 0l315.093333 313.557333c18.730667 18.730667 18.730667 48.341333 0 65.493334a43.52 43.52 0 0 1-32.768 15.616zM724.352 925.610667a45.952 45.952 0 0 1-46.805333-46.805334v-258.986666H345.301333v258.986666c0 26.496-20.266667 46.762667-46.805333 46.762667a45.952 45.952 0 0 1-46.805333-46.762667V573.013333c0-26.538667 20.266667-46.805333 46.805333-46.805333h425.856c26.538667 0 46.805333 20.266667 46.805333 46.805333v305.749334a46.933333 46.933333 0 0 1-46.805333 46.762666zM724.352 423.253333H298.496a45.952 45.952 0 0 1-46.805333-46.762666V133.162667c0-26.538667 20.266667-46.805333 46.805333-46.805334s46.805333 20.266667 46.805333 46.805334v196.522666h379.050667c26.538667 0 46.805333 20.266667 46.805333 46.805334a46.933333 46.933333 0 0 1-46.805333 46.805333z" fill="#172B4D" p-id="5450"></path></svg>',
keystroke: 'Ctrl+S'
});
// Execute a callback function when the button is clicked
button.on('execute', () => {
saveData(getPageContent())
});
return button;
});
// 添加快捷键 Ctrl+S 保存
editor.keystrokes.set('Ctrl+S', (event, cancel) => {
saveData(getPageContent());
cancel();
});
}
}
// 配置CKEditor5
function setConfig() {
// 获取用户的样式配置
const userConfig = getUserConfigFromBackend();
return {
toolbar: {
items: [
'undo',
'redo',
'|',
'heading',
'style',
'|',
'fontSize',
'fontFamily',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'|',
'link',
'insertImage',
'insertTable',
'highlight',
'codeBlock',
'blockquote',
'|',
'alignment',
'bulletedList',
'numberedList',
'outdent',
'indent',
'|', 'ExportToWord', 'ExportToPDF', 'translate', 'SideBar', 'SaveButton'
],
shouldNotGroupWhenFull: true
},
plugins: [
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
Export2Word, Translation, Export2PDF, ToggleSideBar, SaveButton
],
balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'],
//自定义设置字体
fontFamily: {
// 自定义字体
options: userConfig.fontFamily.options,
// 启用对所有字体名称的支持
supportAllValues: true,
},
fontSize: {
// 五号,小四,四号,小三,三号,小二,二号
options: userConfig.fontSize.options,
supportAllValues: true
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage'
]
},
initialData:
'',
language: 'zh-cn',
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
mention: {
feeds: [
{
marker: '@',
feed: [
/* See: https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html */
]
}
]
},
menuBar: {
isVisible: true,
removeItems: ['help'],
},
placeholder: 'Type or paste your content here!',
// 用户可以自定义和管理样式
style: {
definitions: userConfig.style.definitions
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
autosave: {
waitingTime: 180000, // (in ms) 3minutes
save() {
// TODO save
return saveData(getPageContent());
}
},
translations: [translations]
}
}
export { Export2Word, Export2PDF, Translation, ToggleSideBar, setConfig };

@ -75,11 +75,6 @@ export function getUserConfigFromBackend() {
name: 'Code (bright)', name: 'Code (bright)',
element: 'pre', element: 'pre',
classes: ['fancy-code', 'fancy-code-bright'] classes: ['fancy-code', 'fancy-code-bright']
},
{
name: 'GradientBorder',
element: 'p',
classes: ['gradientborder']
} }
] ]
} = options; } = options;

@ -9,7 +9,8 @@ module.exports = defineConfig({
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
AutoImport({ AutoImport({
resolvers: [ElementPlusResolver()] resolvers: [ElementPlusResolver(),]
// 自动导入图标组件
}), }),
Components({ Components({
resolvers: [ElementPlusResolver()] resolvers: [ElementPlusResolver()]

Loading…
Cancel
Save