+ );
+}
+
+export default ReactQuill;
diff --git a/public/react/src/common/reactQuill/deepEqual.js b/public/react/src/common/reactQuill/deepEqual.js
new file mode 100644
index 000000000..6f2b276bf
--- /dev/null
+++ b/public/react/src/common/reactQuill/deepEqual.js
@@ -0,0 +1,47 @@
+function deepEqual (prev, current) {
+ if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined
+ return true;
+ }
+
+ if ((!prev && current)
+ || (prev && !current)
+ || (!prev && !current)
+ ) {
+ return false;
+ }
+
+ if (Array.isArray(prev)) {
+ if (!Array.isArray(current)) return false;
+ if (prev.length !== current.length) return false;
+
+ for (let i = 0; i < prev.length; i++) {
+ if (!deepEqual(current[i], prev[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (typeof current === 'object') {
+ if (typeof prev !== 'object') return false;
+ const prevKeys = Object.keys(prev);
+ const curKeys = Object.keys(current);
+
+ if (prevKeys.length !== curKeys.length) return false;
+
+ prevKeys.sort();
+ curKeys.sort();
+
+ for (let i = 0; i < prevKeys.length; i++) {
+ if (prevKeys[i] !== curKeys[i]) return false;
+ const key = prevKeys[i];
+ if (!deepEqual(prev[key], current[key])) return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+export default deepEqual;
diff --git a/public/react/src/common/reactQuill/flatten.js b/public/react/src/common/reactQuill/flatten.js
new file mode 100644
index 000000000..237cb543f
--- /dev/null
+++ b/public/react/src/common/reactQuill/flatten.js
@@ -0,0 +1,26 @@
+/*
+ * @Description: 将多维数组转变成一维数组
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-09 09:35:01
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-16 11:36:22
+ */
+function flatten (array) {
+ return flatten.rec(array, []);
+}
+
+flatten.rec = function flatten (array, result) {
+
+ for (let item of array) {
+ if (Array.isArray(item)) {
+ flatten(item, result);
+ } else {
+ result.push(item);
+ }
+ }
+
+ return result;
+}
+
+export default flatten;
diff --git a/public/react/src/common/reactQuill/index.js b/public/react/src/common/reactQuill/index.js
new file mode 100644
index 000000000..56a1a8d1f
--- /dev/null
+++ b/public/react/src/common/reactQuill/index.js
@@ -0,0 +1,108 @@
+/*
+ * @Description: 入口文件
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-17 10:41:48
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-17 20:34:40
+ */
+import React, { useState, useCallback, useEffect } from 'react';
+import ReactQuill from './lib';
+
+function Wrapper (props) {
+ // 默认工具栏配置项
+ const toolbarConfig = [
+ ['bold', 'italic', 'underline'],
+ [{align: []}, {list: 'ordered'}, {list: 'bullet'}], // 列表
+ [{script: 'sub'}, {script: 'super'}],
+ [{header: [1,2,3,4,5,false]}],
+ ['blockquote', 'code-block'],
+ ['link', 'image', 'video'],
+ ['formula'],
+ ['clean']
+ ];
+
+ const [placeholder] = useState(props.placeholder || 'placeholder');
+ const [disableBold] = useState(false);
+ const [value, setValue] = useState(props.value || '');
+ const [toolbar, setToolbar] = useState(toolbarConfig);
+ const [theme, setTheme] = useState(props.theme || 'snow');
+ const [readOnly] = useState(props.readOnly || false);
+
+ const {
+ onContentChagne, // 当编辑器内容变化时调用该函数
+ showUploadImage, // 显示上传图片, 返回url,主要用于点击图片放大
+ } = props;
+
+ // 配置信息
+ const options = {
+ modules: {
+ toolbar: toolbar,
+ clipboard: {
+ matchVisual: false
+ }
+ },
+ readOnly: readOnly,
+ theme: theme
+ }
+ // 配置信息
+ useEffect (() => {
+ if (props.options) {
+ setToolbar(props.options);
+ }
+ setTheme(props.theme || 'snow');
+ setValue(props.value);
+ }, [props]);
+
+ // 当内容变化时
+ const handleOnChange = useCallback(
+ contents => {
+ if (disableBold) {
+ setValue({
+ ops: contents.ops.map(x => {
+ x = {...x};
+ if (x && x.attributes && x.attributes.bold) {
+ x.attributes = { ...x.attributes };
+ delete x.attributes.bold;
+ if (!Object.keys(x.attributes).length) {
+ delete x.attributes;
+ }
+ }
+ return x;
+ })
+ });
+ } else {
+ setValue(contents);
+ }
+ onContentChagne && onContentChagne(contents);
+ }, [disableBold]
+ );
+
+ // 图片上传
+ const handleUploadImage = (files) => {
+ console.log('选择的图片信息', files);
+ }
+
+ // 显示图片
+ const handleShowUploadImage = (url) => {
+ // console.log('上传的图片url:', url);
+ showUploadImage && showUploadImage(url);
+ }
+
+ return (
+
+ handleShowUploadImage(url)}
+ />
+
+ );
+}
+
+export default Wrapper;
+// ReactDOM.render(
, document.querySelector('#root'));
diff --git a/public/react/src/common/reactQuill/index.scss b/public/react/src/common/reactQuill/index.scss
new file mode 100644
index 000000000..b6da52bf5
--- /dev/null
+++ b/public/react/src/common/reactQuill/index.scss
@@ -0,0 +1,32 @@
+#quill-toolbar{
+ .quill-btn{
+ vertical-align: middle;
+ }
+ .quill_image{
+ display: inline-block;
+ position: relative;
+ vertical-align: middle;
+ width: 28px;
+ height: 24px;
+ overflow: hidden;
+ .image_input{
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ opacity: 0;
+ }
+ .ql-image{
+ position: relative;
+ left: 0;
+ top: 0;
+ }
+ }
+}
+
+.react_quill_area{
+ .ql-toolbar:not(:last-child) {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/public/react/src/common/reactQuill/lib.js b/public/react/src/common/reactQuill/lib.js
new file mode 100644
index 000000000..430a95bb7
--- /dev/null
+++ b/public/react/src/common/reactQuill/lib.js
@@ -0,0 +1,13 @@
+/*
+ * @Description: 导出 ReactQuill
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-09 09:08:24
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-16 11:37:13
+ */
+import ReactQuill from './ReactQuill';
+import useQuill from './useQuill';
+
+export default ReactQuill;
+export { useQuill };
diff --git a/public/react/src/common/reactQuill/useDeepEqualMemo.js b/public/react/src/common/reactQuill/useDeepEqualMemo.js
new file mode 100644
index 000000000..948e21781
--- /dev/null
+++ b/public/react/src/common/reactQuill/useDeepEqualMemo.js
@@ -0,0 +1,27 @@
+/*
+ * @Description:
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-12 19:48:55
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-16 11:38:16
+ */
+import { useState, useEffect } from 'react';
+import deepEqual from './deepEqual';
+
+function useDeepEqual (input) {
+
+ const [value, setValue] = useState(input);
+
+ useEffect(() => {
+
+ if (!deepEqual(input, value)) {
+ setValue(input)
+ }
+
+ }, [input, value]);
+
+ return value;
+}
+
+export default useDeepEqual;
diff --git a/public/react/src/common/reactQuill/useMountQuill.js b/public/react/src/common/reactQuill/useMountQuill.js
new file mode 100644
index 000000000..c2313c480
--- /dev/null
+++ b/public/react/src/common/reactQuill/useMountQuill.js
@@ -0,0 +1,148 @@
+/*
+ * @Description: 创建 reactQuill实例
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-09 09:31:42
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-17 20:42:05
+ */
+import Quill from 'quill'; // 导入quill
+import { useState, useEffect, useMemo } from 'react';
+import flatten from './flatten.js';
+import useDeepEqualMemo from './useDeepEqualMemo';
+import Katex from 'katex';
+import ImageBlot from './ImageBlot';
+import { fetchUploadImage } from '../../services/ojService.js';
+import { getImageUrl } from 'educoder'
+window.katex = Katex;
+
+Quill.register(ImageBlot);
+
+function useMountQuill ({
+ element,
+ options: passedOptions,
+ uploadImage,
+ showUploadImage,
+ imgAttrs = {} // 指定图片的宽高属性
+}) {
+
+ // 是否引入 katex
+ const [katexLoaded, setKatexLoaded] = useState(Boolean(window.katex))
+ const [quill, setQuill] = useState(null);
+
+ const options = useDeepEqualMemo(passedOptions);
+ console.log('use mount quill: ', passedOptions);
+
+ // 判断options中是否包含公式
+ const requireKatex = useMemo(() => {
+ return flatten(options.modules.toolbar).includes('formula');
+ }, [options]);
+
+ // 加载katex
+ useEffect(() => {
+ if (!requireKatex) return;
+ if (katexLoaded) return;
+
+ const interval = setInterval(() => {
+ if (window.katex) {
+ setKatexLoaded(true);
+ clearInterval(interval);
+ }
+ });
+
+ return () => { // 定义回调清除定时器
+ clearInterval(interval);
+ }
+
+ }, [
+ setKatexLoaded,
+ katexLoaded,
+ requireKatex
+ ]);
+
+ // 加载 quill
+ useEffect(() => {
+ if (!element) return;
+ if (requireKatex && !katexLoaded) {
+ element.innerHTML = `
+
+ Loading Katex...
+
+ `
+ }
+ // 清空内容
+ element.innerHTML = '';
+ console.log(element);
+ // 创建 quill 节点
+ const quillNode = document.createElement('div');
+ element.appendChild(quillNode); // 将quill节点追回到 element 元素中
+
+ const quill = new Quill(element, options);
+ setQuill(quill);
+ // 加载上传图片功能
+ if (typeof uploadImage === 'function') {
+ quill.getModule('toolbar').addHandler('image', (e) => {
+ // 创建type类型输入框加载本地图片
+ const input = document.createElement('input');
+ input.setAttribute('type', 'file');
+ input.setAttribute('accept', 'image/*');
+ input.click();
+
+ input.onchange = async (e) => {
+ const file = input.files[0]; // 获取文件信息
+ const formData = new FormData();
+ formData.append('file', file);
+
+ // const reader = new FileReader();
+ // reader.readAsDataURL(file);
+ // console.log('文件信息===>>', reader);
+ // reader.onload = function (e) {
+ // debugger;
+ // console.log('文件信息===>>', e.target.result);
+ // const image = new Image();
+ // image.src = e.target.result;
+
+ // image.onload = function () {
+ // // file.width =
+ // console.log(image.width, image.height);
+ // }
+ // }
+
+ const range = quill.getSelection(true);
+ let fileUrl = ''; // 保存上传成功后图片的url
+ // 上传文件
+ const result = await fetchUploadImage(formData);
+ // 获取上传图片的url
+ if (result.data && result.data.id) {
+ fileUrl = getImageUrl(`api/attachments/${result.data.id}`);
+ }
+ // 根据id获取文件路径
+ const { width, height } = imgAttrs;
+ // console.log('上传图片的url:', fileUrl);
+ if (fileUrl) {
+ quill.insertEmbed(range.index, 'image', {
+ url: fileUrl,
+ alt: '',
+ onClick: showUploadImage,
+ width,
+ height
+ });
+ }
+ }
+ });
+ }
+
+ return () => {
+ element.innerHTML = '';
+ }
+ }, [
+ element,
+ options,
+ requireKatex,
+ katexLoaded,
+ ]);
+
+ return quill;
+}
+
+export default useMountQuill;
diff --git a/public/react/src/common/reactQuill/useQuill.js b/public/react/src/common/reactQuill/useQuill.js
new file mode 100644
index 000000000..b959dbc52
--- /dev/null
+++ b/public/react/src/common/reactQuill/useQuill.js
@@ -0,0 +1,60 @@
+/*
+ * @Description:
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-09 09:09:50
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-17 15:46:50
+ */
+import useQuillPlaceholder from './useQuillPlaceholder';
+import useQuillValueSync from './useQuillValueSync';
+import useQuillOnChange from './useQuillOnChange';
+import useMountQuill from './useMountQuill';
+import { useEffect } from 'react';
+
+function useQuill ({
+ disallowColors,
+ placeholder,
+ uploadImage,
+ onChange,
+ options,
+ value,
+ element,
+ showUploadImage
+}) {
+
+ // 获取 quill 实例
+ const quill = useMountQuill({
+ element,
+ options,
+ uploadImage,
+ showUploadImage
+ });
+
+ useEffect(() => {
+ if (disallowColors && quill) {
+ quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
+ delta.ops = delta.ops.map(op => {
+ if (op.attributes && op.attributes.color) {
+ const { color, ...attributes } = op.attributes;
+ return {
+ ...op,
+ attributes
+ }
+ }
+ return op;
+ });
+ return delta;
+ });
+ }
+ }, [
+ disallowColors,
+ quill
+ ]);
+
+ useQuillPlaceholder(quill, placeholder);
+ useQuillValueSync(quill, value);
+ useQuillOnChange(quill, onChange);
+}
+
+export default useQuill;
diff --git a/public/react/src/common/reactQuill/useQuillOnChange.js b/public/react/src/common/reactQuill/useQuillOnChange.js
new file mode 100644
index 000000000..45333a4e1
--- /dev/null
+++ b/public/react/src/common/reactQuill/useQuillOnChange.js
@@ -0,0 +1,33 @@
+/*
+ * @Description:
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-12 19:49:11
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-16 11:39:27
+ */
+import { useEffect } from 'react';
+
+function useQuillOnChange (quill, onChange) {
+
+ useEffect(() => {
+
+ if (!quill) return;
+ if (typeof onChange !== 'function') return;
+
+ let handler;
+
+ quill.on(
+ 'text-change',
+ (handler = () => {
+ onChange(quill.getContents()); // getContents: 检索编辑器内容
+ })
+ );
+
+ return () => {
+ quill.off('text-change', handler);
+ }
+ }, [quill, onChange]);
+}
+
+export default useQuillOnChange;
diff --git a/public/react/src/common/reactQuill/useQuillPlaceholder.js b/public/react/src/common/reactQuill/useQuillPlaceholder.js
new file mode 100644
index 000000000..ccc341568
--- /dev/null
+++ b/public/react/src/common/reactQuill/useQuillPlaceholder.js
@@ -0,0 +1,22 @@
+/*
+ * @Description:
+ * @Author: tangjiang
+ * @Github:
+ * @Date: 2019-12-09 09:28:34
+ * @LastEditors: tangjiang
+ * @LastEditTime: 2019-12-16 11:39:48
+ */
+import { useEffect } from 'react'
+
+function useQuillPlaceholder (
+ quill,
+ placeholder
+) {
+
+ useEffect(() => {
+ if (!quill || !quill.root) return;
+ quill.root.dataset.placeholder = placeholder;
+ }, [quill, placeholder]);
+}
+
+export default useQuillPlaceholder;
diff --git a/public/react/src/common/reactQuill/useQuillValueSync.js b/public/react/src/common/reactQuill/useQuillValueSync.js
new file mode 100644
index 000000000..696d88949
--- /dev/null
+++ b/public/react/src/common/reactQuill/useQuillValueSync.js
@@ -0,0 +1,31 @@
+import { useEffect, useState } from 'react'
+import deepEqual from './deepEqual.js'
+
+function useQuillValueSync(quill, value) {
+ const [selection, setSelection] = useState(null)
+
+ useEffect(() => {
+ if (!quill) return
+
+ const previous = quill.getContents()
+ const current = value
+
+ if (!deepEqual(previous, current)) {
+ setSelection(quill.getSelection())
+ if (typeof value === 'string') {
+ quill.clipboard.dangerouslyPasteHTML(value, 'api')
+ } else {
+ quill.setContents(value)
+ }
+ }
+ }, [quill, value, setSelection])
+
+ useEffect(() => {
+ if (quill && selection) {
+ quill.setSelection(selection)
+ setSelection(null)
+ }
+ }, [quill, selection, setSelection])
+}
+
+export default useQuillValueSync
diff --git a/public/react/src/modules/developer/DeveloperHome.js b/public/react/src/modules/developer/DeveloperHome.js
index 5f787659e..73a5c37a1 100644
--- a/public/react/src/modules/developer/DeveloperHome.js
+++ b/public/react/src/modules/developer/DeveloperHome.js
@@ -16,7 +16,7 @@ import MultipTags from './components/multiptags';
// import { Link } from 'react-router-dom';
import CONST from '../../constants';
import { withRouter } from 'react-router';
-import { toStore } from 'educoder';
+import { toStore, CNotificationHOC } from 'educoder';
// import MyIcon from '../../common/components/MyIcon';
const {tagBackground, diffText} = CONST;
@@ -249,17 +249,26 @@ class DeveloperHome extends React.PureComponent {
// 删除
handleClickDelete = (record) => {
const { deleteItem } = this.props;
- Modal.confirm({
- title: '删除',
+ this.props.confirm({
+ title: '提示',
content: `确定要删除${record.name}吗?`,
- okText: '确定',
- cancelText: '取消',
onOk () {
// 调用删除接口
console.log(record.identifier);
deleteItem(record.identifier);
}
});
+ // Modal.confirm({
+ // title: '删除',
+ // content: `确定要删除${record.name}吗?`,
+ // okText: '确定',
+ // cancelText: '取消',
+ // onOk () {
+ // // 调用删除接口
+ // console.log(record.identifier);
+ // deleteItem(record.identifier);
+ // }
+ // });
}
// table条件变化时
handleTableChange = (pagination, filters, sorter) => {
@@ -562,5 +571,5 @@ const mapDispatchToProps = (dispatch) => ({
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
-)(DeveloperHome));
+)(CNotificationHOC() (DeveloperHome)));
// export default DeveloperHome;
diff --git a/public/react/src/modules/developer/components/controlSetting/index.js b/public/react/src/modules/developer/components/controlSetting/index.js
index 587e1bee9..1d4d7834c 100644
--- a/public/react/src/modules/developer/components/controlSetting/index.js
+++ b/public/react/src/modules/developer/components/controlSetting/index.js
@@ -4,10 +4,10 @@
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
- * @LastEditTime: 2019-12-13 17:32:33
+ * @LastEditTime: 2019-12-19 19:47:32
*/
import './index.scss';
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useEffect } from 'react';
import { Tabs, Button, Icon } from 'antd';
import { connect } from 'react-redux';
import InitTabCtx from '../initTabCtx';
@@ -23,14 +23,15 @@ const ControlSetting = (props) => {
submitLoading,
identifier,
excuteState,
+ showOrHideControl,
commitRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,
- showOrHideControl,
+ changeShowOrHideControl,
// debuggerCode,
// startDebuggerCode, // 外部存入
onDebuggerCode,
- updateCode,
+ // updateCode,
onSubmitForm
} = props;
const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
@@ -44,10 +45,14 @@ const ControlSetting = (props) => {
setDefaultActiveKey(key);
}
+ useEffect(() => {
+ setShowTextResult(props.showOrHideControl);
+ }, [props]);
+
// 显示/隐藏tab
const handleShowControl = () => {
setShowTextResult(!showTextResult);
- showOrHideControl(!showTextResult);
+ changeShowOrHideControl(!showTextResult);
}
// 调试代码
@@ -55,7 +60,7 @@ const ControlSetting = (props) => {
// console.log(formRef.current.handleTestCodeFormSubmit);
// 调出控制台界面
setShowTextResult(true);
- showOrHideControl(true);
+ changeShowOrHideControl(true);
formRef.current.handleTestCodeFormSubmit(() => {
setDefaultActiveKey('2');
});
@@ -84,7 +89,7 @@ const ControlSetting = (props) => {
@@ -131,19 +136,20 @@ const ControlSetting = (props) => {
const mapStateToProps = (state) => {
const {commonReducer, ojForUserReducer} = state;
- const {loading, excuteState, submitLoading } = commonReducer;
+ const {loading, excuteState, submitLoading, showOrHideControl } = commonReducer;
const { commitRecordDetail } = ojForUserReducer;
return {
loading,
submitLoading,
excuteState,
+ showOrHideControl,
// identifier: user_program_identifier,
commitRecordDetail // 提交详情
};
};
// changeSubmitLoadingStatus
const mapDispatchToProps = (dispatch) => ({
- showOrHideControl: (flag) => dispatch(actions.showOrHideControl(flag)),
+ changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
debuggerCode: (identifier, values) => dispatch(actions.debuggerCode(identifier, values)),
diff --git a/public/react/src/modules/developer/components/controlSetting/index.scss b/public/react/src/modules/developer/components/controlSetting/index.scss
index 97838ce5c..31beda8a5 100644
--- a/public/react/src/modules/developer/components/controlSetting/index.scss
+++ b/public/react/src/modules/developer/components/controlSetting/index.scss
@@ -2,7 +2,8 @@
position: absolute;
bottom: 0;
width: 100%;
- background:rgba(30,30,30,1);
+ // background: red;
+ // background:rgba(30,30,30,1);
// height: 56px;
.control_tab{
position: absolute;
@@ -52,8 +53,8 @@
height: 56px;
padding-right: 30px;
padding-left: 10px;
- // background: #000;
- background:rgba(48,48,48,1);
+ background: rgba(18,28,36,1);
+ // background:rgba(48,48,48,1);
}
.setting_drawer{
diff --git a/public/react/src/modules/developer/components/execResult/index.js b/public/react/src/modules/developer/components/execResult/index.js
index 32bbbee91..6f9341b9a 100644
--- a/public/react/src/modules/developer/components/execResult/index.js
+++ b/public/react/src/modules/developer/components/execResult/index.js
@@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-28 08:44:54
* @LastEditors: tangjiang
- * @LastEditTime: 2019-12-10 09:24:02
+ * @LastEditTime: 2019-12-19 10:44:16
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@@ -38,6 +38,15 @@ function ExecResult (props) {
);
+
+ const renderError = () => (
+