diff --git a/ckeditor5/handbook.md b/ckeditor5/handbook.md index 7f18198..602ba84 100644 --- a/ckeditor5/handbook.md +++ b/ckeditor5/handbook.md @@ -11,7 +11,63 @@ npm run serve 直接修改`App.vue`,见Export2Word插件 #### Export2Word插件 -bug1:图片只能插入url而不是base64,否则导出错误,导出的内容会在图片前截断 +bug1:图片导出为word会恢复原始大小,而不是在编辑器中显示的大小。 +bug2: 导出格式部分错误,如字体颜色、字体背景颜色、高亮 -bug2: 导出格式部分错误,如字体背景颜色等,应该是style没有提取出 +#### Export2PDF插件 +bug1: 导出后编辑器框会显示出被选中的颜色 已修复√ +bug2: 导出格式部分错误,字体颜色可以正常显示但背景颜色和高亮不行 +#### 智能润色 +选中需要处理的文本后,点击对应按钮即可触发实现,对应逻辑需要实现,框架已搭建。见插件Translate + +#### 智能格式排版 +· 样式库的管理和编辑 +利用ckeditor5已有插件`style`进行配置。可以对符合要求的block等应用选中的样式。只需要设定好样式库,添加`大模型生成css样式``用户自定义样式管理`即可 +https://ckeditor.com/docs/ckeditor5/latest/features/style.html + +编辑器初始化时 +``` +// 配置文件定义样式 +import { ClassicEditor, Style } from 'ckeditor5'; + +ClassicEditor + .create( document.querySelector( '#editor' ), { + plugins: [ Style, /* ... */ ], + toolbar: { + items: [ + 'style', + // More toolbar items. + // ... + ], + }, + style: { + definitions: [ + // Styles definitions. + // ... + ] + } + } ) + .then( /* ... */ ) + .catch( /* ... */ ); +``` +``` +// 定义对应样式的css格式 +.ck.ck-content h3.category { + font-family: 'Bebas Neue'; + font-size: 20px; + font-weight: bold; + color: #d1d1d1; + letter-spacing: 10px; + margin: 0; + padding: 0; +} + +.ck.ck-content p.info-box { + padding: 1.2em 2em; + border: 1px solid #e91e63; + border-left: 10px solid #e91e63; + border-radius: 5px; + margin: 1.5em; +} +``` diff --git a/ckeditor5/public/style.css b/ckeditor5/public/style.css index 4a11039..71a1451 100644 --- a/ckeditor5/public/style.css +++ b/ckeditor5/public/style.css @@ -6,10 +6,12 @@ margin: 0 !important; } } + .main-container { - --ckeditor5-preview-height: 700px; + /* --ckeditor5-preview-height: 700px; */ font-family: '宋体'; width: fit-content; + height: 100%; margin-left: auto; margin-right: auto; } @@ -35,7 +37,7 @@ box-shadow: 0 2px 3px hsla(0, 0%, 0%, 0.078); } -.editor-container_document-editor .editor-container__toolbar > .ck.ck-toolbar { +.editor-container_document-editor .editor-container__toolbar>.ck.ck-toolbar { flex-grow: 1; width: 0; border-bottom-right-radius: 0; @@ -45,7 +47,7 @@ border-right: 0; } -.editor-container_document-editor .editor-container__menu-bar > .ck.ck-menu-bar { +.editor-container_document-editor .editor-container__menu-bar>.ck.ck-menu-bar { border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-top: 0; @@ -114,18 +116,14 @@ --background-color: #e91e63; padding: 1.2em 2em; border: 1px solid var(--background-color); - background: linear-gradient( - 135deg, + background: linear-gradient(135deg, var(--background-color) 0%, var(--background-color) var(--background-size), - transparent var(--background-size) - ), - linear-gradient( - 135deg, + transparent var(--background-size)), + linear-gradient(135deg, transparent calc(100% - var(--background-size)), var(--background-color) calc(100% - var(--background-size)), - var(--background-color) - ); + var(--background-color)); border-radius: 10px; margin: 1.5em 2em; box-shadow: 5px 5px 0 #ffe6ef; @@ -207,3 +205,66 @@ color: #000; box-shadow: 5px 5px 0 #b3b3b3; } + +.ck-content p.gradientborder { + --borderWidth: 12px; + --bRadius: 5px; + width: 60%; + height: 60%; + position: relative; + z-index: 0; + overflow: hidden; + padding: 2rem; + z-index: 0; + border-radius: --bRadius; + + &::after, + &::before { + box-sizing: border-box; + } + + &::before { + content: ''; + position: absolute; + left: -50%; + top: -50%; + width: 200%; + height: 200%; + z-index: -2; + background-repeat: no-repeat; + background-size: 50% 50%, 50% 50%; + background-position: 0 0, 100% 0, 100% 100%, 0 100%; + background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5); + animation: rotate 4s linear infinite; + + @keyframes rotate { + 100% { + transform: rotate(1turn); + } + } + } + + &::after { + content: ''; + position: absolute; + z-index: -1; + left: calc(var(--borderWidth) / 2); + top: calc(var(--borderWidth) / 2); + width: calc(100% - var(--borderWidth)); + height: calc(100% - var(--borderWidth)); + background: white; + border-radius: --bRadius; + /* 这一行是为了方便查看原来的样子的 */ + animation: opacityChange 3s infinite alternate; + } + + @keyframes opacityChange { + 50% { + opacity: 1; + } + + 100% { + opacity: .5; + } + } +} \ No newline at end of file diff --git a/ckeditor5/src/App.vue b/ckeditor5/src/App.vue index 37c2ea5..d2e0b9a 100644 --- a/ckeditor5/src/App.vue +++ b/ckeditor5/src/App.vue @@ -29,6 +29,7 @@ import { Autosave, BalloonToolbar, Base64UploadAdapter, + BlockQuote, Bold, Code, CodeBlock, @@ -76,7 +77,7 @@ import { SpecialCharactersMathematical, SpecialCharactersText, Strikethrough, - // Style, + Style, Subscript, Superscript, Table, @@ -98,9 +99,10 @@ import { // SplitButtonView, Collection, addListToDropdown, + // Position, // Model, // addToolbarToDropdown - + } from 'ckeditor5'; // import translations from 'ckeditor5/translations/zh-cn.js'; @@ -116,18 +118,24 @@ import { saveAs } from 'file-saver'; // import SplitButtonView from "@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview"; // 将当前页面的样式转为内联 -function getStyle(){ +function getStyle() { let str = '
'; const styles = document.querySelectorAll('style'); - for(let i = 0; i < styles.length; i++){ - str+=styles[i].outerHTML; + for (let i = 0; i < styles.length; i++) { + str += styles[i].outerHTML; } str += ""; - str += "" - // str += "" - str += "" + str += "" + // str += "" + str += "" return str; } +// 获取用户编辑的内容 +function getPageContent(){ + const pageContent = document.querySelector("#app > div > div > div > div.editor-container__editor-wrapper > div > div > div.ck.ck-reset.ck-editor.ck-rounded-corners > div.ck.ck-editor__main") + return pageContent.innerHTML; +} +// 导出为docx插件 class Export2Word extends Plugin { init() { const editor = this.editor; @@ -148,10 +156,10 @@ class Export2Word extends Plugin { // Execute a callback function when the button is clicked button.on('execute', () => { - const pageContent = document.querySelector("#app > div > div > div > div.editor-container__editor-wrapper > div > div > div.ck.ck-reset.ck-editor.ck-rounded-corners > div.ck.ck-editor__main") - const style=getStyle(); - const page = ''+style+'' + pageContent.outerHTML + '' - + const pageContent = getPageContent(); + const style = getStyle(); + const page = '' + style + '' + pageContent + '' + // console.log(page); asBlob(page).then(data => { saveAs(data, 'file.docx') // save as docx file @@ -160,28 +168,29 @@ class Export2Word extends Plugin { return button; }); - + // 增加菜单栏? 不显示按钮 - editor.ui.extendMenuBar({ - menu: { - menuId: 'export', - label: '导出', - groups: [ - { - groupId: 'export', - items: [ - 'ExportToWord' - ] - } - ] - }, - position: 'after:help' - } - ); + // editor.ui.extendMenuBar({ + // menu: { + // menuId: 'export', + // label: '导出', + // groups: [ + // { + // groupId: 'export', + // items: [ + // 'ExportToWord' + // ] + // } + // ] + // }, + // position: 'after:help' + // } + // ); } } -class Export2PDF extends Plugin{ +// 导出为PDF插件 +class Export2PDF extends Plugin { init() { const editor = this.editor; @@ -201,14 +210,15 @@ class Export2PDF extends Plugin{ // Execute a callback function when the button is clicked button.on('execute', () => { - const pageContent = document.querySelector("#app > div > div > div > div.editor-container__editor-wrapper > div > div > div.ck.ck-reset.ck-editor.ck-rounded-corners > div.ck.ck-editor__main"); - const style=getStyle(); - const page = ''+style+'' + pageContent.outerHTML + '' - const newWindow = window.open('','PrintDocument', 'height=600,width=700,top=50,left=50'); + const pageContent = getPageContent(); + const style = getStyle(); + // 去掉element中的 ck-focused ck-weight_selected消除页面和图片的蓝边 + const page = '' + style + '' + pageContent.replaceAll('ck-focused', 'ck-blurred').replaceAll('ck-weight_selected', '') + '' + 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.onafterprint = function () { newWindow.close(); } }); @@ -217,76 +227,75 @@ class Export2PDF extends Plugin{ }); } } - +// 智能润色插件 class Translation extends Plugin { - init() { - // console.log('Translation initialized!'); + init() { + // console.log('Translation initialized!'); - this.editor.ui.componentFactory.add('translate', (locale) => { - const dropdownView = createDropdown(locale); - dropdownView.buttonView.set({ - label: '智能助手', - withText: true, - }); + this.editor.ui.componentFactory.add('translate', (locale) => { + const dropdownView = createDropdown(locale); + dropdownView.buttonView.set({ + label: '智能助手', + withText: true, + }); - const items = new Collection(); - items.add( { - type: 'button', - model: { - id: 'summary', - withText: true, - label: '摘要', - } - } ); - items.add( { - type: 'button', - model: { - id: 'decoration', - withText: true, - label: '润色' - } - } ); - items.add( { - type: 'button', - model: { - id: 'extension', - withText: true, - label: '续写' - } - } ); - items.add( { - type: 'button', - model: { - id: 'correction', - withText: true, - label: '修改' - } - } ); - items.add( { - type: 'button', - model: { - id: 'translation', - withText: true, - label: '翻译' - } - } ); - addListToDropdown(dropdownView, items); + const items = new Collection(); + items.add({ + type: 'button', + model: { + id: 'summary', + withText: true, + label: '摘要', + } + }); + items.add({ + type: 'button', + model: { + id: 'decoration', + withText: true, + label: '润色' + } + }); + items.add({ + type: 'button', + model: { + id: 'extension', + withText: true, + label: '续写' + } + }); + items.add({ + type: 'button', + model: { + id: 'correction', + withText: true, + label: '修改' + } + }); + items.add({ + type: 'button', + model: { + id: 'translation', + withText: true, + label: '翻译' + } + }); + addListToDropdown(dropdownView, items); - dropdownView.on('execute', (eventInfo) => { - const {id,label} = eventInfo.source; + dropdownView.on('execute', (eventInfo) => { + const { id, label } = eventInfo.source; // 获取选中的文本,用来进行后续操作 const selectionText = window.getSelection().toString(); - if ( id === 'summary' ) { + if (id === 'summary') { // this.editor.execute('ExportToWord'); - console.log('Object (en):', label, selectionText); - } - }); + console.log('Object (en):', label, selectionText); + } + }); - return dropdownView; - }); - } + return dropdownView; + }); + } } - export default { name: 'app', data() { @@ -294,10 +303,11 @@ export default { isLayoutReady: false, config: null, // CKEditor needs the DOM tree before calculating the configuration. // editor: DecoupledEditor - editor:ClassicEditor + editor: ClassicEditor }; }, mounted() { + const userConfig = getUserConfigFromBackend(); this.config = { toolbar: { items: [ @@ -305,7 +315,7 @@ export default { 'redo', '|', 'heading', - // 'style', + 'style', '|', 'fontSize', 'fontFamily', @@ -321,14 +331,14 @@ export default { 'insertTable', 'highlight', 'codeBlock', + 'blockquote', '|', 'alignment', - '|', 'bulletedList', 'numberedList', 'outdent', 'indent', - '|', 'ExportToWord','ExportToPDF','translate' + '|', 'ExportToWord', 'ExportToPDF', 'translate' ], shouldNotGroupWhenFull: true }, @@ -341,6 +351,7 @@ export default { Autosave, BalloonToolbar, Base64UploadAdapter, + BlockQuote, Bold, Code, CodeBlock, @@ -388,7 +399,7 @@ export default { SpecialCharactersMathematical, SpecialCharactersText, Strikethrough, - // Style, + Style, Subscript, Superscript, Table, @@ -401,37 +412,19 @@ export default { TodoList, Underline, Undo, - Export2Word,Translation,Export2PDF + Export2Word, Translation, Export2PDF ], balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'], //自定义设置字体 fontFamily: { - // 显示所有字体 + // 自定义字体 + options: userConfig.fontFamily.options, + // 启用对所有字体名称的支持 supportAllValues: true, - // 增加字体 - options: [ - 'default', - '宋体', - '新宋体', - '仿宋', - '楷体', - '微软雅黑', - '黑体', - '华文仿宋', - '华文楷体', - '华文隶书', - '华文宋体', - '华文细黑', - '华文新魏', - '华文行楷', - '华文中宋', - '隶书', - '苹方 常规', - '幼圆', - ], }, fontSize: { - options: [10, 12, 14, 'default', 18, 20, 22], + // 五号,小四,四号,小三,三号,小二,二号 + options: userConfig.fontSize.options, supportAllValues: true }, heading: { @@ -501,6 +494,9 @@ export default { 'resizeImage' ] }, + // 初始化数据,新建页面时 显示空白/选择的模板 + // 打开已有页面时将页面数据传入 + // TODO initialData: '\n You\'ve successfully created a CKEditor 5 project. This powerful text editor will enhance your application, enabling rich text editing\n capabilities that are customizable and easy to use.\n
\n\n Keep experimenting, and don\'t hesitate to push the boundaries of what you can achieve with CKEditor 5. Your feedback is invaluable to us\n as we strive to improve and evolve. Happy editing!\n
\n\n See this text, but the editor is not starting up? Check the browser\'s console for clues and guidance. It may be related to an incorrect\n license key if you use premium features or another feature-related requirement. If you cannot make it work, file a GitHub issue, and we\n will help as soon as possible!\n
\n', language: 'zh-cn', @@ -536,60 +532,24 @@ export default { }, menuBar: { isVisible: true, + removeItems: [ 'help' ], }, placeholder: 'Type or paste your content here!', - // style: { - // definitions: [ - // { - // name: 'Article category', - // element: 'h3', - // classes: ['category'] - // }, - // { - // name: 'Title', - // element: 'h2', - // classes: ['document-title'] - // }, - // { - // name: 'Subtitle', - // element: 'h3', - // classes: ['document-subtitle'] - // }, - // { - // name: 'Info box', - // element: 'p', - // classes: ['info-box'] - // }, - // { - // name: 'Side quote', - // element: 'blockquote', - // classes: ['side-quote'] - // }, - // { - // name: 'Marker', - // element: 'span', - // classes: ['marker'] - // }, - // { - // name: 'Spoiler', - // element: 'span', - // classes: ['spoiler'] - // }, - // { - // name: 'Code (dark)', - // element: 'pre', - // classes: ['fancy-code', 'fancy-code-dark'] - // }, - // { - // name: 'Code (bright)', - // element: 'pre', - // classes: ['fancy-code', 'fancy-code-bright'] - // } - // ] - // }, + // 用户可以自定义和管理样式 + 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] }; @@ -605,4 +565,118 @@ export default { } } }; +// 更灵活配置 +function getUserConfigFromBackend(){ + // TODO 请求用户配置 + const options={} + // 字体、字号、样式 + // TODO + const { + fontFamilyOptions = [ + 'default', + '宋体', + '新宋体', + '仿宋', + '楷体', + '微软雅黑', + '黑体', + '华文仿宋', + '华文楷体', + '华文隶书', + '华文宋体', + '华文细黑', + '华文新魏', + '华文行楷', + '华文中宋', + '隶书', + '苹方 常规', + '幼圆', + 'Times New Roman' + ], + // 五号,小四,四号,小三,三号,小二,二号 + fontSizeOptions = [10.5, 12, 14,15, 'default', 18,22], + styleDefinitions = [ + { + name: 'Article category', + element: 'h3', + classes: ['category'] + }, + { + name: 'Title', + element: 'h2', + classes: ['document-title'] + }, + { + name: 'Subtitle', + element: 'h3', + classes: ['document-subtitle'] + }, + { + name: 'Info box', + element: 'p', + classes: ['info-box'] + }, + { + name: 'Side quote', + element: 'blockquote', + classes: ['side-quote'] + }, + { + name: 'Marker', + element: 'span', + classes: ['marker'] + }, + { + name: 'Spoiler', + element: 'span', + classes: ['spoiler'] + }, + { + name: 'Code (dark)', + element: 'pre', + classes: ['fancy-code', 'fancy-code-dark'] + }, + { + name: 'Code (bright)', + element: 'pre', + classes: ['fancy-code', 'fancy-code-bright'] + }, + { + name: 'GradientBorder', + element: 'p', + classes: ['gradientborder'] + } + ] + } = options; + // 如果传入的options没有对应项,使用默认值 + return { + fontFamily: { + options: fontFamilyOptions + }, + fontSize: { + options: fontSizeOptions + }, + style: { + definitions: styleDefinitions + } + }; +} +// TODO 实现自动保存saveData方法,将编辑内容发送至后端 +function saveData( data ) { + // return new Promise( resolve => { + // setTimeout( () => { + // console.log( 'Saved', data ); + + // resolve(); + // }, HTTP_SERVER_LAG ); + // } ); + console.log(data); +} +// TODO 手动保存 +// function saveByHand(){ + +// this.editor.plugins.get('Autosave').save().then(() => { +// console.log('Data saved successfully'); +// }); +// }