commit
						b301628705
					
				@ -1,16 +1,16 @@
 | 
				
			||||
class AddUniqIndexToPollVotes < ActiveRecord::Migration[5.2]
 | 
				
			||||
  def change
 | 
				
			||||
    remove_index :poll_votes, column: [:poll_question_id, :user_id]
 | 
				
			||||
 | 
				
			||||
    change_column_default :poll_votes, :poll_question_id, from: nil, to: -1
 | 
				
			||||
    PollVote.where(poll_answer_id: nil).update_all(poll_answer_id: -1)
 | 
				
			||||
 | 
				
			||||
    sql = %Q(delete from poll_votes where (poll_question_id, user_id, poll_answer_id) in
 | 
				
			||||
            (select * from (select poll_question_id, user_id, poll_answer_id from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1) a)
 | 
				
			||||
            and id not in (select * from (select min(id) from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1 order by id) b))
 | 
				
			||||
    ActiveRecord::Base.connection.execute sql
 | 
				
			||||
 | 
				
			||||
    add_index :poll_votes, [:poll_question_id, :user_id, :poll_answer_id], name: 'poll_answer_index', unique: true
 | 
				
			||||
    # remove_index :poll_votes, column: [:poll_question_id, :user_id]
 | 
				
			||||
    #
 | 
				
			||||
    # change_column_default :poll_votes, :poll_question_id, from: nil, to: -1
 | 
				
			||||
    # PollVote.where(poll_answer_id: nil).update_all(poll_answer_id: -1)
 | 
				
			||||
    #
 | 
				
			||||
    # sql = %Q(delete from poll_votes where (poll_question_id, user_id, poll_answer_id) in
 | 
				
			||||
    #         (select * from (select poll_question_id, user_id, poll_answer_id from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1) a)
 | 
				
			||||
    #         and id not in (select * from (select min(id) from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1 order by id) b))
 | 
				
			||||
    # ActiveRecord::Base.connection.execute sql
 | 
				
			||||
    #
 | 
				
			||||
    # add_index :poll_votes, [:poll_question_id, :user_id, :poll_answer_id], name: 'poll_answer_index', unique: true
 | 
				
			||||
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
 | 
				
			||||
@ -0,0 +1,5 @@
 | 
				
			||||
class AddIndexToExerciseAnswers < ActiveRecord::Migration[5.2]
 | 
				
			||||
  def change
 | 
				
			||||
    add_index :exercise_answers, [:exercise_question_id, :user_id], name: "index_on_question_id_user_id"
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,16 @@
 | 
				
			||||
class AddIndexToExerciseChoice < ActiveRecord::Migration[5.2]
 | 
				
			||||
  def change
 | 
				
			||||
    change_column_default :exercise_answers, :exercise_choice_id, from: nil, to: -1
 | 
				
			||||
    ExerciseAnswer.where(exercise_choice_id: nil).update_all(exercise_choice_id: -1)
 | 
				
			||||
 | 
				
			||||
    sql = %Q(delete from exercise_answers where (exercise_question_id, user_id, exercise_choice_id) in
 | 
				
			||||
            (select * from (select exercise_question_id, user_id, exercise_choice_id from exercise_answers group by exercise_question_id, user_id, exercise_choice_id having count(*) > 1) a)
 | 
				
			||||
            and id not in (select * from (select min(id) from exercise_answers group by exercise_question_id, user_id, exercise_choice_id having count(*) > 1 order by id) b))
 | 
				
			||||
    ActiveRecord::Base.connection.execute sql
 | 
				
			||||
 | 
				
			||||
    add_index :exercise_answers, [:exercise_question_id, :user_id, :exercise_choice_id], name: 'exercise_choice_index', unique: true
 | 
				
			||||
 | 
				
			||||
    remove_index :exercise_answers, name: :index_on_question_id_user_id
 | 
				
			||||
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,15 @@
 | 
				
			||||
class AddIndexToPollAnswer < ActiveRecord::Migration[5.2]
 | 
				
			||||
  def change
 | 
				
			||||
    remove_index :poll_votes, column: [:poll_question_id, :user_id]
 | 
				
			||||
 | 
				
			||||
    change_column_default :poll_votes, :poll_question_id, from: nil, to: -1
 | 
				
			||||
    PollVote.where(poll_answer_id: nil).update_all(poll_answer_id: -1)
 | 
				
			||||
 | 
				
			||||
    sql = %Q(delete from poll_votes where (poll_question_id, user_id, poll_answer_id) in
 | 
				
			||||
            (select * from (select poll_question_id, user_id, poll_answer_id from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1) a)
 | 
				
			||||
            and id not in (select * from (select min(id) from poll_votes group by poll_question_id, user_id, poll_answer_id having count(*) > 1 order by id) b))
 | 
				
			||||
    ActiveRecord::Base.connection.execute sql
 | 
				
			||||
 | 
				
			||||
    add_index :poll_votes, [:poll_question_id, :user_id, :poll_answer_id], name: 'poll_answer_index', unique: true
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								@ -0,0 +1,334 @@
 | 
				
			||||
/*
 | 
				
			||||
 * @Description: 
 | 
				
			||||
 * @Author: tangjiang
 | 
				
			||||
 * @Github: 
 | 
				
			||||
 * @Date: 2019-12-01 09:17:07
 | 
				
			||||
 * @LastEditors: tangjiang
 | 
				
			||||
 * @LastEditTime: 2019-12-02 16:33:35
 | 
				
			||||
 */
 | 
				
			||||
