mirror of https://gitee.com/godoos/godoos.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
947 lines
26 KiB
947 lines
26 KiB
var markdownTitle = "";
|
|
let aiSelected = "";
|
|
let componentID = "";
|
|
const MarkdownIt = window.markdownit;
|
|
// 创建 markdown-it 实例
|
|
const md = new MarkdownIt();
|
|
// // 函数:将AST转换为JSON
|
|
function astToJson(tokens) {
|
|
const result = [];
|
|
let currentSection = null;
|
|
|
|
tokens.forEach((token) => {
|
|
if (token.type === "heading_open") {
|
|
const level = parseInt(token.tag.slice(1));
|
|
currentSection = {
|
|
type: "heading",
|
|
level: level,
|
|
text: "",
|
|
};
|
|
result.push(currentSection);
|
|
} else if (token.type === "inline" && currentSection) {
|
|
currentSection.text += token.content;
|
|
} else if (token.type === "paragraph_open") {
|
|
currentSection = {
|
|
type: "paragraph",
|
|
text: "",
|
|
};
|
|
result.push(currentSection);
|
|
} else if (
|
|
token.type === "inline" &&
|
|
currentSection &&
|
|
currentSection.type === "paragraph"
|
|
) {
|
|
currentSection.text += token.content;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
var CustomHookA = Cherry.createSyntaxHook(
|
|
"codeBlock",
|
|
Cherry.constants.HOOKS_TYPE_LIST.PAR,
|
|
{
|
|
makeHtml(str) {
|
|
console.warn("custom hook", "hello");
|
|
return str;
|
|
},
|
|
rule(str) {
|
|
const regex = {
|
|
begin: "",
|
|
content: "",
|
|
end: "",
|
|
};
|
|
regex.reg = new RegExp(regex.begin + regex.content + regex.end, "g");
|
|
return regex;
|
|
},
|
|
}
|
|
);
|
|
/**
|
|
* 自定义一个自定义菜单
|
|
* 点第一次时,把选中的文字变成同时加粗和斜体
|
|
* 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本
|
|
*/
|
|
var customMenuA = Cherry.createMenuHook("加粗斜体", {
|
|
iconName: "font",
|
|
onClick: function (selection) {
|
|
// 获取用户选中的文字,调用getSelection方法后,如果用户没有选中任何文字,会尝试获取光标所在位置的单词或句子
|
|
let $selection = this.getSelection(selection) || "同时加粗斜体";
|
|
// 如果是单选,并且选中内容的开始结束内没有加粗语法,则扩大选中范围
|
|
if (!this.isSelections && !/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
|
|
this.getMoreSelection("***", "***", () => {
|
|
const newSelection = this.editor.editor.getSelection();
|
|
const isBoldItalic = /^\s*(\*\*\*)[\s\S]+(\1)/.test(newSelection);
|
|
if (isBoldItalic) {
|
|
$selection = newSelection;
|
|
}
|
|
return isBoldItalic;
|
|
});
|
|
}
|
|
// 如果选中的文本中已经有加粗语法了,则去掉加粗语法
|
|
if (/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
|
|
return $selection.replace(
|
|
/(^)(\s*)(\*\*\*)([^\n]+)(\3)(\s*)($)/gm,
|
|
"$1$4$7"
|
|
);
|
|
}
|
|
/**
|
|
* 注册缩小选区的规则
|
|
* 注册后,插入“***TEXT***”,选中状态会变成“***【TEXT】***”
|
|
* 如果不注册,插入后效果为:“【***TEXT***】”
|
|
*/
|
|
this.registerAfterClickCb(() => {
|
|
this.setLessSelection("***", "***");
|
|
});
|
|
return $selection.replace(/(^)([^\n]+)($)/gm, "$1***$2***$3");
|
|
},
|
|
});
|
|
/**
|
|
* 定义一个空壳,用于自行规划cherry已有工具栏的层级结构
|
|
*/
|
|
var customMenuB = Cherry.createMenuHook("实验室", {
|
|
icon: {
|
|
type: "svg",
|
|
content:
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>',
|
|
iconStyle: "width: 15px; height: 15px; vertical-align: middle;",
|
|
},
|
|
});
|
|
var exportDataHook = Cherry.createMenuHook("导出", {
|
|
subMenuConfig: [
|
|
{
|
|
noIcon: true,
|
|
name: "思维导图",
|
|
onclick: () => {
|
|
if (!markdownTitle || markdownTitle == "") {
|
|
markdownTitle = window.prompt("请输入文稿标题");
|
|
}
|
|
const postData = {
|
|
title: markdownTitle,
|
|
content: cherry.getMarkdown(),
|
|
};
|
|
window.parent.postMessage({ type: "saveMind", data: postData }, "*");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "PPTX",
|
|
onclick: () => {
|
|
if (!markdownTitle || markdownTitle == "") {
|
|
markdownTitle = window.prompt("请输入文稿标题");
|
|
}
|
|
const markdownContent = cherry.getMarkdown();
|
|
createPPTDialog(markdownContent);
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "PDF",
|
|
onclick: () => {
|
|
cherry.export();
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "长图",
|
|
onclick: () => {
|
|
cherry.export("img");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "markdown",
|
|
onclick: () => {
|
|
cherry.export("markdown");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "word",
|
|
onclick: () => {
|
|
if (!markdownTitle || markdownTitle == "") {
|
|
markdownTitle = window.prompt("请输入文稿标题");
|
|
}
|
|
const content = cherry.getHtml();
|
|
const converted = htmlDocx.asBlob(content);
|
|
// 创建一个隐藏的 <a> 元素
|
|
const a = document.createElement("a");
|
|
a.style.display = "none";
|
|
document.body.appendChild(a);
|
|
// 设置 <a> 元素的 href 属性为 Blob URL
|
|
a.href = URL.createObjectURL(converted);
|
|
a.download = `${markdownTitle}.docx`;
|
|
// 触发点击事件
|
|
a.click();
|
|
// 清理
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(a.href);
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "html",
|
|
onclick: () => {
|
|
cherry.export("html");
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
// 生成ppt弹窗
|
|
function createPPTDialog(markdownContent) {
|
|
// 将Markdown内容转换为HTML
|
|
const htmlContent = cherry.getHtml();
|
|
// 解析Markdown为AST
|
|
const tokens = md.parse(markdownContent, {});
|
|
// 将AST转换为JSON
|
|
const jsonResult = astToJson(tokens);
|
|
console.log(JSON.stringify(jsonResult, null, 2));
|
|
|
|
const dialog = document.createElement("div");
|
|
dialog.className = "cherry-pptx-dialog";
|
|
dialog.innerHTML = `
|
|
<div class="cherry-pptx-header">
|
|
<span>导出为PPTX</span>
|
|
<button class="close-btn">X</button>
|
|
</div>
|
|
<div class="cherry-pptx-content">
|
|
<div class="cherry-pptx-preview">
|
|
<h3>内容预览</h3>
|
|
<div id="markdown-preview">${htmlContent}</div>
|
|
</div>
|
|
<div class="cherry-pptx-options">
|
|
<h3>选择模板</h3>
|
|
<div class="template-list">
|
|
${["基础", "科技", "总结", "产品", "太空人", "党建", "简约紫"]
|
|
.map(
|
|
(item) =>
|
|
`<div class="template-card" data-template="${item}">${item}</div>`
|
|
)
|
|
.join("")}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cherry-pptx-footer">
|
|
<button id="ppt-btn">一键生成PPT</button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
// 关闭按钮事件
|
|
dialog.querySelector(".close-btn").addEventListener("click", () => {
|
|
document.body.removeChild(dialog);
|
|
});
|
|
|
|
// 模板卡片点击事件
|
|
let selectedTemplate;
|
|
const templateCards = dialog.querySelectorAll(".template-card");
|
|
templateCards.forEach((card) => {
|
|
card.addEventListener("click", () => {
|
|
templateCards.forEach((c) => c.classList.remove("selected"));
|
|
card.classList.add("selected");
|
|
selectedTemplate = card.getAttribute("data-template");
|
|
});
|
|
});
|
|
|
|
// 生成PPT按钮事件
|
|
dialog.querySelector("#ppt-btn").addEventListener("click", () => {
|
|
if (selectedTemplate === undefined) {
|
|
showModal({
|
|
titleText: "提示",
|
|
contentText: "请选择一个模板",
|
|
showOk: true,
|
|
});
|
|
return;
|
|
}
|
|
const postData = {
|
|
title: markdownTitle,
|
|
content: markdownContent,
|
|
template: selectedTemplate,
|
|
};
|
|
console.log(postData);
|
|
|
|
window.parent.postMessage({ type: "savePPT", data: postData }, "*");
|
|
document.body.removeChild(dialog);
|
|
});
|
|
}
|
|
|
|
class AiDialogClass {
|
|
actionArr = {
|
|
creation_leader: {
|
|
title: "文章选择",
|
|
btn: ["aiCancle", "aiOutline"],
|
|
},
|
|
creation_builder: {
|
|
title: "大纲",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle", "aiArticle"],
|
|
},
|
|
article: {
|
|
title: "文章",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle", "checkOutline"],
|
|
},
|
|
creation_continuation: {
|
|
title: "续写",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle"],
|
|
},
|
|
creation_optimization: {
|
|
title: "优化",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle"],
|
|
},
|
|
creation_proofreading: {
|
|
title: "纠错",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle"],
|
|
},
|
|
creation_summarize: {
|
|
title: "总结",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle"],
|
|
},
|
|
creation_translation: {
|
|
title: "翻译",
|
|
btn: ["aiAdd", "aiReplace", "aiCancle"],
|
|
},
|
|
};
|
|
buttons = [
|
|
{
|
|
title: "追加",
|
|
action: "aiAdd",
|
|
method: this.addAiContent,
|
|
},
|
|
{
|
|
title: "替换",
|
|
action: "aiReplace",
|
|
method: this.replaceAiContent,
|
|
},
|
|
{
|
|
title: "舍弃",
|
|
action: "aiCancle",
|
|
method: this.closeDialog,
|
|
},
|
|
{
|
|
title: "生成大纲",
|
|
action: "aiOutline",
|
|
method: this.createAiOutline,
|
|
},
|
|
{
|
|
title: "生成文章",
|
|
action: "aiArticle",
|
|
method: this.createAiArticle,
|
|
},
|
|
{
|
|
title: "查看大纲",
|
|
action: "checkOutline",
|
|
method: this.cheakAiContent,
|
|
},
|
|
];
|
|
outline = {
|
|
title: "",
|
|
content: "",
|
|
category: "",
|
|
};
|
|
constructor() {
|
|
this.container = document.querySelector("#aiDialog");
|
|
this.createDialog();
|
|
}
|
|
// 生成弹窗的基本框架
|
|
createDialog() {
|
|
const dialog = document.createElement("div");
|
|
dialog.innerHTML = `
|
|
<div class="ai-markdown-dialog" style="padding: 0px 15px;">
|
|
<div class="ai-dialog-title">${this.action}</div>
|
|
<div class="ai-content-box">
|
|
<div class="ai-mask"><i id="aiLoader"></i></div>
|
|
<textarea class="ai-dialog-content" ></textarea>
|
|
</div>
|
|
<div class="ai-outline-choose hide">
|
|
<div>
|
|
<label for="">标题:</label>
|
|
<input id="aiTitle" type="text" placeholder="请输入标题">
|
|
</div>
|
|
<div>
|
|
<label for="">分类:</label>
|
|
<select name="articleClassification" id="aiSelect">
|
|
<option value="论文">论文</option>
|
|
<option value="合同">合同</option>
|
|
<option value="项目">项目</option>
|
|
<option value="投标">投标</option>
|
|
<option value="招标">招标</option>
|
|
<option value="散文">散文</option>
|
|
<option value="产品说明">产品说明</option>
|
|
<option value="销售计划">销售计划</option>
|
|
<option value="年度计划">年度计划</option>
|
|
<option value="战略规划">战略规划</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="button-box">
|
|
<button class="ai-dialog-button" data-type="aiOutline">生成大纲</button>
|
|
<button class="ai-dialog-button" id="checkOutline" data-type="checkOutline">修改大纲</button>
|
|
<button class="ai-dialog-button" data-type="aiArticle">生成文章</button>
|
|
<button class="ai-dialog-button" data-type="aiAdd">追加</button>
|
|
<button class="ai-dialog-button" data-type="aiReplace">替换</button>
|
|
<button class="ai-dialog-button" data-type="aiCancle">取消</button>
|
|
</div>
|
|
</div>`;
|
|
this.container.appendChild(dialog);
|
|
// 绑定事件
|
|
const aiOptions = Array.from(
|
|
this.container.querySelectorAll(".ai-dialog-button")
|
|
);
|
|
aiOptions.forEach((item) => {
|
|
item.addEventListener("click", () => {
|
|
const actionType = item.getAttribute("data-type");
|
|
const btn = this.buttons.find((item) => item.action == actionType);
|
|
if (btn && btn.method) {
|
|
btn.method(this.container, this);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
// 打开弹窗
|
|
openDialog(action) {
|
|
this.container.classList.remove("hide");
|
|
const title = "AI - " + this.actionArr[action].title;
|
|
this.container.querySelector(".ai-dialog-title").innerText = title;
|
|
this.addButton(action);
|
|
}
|
|
// 根据不同选择,展示不同弹窗
|
|
addButton(action) {
|
|
if (action == "creation_leader") {
|
|
this.container
|
|
.querySelector(".ai-outline-choose")
|
|
.classList.remove("hide");
|
|
this.container.querySelector(".ai-content-box").classList.add("hide");
|
|
} else {
|
|
this.container.querySelector(".ai-outline-choose").classList.add("hide");
|
|
this.container.querySelector(".ai-content-box").classList.remove("hide");
|
|
this.container.querySelector(".ai-mask").classList.remove("hide");
|
|
}
|
|
const buttonArr = Array.from(
|
|
this.container.querySelectorAll(".ai-dialog-button")
|
|
);
|
|
buttonArr.forEach((item) => {
|
|
const actionType = item.getAttribute("data-type");
|
|
if (this.actionArr[action].btn.indexOf(actionType) !== -1) {
|
|
item.classList.remove("hide");
|
|
} else {
|
|
item.classList.add("hide");
|
|
}
|
|
});
|
|
}
|
|
// 将内容放入textarea中
|
|
putInTextarea(action, content) {
|
|
// this.sendRequest(action,content)
|
|
this.container.querySelector("textarea").value = content;
|
|
this.container.querySelector(".ai-mask").classList.add("hide");
|
|
}
|
|
// 发送请求
|
|
sendRequest(action, data) {
|
|
if (action !== "creation_leader") {
|
|
window.parent.postMessage(
|
|
{
|
|
type: "aiCreater",
|
|
data,
|
|
action,
|
|
},
|
|
"*"
|
|
);
|
|
}
|
|
this.openDialog(action);
|
|
}
|
|
// 关闭弹窗
|
|
closeDialog(dialog) {
|
|
dialog.querySelector("textarea").value = "";
|
|
this.outline = {
|
|
title: "",
|
|
content: "",
|
|
category: "",
|
|
};
|
|
dialog?.classList.add("hide");
|
|
}
|
|
// 追加
|
|
addAiContent(dialog, that) {
|
|
const content = dialog.querySelector("textarea").value;
|
|
cherry.insert(content, false);
|
|
that.closeDialog(dialog);
|
|
}
|
|
// 替换
|
|
replaceAiContent(dialog, that) {
|
|
const content = dialog.querySelector("textarea").value;
|
|
cherry.setMarkdown(content);
|
|
that.closeDialog(dialog);
|
|
}
|
|
// 生成大纲
|
|
createAiOutline(dialog, that) {
|
|
that.addButton("creation_leader");
|
|
const content = {
|
|
title: dialog.querySelector("#aiTitle").value,
|
|
category: dialog.querySelector("#aiSelect").value,
|
|
};
|
|
that.outline.title = content.title;
|
|
that.outline.category = content.category;
|
|
if (content.title !== "") {
|
|
window.parent.postMessage(
|
|
{
|
|
type: "aiCreater",
|
|
data: content,
|
|
action: "creation_leader",
|
|
},
|
|
"*"
|
|
);
|
|
that.openDialog("creation_builder");
|
|
} else {
|
|
showModal({
|
|
titleText: "提示",
|
|
contentText: "请输入标题",
|
|
showOk: true,
|
|
});
|
|
}
|
|
}
|
|
// 生成文章
|
|
createAiArticle(dialog, that) {
|
|
const data = {
|
|
outline: dialog.querySelector("textarea").value,
|
|
title: that.outline.title || "AI - 文章",
|
|
};
|
|
that.outline.content = data.outline;
|
|
window.parent.postMessage(
|
|
{
|
|
type: "aiCreater",
|
|
data,
|
|
action: "creation_builder",
|
|
},
|
|
"*"
|
|
);
|
|
that.openDialog("article");
|
|
}
|
|
//查看大纲
|
|
cheakAiContent(dialog, that) {
|
|
that.openDialog("creation_builder");
|
|
dialog.querySelector("textarea").value = that.outline.content;
|
|
dialog.querySelector(".ai-mask").classList.add("hide");
|
|
}
|
|
}
|
|
const aiDialog = new AiDialogClass();
|
|
const aiTips = (action) => {
|
|
if (aiSelected == "") {
|
|
showModal({
|
|
titleText: "提示",
|
|
contentText: "请选择内容",
|
|
showOk: true,
|
|
});
|
|
return;
|
|
}
|
|
aiDialog.sendRequest(action, aiSelected);
|
|
};
|
|
var aiEditMenu = Cherry.createMenuHook("AI", {
|
|
subMenuConfig: [
|
|
{
|
|
noIcon: true,
|
|
name: "优化",
|
|
onclick: () => {
|
|
aiTips("creation_optimization");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "纠错",
|
|
onclick: () => {
|
|
aiTips("creation_proofreading");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "续写",
|
|
onclick: () => {
|
|
aiTips("creation_continuation");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "翻译",
|
|
onclick: () => {
|
|
aiTips("creation_translation");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "总结",
|
|
onclick: () => {
|
|
aiTips("creation_summarize");
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "大纲",
|
|
onclick: () => {
|
|
aiDialog.openDialog("creation_leader");
|
|
//aiDialog.sendRequest('creation_leader', cherry.getMarkdown())
|
|
},
|
|
},
|
|
],
|
|
});
|
|
const saveData = () => {
|
|
//console.log(markdownTitle)
|
|
if (!markdownTitle || markdownTitle == "") {
|
|
markdownTitle = window.prompt("请输入文稿标题");
|
|
}
|
|
const postData = { title: markdownTitle, content: cherry.getMarkdown() };
|
|
window.parent.postMessage(
|
|
{ type: "exportMd", data: JSON.stringify(postData), componentID },
|
|
"*"
|
|
);
|
|
};
|
|
var saveMenu = Cherry.createMenuHook("保存", {
|
|
onClick: function () {
|
|
//console.log(selection)
|
|
saveData();
|
|
},
|
|
});
|
|
/**
|
|
* 定义带图表表格的按钮
|
|
*/
|
|
var customMenuTable = Cherry.createMenuHook("图表", {
|
|
iconName: "trendingUp",
|
|
subMenuConfig: [
|
|
{
|
|
noIcon: true,
|
|
name: "折线图",
|
|
onclick: (event) => {
|
|
cherry.insert(
|
|
"\n| :line:{x,y} | Header1 | Header2 | Header3 | Header4 |\n| ------ | ------ | ------ | ------ | ------ |\n| Sample1 | 11 | 11 | 4 | 33 |\n| Sample2 | 112 | 111 | 22 | 222 |\n| Sample3 | 333 | 142 | 311 | 11 |\n"
|
|
);
|
|
},
|
|
},
|
|
{
|
|
noIcon: true,
|
|
name: "柱状图",
|
|
onclick: (event) => {
|
|
cherry.insert(
|
|
"\n| :bar:{x,y} | Header1 | Header2 | Header3 | Header4 |\n| ------ | ------ | ------ | ------ | ------ |\n| Sample1 | 11 | 11 | 4 | 33 |\n| Sample2 | 112 | 111 | 22 | 222 |\n| Sample3 | 333 | 142 | 311 | 11 |\n"
|
|
);
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
var basicConfig = {
|
|
id: "markdown",
|
|
externals: {
|
|
echarts: window.echarts,
|
|
katex: window.katex,
|
|
MathJax: window.MathJax,
|
|
},
|
|
isPreviewOnly: false,
|
|
engine: {
|
|
global: {
|
|
urlProcessor(url, srcType) {
|
|
console.log(`url-processor`, url, srcType);
|
|
return url;
|
|
},
|
|
},
|
|
syntax: {
|
|
image: {
|
|
videoWrapper: (link, type, defaultWrapper) => {
|
|
console.log(type);
|
|
return defaultWrapper;
|
|
},
|
|
},
|
|
autoLink: {
|
|
/** 生成的<a>标签追加target属性的默认值 空:在<a>标签里不会追加target属性, _blank:在<a>标签里追加target="_blank"属性 */
|
|
target: "",
|
|
/** 生成的<a>标签追加rel属性的默认值 空:在<a>标签里不会追加rel属性, nofollow:在<a>标签里追加rel="nofollow:在"属性*/
|
|
rel: "",
|
|
/** 是否开启短链接 */
|
|
enableShortLink: true,
|
|
/** 短链接长度 */
|
|
shortLinkLength: 20,
|
|
},
|
|
codeBlock: {
|
|
theme: "twilight",
|
|
wrap: true, // 超出长度是否换行,false则显示滚动条
|
|
lineNumber: true, // 默认显示行号
|
|
copyCode: true, // 是否显示“复制”按钮
|
|
editCode: true, // 是否显示“编辑”按钮
|
|
changeLang: true, // 是否显示“切换语言”按钮
|
|
expandCode: false, // 是否展开/收起代码块,当代码块行数大于10行时,会自动收起代码块
|
|
selfClosing: true, // 自动闭合,为true时,当md中有奇数个```时,会自动在md末尾追加一个```
|
|
},
|
|
table: {
|
|
enableChart: true,
|
|
},
|
|
fontEmphasis: {
|
|
allowWhitespace: false, // 是否允许首尾空格
|
|
},
|
|
strikethrough: {
|
|
needWhitespace: false, // 是否必须有前后空格
|
|
},
|
|
mathBlock: {
|
|
engine: "katex", // katex或MathJax
|
|
src: "/markdown/katex/katex.min.js",
|
|
//src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins,则需要使用该url通过script标签引入
|
|
},
|
|
inlineMath: {
|
|
engine: "katex", // katex或MathJax
|
|
},
|
|
// emoji: {
|
|
// useUnicode: true,
|
|
// customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8',
|
|
// upperCase: false,
|
|
// },
|
|
// htmlBlock: {
|
|
// filterStyle: true,
|
|
// }
|
|
toc: {
|
|
tocStyle: "nested",
|
|
},
|
|
// 'header': {
|
|
// strict: false
|
|
// }
|
|
},
|
|
customSyntax: {
|
|
// SyntaxHookClass
|
|
CustomHook: {
|
|
syntaxClass: CustomHookA,
|
|
force: false,
|
|
after: "br",
|
|
},
|
|
},
|
|
},
|
|
multipleFileSelection: {
|
|
video: true,
|
|
audio: false,
|
|
image: true,
|
|
word: false,
|
|
pdf: true,
|
|
file: true,
|
|
},
|
|
toolbars: {
|
|
toolbar: [
|
|
"undo",
|
|
"redo",
|
|
"|",
|
|
// 把字体样式类按钮都放在加粗按钮下面
|
|
{
|
|
bold: [
|
|
"bold",
|
|
"italic",
|
|
"underline",
|
|
"strikethrough",
|
|
"sub",
|
|
"sup",
|
|
"ruby",
|
|
"customMenuAName",
|
|
],
|
|
},
|
|
// 'bold',
|
|
// 'italic',
|
|
// {
|
|
// strikethrough: ['strikethrough', 'underline', 'sub', 'sup', 'ruby'],
|
|
// },
|
|
"size",
|
|
"|",
|
|
"color",
|
|
"header",
|
|
{
|
|
ol: ["ol", "ul", "checklist", "panel"],
|
|
},
|
|
|
|
"justify",
|
|
//'formula',
|
|
{
|
|
insert: [
|
|
"drawIo",
|
|
"image",
|
|
"audio",
|
|
"video",
|
|
"link",
|
|
"ruby",
|
|
"detail",
|
|
"hr",
|
|
"br",
|
|
"code",
|
|
"inlineCode",
|
|
"formula",
|
|
"toc",
|
|
"table",
|
|
"pdf",
|
|
"word",
|
|
"file",
|
|
],
|
|
},
|
|
"graph",
|
|
"customMenuTable",
|
|
//'togglePreview',
|
|
//'codeTheme',
|
|
"fullScreen",
|
|
"search",
|
|
"togglePreview",
|
|
],
|
|
toolbarRight: [
|
|
"saveMenu",
|
|
"|",
|
|
"exportDataHook",
|
|
"changeLocale",
|
|
"wordCount",
|
|
"aiEditMenu",
|
|
],
|
|
bubble: [
|
|
"bold",
|
|
"italic",
|
|
"underline",
|
|
"strikethrough",
|
|
"sub",
|
|
"sup",
|
|
"quote",
|
|
"ruby",
|
|
"|",
|
|
"size",
|
|
"color",
|
|
], // array or false
|
|
//sidebar: ['mobilePreview', 'copy', 'theme', 'publish'],
|
|
sidebar: ["mobilePreview", "copy", "shortcutKey", "theme"],
|
|
toc: {
|
|
updateLocationHash: true, // 要不要更新URL的hash
|
|
defaultModel: "pure", // pure: 精简模式/缩略模式,只有一排小点; full: 完整模式,会展示所有标题
|
|
},
|
|
//toc: false,
|
|
customMenu: {
|
|
customMenuAName: customMenuA,
|
|
customMenuBName: customMenuB,
|
|
saveMenu,
|
|
customMenuTable,
|
|
exportDataHook,
|
|
aiEditMenu,
|
|
},
|
|
shortcutKeySettings: {
|
|
/** 是否替换已有的快捷键, true: 替换默认快捷键; false: 会追加到默认快捷键里,相同的shortcutKey会覆盖默认的 */
|
|
isReplace: false,
|
|
shortcutKeyMap: {
|
|
"Control-S": {
|
|
hookName: "saveMenu",
|
|
aliasName: "保存",
|
|
},
|
|
"Alt-Digit1": {
|
|
hookName: "header",
|
|
aliasName: "标题",
|
|
},
|
|
"Control-Shift-KeyX": {
|
|
hookName: "bold",
|
|
aliasName: "加粗",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
drawioIframeUrl: "./drawio.html",
|
|
previewer: {
|
|
// 自定义markdown预览区域class
|
|
// className: 'markdown'
|
|
floatWhenClosePreviewer: true,
|
|
},
|
|
keydown: [],
|
|
//extensions: [],
|
|
callback: {
|
|
changeString2Pinyin: pinyin,
|
|
onClickPreview: (event) => {
|
|
console.log("onClickPreview", event);
|
|
},
|
|
},
|
|
event: {
|
|
selectionChange: ({ selections, lastSelections, info }) => {
|
|
aiSelected = lastSelections[0];
|
|
//console.log(aiSelected)
|
|
},
|
|
},
|
|
editor: {
|
|
id: "cherry-text",
|
|
name: "cherry-text",
|
|
autoSave2Textarea: true,
|
|
//defaultModel: 'editOnly',
|
|
defaultModel: "edit&preview",
|
|
showFullWidthMark: true, // 是否高亮全角符号 ·|¥|、|:|“|”|【|】|(|)|《|》
|
|
showSuggestList: true, // 是否显示联想框
|
|
},
|
|
// cherry初始化后是否检查 location.hash 尝试滚动到对应位置
|
|
autoScrollByHashAfterInit: true,
|
|
// locale: 'en_US',
|
|
themeSettings: {
|
|
mainTheme: "light",
|
|
},
|
|
};
|
|
var config = Object.assign({}, basicConfig, { value: "" });
|
|
window.cherry = new Cherry(config);
|
|
|
|
const debouncedHandleKeyDown = (event) => {
|
|
// 确保仅在我们的按钮获得焦点时处理快捷键
|
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
|
|
event.stopPropagation(); // 先阻止事件冒泡
|
|
event.preventDefault(); // 再阻止默认行为
|
|
saveData();
|
|
}
|
|
};
|
|
function isBase64(str) {
|
|
if (str === "" || str.trim() === "") {
|
|
return false;
|
|
}
|
|
try {
|
|
return btoa(atob(str)) == str;
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|
|
function decodeBase64(base64String) {
|
|
// 将Base64字符串分成每64个字符一组
|
|
const padding =
|
|
base64String.length % 4 === 0 ? 0 : 4 - (base64String.length % 4);
|
|
base64String += "=".repeat(padding);
|
|
|
|
// 使用atob()函数解码Base64字符串
|
|
const binaryString = atob(base64String);
|
|
|
|
// 将二进制字符串转换为TypedArray
|
|
const bytes = new Uint8Array(binaryString.length);
|
|
for (let i = 0; i < binaryString.length; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
|
|
// 将TypedArray转换为字符串
|
|
return new TextDecoder("utf-8").decode(bytes);
|
|
}
|
|
const eventHandler = (e) => {
|
|
const eventData = e.data;
|
|
componentID = eventData.componentID ? eventData.componentID : "";
|
|
if (eventData.type === "start") {
|
|
markdownTitle = eventData.title || "未命名文稿";
|
|
return;
|
|
}
|
|
// console.log(markdownTitle)
|
|
if (eventData.type === "init") {
|
|
const data = eventData.data;
|
|
markdownTitle = data.title || "未命名文稿";
|
|
if (!data) {
|
|
return;
|
|
}
|
|
let content = data.content;
|
|
|
|
if (isBase64(content)) {
|
|
content = decodeBase64(content);
|
|
} else if (content instanceof ArrayBuffer) {
|
|
content = new TextDecoder("utf-8").decode(content);
|
|
}
|
|
|
|
cherry.setMarkdown(content);
|
|
}
|
|
if (eventData.type == "aiReciver") {
|
|
aiDialog.putInTextarea(eventData.action, eventData.data);
|
|
}
|
|
};
|
|
window.addEventListener("load", () => {
|
|
window.parent.postMessage({ type: "initSuccess" }, "*");
|
|
window.addEventListener("message", eventHandler);
|
|
document.addEventListener("keydown", debouncedHandleKeyDown);
|
|
});
|
|
window.addEventListener("unload", () => {
|
|
window.removeEventListener("message", eventHandler);
|
|
document.removeEventListener("keydown", debouncedHandleKeyDown);
|
|
});
|
|
|