forgeplus-react/src/modules/tpm/challengesnew/tpm-md-editor.js

272 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { Fragment, useEffect, useRef, useState } from 'react'
import { getUploadActionUrl, getUrl } from 'educoder'
import ResizeObserver from 'resize-observer-polyfill';
import '../../courses/css/Courses.css'
import './css/TPMchallengesnew.css'
import 'codemirror/lib/codemirror.css'
const $ = window.$
const mdIcons = ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "inline-latex", "latex", '|', "image", "table", '|', "line-break", "watch", "clear"]
const NULL_CH = '▁'
function md_add_data(k, mdu, d) {
window.sessionStorage.setItem(k + mdu, d);
}
// 清空保存的数据
function md_clear_data(k, mdu, id) {
window.sessionStorage.removeItem(k + mdu);
var id1 = "#e_tip_" + id;
var id2 = "#e_tips_" + id;
if (k == 'content') {
$(id2).html(" ");
} else {
$(id1).html(" ");
}
}
window.md_clear_data = md_clear_data
function md_rec_data(k, mdu, id) {
if (window.sessionStorage.getItem(k + mdu) !== null) {
var editor = $("#e_tips_" + id).data('editor');
editor.setValue(window.sessionStorage.getItem(k + mdu));
// /shixuns/b5hjq9zm/challenges/3977/tab=3 setValue可能导致editor样式问题
md_clear_data(k, mdu, id);
}
}
window.md_rec_data = md_rec_data;
function md_elocalStorage(editor, mdu, id) {
let oc = window.sessionStorage.getItem('content' + mdu)
if (oc !== null && oc != editor.getValue()) {
$("#e_tips_" + id).data('editor', editor);
let h = '您上次有已保存的数据,是否<a style="cursor: pointer;" class="link-color-blue" onclick="md_rec_data(\'content\',\'' + mdu + '\',\'' + id + '\')">恢复</a> ? / <a style="cursor: pointer;" class="link-color-blue" onclick="md_clear_data(\'content\',\'' + mdu + '\',\'' + id + '\')">不恢复</a>';
$("#e_tips_" + id).html(h)
}
let tid = setInterval(function () {
let d = new Date();
let h = d.getHours();
let m = d.getMinutes();
let s = d.getSeconds();
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
if (editor.getValue().trim() != "") {
md_add_data("content", mdu, editor.getValue());
let id2 = "#e_tips_" + id;
let textStart = " 数据已于 "
let text = textStart + h + ':' + m + ':' + s + " 保存 ";
// 占位符
let oldHtml = $(id2).html();
if (oldHtml && oldHtml != ' ' && oldHtml.startsWith(textStart) == false) {
$(id2).html(oldHtml.split(' (')[0] + ` (${text})`);
} else {
$(id2).html(text);
}
}
}, 10000)
return tid
}
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true }) => {
const editorEl = useRef()
const resizeBarEl = useRef()
const [editorInstance, setEditorInstance] = useState()
const containerId = `mdEditor_${mdID}`
const editorBodyId = `mdEditors_${mdID}`
const tipId = `e_tips_mdEditor_${mdID}`
function onLayout() {
let ro
if (editorEl.current) {
ro = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) {
editorInstance.resize()
editorInstance.cm.refresh()
editorInstance.cm.focus()
}
}
})
ro.observe(editorEl.current)
}
return ro
}
useEffect(() => {
if (editorInstance) {
return
}
if (!startInit) { return }
const editor_instance = window.editormd(containerId, {
width,
height,
path: getUrl("/editormd/lib/"),
markdown: initValue,
syncScrolling: "single",
tex: true,
tocm: true,
emoji: !!emoji,
taskList: true,
codeFold: true,
searchReplace: true,
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
watch: watch === undefined ? true : watch,
saveHTMLToTextarea: true,
dialogMaskOpacity: 0.6,
placeholder: placeholder,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: getUploadActionUrl(),
toolbarIcons: function () {
return showNullButton ? [...mdIcons, 'null-button'] : mdIcons
},
toolbarIconsClass: {
"line-break": "fa-minus",
},
toolbarCustomIcons: {
"inline-latex": "<a title='行内公式' class='latex' ><i name='inline-latex' class='fa iconfont icon-hangneigongshi font-14'></i></a>",
"latex": "<a title='多行公式' class='latex' ><i name='latex' class='fa iconfont icon-duohanggongshi font-16'></i></a>",
"null-button": "<a class='pr' title='增加填空'><i class='border-left'><span></span></i><span name='null-button' class='fa fillTip'>点击插入填空项</span><i class='iconfont fa icon-edit font-16' name='null-button'></i></a>",
},
toolbarHandlers: {
"line-break": function (cm, icon, cursor, selection) {
cm.replaceSelection("<br/>")
},
"null-button": function (cm, icon, cursor, selection) {
if (selection === "") {
cm.setCursor(cursor.line, cursor.ch + 1)
}
cm.replaceSelection(NULL_CH)
},
"inline-latex": function (cm, icon, cursor, selection) {
cm.replaceSelection("$$" + selection + "$$");
cm.setCursor(cursor.line, cursor.ch + 2);
cm.focus()
},
"latex": function (cm, icon, cursor, selection) {
cm.replaceSelection("```latex\n\n" + selection + "```");
cm.setCursor(cursor.line + 1, 0);
cm.focus()
},
},
lang: {
toolbar: {
"latex": "多行公式",
"line-break": "换行"
}
},
onload: function () {
setEditorInstance(editor_instance)
}
})
}, [containerId, editorInstance, startInit])
const cmEl = editorInstance && editorInstance.cm
useEffect(() => {
if (cmEl) {
let tid = null
let ro
if (onCMBlur) {
editorInstance.cm.on('blur', () => { onCMBlur(editorInstance.getValue()) })
}
if (onCMBeforeChange) {
editorInstance.cm.on('beforeChange', (cm, change) => {
onCMBeforeChange(cm, change)
})
}
if (!noStorage) {
tid = md_elocalStorage(editorInstance, `MDEditor__${containerId}`, containerId)
}
if (onChange) {
editorInstance.cm.on('change', (cm) => {
onChange(cm.getValue())
})
}
ro = onLayout()
return () => {
if (!noStorage) {
clearInterval(tid)
}
if (ro) {
ro.unobserve(editorEl.current)
}
}
}
}, [cmEl])
useEffect(() => {
if (editorInstance && initValue !== undefined) {
if (initValue != null && initValue != editorInstance.getValue()) {
editorInstance.setValue(initValue.toString())
}
}
}, [editorInstance, initValue, containerId])
useEffect(() => {
if (resizeBarEl.current) {
let el = resizeBarEl.current
let dragging = false
let startY = 0
function onMouseDown(e) {
dragging = true
startY = e.pageY
}
function onMouseUp() {
dragging = false
}
function onMouseMove(e) {
if (dragging) {
let delta = e.pageY - startY
if (delta < 0) {
delta = 0
}
if (delta > 300) {
delta = 300
}
let resizeH = height + delta + 'px'
editorInstance.resize('', resizeH)
}
}
el.addEventListener('mousedown', onMouseDown)
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
return () => {
el.removeEventListener('mousedown', onMouseDown)
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
}
}, [
editorInstance, resizeBarEl
])
return (
<Fragment>
<div ref={editorEl} className={`df ${className} ${imageExpand && 'editormd-image-click-expand'} `} >
<div className={`edu-back-greyf5 radius4 editormd ${error ? 'error' : ''}`} id={containerId} >
<textarea style={{ display: 'none' }} id={editorBodyId} name="content"></textarea>
<div className="CodeMirror cm-s-defualt"></div>
</div>
</div>
{showResizeBar ? <a ref={resizeBarEl} className='editor-resize'></a> : null}
<div className={"fr rememberTip"}>
{noStorage == true ? null : <div id={tipId} className="edu-txt-right color-grey-cd font-12"></div>}
</div>
</Fragment>
)
}