import 'quill/dist/quill.core.css';
 | 
				
			||||
import 'quill/dist/quill.bubble.css';
 | 
				
			||||
import 'quill/dist/quill.snow.css';
 | 
				
			||||
import './index.scss';
 | 
				
			||||
import React, { useState, useImperativeHandle, useRef, useEffect } from 'react';
 | 
				
			||||
import { Form, Input, InputNumber, Button, Select } from 'antd';
 | 
				
			||||
import { connect } from 'react-redux';
 | 
				
			||||
import AddTestDemo from './AddTestDemo';
 | 
				
			||||
import QuillEditor from '../../../quillEditor';
 | 
				
			||||
import actions from '../../../../../redux/actions';
 | 
				
			||||
import CONST from '../../../../../constants';
 | 
				
			||||
 | 
				
			||||
const {jcLabel} = CONST;
 | 
				
			||||
const { Option } = Select;
 | 
				
			||||
const FormItem = Form.Item;
 | 
				
			||||
 | 
				
			||||
const maps = {
 | 
				
			||||
  language: [
 | 
				
			||||
    { title: 'C', key: 'C' },
 | 
				
			||||
    { title: 'C++', key: 'C++' },
 | 
				
			||||
    { title: 'Python', key: 'Python' },
 | 
				
			||||
    { title: 'Java', key: 'Java' }
 | 
				
			||||
  ],
 | 
				
			||||
  difficult: [
 | 
				
			||||
    { title: '简单', key: '1' },
 | 
				
			||||
    { title: '中等', key: '2'},
 | 
				
			||||
    { title: '困难', key: '3' }
 | 
				
			||||
  ],
 | 
				
			||||
  category: [
 | 
				
			||||
    { title: '程序设计', key: '1' },
 | 
				
			||||
    { title: '算法', key: '2'}
 | 
				
			||||
  ],
 | 
				
			||||
  openOrNot: [
 | 
				
			||||
    { title: '公开', key: '1' },
 | 
				
			||||
    { title: '私有', key: '0' }
 | 
				
			||||
  ]
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
function EditTab (props, ref) {
 | 
				
			||||
 | 
				
			||||
  const { 
 | 
				
			||||
    form,
 | 
				
			||||
    ojForm,
 | 
				
			||||
    position, 
 | 
				
			||||
    testCases,
 | 
				
			||||
    addTestCase,
 | 
				
			||||
    deleteTestCase,
 | 
				
			||||
    testCasesValidate,
 | 
				
			||||
    getFormData
 | 
				
			||||
  } = props;
 | 
				
			||||
 | 
				
			||||
  const { getFieldDecorator } = form;
 | 
				
			||||
 | 
				
			||||
  const formRef = useRef(null);
 | 
				
			||||
  const [description, setDescription] = useState('');
 | 
				
			||||
 | 
				
			||||
  // 获取表单label
 | 
				
			||||
  const myLabel = (name, subTitle) => {
 | 
				
			||||
    if (subTitle) {
 | 
				
			||||
      return (
 | 
				
			||||
        <span className={'label_text'}>
 | 
				
			||||
          {name}
 | 
				
			||||
          <span className={'label_sub_text'}>
 | 
				
			||||
            ({subTitle})
 | 
				
			||||
          </span>
 | 
				
			||||
        </span>
 | 
				
			||||
      )
 | 
				
			||||
    } else {
 | 
				
			||||
      return (
 | 
				
			||||
        <span className={'label_text'}>{name}</span>
 | 
				
			||||
      )
 | 
				
			||||
    }
 | 
				
			||||
  };
 | 
				
			||||
  // 获取下拉列表项
 | 
				
			||||
  const getOptions = (key) => {
 | 
				
			||||
    return maps[key].map((opt, i) => {
 | 
				
			||||
      return (
 | 
				
			||||
        <Option value={opt.key} key={`opt_${i}`}>{opt.title}</Option>
 | 
				
			||||
      );
 | 
				
			||||
    });
 | 
				
			||||
  };
 | 
				
			||||
  // 向外暴露的方法
 | 
				
			||||
  useImperativeHandle(ref, () => ({
 | 
				
			||||
    validateForm () {
 | 
				
			||||
      props.form.validateFields((err, values) => {
 | 
				
			||||
        if (!err) {
 | 
				
			||||
          getFormData(() => {
 | 
				
			||||
            return values; 
 | 
				
			||||
          });
 | 
				
			||||
        } else {
 | 
				
			||||
          return;
 | 
				
			||||
        }
 | 
				
			||||
      })
 | 
				
			||||
    }
 | 
				
			||||
  }));
 | 
				
			||||
  // 添加测试用例
 | 
				
			||||
  const handleAddTest = () => {
 | 
				
			||||
    const obj = { // 测试用例参数
 | 
				
			||||
      input: '',
 | 
				
			||||
      output: '',
 | 
				
			||||
      position: position,
 | 
				
			||||
      isAdd: true // 新增的测试用例
 | 
				
			||||
    }
 | 
				
			||||
    const validateObj = { // 测试用例验证参数
 | 
				
			||||
      input: {
 | 
				
			||||
        validateStatus: '',
 | 
				
			||||
        errMsg: ''
 | 
				
			||||
      },
 | 
				
			||||
      output: {
 | 
				
			||||
        validateStatus: '',
 | 
				
			||||
        errMsg: ''
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    addTestCase({testCase: obj, tcValidate: validateObj});
 | 
				
			||||
    // TODO 点击新增时,需要滚到到最底部
 | 
				
			||||
    // this.editorRef.current.scrollTop
 | 
				
			||||
    // const oDiv = this.editorRef.current;
 | 
				
			||||
    // oDiv.scrollTo(oDiv.scrollLeft, 99999);
 | 
				
			||||
    // console.log(oDiv.scrollTop);
 | 
				
			||||
    // oDiv.scrollTop = 99999;
 | 
				
			||||
  }
 | 
				
			||||
  // 渲染测试用例
 | 
				
			||||
  const renderTestCase = () => {
 | 
				
			||||
    return testCases.map((item, i) => {
 | 
				
			||||
      return (
 | 
				
			||||
        <AddTestDemo
 | 
				
			||||
          key={`key_${i}`}
 | 
				
			||||
          onSubmitTest={handleSubmitTest} 
 | 
				
			||||
          onDeleteTest={handleDeleteTest} 
 | 
				
			||||
          testCase={item}
 | 
				
			||||
          testCaseValidate={testCasesValidate[i]}
 | 
				
			||||
          index={i}
 | 
				
			||||
        />
 | 
				
			||||
      )
 | 
				
			||||
    });
 | 
				
			||||
  };
 | 
				
			||||
  // 提交测试用例
 | 
				
			||||
  const handleSubmitTest = (obj) => {
 | 
				
			||||
    console.log('提交的测试用例: ', obj);
 | 
				
			||||
  };
 | 
				
			||||
  // 删除测试用例
 | 
				
			||||
  const handleDeleteTest = (obj) => {
 | 
				
			||||
    console.log('删除的测试用例: ', obj);
 | 
				
			||||
    deleteTestCase(obj);
 | 
				
			||||
  };
 | 
				
			||||
  // 描述信息改变时
 | 
				
			||||
  const handleChangeDescription = (value) => {
 | 
				
			||||
    console.log('描述信息改变: ', value);
 | 
				
			||||
    if (value) {
 | 
				
			||||
      setDescription(value);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
    if (description) {
 | 
				
			||||
      props.form.setFieldsValue({
 | 
				
			||||
        description: description
 | 
				
			||||
      }, function () {
 | 
				
			||||
        console.log('设置成功。。。');
 | 
				
			||||
      });
 | 
				
			||||
    }
 | 
				
			||||
  }, [description]);
 | 
				
			||||
 | 
				
			||||
  return (
 | 
				
			||||
    <div className={'editor_area'}>
 | 
				
			||||
      <Form 
 | 
				
			||||
        hideRequiredMark={true}
 | 
				
			||||
        className={'editor_form'} 
 | 
				
			||||
        ref={formRef}>
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_60`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['name'])}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('name', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '任务名称不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: ojForm.name
 | 
				
			||||
            })(<Input placeholder="请输入任务名称"/>)
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
        
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_40`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['language'])}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('language', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '语言不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: ojForm.language
 | 
				
			||||
            })(
 | 
				
			||||
              <Select>
 | 
				
			||||
                {getOptions('language')}
 | 
				
			||||
              </Select>)
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_100`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['description'])}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('description', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '描述信息不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: ojForm.description
 | 
				
			||||
            })(<QuillEditor
 | 
				
			||||
                style={{ height: '300px' }}
 | 
				
			||||
                placeholder="请输入描述信息"
 | 
				
			||||
                htmlCtx={ojForm.description}
 | 
				
			||||
                onEditorChange={handleChangeDescription}
 | 
				
			||||
              />)
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_50 flex_50_left`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['difficult'], '任务的难易程度')}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('difficult', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '难度不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: `${ojForm.difficult || ''}`
 | 
				
			||||
            })(
 | 
				
			||||
              <Select>
 | 
				
			||||
                {getOptions('difficult')}
 | 
				
			||||
              </Select>
 | 
				
			||||
            )
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_50 flex_50_right`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['timeLimit'], '程序允许时间限制时长,单位:秒')}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('timeLimit', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '时间限制不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: ojForm.timeLimit
 | 
				
			||||
            })(<InputNumber min={0} style={{ width: '100%' }} />)
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_50 flex_50_left`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['category'], '任务所属分类')}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('category', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '任务名称不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: `${ojForm.category || ''}`
 | 
				
			||||
            })(
 | 
				
			||||
              <Select>
 | 
				
			||||
                {getOptions('category')}
 | 
				
			||||
              </Select>
 | 
				
			||||
            )
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
 | 
				
			||||
        <FormItem
 | 
				
			||||
          className={`input_area flex_50 flex_50_right`}
 | 
				
			||||
          label={<span>{myLabel(jcLabel['openOrNot'])}</span>}
 | 
				
			||||
        >
 | 
				
			||||
          {
 | 
				
			||||
            getFieldDecorator('openOrNot', {
 | 
				
			||||
              rules: [
 | 
				
			||||
                { required: true, message: '任务名称不能为空' }
 | 
				
			||||
              ],
 | 
				
			||||
              initialValue: `${ojForm.openOrNot}`
 | 
				
			||||
            })(
 | 
				
			||||
              <Select>
 | 
				
			||||
                {getOptions('openOrNot')}
 | 
				
			||||
              </Select>
 | 
				
			||||
            )
 | 
				
			||||
          }
 | 
				
			||||
        </FormItem>
 | 
				
			||||
      </Form>
 | 
				
			||||
      {/* 添加测试用例 */}
 | 
				
			||||
      <div className="test_demo_title">
 | 
				
			||||
          <h2>测试用例</h2>
 | 
				
			||||
          <Button type="primary" onClick={handleAddTest}>添加测试用例</Button>
 | 
				
			||||
        </div>
 | 
				
			||||
        <div className="test_demo_ctx">
 | 
				
			||||
          { renderTestCase() }
 | 
				
			||||
        </div>
 | 
				
			||||
    </div>
 | 
				
			||||
  );
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
const mapStateToProps = (state) => {
 | 
				
			||||
  const ojFormReducer = state.ojFormReducer;
 | 
				
			||||
  const {ojForm, position, testCases, testCasesValidate} = ojFormReducer;
 | 
				
			||||
  return {
 | 
				
			||||
    ojForm,
 | 
				
			||||
    testCases,
 | 
				
			||||
    testCasesValidate,
 | 
				
			||||
    position
 | 
				
			||||
  };
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
const mapDispatchToProps = (dispatch) => ({
 | 
				
			||||
  // 新增测试用例
 | 
				
			||||
  addTestCase: (value) => dispatch(actions.addTestCase(value)),
 | 
				
			||||
  // 删除测试用例
 | 
				
			||||
  deleteTestCase: (value) => dispatch(actions.deleteTestCase(value)),
 | 
				
			||||
})
 | 
				
			||||
 | 
				
			||||
// EditTab = React.formRef(EditTab);
 | 
				
			||||
// EditTab = React.forwardRef(EditTab);
 | 
				
			||||
 | 
				
			||||
export default connect(
 | 
				
			||||
  mapStateToProps,
 | 
				
			||||
  mapDispatchToProps
 | 
				
			||||
)(Form.create()(
 | 
				
			||||
  React.forwardRef(EditTab)
 | 
				
			||||
));
 | 
				
			||||
@ -1,25 +1 @@
 | 
				
			||||
// .split-pane-left{
 | 
				
			||||
//   .ant-tabs-nav-wrap{
 | 
				
			||||
//     padding: 0 30px;
 | 
				
			||||
//   }
 | 
				
			||||
//   .ant-tabs-bar{
 | 
				
			||||
//     margin: 0;
 | 
				
			||||
//   }
 | 
				
			||||
//   // .ant-tabs-tabpane{
 | 
				
			||||
//   //   padding-top: 10px;
 | 
				
			||||
//   //   height: calc(100vh - 110px);
 | 
				
			||||
//   //   overflow: auto;
 | 
				
			||||
//   // }
 | 
				
			||||
 | 
				
			||||
//   .ant-form-item-control{
 | 
				
			||||
//     line-height: 1;
 | 
				
			||||
//   }
 | 
				
			||||
 | 
				
			||||
//   .editor_area,
 | 
				
			||||
//   .prev_area{
 | 
				
			||||
//     height: calc(100vh - 110px);
 | 
				
			||||
//     overflow-y: auto;
 | 
				
			||||
//     padding: 20px 0;
 | 
				
			||||
//   }
 | 
				
			||||
// }
 | 
				
			||||
@import '../../split_pane_resizer.scss';
 | 
				
			||||
 | 
				
			||||
@ -1,215 +1,63 @@
 | 
				
			||||
/*
 | 
				
			||||
 * @Description: 右侧代码块 
 | 
				
			||||
 * @Author: tangjiang 
 | 
				
			||||
 * @Date: 2019-11-18 08:42:04 
 | 
				
			||||
 * @Last Modified by: tangjiang
 | 
				
			||||
 * @Last Modified time: 2019-11-20 00:00:34
 | 
				
			||||
 * @Description: 
 | 
				
			||||
 * @Author: tangjiang
 | 
				
			||||
 * @Github: 
 | 
				
			||||
 * @Date: 2019-12-01 10:18:35
 | 
				
			||||
 * @LastEditors: tangjiang
 | 
				
			||||
 * @LastEditTime: 2019-12-03 09:11:50
 | 
				
			||||
 */
 | 
				
			||||
 | 
				
			||||
import './index.scss';
 | 
				
			||||
 | 
				
			||||
import React, { Fragment, useState, useRef, useEffect } from 'react';
 | 
				
			||||
import { Icon, Drawer, Tabs, Button, notification } from 'antd';
 | 
				
			||||
import _ from 'lodash';
 | 
				
			||||
import MonacoEditor from '@monaco-editor/react';
 | 
				
			||||
import React from 'react';
 | 
				
			||||
import { connect } from 'react-redux';
 | 
				
			||||
import InitTabCtx from './initTabCtx';
 | 
				
			||||
import SettingDrawer from '../../components/monacoSetting';
 | 
				
			||||
import CONST from '../../../../constants';
 | 
				
			||||
import MyMonacoEditor from '../../components/myMonacoEditor';
 | 
				
			||||
import ControlSetting from '../../components/controlSetting';
 | 
				
			||||
import actions from '../../../../redux/actions';
 | 
				
			||||
 | 
				
			||||
const { fontSetting, opacitySetting } = CONST;
 | 
				
			||||
 | 
				
			||||
const { TabPane } = Tabs;
 | 
				
			||||
 | 
				
			||||
const RightPaneCode = (props) => {
 | 
				
			||||
 | 
				
			||||
  const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
 | 
				
			||||
  const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
 | 
				
			||||
  const [showTextResult, setShowTextResult] = useState(false); // 是否点击控制台按钮
 | 
				
			||||
  const [editCode, setEditCode] = useState(()=> {
 | 
				
			||||
    return '#include <stdio.h>';
 | 
				
			||||
  });  // monaco编辑器内容
 | 
				
			||||
  const [language, setLanguage] = useState('C')
 | 
				
			||||
  const [fontSize,setFontSize] = useState(12);
 | 
				
			||||
  const editorRef = useRef(null); // 编辑器ref
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
    if (props.language) {
 | 
				
			||||
      // console.log('当前输入的代码:', editCode);
 | 
				
			||||
      // console.log('当前输入的语言:', props.language);
 | 
				
			||||
      setLanguage(props.language)
 | 
				
			||||
    }
 | 
				
			||||
  }, [props.language]);
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
  }, [props.testCases]);
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
  }, [editCode]);
 | 
				
			||||
 | 
				
			||||
  // 监听store中编辑器内容变化
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
    setEditCode(props.code);
 | 
				
			||||
  }, [props.code]);
 | 
				
			||||
 | 
				
			||||
  // 打开设置
 | 
				
			||||
  const handleShowDrawer = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    setShowDrawer(true);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 关闭设置
 | 
				
			||||
  const handleDrawerClose = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    setShowDrawer(false);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 切换tab
 | 
				
			||||
  const handleTabChange = (key) => {
 | 
				
			||||
    setDefaultActiveKey(key);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 显示/隐藏tab
 | 
				
			||||
  const handleShowControl = () => {
 | 
				
			||||
    setShowTextResult(!showTextResult);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 侧边栏改变字体大小
 | 
				
			||||
  const handleFontSizeChange = (value) => {
 | 
				
			||||
    setFontSize(value);
 | 
				
			||||
  }
 | 
				
			||||
  // 文本框内容变化时,记录文本框内容
 | 
				
			||||
  const handleEditorChange = (origin, monaco) => {
 | 
				
			||||
    editorRef.current = monaco; // 获取当前monaco实例
 | 
				
			||||
    setEditCode(origin); // 保存编辑器初始值
 | 
				
			||||
    editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
 | 
				
			||||
      // TODO 需要优化 节流
 | 
				
			||||
      const val = editorRef.current.getValue();
 | 
				
			||||
      setEditCode(val); 
 | 
				
			||||
      // 保存当前代码
 | 
				
			||||
      props.saveOjFormCode(val);
 | 
				
			||||
    });
 | 
				
			||||
  }
 | 
				
			||||
