From 022d7e16c312806aed4d4800afb167439fbf7624 Mon Sep 17 00:00:00 2001 From: joefalmko Date: Mon, 9 Dec 2024 02:17:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84ai=E6=8E=92=E7=89=88=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=B0=86=E6=96=87=E4=BB=B6=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=A4=A7=E6=A8=A1=E5=9E=8B=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=8E=92=E7=89=88=E5=90=8E=EF=BC=8C=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E7=94=A8=E6=88=B7=E5=AE=9A=E4=B9=89=E7=9A=84=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E8=A7=84=E5=88=99=EF=BC=8C=E5=A6=82=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=9D=97=E3=80=81=E5=9D=97=E5=BC=95=E7=94=A8=E3=80=81=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E6=A0=B7=E5=BC=8F=E7=AD=89=EF=BC=8C=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E6=AF=8F=E7=BA=A7=E6=A0=87=E9=A2=98=E7=9A=84?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=A6=82=EF=BC=88=E4=B8=80=EF=BC=89=20?= =?UTF-8?q?=E4=B8=80=E3=80=81=20I.=20=E7=AD=89=E5=B9=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=EF=BC=8C=E4=B8=8D=E4=BC=9A=E6=94=B9=E5=8F=98?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E7=9A=84=E5=9B=BE=E7=89=87=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BB=85=E5=AF=B9=E6=96=87=E6=9C=AC=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E4=B8=8D=E4=BC=9A=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E6=96=87=E6=9C=AC=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/app/prompt/layout_generate.prompt | 1 + coeditor_frontend/src/components/plugins.js | 379 ++++++++++++------ coeditor_frontend/src/components/utils.js | 56 ++- .../src/public/generatedStyle.css | 205 ++++++++++ coeditor_frontend/src/public/style.css | 187 --------- coeditor_frontend/src/views/CkeditorView.vue | 1 + 6 files changed, 517 insertions(+), 312 deletions(-) create mode 100644 coeditor_frontend/src/public/generatedStyle.css diff --git a/GinSkeleton/storage/app/prompt/layout_generate.prompt b/GinSkeleton/storage/app/prompt/layout_generate.prompt index f084a58..a1a6ab8 100644 --- a/GinSkeleton/storage/app/prompt/layout_generate.prompt +++ b/GinSkeleton/storage/app/prompt/layout_generate.prompt @@ -1,4 +1,5 @@ 请将以下文章进行重新排版,使其格式更加美观且井然有序,同时保留文章的所有原始内容和文字,不要对内容进行任何修改。 请你调整标题样式,将所有标题改为清晰的分级结构。文章标题使用#,一级标题使用##,以此类推。 正文部分的段落需适当分段,避免过长或过短。 请确保整体排版整洁、易于阅读。 +所有的图片已被替换为tag“图片1”、“图片2”等,在处理时不要考虑图片本身,不要删除这些tag。 不要对文章的原始内容进行修改。只返回重新排版后的内容,不要返回其他的额外内容。 \ No newline at end of file diff --git a/coeditor_frontend/src/components/plugins.js b/coeditor_frontend/src/components/plugins.js index 0c80b03..af33b58 100644 --- a/coeditor_frontend/src/components/plugins.js +++ b/coeditor_frontend/src/components/plugins.js @@ -83,12 +83,13 @@ import { getPageContent, getUserConfigFromBackend, saveData, - // markdown2html, - html2markdown + markdown2html, + html2markdown, + getUserAILayoutConfig } from './utils'; import mitt from 'mitt'; // 导出为docx插件 -function exportWord(){ +function exportWord() { const pageContent = getPageContent(); const style = getStyle(); const page = '' + style + '' + pageContent + '' @@ -120,7 +121,7 @@ class Export2Word extends Plugin { button.on('execute', () => { exportWord(); }); - + // 添加快捷键 Ctrl+W 导出为docx editor.keystrokes.set('Ctrl+W', (event, cancel) => { exportWord(); @@ -202,53 +203,53 @@ class Export2PDF extends Plugin { // 智能润色发送消息 function sendDORMsg(type, fullText) { - - // 获取选中的文本,用来进行后续操作 - const selectionText = window.getSelection().toString(); - if (selectionText.trim() === '') return; - const formData = new FormData(); - formData.append('doc', selectionText); - formData.append('background', fullText); - formData.append('type', type); - const requestOptions= { - method: 'POST', - body: formData - }; - - console.log("formData:", formData); - const store = window.store; - const res = {oldContent:selectionText, newContent:'...'}; - store.commit('setCurrentTag', type); - store.commit('addContentToTag', {tag:type, newContent:res}); - const index = store.getters.getCurrentindex; - console.log("index", index); - emitter.emit('show-refine-doc-sidebar', type); - - fetch("/web_api/admin/ai_doc/doc_refine", requestOptions) - .then(response => { - if (!response.ok) { - throw new Error("请求出错"); - } - - // 根据返回的数据格式进行相应处理,这里假设返回的数据是JSON格式,所以使用response.json()解析 - return response.json(); - }) - .then(data => { - - // console.log("useStore", store); - const newRes = {oldContent:selectionText, newContent:data.data.new_doc}; - store.commit('changeContentForTag', {tag:type, index:index, newContent:newRes}); - emitter.emit('show-refine-doc-sidebar', type); - }) - .catch(error => { - console.error("POST请求出错:", error); - }); - + + // 获取选中的文本,用来进行后续操作 + const selectionText = window.getSelection().toString(); + if (selectionText.trim() === '') return; + const formData = new FormData(); + formData.append('doc', selectionText); + formData.append('background', fullText); + formData.append('type', type); + const requestOptions = { + method: 'POST', + body: formData + }; + + console.log("formData:", formData); + const store = window.store; + const res = { oldContent: selectionText, newContent: '...' }; + store.commit('setCurrentTag', type); + store.commit('addContentToTag', { tag: type, newContent: res }); + const index = store.getters.getCurrentindex; + console.log("index", index); + emitter.emit('show-refine-doc-sidebar', type); + + fetch("/web_api/admin/ai_doc/doc_refine", requestOptions) + .then(response => { + if (!response.ok) { + throw new Error("请求出错"); + } + + // 根据返回的数据格式进行相应处理,这里假设返回的数据是JSON格式,所以使用response.json()解析 + return response.json(); + }) + .then(data => { + + // console.log("useStore", store); + const newRes = { oldContent: selectionText, newContent: data.data.new_doc }; + store.commit('changeContentForTag', { tag: type, index: index, newContent: newRes }); + emitter.emit('show-refine-doc-sidebar', type); + }) + .catch(error => { + console.error("POST请求出错:", error); + }); + } class RefineDoc extends Plugin { - init() { + init() { // console.log('Translation initialized!'); this.editor.ui.componentFactory.add('RefineDoc', (locale) => { @@ -302,14 +303,14 @@ class RefineDoc extends Plugin { addListToDropdown(dropdownView, items); dropdownView.on('execute', (eventInfo) => { - const id = eventInfo.source.id; - sendDORMsg(id, ''); - }); + const id = eventInfo.source.id; + sendDORMsg(id, ''); + }); - return dropdownView; - }); - } - } + return dropdownView; + }); + } +} // 侧边栏按钮 class ToggleSideBar extends Plugin { @@ -384,22 +385,162 @@ class SaveButton extends Plugin { } } // AI 自动排版 -function aiformat(){ +async function aiformat() { console.log("ai formatting") const editor = window.editor; - const doc_content = editor.getData() + let doc_content = editor.getData() console.log(doc_content); // TODO 处理html文件 // step 1 - split images and insert text tag + // match
+ const img_tag = /(.*?)<\/figure>/g + const img_list = doc_content.match(img_tag) + console.log(img_list) + // replace img tag with text tag + if (img_list) { + console.log("replace img tag") + for (let i = 0; i < img_list.length; i++) { + const img = img_list[i] + const text = `图片${i + 1}` + doc_content = doc_content.replace(img, text) + } + } const markdown_content = html2markdown(doc_content) console.log(markdown_content) - // TODO 请求大模型 // step 2 - convert markdown response to html text and setData + // 向后端调用API并接受response + var result = '' + try { + // const response = await fetch('/web_api/admin/ai_layout/layout_generate', { + const response = await fetch('http://localhost:14514/admin/ai_layout/layout_generate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + doc_content: markdown_content, + }) + }); + + if (!response.body) { + throw new Error('No response body'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder('utf-8'); + /* eslint-disable no-constant-condition */ + let i = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const slice = decoder.decode(value, { stream: true }); + result += slice; + i++; + if (i % 10 == 0) { + // 流式展示ai排版后的内容 + const html_content = markdown2html(result) + editor.setData(html_content) + } + } + /* eslint-enable no-constant-condition */ + } catch (error) { + console.error('Error:', error); + } // step 3 - recover original images + const markdown_response = result + let html_content = markdown2html(markdown_response) + + console.log("html_content:\n\n",html_content) + // insert original img tag + if (img_list) { + console.log("insert img tag") + for (let i = 0; i < img_list.length; i++) { + const img = img_list[i] + const text = `图片${i + 1}` + html_content = html_content.replace(text, img) + } + } + editor.setData(html_content) // step 4 - fetch users config + const user_config = getUserAILayoutConfig() + // step 5 - apply title styles of user - // step 6 - apply others styles of user + // get document element + const pageContent = document.querySelector("#app > div > div > div.editor-container.editor-container_document-editor.editor-container_include-style > div.editor-container__editor-wrapper > div > div > div") + // 处理title样式 + let title = pageContent.querySelector("h1") + if (title) { + title.classList.add(user_config.titleStyle.option) + } + + // 处理一二三级heading样式 + for (let i = 0; i < 3; i++) { + const heading = pageContent.querySelectorAll(`h${i + 2}`); + if (heading.length > 0) { + const headingTag = user_config.headingStyle.option[i][0]; + const headingClass = user_config.headingStyle.option[i][1]; + console.log("headingTag:\n\n",headingTag) + console.log("headingClass:\n\n",headingClass) + // for each element + heading.forEach((element) => { + // reset counter for heading + const parentNode = element.parentNode; + let currentCounterReset = parentNode.style.counterReset; + if (currentCounterReset) { + // currentCounterReset 不存在该counterreset + if (currentCounterReset.indexOf(headingClass + "counter") == -1) { + currentCounterReset += " " + headingClass + "counter"; + } + } else { + currentCounterReset = headingClass + "counter"; + } + parentNode.style.setProperty('counter-reset', currentCounterReset); + element.classList.add(headingTag,headingClass); + }) + } + } + + // 处理正文样式 + const paragraph = pageContent.querySelectorAll("p") + if (paragraph.length > 0) { + for (let i = 0; i < paragraph.length; i++) { + const element = paragraph[i]; + for(let i = 0; i < user_config.bodyStyle.option.length; i++) { + element.classList.add(user_config.bodyStyle.option[i]); + } + } + } + + // 处理块引用样式 + const blockquote = pageContent.querySelectorAll("blockquote") + if (blockquote.length > 0) { + for (let i = 0; i < blockquote.length; i++) { + const element = blockquote[i]; + for(let i = 0; i < user_config.blockquote.option.length; i++) { + element.classList.add(user_config.blockquote.option[i]); + } + } + } + + // 处理代码块样式 + const pre = pageContent.querySelectorAll("pre") + if (pre.length > 0) { + for (let i = 0; i < pre.length; i++) { + const element = pre[i]; + for(let i = 0; i < user_config.codeBlockStyle.option.length; i++) { + element.classList.add(user_config.codeBlockStyle.option[i]); + } + } + } + + // 处理列表样式 + const ul = pageContent.querySelectorAll("ul") + if (ul.length > 0) { + for (let i = 0; i < ul.length; i++) { + ul[i].classList.add(user_config.listStyle.option); + } + } } class AiFormat extends Plugin { @@ -438,66 +579,66 @@ class AiFormat extends Plugin { // 智能识别发送消息 function sendRecMsg(type) { - const selection = window.getSelection(); - const formData = new FormData(); - var src = ''; - if (type === 'pic_recognition') { - const range = selection.getRangeAt(0); - const imageElements = range.commonAncestorContainer.parentNode.querySelectorAll('img'); - if (imageElements.length > 0) { - const selectedImage = imageElements[0]; - src = selectedImage.getAttribute('src'); - const prefix = "data:image/png;base64,"; - if (src.startsWith(prefix)) { - src = src.substring(prefix.length); - } - } - if (src.trim() === '') return; - formData.append('pic', src); - } - else if (type === 'voc_recognition') { - return; - } - else{ - return; - } - const requestOptions= { - method: 'POST', - body: formData - }; - - const store = window.store; - const res = {oldContent:'', newContent:'...'}; - store.commit('setCurrentTag', type); - store.commit('addContentToTag', {tag:type, newContent:res}); - const index = store.getters.getCurrentindex; - emitter.emit('show-ai-recg-sidebar', type); - - fetch('/web_api/admin/ai_recognition/' + type, requestOptions) - .then(response => { - if (!response.ok) { - throw new Error("请求出错"); - } - - // 根据返回的数据格式进行相应处理,这里假设返回的数据是JSON格式,所以使用response.json()解析 - return response.json(); - }) - .then(data => { - - // console.log("useStore", store); - const newRes = {oldContent:'', newContent:data.data.words}; - store.commit('changeContentForTag', {tag:type, index:index, newContent:newRes}); - emitter.emit('show-ai-recg-sidebar', type); - }) - .catch(error => { - console.error("POST请求出错:", error); - }); - + const selection = window.getSelection(); + const formData = new FormData(); + var src = ''; + if (type === 'pic_recognition') { + const range = selection.getRangeAt(0); + const imageElements = range.commonAncestorContainer.parentNode.querySelectorAll('img'); + if (imageElements.length > 0) { + const selectedImage = imageElements[0]; + src = selectedImage.getAttribute('src'); + const prefix = "data:image/png;base64,"; + if (src.startsWith(prefix)) { + src = src.substring(prefix.length); + } + } + if (src.trim() === '') return; + formData.append('pic', src); + } + else if (type === 'voc_recognition') { + return; + } + else { + return; + } + const requestOptions = { + method: 'POST', + body: formData + }; + + const store = window.store; + const res = { oldContent: '', newContent: '...' }; + store.commit('setCurrentTag', type); + store.commit('addContentToTag', { tag: type, newContent: res }); + const index = store.getters.getCurrentindex; + emitter.emit('show-ai-recg-sidebar', type); + + fetch('/web_api/admin/ai_recognition/' + type, requestOptions) + .then(response => { + if (!response.ok) { + throw new Error("请求出错"); + } + + // 根据返回的数据格式进行相应处理,这里假设返回的数据是JSON格式,所以使用response.json()解析 + return response.json(); + }) + .then(data => { + + // console.log("useStore", store); + const newRes = { oldContent: '', newContent: data.data.words }; + store.commit('changeContentForTag', { tag: type, index: index, newContent: newRes }); + emitter.emit('show-ai-recg-sidebar', type); + }) + .catch(error => { + console.error("POST请求出错:", error); + }); + } class PicRecog extends Plugin { - init() { + init() { // console.log('Translation initialized!'); this.editor.ui.componentFactory.add('PicRecog', () => { @@ -516,8 +657,8 @@ class PicRecog extends Plugin { return button; }); - } - } + } +} // 配置CKEditor5 @@ -554,7 +695,7 @@ function setConfig() { 'numberedList', 'outdent', 'indent', - '|', 'ExportToWord', 'ExportToPDF', 'RefineDoc', 'SideBar', 'SaveButton','AiFormat' + '|', 'ExportToWord', 'ExportToPDF', 'RefineDoc', 'SideBar', 'SaveButton', 'AiFormat' ], shouldNotGroupWhenFull: true }, @@ -630,7 +771,7 @@ function setConfig() { Undo, Export2Word, RefineDoc, Export2PDF, ToggleSideBar, SaveButton, AiFormat, PicRecog ], - balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList','|','AiFormat'], + balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList', '|', 'AiFormat'], //自定义设置字体 fontFamily: { // 自定义字体 @@ -768,4 +909,4 @@ function setConfig() { } const emitter = new mitt(); export default emitter; -export {Export2Word, Export2PDF, RefineDoc, ToggleSideBar, PicRecog, setConfig}; +export { Export2Word, Export2PDF, RefineDoc, ToggleSideBar, PicRecog, setConfig }; diff --git a/coeditor_frontend/src/components/utils.js b/coeditor_frontend/src/components/utils.js index 7137295..911279b 100644 --- a/coeditor_frontend/src/components/utils.js +++ b/coeditor_frontend/src/components/utils.js @@ -29,7 +29,7 @@ export function getUserConfigFromBackend() { 'Times New Roman' ], // 五号,小四,四号,小三,三号,小二,二号 - fontSizeOptions = [14, 'default', 16,18.6,20, 21.3,24,29.3], + fontSizeOptions = [14, 'default', 16, 18.6, 20, 21.3, 24, 29.3], styleDefinitions = [ { name: 'Article category', @@ -142,7 +142,7 @@ export function getAndApplyUserStyles() { // markdown转html 便于将大语言模型的输出(一般为markdown格式)转换为ckeditor的html格式 // 利用ckeditor markdown插件的功能子类,但不能在CkeditorView.vue中直接使用markdown插件 // 否则会改变编辑器数据处理器为markdown,即getData()需要传入markdown string,setData()返回markdown string -export function markdown2html(markdownString){ +export function markdown2html(markdownString) { const markdownToHtml = new MarkdownToHtml(); const htmlString = markdownToHtml.parse(markdownString); return htmlString; @@ -150,8 +150,52 @@ export function markdown2html(markdownString){ // html转markdown 便于将文件内容发送给大语言模型来进行排版 // 再利用 @markdown2html 将大语言模型的返回内容重新转换为文件内容并展示 或做进一步处理 -export function html2markdown(htmlString){ - const htmltomarkdown = new HtmlToMarkdown(); - const markdownString = htmltomarkdown.parse(htmlString); - return markdownString +export function html2markdown(htmlString) { + const htmltomarkdown = new HtmlToMarkdown(); + const markdownString = htmltomarkdown.parse(htmlString); + return markdownString +} + +// 请求用户AI生成的样式配置 +export function getUserAILayoutConfig() { + // TODO 请求用户AI生成的样式配置 + const options = {}; + // 对应的样式定义在`generatedStyle.css`中 + const { + // title + titleStyleOption = 'title1', + // 标题样式配置 如 一、二 (1)、(2) 1. 2. I. II. A. B. (一)、(二) + // 每级标题按顺序应用 [标题大小,标题样式] 大小也可自定义 + headingStyleOption = [['h3', 'heading1'], ['h4', 'heading2'], ['h5', 'heading3']], // limit 3 + // 列表样式配置 + listStyleOption = 'square', + // 下面的各项样式每一个对应的均会应用 + // 正文样式配置 小四宋体 + bodyStyleOption = ['normal-text'], + // 块引用样式配置 + blockquoteStyleOption = ['side-quote'], + // 代码块样式配置 + codeBlockStyleOption = ['fancy-code', 'fancy-code-bright'], + } = options; + // 如果传入的options没有对应项,使用默认值 + return { + titleStyle: { + option: titleStyleOption + }, + headingStyle: { + option: headingStyleOption + }, + bodyStyle: { + option: bodyStyleOption + }, + blockquoteStyle: { + option: blockquoteStyleOption + }, + codeBlockStyle: { + option: codeBlockStyleOption + }, + listStyle: { + option: listStyleOption + } + }; } \ No newline at end of file diff --git a/coeditor_frontend/src/public/generatedStyle.css b/coeditor_frontend/src/public/generatedStyle.css new file mode 100644 index 0000000..231b66b --- /dev/null +++ b/coeditor_frontend/src/public/generatedStyle.css @@ -0,0 +1,205 @@ +/* default */ +/* title style */ +/* 标题样式一 黑体二号粗体 */ +.ck-content .title1{ + font-family: '黑体'; + font-size: 29.3px; + font-weight: 'bold'; +} +/* heading style */ +/* 使用前必须在对应元素的父元素中将对应计数器重置 */ +.ck-content .heading1::before{ + counter-increment: heading1counter; + /* (一)(二) */ + content: "(" counter(heading1counter, cjk-ideographic) ") "; +} +.ck-content .heading2::before{ + counter-increment: heading2counter; + /* 一、 二、 */ + content: counter(heading2counter, cjk-ideographic) "、 "; +} +.ck-content .heading3::before{ + counter-increment: heading3counter; + /* 1. 2. */ + content: counter(heading3counter) ". "; +} +.ck-content .heading4::before{ + counter-increment: heading4counter; + /* 1) 2) */ + content: counter(heading4counter) ") "; +} +.ck-content .heading5::before{ + counter-increment: heading5counter; + /* 第一章 第二章 */ + content: "第" counter(heading5counter, cjk-ideographic) "章 "; +} +.ck-content .heading6::before{ + counter-increment: heading6counter; + /* 第一小节 第二小节 */ + content: "第" counter(heading6counter, cjk-ideographic) "小节 "; +} +.ck-content .heading7::before{ + counter-increment: heading7counter; + /* I. II. */ + content: counter(heading7counter, upper-roman) ". "; +} +.ck-content .heading8::before{ + counter-increment: heading8counter; + /* A. B. */ + content: counter(heading8counter,upper-alpha) ". "; +} +.ck-content .heading9::before{ + counter-increment: heading9counter; + /* a. b. */ + content: counter(heading9counter,lower-alpha) ". "; +} +.ck-content .heading10::before{ + counter-increment: heading10counter; + /* i. ii. */ + content: counter(heading10counter, lower-roman) ". "; +} +/*style插件样式*/ +.ck-content h3.category { + font-family: 'Oswald'; + font-size: 20px; + font-weight: bold; + color: #555; + letter-spacing: 10px; + margin: 0; + padding: 0; +} + +.ck-content h2.document-title { + font-family: 'Oswald'; + font-size: 50px; + font-weight: bold; + margin: 0; + padding: 0; + border: 0; +} + +.ck-content h3.document-subtitle { + font-family: 'Oswald'; + font-size: 20px; + color: #555; + margin: 0 0 1em; + font-weight: bold; + padding: 0; +} + +.ck-content p.info-box { + --background-size: 30px; + --background-color: #e91e63; + padding: 1.2em 2em; + border: 1px solid var(--background-color); + background: linear-gradient(135deg, + var(--background-color) 0%, + var(--background-color) var(--background-size), + transparent var(--background-size)), + linear-gradient(135deg, + transparent calc(100% - var(--background-size)), + var(--background-color) calc(100% - var(--background-size)), + var(--background-color)); + border-radius: 10px; + margin: 1.5em 2em; + box-shadow: 5px 5px 0 #ffe6ef; +} + +.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; +} + +.ck-content span.marker { + background: yellow; +} + +.ck-content span.spoiler { + background: #000; + color: #000; +} + +.ck-content span.spoiler:hover { + background: #000; + color: #fff; +} + +.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; +} + +.ck-content pre.fancy-code-bright { + background: #dddfe0; + color: #000; + box-shadow: 5px 5px 0 #b3b3b3; +} +/* list style */ +.ck-content ul.disc { + list-style-type: disc; +} +.ck-content ul.circle { + list-style-type: circle; +} +.ck-content ul.square { + list-style-type: square; +} +/* body style */ +.ck-content p.normal-text { + font-family: '宋体'; + font-size: 16px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/coeditor_frontend/src/public/style.css b/coeditor_frontend/src/public/style.css index c14ed8f..f0ec932 100644 --- a/coeditor_frontend/src/public/style.css +++ b/coeditor_frontend/src/public/style.css @@ -91,191 +91,4 @@ .main-container.sidebar-open .editor-container__editor-wrapper { transition: margin-left 0.3s ease; margin-left: 350px; -} - -/*用户定义的style插件样式*/ -.ck-content h3.category { - font-family: 'Oswald'; - font-size: 20px; - font-weight: bold; - color: #555; - letter-spacing: 10px; - margin: 0; - padding: 0; -} - -.ck-content h2.document-title { - font-family: 'Oswald'; - font-size: 50px; - font-weight: bold; - margin: 0; - padding: 0; - border: 0; -} - -.ck-content h3.document-subtitle { - font-family: 'Oswald'; - font-size: 20px; - color: #555; - margin: 0 0 1em; - font-weight: bold; - padding: 0; -} - -.ck-content p.info-box { - --background-size: 30px; - --background-color: #e91e63; - padding: 1.2em 2em; - border: 1px solid var(--background-color); - background: linear-gradient(135deg, - var(--background-color) 0%, - var(--background-color) var(--background-size), - transparent var(--background-size)), - linear-gradient(135deg, - transparent calc(100% - var(--background-size)), - var(--background-color) calc(100% - var(--background-size)), - var(--background-color)); - border-radius: 10px; - margin: 1.5em 2em; - box-shadow: 5px 5px 0 #ffe6ef; -} - -.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; -} - -.ck-content span.marker { - background: yellow; -} - -.ck-content span.spoiler { - background: #000; - color: #000; -} - -.ck-content span.spoiler:hover { - background: #000; - color: #fff; -} - -.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; -} - -.ck-content pre.fancy-code-bright { - background: #dddfe0; - 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/coeditor_frontend/src/views/CkeditorView.vue b/coeditor_frontend/src/views/CkeditorView.vue index 122f873..bc2723c 100644 --- a/coeditor_frontend/src/views/CkeditorView.vue +++ b/coeditor_frontend/src/views/CkeditorView.vue @@ -308,6 +308,7 @@ import { } from 'ckeditor5'; import 'ckeditor5/ckeditor5.css'; import '../public/sidebar.css'; +import '../public/generatedStyle.css'; import { ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu } from 'element-plus'; import emitter, { setConfig } from '../components/plugins' // import {getUserConfigFromBackend,saveData,getPageContent,getAndApplyUserStyles} from './components/utils';