import Cherry from "cherry-markdown";
import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react"
interface SelectProps {
options: string[];
onChange: (idx: number) => void;
selected: number;
}
const Select = ({ options, onChange, selected }: SelectProps) => {
const [showOptions, setShowOptions] = useState(false);
return (
<div className="custom-select-container">
<div
className={showOptions ? "custom-select active" : "custom-select"}
onClick={() => { setShowOptions(prev => !prev); }}
>
{options[selected]}
</div>
{showOptions && (
<ul className="custom-option-wrapper">
{options.map((option: string, idx: number) => {
return (
<li key={idx}
className="custom-option"
onClick={() => { onChange(idx); setShowOptions(false); }}
>
{option}
</li>
)
})}
</ul>
)}
</div>
);
}
const getFullCode = (code: string) => `\`\`\`javascript
import { useEffect, useState, useRef } from 'react';
import 'cherry-markdown/dist/cherry-markdown.css';
import Cherry from 'cherry-markdown';
export default () => {
const editorRef = useRef(null);
const [cherry, setCherry] = useState(null);
useEffect(() => {
if (cherry == null) {
// 初始化编辑器
const config = {
el: editorRef.current,
value: '',
callback: {
afterChange: (md, html) => console.log('change'),
},
};
const cherryObj = new Cherry(config);
setCherry(cherryObj);
// api操作如下
${code}
}
},[])
return (<div ref={editorRef} />);
}
\`\`\``
const data = [
{
name: "Cherry API", api: [
{
name: "setMarkdown", title: `setMarkdown(content:string, keepCursor = false)`,
desc: `设置内容,直接编辑器中的全部文本。setValue(content:string, keepCursor = false)有同样的功能,keepCursor = true 时更新内容的时候保持光标位置`,
code: `cherryObj.setMarkdown("初始内容");
setTimeout(()=>{cherryObj.setMarkdown("2秒后替换成的新内容")},2000);`,
markdown: `# Cherry API
## setMarkdown(content:string, keepCursor = false)
设置内容
${getFullCode(`cherryObj.setMarkdown("初始内容");
setTimeout(()=>{cherryObj.setMarkdown("2秒后替换成的新内容")},2000);`)}
`
},
{
name: "insert",
title: `insert(content: string, isSelect = false, anchor = false, focus = true)`,
desc: `插入内容\nisSelect = true 选中刚插入的内容\nanchor = false 在光标处插入内容\nanchor = [1, 3] 在第2行第4个字符处插入内容`,
code: `cherryObj.insert("在光标处插入内容");
cherryObj.insert("在第二行插入内容,并选中插入的内容", true, [1, 0]);`,
markdown: `# Cherry API
## insert(content: string, isSelect = false, anchor = false, focus = true)
在光标处或者指定行 + 偏移量插入内容
> insert(\`content\`, \`isSelect\`, \`anchor\`, \`focus\`)
- \`content\` 被插入的文本
- \`isSelect\` 是否选中刚插入的内容,默认false,不选中
- \`anchor\` [x,y] 代表x+1行,y+1字符偏移量,默认false 会从光标处插入
- \`focus\` 保持编辑器处于focus状态,默认true,选中编辑器(用户可以继续输入)
${getFullCode(`cherryObj.insert("在光标处插入内容");
cherryObj.insert("在第二行插入内容,并选中插入的内容", true, [1, 0]);`)}`,
},
{
name: "getMarkdown",
title: `getMarkdown()`,
desc: `获取markdown内容`,
code: `alert(cherryObj.getMarkdown());
console.log(cherryObj.getMarkdown());`,
markdown: `# Cherry API
## getMarkdown()
获取markdown内容
${getFullCode(`alert(cherryObj.getMarkdown());
console.log(cherryObj.getMarkdown());`)}`,
},
{
name: "getHtml",
title: `getHtml()`,
desc: `获取渲染后的html内容`,
code: `alert(cherryObj.getHtml());
console.log(cherryObj.getHtml());`,
markdown: `# Cherry API
## getHtml()
获取渲染后的html内容
${getFullCode(`alert(cherryObj.getHtml());
console.log(cherryObj.getHtml());`)}`,
},
{
name: "destroy",
title: `destroy()`,
desc: `销毁函数`,
code: `cherryObj.destroy();`,
markdown: `# Cherry API
## destroy()
销毁函数
${getFullCode(`// cherryObj.destroy(); `)}`,
},
{
name: "resetToolbar",
title: `resetToolbar(type:string, toolbar:array)`,
desc: `重置工具栏
type 修改工具栏的类型 {'toolbar'|'toolbarRight'|'sidebar'|'bubble'|'float'}
toolbar 工具栏配置`,
code: `cherryObj.resetToolbar('toolbar', ['bold', 'table']);`,
markdown: `# Cherry API
## resetToolbar(type:string, toolbar:array)
重置工具栏
type 修改工具栏的类型 {'toolbar'|'toolbarRight'|'sidebar'|'bubble'|'float'}
toolbar 工具栏配置
${getFullCode(`cherryObj.resetToolbar('toolbar', ['bold', 'table']);`)}`,
},
{
name: "export",
title: `export(type:string)`,
desc: `导出预览区域的内容,type:{'pdf'|'img'}`,
code: `if(confirm('导出pdf')) {
cherryObj.export();
}else if(confirm('导出长图')) {
cherryObj.export('img');
}`,
markdown: `# Cherry API
## export(type:string)
导出预览区域的内容,type:{'pdf'|'img'}
${getFullCode(`if(confirm('导出pdf')) {
cherryObj.export();
}else if(confirm('导出长图')) {
cherryObj.export('img');
}`
)}`,
},
{
name: "switchModel",
title: `switchModel(model:string)`,
desc: `切换模式:{'edit&preview'|'editOnly'|'previewOnly'}`,
code: `if(confirm('只读模式')) {
cherryObj.switchModel('previewOnly');
}else if(confirm('纯编辑模式')) {
cherryObj.switchModel('editOnly');
}else if(confirm('双栏编辑模式')) {
cherryObj.switchModel('edit&preview');
}`,
markdown: `# Cherry API
## switchModel(model:string)
切换模式:{'edit&preview'|'editOnly'|'previewOnly'}
${getFullCode(`if(confirm('只读模式')) {
cherryObj.switchModel('previewOnly');
}else if(confirm('纯编辑模式')) {
cherryObj.switchModel('editOnly');
}else if(confirm('双栏编辑模式')) {
cherryObj.switchModel('edit&preview');
}`
)}`,
},
{
name: "getToc",
title: `getToc()`,
desc: `获取由标题组成的目录`,
code: `alert(cherryObj.getToc());
console.log(cherryObj.getToc());`,
markdown: `# Cherry API
## getToc()
获取由标题组成的目录
${getFullCode(`alert(cherryObj.getToc());
console.log(cherryObj.getToc());`)}`,
},
{
name: "getCodeMirror",
title: `getCodeMirror()`,
desc: `获取左侧编辑器实例`,
code: `alert(cherryObj.getCodeMirror());
console.log(cherryObj.getCodeMirror());`,
markdown: `# Cherry API
## getCodeMirror()
获取左侧编辑器实例
${getFullCode(`alert(cherryObj.getCodeMirror());
console.log(cherryObj.getCodeMirror());`)}`,
},
{
name: "getPreviewer",
title: `getPreviewer()`,
desc: `获取右侧预览区对象实例`,
code: `alert(cherryObj.getPreviewer());
console.log(cherryObj.getPreviewer());`,
markdown: `# Cherry API
## getPreviewer()
获取右侧预览区对象实例
${getFullCode(`alert(cherryObj.getPreviewer());
console.log(cherryObj.getPreviewer());`)}`,
},
],
},
{
name: "Cherry.Previewer API",
api: [{
name: "Previewer.scrollToId",
title: `Previewer.scrollToId(id:string)`,
desc: `滚动到对应id的元素位置
id 可以为带#号hash,也可以是id值`,
code: `// 查看可跳转的id
console.log(cherryObj.getToc());
// 两种方式都可获得previewer对象
console.log(cherryObj.previewer == cherryObj.getPreviewer())
//两种写法都可以
// cherryObj.previewer.scrollToId('#test-scroll');
cherryObj.previewer.scrollToId('test-scroll');`,
markdown: `# Cherry API
## Previewer.scrollToId(id:string)
滚动到对应id的元素位置
id 可以为带#号hash,也可以是id值
// 查看可跳转的id
${getFullCode(`// 查看可跳转的id
console.log(cherryObj.getToc());
// 两种方式都可获得previewer对象
console.log(cherryObj.previewer == cherryObj.getPreviewer())
// 两种写法都可以
// cherryObj.previewer.scrollToId('#test-scroll');
cherryObj.previewer.scrollToId('test-scroll');`)}
# Test Scroll`,
}]
},
{
name: "Cherry.engine API",
api: [
{
name: "makeHtml",
title: `engine.makeHtml(markdown:string)`,
desc: `将markdown字符串渲染成Html`,
code: `alert(cherryObj.engine.makeHtml('This is \`inline code\`'));
console.log(cherryObj.engine.makeHtml('This is \`inline code\`'));`,
markdown: `# Cherry.engine API
## engine.makeHtml(markdown:string)
将markdown字符串渲染成Html
${getFullCode(`alert(cherryObj.engine.makeHtml('This is \`inline code\`'));
console.log(cherryObj.engine.makeHtml('This is\`inline code\`'));`)}`,
},
{
name: "makeMarkdown",
title: `engine.makeMarkdown(html:string)`,
desc: `将html字符串渲染成markdown`,
code: `var html = \` < p > This is<code>inline code</code></ >\`;
alert(cherryObj.engine.makeMarkdown(html));
console.log(cherryObj.engine.makeMarkdown(html));`,
markdown: `# Cherry.engine API
## engine.makeMarkdown(html:string)
将html字符串渲染成markdown
${getFullCode(`var html = \` < p > This is < code > inline code</ ></ >\`;
alert(cherryObj.engine.makeMarkdown(html));
console.log(cherryObj.engine.makeMarkdown(html));`)}`,
},
],
},
{
name: "Cherry.toolbar.toolbarHandlers API",
api: [
{
name: "bold",
title: `toolbar.toolbarHandlers.bold()`,
desc: `在cherry编辑区域的选定文本处插入加粗语法`,
code: `cherryObj.toolbar.toolbarHandlers.bold()`,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.bold()
在cherry编辑区域的选定文本处插入加粗语法
${getFullCode(`cherryObj.toolbar.toolbarHandlers.bold()`)}`,
},
{
name: "italic",
title: `toolbar.toolbarHandlers.italic()`,
desc: `在cherry编辑区域的选定文本处插入斜体语法`,
code: `cherryObj.toolbar.toolbarHandlers.italic()`,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.italic()
在cherry编辑区域的选定文本处插入斜体语法
${getFullCode(`cherryObj.toolbar.toolbarHandlers.italic()`)}`,
},
{
name: "header",
title: `toolbar.toolbarHandlers.header(level: int)`,
desc: `在cherry编辑区域的选定文本处插入标题语法`,
code: `cherryObj.toolbar.toolbarHandlers.header(1)
// cherryObj.toolbar.toolbarHandlers.header(2)
// cherryObj.toolbar.toolbarHandlers.header(4)`,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.header(level: int)
在cherry编辑区域的选定文本处插入标题语法
${getFullCode(`cherryObj.toolbar.toolbarHandlers.header(1)
// cherryObj.toolbar.toolbarHandlers.header(2)
// cherryObj.toolbar.toolbarHandlers.header(4)`)}`,
},
{
name: "strikethrough",
title: `toolbar.toolbarHandlers.strikethrough()`,
desc: `在cherry编辑区域的选定文本处插入删除线语法`,
code: `cherryObj.toolbar.toolbarHandlers.strikethrough()`,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.strikethrough()
在cherry编辑区域的选定文本处插入删除线语法
${getFullCode(`cherryObj.toolbar.toolbarHandlers.strikethrough()`)}`,
},
{
name: "list",
title: `toolbar.toolbarHandlers.list(type: string)`,
desc: `在cherry编辑区域的选定文本处插入有序、无序列表或者checklist语法`,
code: `if(confirm('有序列表')) {
cherryObj.toolbar.toolbarHandlers.list(1);
}else if (confirm('无序列表')) {
cherryObj.toolbar.toolbarHandlers.list('2');
} else if (confirm('checklist')) {
cherryObj.toolbar.toolbarHandlers.list(3);
} `,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.list(type: string)
在cherry编辑区域的选定文本处插入有序、无序列表或者checklist语法
| level | 效果 |
|:-:|:-:|
| 1 | ol 列表 |
| 2 | ul 列表 |
| 3 | checklist |
${getFullCode(`if(confirm('有序列表')) {
cherryObj.toolbar.toolbarHandlers.list(1);
}else if (confirm('无序列表')) {
cherryObj.toolbar.toolbarHandlers.list('2');
} else if (confirm('checklist')) {
cherryObj.toolbar.toolbarHandlers.list(3);
} `)}`,
},
{
name: "insert",
title: `toolbar.toolbarHandlers.insert(type: string)`,
desc: `在cherry编辑区域的光标处插入特定语法`,
code: `if (confirm('插入3*4的表格')) {
cherryObj.toolbar.toolbarHandlers.insert('normal-table-3*4');
} else if (confirm('插入checklist')) {
cherryObj.toolbar.toolbarHandlers.insert('checklist');
} `,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.insert(type: string)
在cherry编辑区域的光标处插入特定语法:
| type | 效果 |
|:-:|:-:|
| 'hr' | 删除线 |
| 'br' | 强制换行 |
| 'code' | 代码块 |
| 'formula' | 行内公式 |
| 'checklist' | 检查项 |
| 'toc' | 目录 |
| 'link' | 超链接 |
| 'image' | 图片 |
| 'video' | 视频 |
| 'audio' | 音频 |
| 'normal-table' | 插入3行5列的表格 |
| 'normal-table-row*col' | 如 \`normal-table-2*4\` 插入2行(包含表头是3行)4列的表格 |
${getFullCode(`if (confirm('插入3*4的表格')) {
cherryObj.toolbar.toolbarHandlers.insert('normal-table-3*4');
} else if (confirm('插入checklist')) {
cherryObj.toolbar.toolbarHandlers.insert('checklist');
} `)}`,
},
{
name: "graph",
title: `toolbar.toolbarHandlers.graph(type:string)`,
desc: `在cherry编辑区域的光标处插入画图语法`,
code: `cherryObj.toolbar.toolbarHandlers.graph(1)
// cherryObj.toolbar.toolbarHandlers.graph('2')
// cherryObj.toolbar.toolbarHandlers.graph(4)`,
markdown: `# Cherry.toolbar.toolbarHandlers API
## toolbar.toolbarHandlers.graph(type:string)
在cherry编辑区域的光标处插入画图语法
|id |效果 |
|:-:|:-:|
|'1' |流程图 |
|'2' |时序图 |
|'3' |状态图 |
|'4' |类图 |
|'5' |饼图 |
|'6' |甘特图 |
\`\`\`mermaid
graph LR
A[公司] -->| 下 班 | B(菜市场)
B --> C{看见<br>卖西瓜的}
C -->|Yes| D[买一个包子]
C -->|No| E[买一斤包子]
\`\`\`
${getFullCode(`cherryObj.toolbar.toolbarHandlers.graph(1)
// cherryObj.toolbar.toolbarHandlers.graph('2')
// cherryObj.toolbar.toolbarHandlers.graph(4)`)}`,
},
]
}
]
const CodeTextArea = ({ value, onChange }: { value: string, onChange: Dispatch<SetStateAction<string>> }) => {
const ref = useRef<HTMLTextAreaElement | null>(null);
useEffect(() => {
const resize = () => {
if (ref.current) {
ref.current.style.height = 'auto';
ref.current.style.height = `${ref.current.scrollHeight}px`;
}
};
resize();
}, [value]);
return (
<textarea ref={ref} className="code-input" placeholder="请输入可执行的js代码,编辑器对象为'cherryObj'" onChange={e => onChange(e.target.value)} value={value} />
)
}
const Menu = ({ cherryObj }: { cherryObj: Cherry | null }) => {
const [selectedGroup, setSelectedGroup] = useState(0);
const [selectedApi, setSelectedApi] = useState(0);
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [code, setCode] = useState("");
const [allowReset, setAllowReset] = useState(false);
const handleOptionChange = useCallback((groupIdx: number, apiIdx: number) => {
setSelectedGroup(groupIdx);
setSelectedApi(apiIdx);
const content = data[groupIdx].api[apiIdx];
setCode(content.code);
setDesc(content.desc);
setTitle(content.title);
if (cherryObj !== null) {
cherryObj.setMarkdown(content.markdown);
}
}, [cherryObj]);
const reset = useCallback(() => {
const content = data[selectedGroup].api[selectedApi];
setCode(content.code);
if (cherryObj !== null) {
cherryObj.setMarkdown(content.markdown);
}
}, [selectedGroup, selectedApi, cherryObj]);
const computeAllowReset = useCallback(() => {
const content = data[selectedGroup].api[selectedApi];
const allow = content.name !== "resetToolbar" && content.name !== "destroy";
setAllowReset(allow);
}, [selectedGroup, selectedApi]);
useEffect(() => {
if (cherryObj) {
handleOptionChange(selectedGroup, selectedApi);
}
computeAllowReset();
}, [cherryObj, selectedGroup, selectedApi, computeAllowReset, handleOptionChange]);
return (
<div className="menu">
<div>
<span>选择例子</span>
{cherryObj && <>
<Select options={data.map(datum => datum.name)} onChange={(idx: SetStateAction<number>) => { setSelectedGroup(idx); setSelectedApi(0) }} selected={selectedGroup} />
<Select options={data[selectedGroup].api.map(datum => datum.name)} onChange={setSelectedApi} selected={selectedApi} />
</>}
</div>
<h2>{title}</h2>
<h3>描述</h3>
<div className="demo-desc">{desc}</div>
<h3>
<span>代码示例</span>
{cherryObj && <>
<button className="run-btn" onClick={() => { eval(code); }}>点击执行</button>
<button className="run-btn" onClick={reset} disabled={!allowReset}>{allowReset ? "重置" : "请通过刷新恢复"}</button>
</>}
</h3>
<CodeTextArea value={code} onChange={setCode} />
</div>
)
}
export default Menu;