function RightPane (props, ref) {
 | 
				
			||||
 | 
				
			||||
  // 提交按钮点击
 | 
				
			||||
  const handleSubmit = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    if (!editCode) {
 | 
				
			||||
      notification['error']({
 | 
				
			||||
        message: '必填',
 | 
				
			||||
        description: '代码块内容必须输入!'
 | 
				
			||||
      });
 | 
				
			||||
      editorRef.current.focus();
 | 
				
			||||
      return;
 | 
				
			||||
    }
 | 
				
			||||
    props.changePublishLoadingStatus(true);
 | 
				
			||||
    const { onSubmitForm } = props;
 | 
				
			||||
    onSubmitForm(editCode); 
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 调试测试代码
 | 
				
			||||
  // const handleTestCode = () => {
 | 
				
			||||
  //   // 打开控制台信息
 | 
				
			||||
  //   setShowTextResult(true);
 | 
				
			||||
  //   this.formRef.handleTestCodeFormSubmit(() => {
 | 
				
			||||
  //     // 当验证通过后 切换tab 到代码执行结果
 | 
				
			||||
  //     setDefaultActiveKey('2');
 | 
				
			||||
  //   });
 | 
				
			||||
  // }
 | 
				
			||||
  const {
 | 
				
			||||
    // identifier,
 | 
				
			||||
    onSubmitForm,
 | 
				
			||||
    saveOjFormCode
 | 
				
			||||
  } = props;
 | 
				
			||||
  
 | 
				
			||||
  // 控制台点击时 添加active属性
 | 
				
			||||
  const classNames = `control_tab ${showTextResult ? 'move_up move_up_final' : 'move_down_final'}`;
 | 
				
			||||
 | 
				
			||||
  // 配置编辑器属性
 | 
				
			||||
  const editorOptions = {
 | 
				
			||||
    selectOnLineNumbers: true,
 | 
				
			||||
    automaticLayout: true,
 | 
				
			||||
    fontSize: `${fontSize}px`
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 返回渲染值
 | 
				
			||||
  // 代码改变时,保存
 | 
				
			||||
  const handleCodeChange = (code) => {
 | 
				
			||||
    // 保存用户输入的代码
 | 
				
			||||
    saveOjFormCode(code);
 | 
				
			||||
  }
 | 
				
			||||
  // 启动调试代码
 | 
				
			||||
  // const handleDebuggerCode = (value) => {
 | 
				
			||||
  //   console.log('调用的代码调试====', value);
 | 
				
			||||
  // }
 | 
				
			||||
  return (
 | 
				
			||||
    <Fragment>
 | 
				
			||||
      <div className={'right_pane_code_wrap'}>
 | 
				
			||||
        <div className={'code-title'}>
 | 
				
			||||
          <span></span>
 | 
				
			||||
          <Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/>
 | 
				
			||||
        </div>
 | 
				
			||||
        {/** 代码编辑器 */}
 | 
				
			||||
        <MonacoEditor
 | 
				
			||||
          height={showTextResult ? 'calc(100% - 382px)' : 'calc(100% - 112px)'}
 | 
				
			||||
          width="100%"
 | 
				
			||||
          language={language.toLowerCase()}
 | 
				
			||||
          value={editCode}
 | 
				
			||||
          options={editorOptions}
 | 
				
			||||
          theme="dark"
 | 
				
			||||
          editorDidMount={handleEditorChange}
 | 
				
			||||
        />
 | 
				
			||||
        {/* 控制台信息 */}
 | 
				
			||||
        <div className="pane_control_area">
 | 
				
			||||
          <Tabs
 | 
				
			||||
            className={classNames}
 | 
				
			||||
            activeKey={defaultActiveKey} 
 | 
				
			||||
            tabBarStyle={{ backgroundColor: '#000', color: '#fff' }}
 | 
				
			||||
            onChange={handleTabChange}
 | 
				
			||||
          >
 | 
				
			||||
            <TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>
 | 
				
			||||
              <InitTabCtx wrappedComponentRef={(form) => this.formRef = form }/>
 | 
				
			||||
            </TabPane>
 | 
				
			||||
            <TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
 | 
				
			||||
              <h2>代码执行结果</h2>
 | 
				
			||||
            </TabPane>
 | 
				
			||||
          </Tabs>
 | 
				
			||||
          <div className="pane_control_opts">
 | 
				
			||||
            <Button type="link" style={{ color: '#fff' }} onClick={handleShowControl}>控制台 <Icon type={ showTextResult ? "down" : "up" } /></Button>
 | 
				
			||||
            <p>
 | 
				
			||||
              {/* <Button ghost 
 | 
				
			||||
                style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }} 
 | 
				
			||||
                onClick={handleTestCode}
 | 
				
			||||
                disabled={!props.identifier || props.testCases.length === 0}
 | 
				
			||||
              >调试代码</Button> */}
 | 
				
			||||
              <Button 
 | 
				
			||||
                loading={props.submitLoading}
 | 
				
			||||
                type="primary"
 | 
				
			||||
                onClick={handleSubmit}
 | 
				
			||||
            >{props.identifier ? '更新' : '提交'}</Button>
 | 
				
			||||
            </p>
 | 
				
			||||
          </div>
 | 
				
			||||
        </div>
 | 
				
			||||
      </div>
 | 
				
			||||
      <Drawer
 | 
				
			||||
        className={'setting_drawer'}
 | 
				
			||||
        placement="right"
 | 
				
			||||
        closable={false}
 | 
				
			||||
        onClose={handleDrawerClose}
 | 
				
			||||
        visible={showDrawer}
 | 
				
			||||
      >
 | 
				
			||||
        <SettingDrawer {...fontSetting} onChangeFontSize={handleFontSizeChange}/>
 | 
				
			||||
        <SettingDrawer {...opacitySetting}/>
 | 
				
			||||
      </Drawer>
 | 
				
			||||
    </Fragment>
 | 
				
			||||
  );
 | 
				
			||||
    <div className={'right_pane_code_wrap'}>
 | 
				
			||||
      <MyMonacoEditor language={props.language} code={props.code} onCodeChange={handleCodeChange}/>
 | 
				
			||||
      <ControlSetting
 | 
				
			||||
        // identifier={identifier}
 | 
				
			||||
        inputValue={props.input} 
 | 
				
			||||
        onSubmitForm={onSubmitForm}
 | 
				
			||||
        // onDebuggerCode={handleDebuggerCode}
 | 
				
			||||
      />
 | 
				
			||||
    </div>
 | 
				
			||||
  )
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
const mapStateToProps = (state) => {
 | 
				
			||||
  const { ojForm, testCases, identifier, code } = state.ojFormReducer;
 | 
				
			||||
  const { submitLoading } = state.commonReducer;
 | 
				
			||||
  const { ojForm, testCases, code, identifier } = state.ojFormReducer;
 | 
				
			||||
  return {
 | 
				
			||||
    language: ojForm.language,
 | 
				
			||||
    testCases,
 | 
				
			||||
    identifier,
 | 
				
			||||
    code,
 | 
				
			||||
    submitLoading
 | 
				
			||||
    identifier,
 | 
				
			||||
    language: ojForm.language,
 | 
				
			||||
    input: (testCases[0] && testCases[0].input) || '',
 | 
				
			||||
    
 | 
				
			||||
  }
 | 
				
			||||
};
 | 
				
			||||
 | 
				
			||||
const mapDispatchToProps = (dispatch) => ({
 | 
				
			||||
  saveOjFormCode: (code) => dispatch(actions.saveOjFormCode(code)),
 | 
				
			||||
  changePublishLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag))
 | 
				
			||||
  // 保存提交的代码值
 | 
				
			||||
  saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)),
 | 
				
			||||
});
 | 
				
			||||
