完善ai排版,可以将文件内容使用大模型进行初步排版后,应用用户定义的样式规则,如代码块、块引用、文本样式等,可以设置每级标题的样式如(一) 一、 I. 等并自动计数,不会改变原有的图片样式,仅对文本进行处理,不会修改原有文本的内容。

master
joefalmko 9 months ago
parent 8f29601051
commit 022d7e16c3

@ -1,4 +1,5 @@
请将以下文章进行重新排版,使其格式更加美观且井然有序,同时保留文章的所有原始内容和文字,不要对内容进行任何修改。
请你调整标题样式,将所有标题改为清晰的分级结构。文章标题使用#,一级标题使用##,以此类推。
正文部分的段落需适当分段,避免过长或过短。 请确保整体排版整洁、易于阅读。
所有的图片已被替换为tag“图片1”、“图片2”等在处理时不要考虑图片本身不要删除这些tag。
不要对文章的原始内容进行修改。只返回重新排版后的内容,不要返回其他的额外内容。

@ -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 = '<!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>'
@ -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 <figure ... </figure>
const img_tag = /<figure.*?>(.*?)<\/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 = `<text>图片${i + 1}</text>`
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 };

@ -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 stringsetData()返回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',
// 标题样式配置 如 一、二 12 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
}
};
}

@ -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;
}

@ -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;
}
}
}

@ -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';

Loading…
Cancel
Save