// 
 | 
				
			||||
export default connect(
 | 
				
			||||
  mapStateToProps,
 | 
				
			||||
  mapDispatchToProps
 | 
				
			||||
)(RightPaneCode);
 | 
				
			||||
)(RightPane);
 | 
				
			||||
 | 
				
			||||
@ -0,0 +1,215 @@
 | 
				
			||||
/*
 | 
				
			||||
 * @Description: 右侧代码块 
 | 
				
			||||
 * @Author: tangjiang 
 | 
				
			||||
 * @Date: 2019-11-18 08:42:04 
 | 
				
			||||
 * @Last Modified by: tangjiang
 | 
				
			||||
 * @Last Modified time: 2019-11-20 00:00:34
 | 
				
			||||
 */
 | 
				
			||||
 | 
				
			||||
import './index.scss';
 | 
				
			||||
 | 
				
			||||
import React, { Fragment, useState, useRef, useEffect } from 'react';
 | 
				
			||||
import { Icon, Drawer, Tabs, Button, notification } from 'antd';
 | 
				
			||||
import _ from 'lodash';
 | 
				
			||||
import MonacoEditor from '@monaco-editor/react';
 | 
				
			||||
import { connect } from 'react-redux';
 | 
				
			||||
import InitTabCtx from './initTabCtx';
 | 
				
			||||
import SettingDrawer from '../../components/monacoSetting';
 | 
				
			||||
import CONST from '../../../../constants';
 | 
				
			||||
import actions from '../../../../redux/actions';
 | 
				
			||||
 | 
				
			||||
const { fontSetting, opacitySetting } = CONST;
 | 
				
			||||
 | 
				
			||||
const { TabPane } = Tabs;
 | 
				
			||||
 | 
				
			||||
const RightPaneCode = (props) => {
 | 
				
			||||
 | 
				
			||||
  const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
 | 
				
			||||
  const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
 | 
				
			||||
  const [showTextResult, setShowTextResult] = useState(false); // 是否点击控制台按钮
 | 
				
			||||
  const [editCode, setEditCode] = useState(()=> {
 | 
				
			||||
    return '#include <stdio.h>';
 | 
				
			||||
  });  // monaco编辑器内容
 | 
				
			||||
  const [language, setLanguage] = useState('C')
 | 
				
			||||
  const [fontSize,setFontSize] = useState(12);
 | 
				
			||||
  const editorRef = useRef(null); // 编辑器ref
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
    if (props.language) {
 | 
				
			||||
      // console.log('当前输入的代码:', editCode);
 | 
				
			||||
      // console.log('当前输入的语言:', props.language);
 | 
				
			||||
      setLanguage(props.language)
 | 
				
			||||
    }
 | 
				
			||||
  }, [props.language]);
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
  }, [props.testCases]);
 | 
				
			||||
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
  }, [editCode]);
 | 
				
			||||
 | 
				
			||||
  // 监听store中编辑器内容变化
 | 
				
			||||
  useEffect(() => {
 | 
				
			||||
    setEditCode(props.code);
 | 
				
			||||
  }, [props.code]);
 | 
				
			||||
 | 
				
			||||
  // 打开设置
 | 
				
			||||
  const handleShowDrawer = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    setShowDrawer(true);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 关闭设置
 | 
				
			||||
  const handleDrawerClose = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    setShowDrawer(false);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 切换tab
 | 
				
			||||
  const handleTabChange = (key) => {
 | 
				
			||||
    setDefaultActiveKey(key);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 显示/隐藏tab
 | 
				
			||||
  const handleShowControl = () => {
 | 
				
			||||
    setShowTextResult(!showTextResult);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 侧边栏改变字体大小
 | 
				
			||||
  const handleFontSizeChange = (value) => {
 | 
				
			||||
    setFontSize(value);
 | 
				
			||||
  }
 | 
				
			||||
  // 文本框内容变化时,记录文本框内容
 | 
				
			||||
  const handleEditorChange = (origin, monaco) => {
 | 
				
			||||
    editorRef.current = monaco; // 获取当前monaco实例
 | 
				
			||||
    setEditCode(origin); // 保存编辑器初始值
 | 
				
			||||
    editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
 | 
				
			||||
      // TODO 需要优化 节流
 | 
				
			||||
      const val = editorRef.current.getValue();
 | 
				
			||||
      setEditCode(val); 
 | 
				
			||||
      // 保存当前代码
 | 
				
			||||
      props.saveOjFormCode(val);
 | 
				
			||||
    });
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 提交按钮点击
 | 
				
			||||
  const handleSubmit = (e) => {
 | 
				
			||||
    e.preventDefault();
 | 
				
			||||
    if (!editCode) {
 | 
				
			||||
      notification['error']({
 | 
				
			||||
        message: '必填',
 | 
				
			||||
        description: '代码块内容必须输入!'
 | 
				
			||||
      });
 | 
				
			||||
      editorRef.current.focus();
 | 
				
			||||
      return;
 | 
				
			||||
    }
 | 
				
			||||
    props.changePublishLoadingStatus(true);
 | 
				
			||||
    const { onSubmitForm } = props;
 | 
				
			||||
    onSubmitForm(editCode); 
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 调试测试代码
 | 
				
			||||
  // const handleTestCode = () => {
 | 
				
			||||
  //   // 打开控制台信息
 | 
				
			||||
  //   setShowTextResult(true);
 | 
				
			||||
  //   this.formRef.handleTestCodeFormSubmit(() => {
 | 
				
			||||
  //     // 当验证通过后 切换tab 到代码执行结果
 | 
				
			||||
  //     setDefaultActiveKey('2');
 | 
				
			||||
  //   });
 | 
				
			||||
  // }
 | 
				
			||||
  
 | 
				
			||||
  // 控制台点击时 添加active属性
 | 
				
			||||
  const classNames = `control_tab ${showTextResult ? 'move_up move_up_final' : 'move_down_final'}`;
 | 
				
			||||
 | 
				
			||||
  // 配置编辑器属性
 | 
				
			||||
  const editorOptions = {
 | 
				
			||||
    selectOnLineNumbers: true,
 | 
				
			||||
    automaticLayout: true,
 | 
				
			||||
    fontSize: `${fontSize}px`
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // 返回渲染值
 | 
				
			||||
  return (
 | 
				
			||||
    <Fragment>
 | 
				
			||||
      <div className={'right_pane_code_wrap'}>
 | 
				
			||||
        <div className={'code-title'}>
 | 
				
			||||
          <span></span>
 | 
				
			||||
          <Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/>
 | 
				
			||||
        </div>
 | 
				
			||||
        {/** 代码编辑器 */}
 | 
				
			||||
        <MonacoEditor
 | 
				
			||||
          height={showTextResult ? 'calc(100% - 382px)' : 'calc(100% - 112px)'}
 | 
				
			||||
          width="100%"
 | 
				
			||||
          language={language.toLowerCase()}
 | 
				
			||||
          value={editCode}
 | 
				
			||||
          options={editorOptions}
 | 
				
			||||
          theme="dark"
 | 
				
			||||
          editorDidMount={handleEditorChange}
 | 
				
			||||
        />
 | 
				
			||||
        {/* 控制台信息 */}
 | 
				
			||||
        <div className="pane_control_area">
 | 
				
			||||
          <Tabs
 | 
				
			||||
            className={classNames}
 | 
				
			||||
            activeKey={defaultActiveKey} 
 | 
				
			||||
            tabBarStyle={{ backgroundColor: '#000', color: '#fff' }}
 | 
				
			||||
            onChange={handleTabChange}
 | 
				
			||||
          >
 | 
				
			||||
            <TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>
 | 
				
			||||
              <InitTabCtx wrappedComponentRef={(form) => this.formRef = form }/>
 | 
				
			||||
            </TabPane>
 | 
				
			||||
            <TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
 | 
				
			||||
              <h2>代码执行结果</h2>
 | 
				
			||||
            </TabPane>
 | 
				
			||||
          </Tabs>
 | 
				
			||||
          <div className="pane_control_opts">
 | 
				
			||||
            <Button type="link" style={{ color: '#fff' }} onClick={handleShowControl}>控制台 <Icon type={ showTextResult ? "down" : "up" } /></Button>
 | 
				
			||||
            <p>
 | 
				
			||||
              {/* <Button ghost 
 | 
				
			||||
                style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }} 
 | 
				
			||||
                onClick={handleTestCode}
 | 
				
			||||
                disabled={!props.identifier || props.testCases.length === 0}
 | 
				
			||||
              >调试代码</Button> */}
 | 
				
			||||
              <Button 
 | 
				
			||||
                loading={props.submitLoading}
 | 
				
			||||
                type="primary"
 | 
				
			||||
                onClick={handleSubmit}
 | 
				
			||||
            >{props.identifier ? '更新' : '提交'}</Button>
 | 
				
			||||
            </p>
 | 
				
			||||
          </div>
 | 
				
			||||
        </div>
 | 
				
			||||
      </div>
 | 
				
			||||
      <Drawer
 | 
				
			||||
        className={'setting_drawer'}
 | 
				
			||||
        placement="right"
 | 
				
			||||
        closable={false}
 | 
				
			||||
        onClose={handleDrawerClose}
 | 
				
			||||
        visible={showDrawer}
 | 
				
			||||
      >
 | 
				
			||||
        <SettingDrawer {...fontSetting} onChangeFontSize={handleFontSizeChange}/>
 | 
				
			||||
        <SettingDrawer {...opacitySetting}/>
 | 
				
			||||
      </Drawer>
 | 
				
			||||
    </Fragment>
 | 
				
			||||
  );
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
const mapStateToProps = (state) => {
 | 
				
			||||
  const { ojForm, testCases, identifier, code } = state.ojFormReducer;
 | 
				
			||||
  const { submitLoading } = state.commonReducer;
 | 
				
			||||
  return {
 | 
				
			||||
    language: ojForm.language,
 | 
				
			||||
    testCases,
 | 
				
			||||
    identifier,
 | 
				
			||||
    code,
 | 
				
			||||
    submitLoading
 | 
				
			||||
  }
 | 
				
			||||
};
 | 
				
			||||
 | 
				
			||||
const mapDispatchToProps = (dispatch) => ({
 | 
				
			||||
  saveOjFormCode: (code) => dispatch(actions.saveOjFormCode(code)),
 | 
				
			||||
  changePublishLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag))
 | 
				
			||||
});
 | 
				
			||||
// 
 | 
				
			||||
export default connect(
 | 
				
			||||
  mapStateToProps,
 | 
				
			||||
  mapDispatchToProps
 | 
				
			||||
)(RightPaneCode);
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue