From 9e043e2ec4ecd61df21a8892e34b898375045944 Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Wed, 21 Aug 2019 10:23:15 +0800 Subject: [PATCH 01/91] issue --- .../src/modules/courses/busyWork/commonWork.js | 4 +++- .../modules/courses/coursesPublic/NoneData.js | 17 +++++++++++++++-- .../courses/exercise/ExerciseReviewAndAnswer.js | 12 ++++++++++++ .../courses/exercise/question/multiple.js | 14 +++++++------- .../modules/courses/exercise/question/single.js | 16 ++++++++-------- .../modules/courses/graduation/tasks/index.js | 4 +++- .../courses/shixunHomework/shixunHomework.js | 2 ++ .../src/modules/user/usersInfo/InfosBanner.js | 11 +++++++++-- .../src/modules/user/usersInfo/InfosCourse.js | 2 +- .../src/modules/user/usersInfo/InfosPath.js | 2 +- .../src/modules/user/usersInfo/InfosProject.js | 2 +- .../src/modules/user/usersInfo/InfosShixun.js | 4 ++-- 12 files changed, 64 insertions(+), 26 deletions(-) diff --git a/public/react/src/modules/courses/busyWork/commonWork.js b/public/react/src/modules/courses/busyWork/commonWork.js index f9c34cfcf..d5ac0be68 100644 --- a/public/react/src/modules/courses/busyWork/commonWork.js +++ b/public/react/src/modules/courses/busyWork/commonWork.js @@ -143,7 +143,9 @@ class commonWork extends Component{ this.setState({ order:e.key==="all"?"":e.key, page:1, - isSpin:true + isSpin:true, + checkBoxValues:[], + checkAll:false }) let {search}=this.state; this.getList(1,search,e.key==="all"?"":e.key); diff --git a/public/react/src/modules/courses/coursesPublic/NoneData.js b/public/react/src/modules/courses/coursesPublic/NoneData.js index 73406ab5c..4066cea8b 100644 --- a/public/react/src/modules/courses/coursesPublic/NoneData.js +++ b/public/react/src/modules/courses/coursesPublic/NoneData.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import {getImageUrl} from 'educoder'; +import { getImageUrl , getUrl } from 'educoder'; class NoneData extends Component{ constructor(props) { @@ -9,7 +9,20 @@ class NoneData extends Component{ const { style } = this.props; return(
- + +

暂时还没有相关数据哦!

) diff --git a/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js b/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js index 91ca19e7d..abfe5eff7 100644 --- a/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js +++ b/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js @@ -535,6 +535,18 @@ class ExerciseReviewAndAnswer extends Component{ .inputNumber30 .ant-input-number-input-wrap .ant-input-number-input{ height: 28px; } + .setRadioStyle{ + width:100%; + cursor:pointer; + } + .setRadioStyle span:last-child{ + flex:1; + display:flex; + } + .setRadioStyle .ant-radio,.setRadioStyle .ant-checkbox{ + height:16px; + margin-top:2px; + } `} {/*

*/} - + { questionType.question_choices && questionType.question_choices.map((item,key)=>{ let prefix = `${tagArray[key]}.` return(

- {prefix} - {/* */} - {/* */} - + + {prefix} + +

) }) diff --git a/public/react/src/modules/courses/exercise/question/single.js b/public/react/src/modules/courses/exercise/question/single.js index a879bad74..5156019d4 100644 --- a/public/react/src/modules/courses/exercise/question/single.js +++ b/public/react/src/modules/courses/exercise/question/single.js @@ -40,18 +40,18 @@ class single extends Component{ let isJudge = questionType.question_type == 2 return(
- + { questionType.question_choices && questionType.question_choices.map((item,key)=>{ let prefix = isJudge ? undefined : `${tagArray[key]}.` return( -

- {prefix} - {/* */} - {/* */} - +

+ + {prefix} + +

) }) diff --git a/public/react/src/modules/courses/graduation/tasks/index.js b/public/react/src/modules/courses/graduation/tasks/index.js index b54815b2f..f405f69c9 100644 --- a/public/react/src/modules/courses/graduation/tasks/index.js +++ b/public/react/src/modules/courses/graduation/tasks/index.js @@ -384,7 +384,9 @@ class GraduationTasks extends Component{ this.setState({ order: e.key, - isSpin:true + isSpin:true, + checkBoxValues:[], + checkAllValue:false }); let newkey=e.key; diff --git a/public/react/src/modules/courses/shixunHomework/shixunHomework.js b/public/react/src/modules/courses/shixunHomework/shixunHomework.js index e168dcf6e..d84453fc8 100644 --- a/public/react/src/modules/courses/shixunHomework/shixunHomework.js +++ b/public/react/src/modules/courses/shixunHomework/shixunHomework.js @@ -587,6 +587,8 @@ class ShixunHomework extends Component{ let {Coursename,page}=this.state; this.setState({ order: e.key, + checkBoxValues:[], + checkedtype:false, isSpin:true }); let newkey=e.key; diff --git a/public/react/src/modules/user/usersInfo/InfosBanner.js b/public/react/src/modules/user/usersInfo/InfosBanner.js index fe86db2de..b76347f16 100644 --- a/public/react/src/modules/user/usersInfo/InfosBanner.js +++ b/public/react/src/modules/user/usersInfo/InfosBanner.js @@ -8,7 +8,7 @@ import "./usersInfo.css" import "../../courses/css/members.css" import "../../courses/css/Courses.css" -import banner from '../../../images/account/infobanner.png' +import { LinkAfterLogin } from 'educoder' class InfosBanner extends Component{ constructor(props){ @@ -68,7 +68,14 @@ class InfosBanner extends Component{ : - 私信 + + 私信 + }
diff --git a/public/react/src/modules/user/usersInfo/InfosCourse.js b/public/react/src/modules/user/usersInfo/InfosCourse.js index c4c1c77e0..bc401d4d7 100644 --- a/public/react/src/modules/user/usersInfo/InfosCourse.js +++ b/public/react/src/modules/user/usersInfo/InfosCourse.js @@ -130,7 +130,7 @@ class InfosCourse extends Component{ this.props.current_user && this.props.current_user.user_identity != "学生" ? : "" } { - (!data || data.courses.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && + (!data || (data && data.courses.length==0)) && category && } { data && data.courses && data.courses.map((item,key)=>{ diff --git a/public/react/src/modules/user/usersInfo/InfosPath.js b/public/react/src/modules/user/usersInfo/InfosPath.js index d2ed2900c..8ba8d6af8 100644 --- a/public/react/src/modules/user/usersInfo/InfosPath.js +++ b/public/react/src/modules/user/usersInfo/InfosPath.js @@ -152,7 +152,7 @@ class InfosPath extends Component{ this.props.current_user && this.props.current_user.user_identity != "学生" ? :"" } { - (!data || data.subjects.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && + (!data || (data && data.subjects.length==0)) && category && } { data && data.subjects && data.subjects.map((item,key)=>{ diff --git a/public/react/src/modules/user/usersInfo/InfosProject.js b/public/react/src/modules/user/usersInfo/InfosProject.js index 082c18a71..de003a5dd 100644 --- a/public/react/src/modules/user/usersInfo/InfosProject.js +++ b/public/react/src/modules/user/usersInfo/InfosProject.js @@ -125,7 +125,7 @@ class InfosProject extends Component{ :"" } { - (!data || data.projects.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && + (!data || (data && data.projects.length==0)) && category && } { data && data.projects && data.projects.map((item,key)=>{ diff --git a/public/react/src/modules/user/usersInfo/InfosShixun.js b/public/react/src/modules/user/usersInfo/InfosShixun.js index b86e69983..dfcf5985a 100644 --- a/public/react/src/modules/user/usersInfo/InfosShixun.js +++ b/public/react/src/modules/user/usersInfo/InfosShixun.js @@ -115,7 +115,7 @@ class InfosShixun extends Component{ totalCount, isSpin } = this.state; - let isStudent = this.props.isStudent(); + let is_current=this.props.is_current; return(
@@ -161,7 +161,7 @@ class InfosShixun extends Component{ :"" } { - (!data || data.shixuns.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && + (!data || (data && data.shixuns.length==0)) && category && } { data && data.shixuns && data.shixuns.map((item,key)=>{ From cd80ea20f84e547e97e41fc0e2af3b53f9a4abfa Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Wed, 21 Aug 2019 10:35:02 +0800 Subject: [PATCH 02/91] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E5=AE=9E=E8=AE=AD=E6=A8=A1=E5=9D=97=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/user/usersInfo/InfosShixun.js | 228 +----------------- 1 file changed, 9 insertions(+), 219 deletions(-) diff --git a/public/react/src/modules/user/usersInfo/InfosShixun.js b/public/react/src/modules/user/usersInfo/InfosShixun.js index 7a5329729..4799626a9 100644 --- a/public/react/src/modules/user/usersInfo/InfosShixun.js +++ b/public/react/src/modules/user/usersInfo/InfosShixun.js @@ -1,218 +1,11 @@ -<<<<<<< HEAD -import React, { Component } from 'react'; -import { SnackbarHOC } from 'educoder'; -import {BrowserRouter as Router,Route,Switch} from 'react-router-dom'; -import {Tooltip,Menu,Pagination,Spin} from 'antd'; -import Loadable from 'react-loadable'; -import Loading from '../../../Loading'; -import NoneData from '../../courses/coursesPublic/NoneData' -import axios from 'axios'; -import {getImageUrl,setImagesUrl} from 'educoder'; -import { TPMIndexHOC } from '../../tpm/TPMIndexHOC'; -import { CNotificationHOC } from '../../courses/common/CNotificationHOC' -import "./usersInfo.css" - -import Create from './publicCreatNew' - -class InfosShixun extends Component{ - constructor(props){ - super(props); - this.state={ - category:undefined, - page:1, - sort_by:'time', - status:undefined, - per_page:16, - isSpin:false, - - totalCount:undefined, - data:undefined - } - } - - componentDidMount=()=>{ - this.setState({ - isSpin:true - }) - let{category,status,sort_by,page}=this.state; - this.getCourses(category,status,sort_by,page); - } - - getCourses=(category,status,sort_by,page)=>{ - let url=`/users/${this.props.match.params.username}/shixuns.json`; - axios.get((url),{params:{ - category, - status, - sort_by, - page, - per_page:this.props.is_current && category && page ==1?17:16 - }}).then((result)=>{ - if(result){ - this.setState({ - totalCount:result.data.count, - data:result.data, - isSpin:false - }) - } - }).catch((error)=>{ - console.log(error); - }) - } - - //切换种类 - changeCategory=(cate)=>{ - this.setState({ - category:cate, - status:undefined, - page:1, - isSpin:true - }) - let{sort_by}=this.state; - this.getCourses(cate,undefined,sort_by,1); - } - // 切换状态 - changeStatus=(status)=>{ - this.setState({ - status, - page:1, - isSpin:true - }) - let{category,sort_by}=this.state; - this.getCourses(category,status,sort_by,1); - } - //切换页数 - changePage=(page)=>{ - this.setState({ - page, - isSpin:true - }) - let{category,sort_by,status}=this.state; - this.getCourses(category,status,sort_by,page); - } - - // 进入课堂 - turnToCourses=(url)=>{ - this.props.history.push(url); - } - - - // 切换排序方式 - changeOrder= (sort)=>{ - this.setState({ - sort_by:sort, - isSpin:true - }) - let{category,status,page}=this.state; - this.getCourses(category,status,sort,page); - } - - render(){ - let{ - category, - status, - sort_by, - page, - data, - totalCount, - isSpin - } = this.state; - - let is_current=this.props.is_current; - return( -
- - - { - category && category == "manage" && is_current && - - } - { - category && category == "study" && is_current && - - } -
- 共参与{totalCount}个{category?category=="manage"?"发布":"学习":"实训"} -
-
  • - {sort_by=="time"?"时间最新":"语言类别"} -
      -
    • this.changeOrder("time")}>时间最新
    • -
    • this.changeOrder("language")}>语言类别
    • -
    -
  • -
    -
    -
    - { - page == 1 && is_current && !category && this.props.current_user && this.props.current_user.user_identity != "学生" ? - :"" - } - { - (!data || (data && data.shixuns.length==0)) && category && - } - { - data && data.shixuns && data.shixuns.map((item,key)=>{ - return( -
    this.turnToCourses(`/shixuns/${item.identifier}/challenges`)}> - { - item.tag &&
    {item.tag}
    - } - - - -
    -

    - {item.name} -

    -
    -

    -
    -

    - 已完成 {item.finished_challenges_count} / {item.challenges_count} -

    -
    -
    - ) - }) - } -
    - { - totalCount > 15 && -
    - -
    - } -
    -
    - ) - } -} -======= import React, { Component } from 'react'; -import { SnackbarHOC } from 'educoder'; -import {BrowserRouter as Router,Route,Switch} from 'react-router-dom'; -import {Tooltip,Menu,Pagination,Spin} from 'antd'; -import Loadable from 'react-loadable'; -import Loading from '../../../Loading'; + +import { Pagination , Spin } from 'antd'; + import NoneData from '../../courses/coursesPublic/NoneData' import axios from 'axios'; -import {getImageUrl,setImagesUrl} from 'educoder'; -import { TPMIndexHOC } from '../../tpm/TPMIndexHOC'; -import { CNotificationHOC } from '../../courses/common/CNotificationHOC' +import { setImagesUrl } from 'educoder'; + import "./usersInfo.css" import Create from './publicCreatNew' @@ -319,7 +112,7 @@ class InfosShixun extends Component{ totalCount, isSpin } = this.state; - let isStudent = this.props.isStudent(); + let is_current=this.props.is_current; return(
    @@ -361,20 +154,18 @@ class InfosShixun extends Component{
    { - page == 1 && is_current && !category && this.props.current_user && this.props.current_user.user_identity != "学生" ? + page == 1 && is_current && !category && this.props.current_user && this.props.current_user.user_identity != "学生" ? :"" } { - (!data || data.shixuns.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && + (!data || (data && data.shixuns.length==0)) && category && } { data && data.shixuns && data.shixuns.map((item,key)=>{ return(
    this.turnToCourses(`/shixuns/${item.identifier}/challenges`)}> { - item.tag &&
    {item.tag} - {/**/} -
    + item.tag &&
    {item.tag}
    } @@ -406,5 +197,4 @@ class InfosShixun extends Component{ ) } } ->>>>>>> dev_aliyun export default InfosShixun; \ No newline at end of file From f50b79c3433d23837d717dda66091f16294c84ee Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Wed, 21 Aug 2019 13:42:28 +0800 Subject: [PATCH 03/91] issue --- .../react/src/modules/courses/boards/BoardsNew.js | 13 +++++++++---- .../src/modules/courses/coursesPublic/NoneData.js | 2 +- public/react/src/modules/courses/poll/Poll.js | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/public/react/src/modules/courses/boards/BoardsNew.js b/public/react/src/modules/courses/boards/BoardsNew.js index 43be980ed..c96667283 100644 --- a/public/react/src/modules/courses/boards/BoardsNew.js +++ b/public/react/src/modules/courses/boards/BoardsNew.js @@ -358,10 +358,15 @@ class BoardsNew extends Component{ dropdownRender={menu => (
    {menu} - -
    this.refs['addDirModal'].open()}> - 添加目录 -
    + { + isAdmin && + + +
    this.refs['addDirModal'].open()}> + 添加目录 +
    +
    + }
    )} > diff --git a/public/react/src/modules/courses/coursesPublic/NoneData.js b/public/react/src/modules/courses/coursesPublic/NoneData.js index 4066cea8b..35d7a5271 100644 --- a/public/react/src/modules/courses/coursesPublic/NoneData.js +++ b/public/react/src/modules/courses/coursesPublic/NoneData.js @@ -22,7 +22,7 @@ class NoneData extends Component{ } `} - +

    暂时还没有相关数据哦!

    ) diff --git a/public/react/src/modules/courses/poll/Poll.js b/public/react/src/modules/courses/poll/Poll.js index 07be2196c..9574f2492 100644 --- a/public/react/src/modules/courses/poll/Poll.js +++ b/public/react/src/modules/courses/poll/Poll.js @@ -288,6 +288,7 @@ class Poll extends Component{ }) let{type,StudentList_value}=this.state this.InitList(type,StudentList_value,1); + this.props.updataleftNavfun(); } }).catch((error)=>{ console.log(error); From 8a174c3a06bf421090a2cdce66dc389bbc34d635 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 14:38:53 +0800 Subject: [PATCH 04/91] cdn test --- public/react/scripts/build.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/public/react/scripts/build.js b/public/react/scripts/build.js index b2bac24ec..037b0ee42 100644 --- a/public/react/scripts/build.js +++ b/public/react/scripts/build.js @@ -194,7 +194,7 @@ function generateNewIndexJsp() { const newVersion = '1.1.1' let cdnHost = 'https://shixun.educoder.net' cdnHost = 'https://ali-cdn.educoder.net' - // cdnHost = '' + cdnHost = '' var result = data.replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) // .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) @@ -207,11 +207,28 @@ function generateNewIndexJsp() { // .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`) // .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`) - .replace(/https:\/\/testeduplus2.educoder.net/g, ''); + // .replace(/https:\/\/testeduplus2.educoder.net/g, ''); // .replace(/http:\/\/testbdweb.educoder.net/g, ''); // .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css'); + var mainRegex = / + ` + result = data.replace(mainRegex, code) + fs2.writeFile(outputPath, result, 'utf8', function (err) { if (err) return console.log(err); commitAndPush(); From 48e5f9f84b7c15b1e70951ed1d4fa72ba755dba0 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 14:48:38 +0800 Subject: [PATCH 05/91] import './public-path.js'; --- public/react/src/App.js | 1 + public/react/src/public-path.js | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 public/react/src/public-path.js diff --git a/public/react/src/App.js b/public/react/src/App.js index 9621fdc18..3fd121268 100644 --- a/public/react/src/App.js +++ b/public/react/src/App.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import './public-path.js'; import logo from './logo.svg'; import './App.css'; import {LocaleProvider} from 'antd' diff --git a/public/react/src/public-path.js b/public/react/src/public-path.js new file mode 100644 index 000000000..fb82840d4 --- /dev/null +++ b/public/react/src/public-path.js @@ -0,0 +1,6 @@ +/*global __webpack_public_path__ */ +if (window.location.host == '47.96.87.25:48080') { + __webpack_public_path__ = 'http://testali-cdn.educoder.net/react/build/' +} else if (window.location.host == 'www.educoder.net') { + __webpack_public_path__ = 'https://ali-newweb.educoder.net/react/build/' +} \ No newline at end of file From cba575146d3c0ed774a5d4df74bb6046c452b448 Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Wed, 21 Aug 2019 14:50:42 +0800 Subject: [PATCH 06/91] =?UTF-8?q?=E6=AF=95=E8=AE=BE=E9=80=89=E9=A2=98?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E7=9A=84=E9=99=84=E4=BB=B6=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/components/attachment/AttachmentList.js | 11 ++++++++--- .../graduation/topics/GraduateTopicDetailInfo.js | 8 +++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/public/react/src/common/components/attachment/AttachmentList.js b/public/react/src/common/components/attachment/AttachmentList.js index 8ece1c39d..afc55fe1f 100644 --- a/public/react/src/common/components/attachment/AttachmentList.js +++ b/public/react/src/common/components/attachment/AttachmentList.js @@ -1,4 +1,5 @@ import React,{ Component } from "react"; +import { ConditionToolTip } from 'educoder' class AttachmentsList extends Component{ constructor(props){ @@ -12,14 +13,18 @@ class AttachmentsList extends Component{ attachments.map((item,key)=>{ return(

    - + { item.is_pdf && item.is_pdf == true ? - {item.title} + 30 }> + {item.title} + : - {item.title} + 30 }> + {item.title} + } {item.filesize}

    diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetailInfo.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetailInfo.js index 0c76cd9de..9ba334421 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetailInfo.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetailInfo.js @@ -6,7 +6,7 @@ import '../style.css' import axios from "axios"; import GraduateTopicReply from './GraduateTopicReply' -import { ConditionToolTip,MarkdownToHtml } from 'educoder' +import { ConditionToolTip , MarkdownToHtml , AttachmentList } from 'educoder' const $=window.$; const type={1: "设计",2: "论文", 3: "创作"} @@ -60,9 +60,10 @@ class GraduateTopicDetailTable extends Component{ { topicInfo && topicInfo.attachment_list.length>0 &&

    - { + {/* { topicInfo.attachment_list.map((item,key)=>{ return( +

  • 30 }> @@ -72,7 +73,8 @@ class GraduateTopicDetailTable extends Component{
  • ) }) - } + } */} +

    }
    From 5b56c36344f17442eec8647fde0c24eb441f7a81 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 15:16:47 +0800 Subject: [PATCH 07/91] \\ --- public/react/scripts/build.js | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/public/react/scripts/build.js b/public/react/scripts/build.js index 037b0ee42..9ec7f5166 100644 --- a/public/react/scripts/build.js +++ b/public/react/scripts/build.js @@ -195,22 +195,7 @@ function generateNewIndexJsp() { let cdnHost = 'https://shixun.educoder.net' cdnHost = 'https://ali-cdn.educoder.net' cdnHost = '' - var result = data.replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) - // .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) - - // ${cdnHost} 加了cdn后,这个文件里的字体文件加载会有跨域的报错 ../fonts/fontawesome-webfont.eot - // TODO tpi 评测结果关闭也使用了fontawesome - .replace('/css/css_min_all.css', `${cdnHost}/react/build/css/css_min_all.css?v=${newVersion}`) - .replace('/css/iconfont.css', `${cdnHost}/react/build/css/iconfont.css?v=${newVersion}`) - .replace(/\/js\/create_kindeditor.js/g, `${cdnHost}/react/build/js/create_kindeditor.js?v=${newVersion}`) - - // .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`) - // .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`) - - // .replace(/https:\/\/testeduplus2.educoder.net/g, ''); - // .replace(/http:\/\/testbdweb.educoder.net/g, ''); - - // .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css'); + var mainRegex = / ` - result = data.replace(mainRegex, code) + var jsMinAllRegex = / + var result = data + .replace(jsMinAllRegex, '') + // .replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) + // .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) + + // ${cdnHost} 加了cdn后,这个文件里的字体文件加载会有跨域的报错 ../fonts/fontawesome-webfont.eot + // TODO tpi 评测结果关闭也使用了fontawesome + .replace('/css/css_min_all.css', `${cdnHost}/react/build/css/css_min_all.css?v=${newVersion}`) + .replace('/css/iconfont.css', `${cdnHost}/react/build/css/iconfont.css?v=${newVersion}`) + .replace(/\/js\/create_kindeditor.js/g, `${cdnHost}/react/build/js/create_kindeditor.js?v=${newVersion}`) + + .replace(mainRegex, code) + // .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`) + // .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`) + + // .replace(/https:\/\/testeduplus2.educoder.net/g, ''); + // .replace(/http:\/\/testbdweb.educoder.net/g, ''); + + // .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css'); fs2.writeFile(outputPath, result, 'utf8', function (err) { if (err) return console.log(err); From f794810ddd04348bfe10843a39da6d61e86dfaa6 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 15:39:10 +0800 Subject: [PATCH 08/91] =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/react/scripts/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/react/scripts/build.js b/public/react/scripts/build.js index 9ec7f5166..fe18e93e5 100644 --- a/public/react/scripts/build.js +++ b/public/react/scripts/build.js @@ -208,8 +208,8 @@ function generateNewIndexJsp() { } else if (window.location.host == 'www.educoder.net') { _host = 'https://ali-newweb.educoder.net/react/build/' } - document.write(' ` From 6e9b9c62649f1cd77108049cdca41f91e37fc5e0 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 15:39:30 +0800 Subject: [PATCH 09/91] js --- public/react/src/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/react/src/App.js b/public/react/src/App.js index 3fd121268..3949c26a2 100644 --- a/public/react/src/App.js +++ b/public/react/src/App.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import './public-path.js'; +import './public-path'; import logo from './logo.svg'; import './App.css'; import {LocaleProvider} from 'antd' From 26b78bcfb31ff8e82ae73c2b6a31a49bb4bd9cdd Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Wed, 21 Aug 2019 16:12:05 +0800 Subject: [PATCH 10/91] =?UTF-8?q?updateFile=E5=88=87=E6=8D=A2=E5=85=B3?= =?UTF-8?q?=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/react/src/modules/page/MainContentContainer.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/react/src/modules/page/MainContentContainer.js b/public/react/src/modules/page/MainContentContainer.js index 33257703d..3dc3d07d4 100644 --- a/public/react/src/modules/page/MainContentContainer.js +++ b/public/react/src/modules/page/MainContentContainer.js @@ -230,6 +230,7 @@ class MainContentContainer extends Component { && prevProps.game.identifier !== game.identifier) { // 切换关卡时,停止轮训 this.oldGameIdentifier = prevProps.game.identifier; + this.doFileUpdateRequestOnCodeMirrorBlur(prevProps) } } @@ -384,8 +385,8 @@ class MainContentContainer extends Component { } - doFileUpdateRequestOnCodeMirrorBlur = () => { - var fileUpdatePromise = this.doFileUpdateRequest(true) + doFileUpdateRequestOnCodeMirrorBlur = (props) => { + var fileUpdatePromise = this.doFileUpdateRequest(true, undefined, props) if (fileUpdatePromise) { fileUpdatePromise.then((resData) => { if (resData.status === -1) { // 保存文件出现异常 @@ -494,7 +495,8 @@ class MainContentContainer extends Component { } // forTest true 是评测时触发的file_update - doFileUpdateRequest(checkIfCodeChanged, forTest) { + doFileUpdateRequest(checkIfCodeChanged, forTest, props) { + const _props = props || this.props; const { codeStatus } = this.state; if (!forTest && codeStatus !== UPDATED) { // 已修改状态才能保存 return; @@ -519,7 +521,7 @@ class MainContentContainer extends Component { }) return null; } - const { challenge, output_sets, onRunCodeTestFinish, myshixun } = this.props + const { challenge, output_sets, onRunCodeTestFinish, myshixun } = _props // var url = `${locationPath}/file_update?path=${encodeURIComponent(challenge.path)}` var url = `/myshixuns/${myshixun.identifier}/update_file.json` From 76e41f84c1196a7fc3b1a406b3267b603f082784 Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Wed, 21 Aug 2019 16:55:16 +0800 Subject: [PATCH 11/91] =?UTF-8?q?=E8=AF=BE=E5=A0=82=E5=B8=96=E5=AD=90?= =?UTF-8?q?=E8=AF=A6=E6=83=85-=E6=96=B0=E5=A2=9E=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E5=92=8C=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/courses/boards/TopicDetail.js | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/public/react/src/modules/courses/boards/TopicDetail.js b/public/react/src/modules/courses/boards/TopicDetail.js index 7d0e6d37d..542157bfb 100644 --- a/public/react/src/modules/courses/boards/TopicDetail.js +++ b/public/react/src/modules/courses/boards/TopicDetail.js @@ -24,7 +24,7 @@ import '../../forums/RightSection.css' import './TopicDetail.css' import '../common/courseMessage.css' import { Pagination, Tooltip } from 'antd' -import { bytesToSize, ConditionToolTip, markdownToHTML, MarkdownToHtml } from 'educoder' +import { bytesToSize, ConditionToolTip, markdownToHTML, MarkdownToHtml , setImagesUrl } from 'educoder' import SendToCourseModal from '../coursesPublic/modal/SendToCourseModal' import CBreadcrumb from '../common/CBreadcrumb' import { generateComments, generateChildComments, _findById, handleContentBeforeCreateNew, addNewComment @@ -57,6 +57,7 @@ class TopicDetail extends Component { pageCount: 1, comments: [], goldRewardDialogOpen: false, + author:undefined } } componentDidMount() { @@ -85,7 +86,8 @@ class TopicDetail extends Component { memo: Object.assign({}, { ...response.data.data, replies_count: response.data.data.total_replies_count - }, {...this.state.memo}) + }, {...this.state.memo}), + author:response.data.data.author }, () => { }) @@ -514,7 +516,7 @@ class TopicDetail extends Component { render() { const { match, history } = this.props const { recommend_shixun, current_user,author_info } = this.props; - const { memo, comments, hasMoreComments, goldRewardDialogOpen, pageCount, total_count } = this.state; + const { memo, comments, hasMoreComments, goldRewardDialogOpen, pageCount, total_count , author } = this.state; const messageId = match.params.topicId if (this.state.memoLoading || !current_user) { return
    @@ -599,51 +601,54 @@ class TopicDetail extends Component { }
    -
    - {moment(memo.created_on).fromNow()} 发布 -
    - -
    -
    - -
    - - {/* { current_user.admin && - - - - } */} - - - - {memo.visits || '1'} - - - { !!memo.total_replies_count && - - - - { memo.total_replies_count } - - - } - {!!memo.total_praises_count && - - - - { memo.total_praises_count } +
    + +
    +
    + {author && author.name} + {moment(memo.created_on).fromNow()} 发布 +
    + +
    + + {/* { current_user.admin && + + - + } */} + + + + {memo.visits || '1'} + + + { !!memo.total_replies_count && + + + + { memo.total_replies_count } + + } - - + {!!memo.total_praises_count && + + + + { memo.total_praises_count } + + + } + + +
    +
    From b7634406e92f7b40fce6cfe07c16969aeb3d007a Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Thu, 22 Aug 2019 10:17:10 +0800 Subject: [PATCH 12/91] =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=85=B3=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/react/src/modules/page/MainContentContainer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/react/src/modules/page/MainContentContainer.js b/public/react/src/modules/page/MainContentContainer.js index 3dc3d07d4..a51f00902 100644 --- a/public/react/src/modules/page/MainContentContainer.js +++ b/public/react/src/modules/page/MainContentContainer.js @@ -224,13 +224,15 @@ class MainContentContainer extends Component { } componentDidUpdate(prevProps, prevState, snapshot) { - const { game } = this.props + const { game, challenge } = this.props if (game && prevProps.game && prevProps.game.identifier !== game.identifier) { // 切换关卡时,停止轮训 this.oldGameIdentifier = prevProps.game.identifier; this.doFileUpdateRequestOnCodeMirrorBlur(prevProps) + } else if (challenge && (challenge.pathIndex || prevProps.challenge.pathIndex) && challenge.pathIndex != prevProps.challenge.pathIndex) { + this.doFileUpdateRequestOnCodeMirrorBlur(prevProps) } } From dee7e226bea21805e4ed352809a91e363b387e82 Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Thu, 22 Aug 2019 18:23:06 +0800 Subject: [PATCH 13/91] =?UTF-8?q?=E8=AF=BE=E5=A0=82=E5=90=84=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=88=97=E8=A1=A8=E7=9A=84=E7=82=B9=E5=87=BB=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/react/config/webpack.config.dev.js | 3 +- .../courses/exercise/ExerciseListItem.js | 8 ++--- .../graduation/tasks/GraduateTaskItem.js | 15 ++++----- .../modules/courses/graduation/tasks/index.js | 3 +- .../graduation/topics/GraduateTopicItem.js | 4 +-- .../members/modal/CreateGroupByImportModal.js | 32 +++++++------------ .../src/modules/courses/poll/PollListItem.js | 6 ++-- 7 files changed, 30 insertions(+), 41 deletions(-) diff --git a/public/react/config/webpack.config.dev.js b/public/react/config/webpack.config.dev.js index 510bcaa4f..f126bc363 100644 --- a/public/react/config/webpack.config.dev.js +++ b/public/react/config/webpack.config.dev.js @@ -30,7 +30,8 @@ module.exports = { // You may want 'eval' instead if you prefer to see the compiled output in DevTools. // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s // devtool: "cheap-module-eval-source-map", - // 开启调试 + // 开启调试 + devtool: "source-map", // 开启调试 // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. // The first two entry points enable "hot" CSS and auto-refreshes for JS. diff --git a/public/react/src/modules/courses/exercise/ExerciseListItem.js b/public/react/src/modules/courses/exercise/ExerciseListItem.js index e890b92ef..639f5bdc8 100644 --- a/public/react/src/modules/courses/exercise/ExerciseListItem.js +++ b/public/react/src/modules/courses/exercise/ExerciseListItem.js @@ -52,17 +52,17 @@ class ExerciseListItem extends Component{ }) } render(){ - let{item,checkBox}=this.props; + let{item,checkBox,key}=this.props; let {coursesId,Id}=this.props.match.params const IsAdmin =this.props.isAdmin(); const IsStudent =this.props.isStudent(); // console.log(this.props.current_user.user_id) return( -
    +
    window.$(`.exerciseitem${key} input`).click() }> { - IsAdmin && - + IsAdmin && + {checkBox} } diff --git a/public/react/src/modules/courses/graduation/tasks/GraduateTaskItem.js b/public/react/src/modules/courses/graduation/tasks/GraduateTaskItem.js index d30944ef0..92a4832db 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduateTaskItem.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduateTaskItem.js @@ -151,16 +151,12 @@ class GraduateTaskItem extends Component{ coursesId, categoryid, taskid, - + index, + isAdmin } = this.props; - // console.log(discussMessage) - - - - return( -
    +
    window.$(`.taskitem${index} input`).click() }> - { checkBox } - + + { checkBox } + {/* style={{borderTop:data===undefined?"":data.course_identity<4?'1px solid #EBEBEB':'1px solid transparent'}} */} diff --git a/public/react/src/modules/courses/graduation/tasks/index.js b/public/react/src/modules/courses/graduation/tasks/index.js index f405f69c9..1610ff772 100644 --- a/public/react/src/modules/courses/graduation/tasks/index.js +++ b/public/react/src/modules/courses/graduation/tasks/index.js @@ -731,7 +731,7 @@ class GraduationTasks extends Component{ { tasks.map((item, index) => { // console.log(item) return ( -
    +
    diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicItem.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicItem.js index 9f61e69d4..bac04c699 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicItem.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicItem.js @@ -47,7 +47,7 @@ class GraduateTopicItem extends Component{ } -
    +
    window.$(`.topicItem${index} input`).click() }> - { isAdmin ? checkBox : ""} + { isAdmin ? {checkBox} : ""}
    -
    - - - -
    - -

    - -

    -

    点击或拖拽文件到这里上传

    -

    - 单个文件最大150MB -

    -
    + +

    + +

    +

    点击或拖拽文件到这里上传

    +

    + 单个文件最大150MB +

    +
    ) } diff --git a/public/react/src/modules/courses/poll/PollListItem.js b/public/react/src/modules/courses/poll/PollListItem.js index 50c471e76..368fda32b 100644 --- a/public/react/src/modules/courses/poll/PollListItem.js +++ b/public/react/src/modules/courses/poll/PollListItem.js @@ -16,7 +16,7 @@ class PollListItem extends Component{ super(props); } render(){ - let{item,checkBox,courseType}=this.props; + let{item,checkBox,courseType,key}=this.props; let {coursesId}=this.props.match.params; const IsAdmin =this.props.isAdmin(); @@ -28,10 +28,10 @@ class PollListItem extends Component{ let canNotLink = !isAdminOrStudent && item.lock_status == 0 return( -
    +
    window.$(`.pollitem${key} input`).click() }> { IsAdmin && - + {checkBox} } From 6f2fe96e9046d3a5fe0c7785a5f0cc2615f12e9b Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Fri, 23 Aug 2019 11:17:36 +0800 Subject: [PATCH 14/91] issue --- .../busyWork/common/WorkDetailPageHeader.js | 11 +- .../src/modules/courses/exercise/Exercise.js | 2 +- .../courses/exercise/ExerciseListItem.js | 20 +- public/react/src/modules/courses/poll/Poll.js | 2 +- .../src/modules/courses/poll/PollListItem.js | 6 +- .../shixunHomework/ShixunhomeWorkItem.js | 177 +++++++++--------- .../courses/shixunHomework/shixunHomework.js | 3 +- 7 files changed, 109 insertions(+), 112 deletions(-) diff --git a/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js b/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js index a092c5066..4177be8a4 100644 --- a/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js +++ b/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js @@ -85,8 +85,7 @@ class WorkDetailPageHeader extends Component{ background: #fff; } .workDetailPageHeader .summaryname { - line-height: 20px; - margin-top: 13px; + line-height:30px } `} -
    - +
    + {homework_name} {/* {homework_name} */} - {category && 返回} + {category && 返回} {this.props.update_atta && diff --git a/public/react/src/modules/courses/exercise/Exercise.js b/public/react/src/modules/courses/exercise/Exercise.js index 4331bdc6f..044dc87f0 100644 --- a/public/react/src/modules/courses/exercise/Exercise.js +++ b/public/react/src/modules/courses/exercise/Exercise.js @@ -559,7 +559,7 @@ class Exercise extends Component{ {...this.props} {...this.state} item={item} - key={key} + index={key} checkBox={ this.onItemClick(item)} >} diff --git a/public/react/src/modules/courses/exercise/ExerciseListItem.js b/public/react/src/modules/courses/exercise/ExerciseListItem.js index 639f5bdc8..5248f4c1a 100644 --- a/public/react/src/modules/courses/exercise/ExerciseListItem.js +++ b/public/react/src/modules/courses/exercise/ExerciseListItem.js @@ -52,17 +52,17 @@ class ExerciseListItem extends Component{ }) } render(){ - let{item,checkBox,key}=this.props; + let{item,checkBox,index}=this.props; let {coursesId,Id}=this.props.match.params const IsAdmin =this.props.isAdmin(); const IsStudent =this.props.isStudent(); // console.log(this.props.current_user.user_id) return( -
    window.$(`.exerciseitem${key} input`).click() }> +
    window.$(`.exerciseitem${index} input`).click() }> { IsAdmin && - + {checkBox} } @@ -96,20 +96,20 @@ class ExerciseListItem extends Component{ {/*{item.exercise_name}*/} { - this.props.isAdmin()? {item.exercise_name}:"" + to={`/courses/${coursesId}/exercises/${item.id}/student_exercise_list?tab=0`}>{item.exercise_name}:"" } { this.props.isStudent()? - {item.exercise_name}:"" + {item.exercise_name}:"" } { this.props.isNotMember()? item.lock_status === 0 ? {item.exercise_name} - : {item.exercise_name}:"" + : {item.exercise_name}:"" } { @@ -165,8 +165,8 @@ class ExerciseListItem extends Component{ { IsAdmin &&
    - 编辑 - 设置 + 编辑 + 设置
    }

    @@ -193,7 +193,7 @@ class ExerciseListItem extends Component{
    {item.current_status ===0&&item.exercise_status>1?
  • 继续答题
  • : item.current_status ===1&&item.exercise_status>1?
  • 查看答题
  • : - item.current_status ===2&&item.exercise_status>1?
  • this.setgameexercise(`/courses/${coursesId}/exercises/${item.id}/users/${this.props.current_user.login}`)}>开始答题
  • :""} + item.current_status ===2&&item.exercise_status>1?
  • this.setgameexercise(`/courses/${coursesId}/exercises/${item.id}/users/${this.props.current_user.login}`)}>开始答题
  • :""}
    }
    diff --git a/public/react/src/modules/courses/poll/Poll.js b/public/react/src/modules/courses/poll/Poll.js index 9574f2492..b064c289b 100644 --- a/public/react/src/modules/courses/poll/Poll.js +++ b/public/react/src/modules/courses/poll/Poll.js @@ -596,7 +596,7 @@ class Poll extends Component{ {...this.state} courseType={course_types} item={item} - key={key} + index={key} checkBox={ this.onItemClick(item)}>} > ) diff --git a/public/react/src/modules/courses/poll/PollListItem.js b/public/react/src/modules/courses/poll/PollListItem.js index 368fda32b..c4f1c8209 100644 --- a/public/react/src/modules/courses/poll/PollListItem.js +++ b/public/react/src/modules/courses/poll/PollListItem.js @@ -16,7 +16,7 @@ class PollListItem extends Component{ super(props); } render(){ - let{item,checkBox,courseType,key}=this.props; + let{item,checkBox,courseType,index}=this.props; let {coursesId}=this.props.match.params; const IsAdmin =this.props.isAdmin(); @@ -28,10 +28,10 @@ class PollListItem extends Component{ let canNotLink = !isAdminOrStudent && item.lock_status == 0 return( -
    window.$(`.pollitem${key} input`).click() }> +
    window.$(`.pollitem${index} input`).click() }> { IsAdmin && - + {checkBox} } diff --git a/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js b/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js index 38ab63a4f..942c06800 100644 --- a/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js +++ b/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js @@ -124,13 +124,19 @@ class ShixunhomeWorkItem extends Component{ }) } - editname = (name,id) => { + // 实训详情,阻止冒泡 + stopPro = (event) => { + event.stopPropagation() + } + + editname = (name,id,event) => { this.setState({ ModalsRenametype:true, NavmodalValue:name, Navmodalname:"重命名", url:`/homework_commons/${id}/alter_name.json` }) + event.stopPropagation() } cannerNavmoda=()=>{ this.setState({ @@ -157,88 +163,74 @@ class ShixunhomeWorkItem extends Component{ const { checkBox, discussMessage, - taskid, + taskid,index } = this.props; - // - - // allow_late: true //是否开启补交 - - // homework_id: 9250 - - // shixun_identifier: "25ykhpvl" - - // - // console.log("this.props.isAdmin"); - - return( -
    - {this.state.ModalsRenametype===true? - this.cannerNavmoda()} - /> - :""} - - - - {visible===true?:""} - - - -
    -

    实训已经更新了,正在为您重置!

    -
    - -
    - -
    -

    本实训的开启时间:{shixunsmessage}
    开启时间之前不能挑战 -

    -
    -
    - {/*取消*/} - 知道啦 -
    - {/*

    */} - {/*知道了*/} - {/*

    */} -
    - + + { + this.state.ModalsRenametype===true? + this.cannerNavmoda()} + /> + :""} + + {visible===true?:""} + +
    +

    实训已经更新了,正在为您重置!

    +
    + +
    + + +
    +

    本实训的开启时间:{shixunsmessage}
    开启时间之前不能挑战 +

    +
    +
    + {/*取消*/} + 知道啦 +
    + {/*

    */} + {/*知道了*/} + {/*

    */} +
    +
    window.$(`.shixunitem${index} input`).click() } > - {this.props.isAdmin?checkBox:""} + {this.props.isAdmin? + {checkBox} + : + "" + }
    - {this.props.isAdmin?
    - 实训详情 - {this.props.isAdminOrCreator()?this.editname(discussMessage.name,discussMessage.homework_id)} className={"btn colorblue ml20 font-16"}>重命名:""} + {this.props.isAdmin?
    this.stopPro(event)} className={this.props.isAdminOrCreator()?"homepagePostSetting homepagePostSettingname":"homepagePostSetting homepagePostSettingbox"} style={{"right":"-2px","top":"44px","display":"block"}}> + 实训详情 + {this.props.isAdminOrCreator()?this.editname(discussMessage.name,discussMessage.homework_id,event)} className={"btn colorblue ml20 font-16"}>重命名:""} {/* 设置*/} 设置
    :""} @@ -408,6 +404,7 @@ class ShixunhomeWorkItem extends Component{
    + ) } } diff --git a/public/react/src/modules/courses/shixunHomework/shixunHomework.js b/public/react/src/modules/courses/shixunHomework/shixunHomework.js index d84453fc8..cdbac6760 100644 --- a/public/react/src/modules/courses/shixunHomework/shixunHomework.js +++ b/public/react/src/modules/courses/shixunHomework/shixunHomework.js @@ -1163,8 +1163,9 @@ class ShixunHomework extends Component{ isStudent={this.props.isStudent()} isNotMember={this.props.isNotMember()} isClassManagement={this.props.isClassManagement()} - checkBox={this.props.isAdmin()?:""} + checkBox={this.props.isAdmin()?:""} match={this.props.match} + index={index} coursedata={this.props.coursedata} coursupdata={()=>this.homeworkupdatalist(Coursename,page,order)} course_identity={datas.course_identity} From 9aa184eecc256b5ed94951962b459448567eccbc Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Fri, 23 Aug 2019 11:32:36 +0800 Subject: [PATCH 15/91] =?UTF-8?q?=E7=BB=84=E9=95=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../react/src/modules/courses/busyWork/CommonWorkList.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/react/src/modules/courses/busyWork/CommonWorkList.js b/public/react/src/modules/courses/busyWork/CommonWorkList.js index 528aa1d9e..f79507d4d 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkList.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkList.js @@ -97,7 +97,12 @@ function buildColumns(that, student_works, studentData) { }} title={text && text.length > 5 ? text : ''}> {/* */} - {text} + {record.is_leader ? +
    +
    {text}
    +
    组长
    +
    + : {text}}
    ), }] From 05d0f8779bacdce6f0cb4064b7063e6fdf34f2cd Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Fri, 23 Aug 2019 16:15:53 +0800 Subject: [PATCH 16/91] small --- .../courses/busyWork/CommonWorkAppraise.js | 32 +++++++++++++++---- .../courses/busyWork/CommonWorkList.js | 8 ++--- .../courses/busyWork/common/LeaderIcon.js | 17 ++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 public/react/src/modules/courses/busyWork/common/LeaderIcon.js diff --git a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js index 3a7e25fcd..c9d18a154 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js @@ -15,7 +15,7 @@ import WorkDetailPageHeader from './common/WorkDetailPageHeader' import CommonWorkAppraiseReply from './reply/CommonWorkAppraiseReply' import Example from './TestHooks' import CommonWorkAppraiseReviseAttachments from './CommonWorkAppraiseReviseAttachments' - +import LeaderIcon from './common/LeaderIcon' const { Option} = Select; const CheckboxGroup = Checkbox.Group; const confirm = Modal.confirm; @@ -88,6 +88,13 @@ class CommonWorkAppraise extends Component{ console.log(error) }) } + componentDidUpdate(prevProps, prevState) { + if (this.props.match.params.studentWorkId != prevProps.match.params.studentWorkId) { + this.getWork(); + this.getReviseAttachments() + } + } + componentDidMount() { this.getWork(); this.getReviseAttachments() @@ -251,12 +258,25 @@ class CommonWorkAppraise extends Component{ {is_evaluation != true && work_members && !!work_members.length &&
    - 其他组员 + 全部组员
    -
    - {work_members.map((item, index) => { - return item.user_name + ' ' - })} +
    +
    + 当前组员:{author_name} +
    +
    + 其他组员: + {work_members.map((item, index) => { + return + this.props.toWorkDetailPage(this.props.match.params, null, item.work_id)} + > + {item.user_name} + {item.is_leader && } + + })} +
    +
    } diff --git a/public/react/src/modules/courses/busyWork/CommonWorkList.js b/public/react/src/modules/courses/busyWork/CommonWorkList.js index f79507d4d..28e54fff3 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkList.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkList.js @@ -15,7 +15,7 @@ import WorkDetailPageHeader from './common/WorkDetailPageHeader' import PublishRightnow from './PublishRightnow' import ModulationModal from "../coursesPublic/ModulationModal"; import AccessoryModal from "../coursesPublic/AccessoryModal"; - +import LeaderIcon from './common/LeaderIcon' const { Option} = Select; const CheckboxGroup = Checkbox.Group; const confirm = Modal.confirm; @@ -98,9 +98,9 @@ function buildColumns(that, student_works, studentData) { {/* */} {record.is_leader ? -
    -
    {text}
    -
    组长
    +
    +
    {text}
    +
    : {text}}
    diff --git a/public/react/src/modules/courses/busyWork/common/LeaderIcon.js b/public/react/src/modules/courses/busyWork/common/LeaderIcon.js new file mode 100644 index 000000000..2718c7e8c --- /dev/null +++ b/public/react/src/modules/courses/busyWork/common/LeaderIcon.js @@ -0,0 +1,17 @@ +import React,{Component} from "React"; + +export default function LeaderIcon(props = {}) { + if (props.small) { +
    组长
    + } + return ( +
    组长
    + ) +} \ No newline at end of file From daa49f4277fd47fdfd73b65a22001dc2da42cfa5 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Fri, 23 Aug 2019 17:32:32 +0800 Subject: [PATCH 17/91] =?UTF-8?q?=E7=BB=84=E9=95=BF=20=E8=AF=84=E9=98=85?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/courses/busyWork/CommonWorkAppraise.js | 6 ++++-- .../src/modules/courses/busyWork/common/LeaderIcon.js | 10 ++++++---- .../src/modules/page/layers/ImageLayerOfCommentHOC.js | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js index c9d18a154..b3c9d1178 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js @@ -92,6 +92,7 @@ class CommonWorkAppraise extends Component{ if (this.props.match.params.studentWorkId != prevProps.match.params.studentWorkId) { this.getWork(); this.getReviseAttachments() + this.commonWorkAppraiseReply && this.commonWorkAppraiseReply.fetchAllComments() } } @@ -163,7 +164,7 @@ class CommonWorkAppraise extends Component{ attachments, homework_id, project_info, work_members, is_evaluation, description, update_user_name, update_time, commit_time, author_name, revise_attachments, revise_reason, atta_update_user, atta_update_time, atta_update_user_login, - Modalstype,Modalstopval,ModalCancel,ModalSave,loadtype + Modalstype,Modalstopval,ModalCancel,ModalSave,loadtype, is_leader_work } =this.state; let courseId=this.props.match.params.coursesId; @@ -262,7 +263,7 @@ class CommonWorkAppraise extends Component{
    - 当前组员:{author_name} + 当前组员:{author_name} {is_leader_work && }
    其他组员: @@ -286,6 +287,7 @@ class CommonWorkAppraise extends Component{ {/* task_type={datalist&&datalist.task_type} */} {this.commonWorkAppraiseReply = ref}} >
    diff --git a/public/react/src/modules/courses/busyWork/common/LeaderIcon.js b/public/react/src/modules/courses/busyWork/common/LeaderIcon.js index 2718c7e8c..526e165c5 100644 --- a/public/react/src/modules/courses/busyWork/common/LeaderIcon.js +++ b/public/react/src/modules/courses/busyWork/common/LeaderIcon.js @@ -1,8 +1,9 @@ import React,{Component} from "React"; export default function LeaderIcon(props = {}) { + let icon = null; if (props.small) { -
    组长
    + } else { + icon =
    组长
    + } - return ( -
    组长
    - ) + return icon } \ No newline at end of file diff --git a/public/react/src/modules/page/layers/ImageLayerOfCommentHOC.js b/public/react/src/modules/page/layers/ImageLayerOfCommentHOC.js index 50072c627..d19aa03ec 100644 --- a/public/react/src/modules/page/layers/ImageLayerOfCommentHOC.js +++ b/public/react/src/modules/page/layers/ImageLayerOfCommentHOC.js @@ -37,6 +37,8 @@ export function ImageLayerOfCommentHOC(options = {}) { } // jQuery._data( $('.newMain')[0], "events" ) componentDidMount() { + this.props.wrappedComponentRef && this.props.wrappedComponentRef(this.refs['wrappedComponentRef']) + // commentsDelegateParent #game_left_contents #tab_con_4 setTimeout(() => { $(options.parentSelector || ".commentsDelegateParent") @@ -60,7 +62,7 @@ export function ImageLayerOfCommentHOC(options = {}) { - + ) From aeb3b8b99e040bf241db25384921c573606f79c5 Mon Sep 17 00:00:00 2001 From: hjm <63528605@qq.com> Date: Fri, 23 Aug 2019 22:41:16 +0800 Subject: [PATCH 18/91] leader --- .../react/src/modules/courses/busyWork/CommonWorkAppraise.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js index b3c9d1178..1ca35d630 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkAppraise.js @@ -170,6 +170,7 @@ class CommonWorkAppraise extends Component{ let courseId=this.props.match.params.coursesId; let category_id=this.props.match.params.category_id; let studentWorkId=this.props.match.params.studentWorkId; + const isAdmin = this.props.isAdmin() return( { return + isAdmin ? this.props.toWorkDetailPage(this.props.match.params, null, item.work_id)} > {item.user_name} - {item.is_leader && } + : {item.user_name} + {item.is_leader && } })}
    From 33d5f95a84ce40dd9faf09487d7ad1cb5417044e Mon Sep 17 00:00:00 2001 From: caicai8 <1149225589@qq.com> Date: Mon, 26 Aug 2019 09:51:09 +0800 Subject: [PATCH 19/91] =?UTF-8?q?=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 6 +- Gemfile.lock | 29 + app/assets/images/logo.png | Bin 0 -> 2816 bytes .../javascripts/additional-methods.min.js | 4 + app/assets/javascripts/admin.js | 42 + .../admins/daily_school_statistics/index.js | 12 + .../admins/school_statistics/index.js | 135 + app/assets/javascripts/admins/sidebar.js | 16 + app/assets/javascripts/admins/users/edit.js | 156 + app/assets/javascripts/admins/users/index.js | 121 + app/assets/javascripts/application.js | 3 + .../javascripts/bootstrap-datepicker.js | 2039 ++++++ app/assets/javascripts/bootstrap-notify.js | 350 + .../javascripts/bootstrap-notify.min.js | 2 + .../i18n/bootstrap-datepicker.zh-CN.min.js | 1 + .../i18n/jquery-validate-message-zh.js | 33 + .../javascripts/i18n/select2-i18n.zh-CN.js | 3 + app/assets/javascripts/jquery.cookie.min.js | 2 + app/assets/javascripts/jquery.cxselect.js | 403 ++ app/assets/javascripts/jquery.cxselect.min.js | 11 + app/assets/javascripts/jquery.validate.js | 1650 +++++ app/assets/javascripts/jquery.validate.min.js | 4 + app/assets/javascripts/select2.js | 5891 +++++++++++++++++ app/assets/javascripts/select2.min.js | 2 + app/assets/stylesheets/admin.scss | 46 + app/assets/stylesheets/admins/common.scss | 104 + .../admins/daily-school-statistics.scss | 5 + .../stylesheets/admins/school-statistics.scss | 43 + app/assets/stylesheets/admins/sidebar.scss | 215 + app/assets/stylesheets/admins/users.scss | 36 + app/assets/stylesheets/application.scss | 1 + .../stylesheets/bootstrap-datepicker.scss | 477 ++ .../bootstrap-datepicker.standalone.scss | 510 ++ .../stylesheets/select2-bootstrap4.min.scss | 1 + app/assets/stylesheets/select2.min.scss | 1 + app/controllers/accounts_controller.rb | 2 +- app/controllers/admins/base_controller.rb | 38 + .../daily_school_statistics_controller.rb | 36 + .../admins/dashboards_controller.rb | 4 + .../admins/school_statistics_controller.rb | 49 + app/controllers/admins/users_controller.rb | 62 + app/controllers/application_controller.rb | 8 +- .../concerns/admins/error_rescue_handler.rb | 24 + .../concerns/admins/paginate_helper.rb | 24 + .../concerns/admins/render_helper.rb | 47 + app/controllers/concerns/git_helper.rb | 3 +- app/controllers/courses_controller.rb | 71 +- .../exercise_answers_controller.rb | 2 +- .../exercise_questions_controller.rb | 202 +- app/controllers/exercises_controller.rb | 3 +- .../graduation_tasks_controller.rb | 13 +- .../homework_commons_controller.rb | 29 +- app/controllers/messages_controller.rb | 3 +- app/controllers/polls_controller.rb | 3 +- app/controllers/shixuns_controller.rb | 2 +- app/controllers/subjects_controller.rb | 8 - app/helpers/application_helper.rb | 89 + app/helpers/courses_helper.rb | 2 +- app/helpers/exercises_helper.rb | 192 +- app/helpers/homework_commons_helper.rb | 16 +- app/helpers/subjects_helper.rb | 4 +- app/jobs/create_student_work_job.rb | 21 + app/libs/sidebar_util.rb | 11 + app/models/attachment.rb | 2 +- app/models/course.rb | 5 +- app/models/homework_common.rb | 8 +- app/models/inform.rb | 3 + app/models/old_message_detail.rb | 2 + app/models/school.rb | 3 + app/models/school_daily_active_user.rb | 3 + app/models/school_daily_report.rb | 5 + app/models/school_report.rb | 3 + app/models/subject.rb | 16 +- app/models/user.rb | 11 +- app/queries/admins/user_query.rb | 40 + .../admins/school_daily_statistic_service.rb | 123 + .../statistic_school_contrast_data_service.rb | 80 + .../statistic_school_data_grow_service.rb | 107 + app/services/admins/update_user_service.rb | 52 + app/services/homeworks_service.rb | 3 +- app/services/reward_grade_service.rb | 5 +- app/services/users/course_service.rb | 4 +- .../statistic_school_daily_report_task.rb | 69 + app/tasks/statistic_school_report_task.rb | 20 + app/templates/shared/main.css | 9 +- .../daily_school_statistics/export.xlsx.axlsx | 13 + .../daily_school_statistics/index.html.erb | 29 + .../daily_school_statistics/index.js.erb | 1 + .../shared/_list.html.erb | 47 + app/views/admins/dashboards/index.html.erb | 188 + .../admins/kaminari/_first_page.html.erb | 11 + app/views/admins/kaminari/_gap.html.erb | 13 + app/views/admins/kaminari/_last_page.html.erb | 11 + app/views/admins/kaminari/_next_page.html.erb | 11 + app/views/admins/kaminari/_page.html.erb | 19 + app/views/admins/kaminari/_paginator.html.erb | 27 + app/views/admins/kaminari/_prev_page.html.erb | 11 + .../admins/school_statistics/contrast.js.erb | 1 + .../admins/school_statistics/index.html.erb | 49 + .../admins/school_statistics/index.js.erb | 1 + .../shared/_contrast_list.html.erb | 72 + .../school_statistics/shared/_list.html.erb | 56 + app/views/admins/shared/404.html.erb | 6 + app/views/admins/shared/422.html.erb | 6 + app/views/admins/shared/500.html.erb | 6 + app/views/admins/shared/_alert.html.erb | 6 + app/views/admins/shared/_breadcrumb.html.erb | 13 + .../admins/shared/_flash_notice.html.erb | 20 + .../admins/shared/_no_data_for_table.html.erb | 1 + app/views/admins/shared/_paginate.html.erb | 6 + app/views/admins/shared/_sidebar.html.erb | 42 + .../admins/shared/after_render_js_hook.js.erb | 3 + app/views/admins/shared/delete.js.erb | 20 + app/views/admins/shared/error.js.erb | 7 + app/views/admins/users/edit.html.erb | 137 + app/views/admins/users/index.html.erb | 35 + app/views/admins/users/index.js.erb | 1 + .../users/shared/_reward_grade_modal.html.erb | 26 + .../admins/users/shared/_user_list.html.erb | 60 + app/views/admins/users/show.html.erb | 6 + app/views/courses/informs.json.jbuilder | 6 +- app/views/courses/settings.json.jbuilder | 6 +- .../exercises/_shixun_details.json.jbuilder | 5 +- .../homework_commons/subjects.json.jbuilder | 8 +- app/views/layouts/admin.html.erb | 532 +- .../student_works/shixun_work.json.jbuilder | 4 +- app/views/subjects/show.json.jbuilder | 23 +- config/admins/sidebar.yml | 1 + config/configuration.yml.example | 17 + config/initializers/aliyun_vod_init.rb | 28 +- config/initializers/assets.rb | 2 +- config/initializers/simple_form.rb | 179 + config/initializers/simple_form_bootstrap.rb | 439 ++ config/locales/kaminari/zh-CN.yml | 17 + config/locales/school_daily_reports/zh-CN.yml | 9 + config/locales/simple_form.en.yml | 31 + config/locales/tidings/zh-CN.yml | 8 +- config/routes.rb | 24 +- ...20190822022306_add_exercise_user_update.rb | 39 + ...0823015610_add_message_count_for_boards.rb | 9 + ...3_modify_course_introduction_for_boards.rb | 42 + ...modify_contents_for_old_message_details.rb | 13 + ...823090957_rechange_exercise_1936_scores.rb | 19 + ...0824032658_migrate_subject_shixun_count.rb | 9 + dump.rdb | Bin 2086 -> 2197 bytes lib/tasks/course_end.rake | 2 +- lib/tasks/excellent_course_exercise.rake | 27 +- lib/tasks/public_course.rake | 19 + lib/tasks/public_message.rake | 109 +- lib/templates/erb/scaffold/_form.html.erb | 15 + .../javascripts/educoder/province-data.json | 254 + public/react/public/css/edu-all.css | 20 +- public/react/public/css/iconfont.css | 28 +- public/react/scripts/build.js | 2 +- public/react/src/App.js | 7 +- public/react/src/AppConfig.js | 4 +- public/react/src/common/UrlTool.js | 8 +- public/react/src/common/educoder.js | 2 + .../react/src/context/TPIContextProvider.js | 2 +- public/react/src/index.css | 6 +- .../modules/courses/boards/BoardsListItem.js | 2 +- .../courses/busyWork/CommonWorkDetailIndex.js | 16 +- .../src/modules/courses/busyWork/NewWork.js | 9 +- .../courses/common/CNotificationHOC.js | 10 +- .../courses/coursesDetail/CoursesBanner.js | 55 +- .../courses/coursesDetail/CoursesLeftNav.js | 4 +- .../courses/coursesPublic/AccessoryModal.js | 26 +- .../courses/coursesPublic/Addcourses.js | 14 +- .../courses/coursesPublic/CoursesListType.js | 4 +- .../courses/coursesPublic/HomeworkModal.js | 1 + .../courses/coursesPublic/ModulationModal.js | 22 +- .../courses/coursesPublic/SelectResource.js | 1 + .../courses/coursesPublic/SendToFilesModal.js | 2 +- .../coursesPublic/modal/CheckCodeModal.js | 2 +- .../coursesPublic/modal/SendToCourseModal.js | 2 +- .../coursesPublic/modal/ShixunModal2.js | 4 +- .../react/src/modules/courses/css/Courses.css | 4 + .../react/src/modules/courses/css/members.css | 8 + .../modules/courses/elearning/Elearning.js | 120 +- .../modules/courses/exercise/ExerciseNew.js | 46 +- .../exercise/Testpapersettinghomepage.js | 6 +- .../courses/exercise/new/JudgeEditor.js | 6 +- .../courses/exercise/new/NullEditor.js | 361 - .../courses/exercise/new/SingleEditor.js | 320 - .../courses/exercise/question/shixunAnswer.js | 2 +- .../courses/gradinforms/Bullsubdirectory.js | 330 + .../modules/courses/gradinforms/Eduinforms.js | 337 +- .../courses/gradinforms/myysleduinforms.css | 72 +- .../tasks/GraduationTasksSubmitedit.js | 22 +- .../tasks/GraduationTasksSubmitnew.js | 6 +- .../tasks/GraduationTasksappraise.js | 4 +- .../graduation/tasks/GraduationTasksedit.js | 14 +- .../graduation/tasks/GraduationTasksnew.js | 11 +- .../tasks/GraduationTaskssetting.js | 5 +- .../tasks/GraduationTaskssettinglist.js | 5 +- .../tasks/GraduationTaskssettingquestions.js | 3 +- .../graduation/topics/GraduateTopicDetail.js | 2 +- .../graduation/topics/GraduateTopicNew.js | 15 +- .../courses/members/CourseGroupChooser.js | 1 + .../courses/members/modal/AddStudentModal.js | 2 +- .../courses/members/modal/AddTeacherModal.js | 2 +- .../src/modules/courses/new/Goldsubject.js | 41 +- .../modules/courses/poll/PollDetailIndex.js | 2 +- .../courses/poll/PollDetailTabThird.js | 4 +- .../src/modules/courses/poll/PollInfo.js | 4 +- .../react/src/modules/courses/poll/PollNew.js | 96 +- .../shixunHomework/Listofworksstudentone.js | 4 +- .../shixunHomework/ShixunHomeworkPage.js | 24 +- .../shixunHomework/ShixunWorkDetails.js | 4 +- .../shixunHomework/ShixunWorkReport.js | 5 +- .../Shixunworkdetails/ShixunWorkModal.js | 1 + .../TraineetraininginformationModal.js | 4 +- .../shixunHomework/Trainingjobsetting.js | 13 +- public/react/src/modules/home/shixunsHome.js | 2 +- .../modules/modals/Certifiedprofessional.js | 14 +- .../react/src/modules/modals/Jointheclass.js | 47 +- .../src/modules/modals/WordNumberTextarea.css | 42 + .../src/modules/modals/WordNumberTextarea.js | 23 + .../src/modules/page/MainContentContainer.js | 4 +- .../page/component/monaco/TPIMonaco.js | 2 +- .../paths/PathDetail/DetailCardsEditAndAdd.js | 2 +- .../src/modules/paths/PathDetail/DetailTop.js | 553 -- .../paths/PathDetail/PathDetailIndex.js | 76 +- .../src/modules/paths/PathDetail/sendPanel.js | 2 +- public/react/src/modules/paths/PathNew.js | 16 +- .../src/modules/paths/ShixunPathSearch.js | 4 +- public/react/src/modules/tpm/NewHeader.js | 24 +- public/react/src/modules/tpm/TPMIndexHOC.js | 10 +- .../src/modules/tpm/component/TPMright.css | 4 +- .../tpm/shixuns/shixunCss/shixunCard.css | 2 +- .../src/modules/user/usersInfo/InfosBank.js | 2 +- .../src/modules/user/usersInfo/banner_out.js | 2 +- .../usersInfo/video/AliyunUploaderManager.js | 4 +- .../user/usersInfo/video/EditVideoModal.js | 2 +- .../user/usersInfo/video/VideoUploadList.js | 2 +- public/react/src/search/SearchPage.js | 2 +- public/stylesheets/css/edu-common.css | 4 + public/stylesheets/educoder/edu-all.css | 31 +- public/stylesheets/educoder/edu-main.css | 6 +- spec/jobs/create_student_work_job_spec.rb | 5 + spec/models/old_message_detail_spec.rb | 5 + 241 files changed, 17310 insertions(+), 2493 deletions(-) create mode 100644 app/assets/images/logo.png create mode 100644 app/assets/javascripts/additional-methods.min.js create mode 100644 app/assets/javascripts/admin.js create mode 100644 app/assets/javascripts/admins/daily_school_statistics/index.js create mode 100644 app/assets/javascripts/admins/school_statistics/index.js create mode 100644 app/assets/javascripts/admins/sidebar.js create mode 100644 app/assets/javascripts/admins/users/edit.js create mode 100644 app/assets/javascripts/admins/users/index.js create mode 100644 app/assets/javascripts/bootstrap-datepicker.js create mode 100644 app/assets/javascripts/bootstrap-notify.js create mode 100644 app/assets/javascripts/bootstrap-notify.min.js create mode 100644 app/assets/javascripts/i18n/bootstrap-datepicker.zh-CN.min.js create mode 100644 app/assets/javascripts/i18n/jquery-validate-message-zh.js create mode 100644 app/assets/javascripts/i18n/select2-i18n.zh-CN.js create mode 100644 app/assets/javascripts/jquery.cookie.min.js create mode 100644 app/assets/javascripts/jquery.cxselect.js create mode 100644 app/assets/javascripts/jquery.cxselect.min.js create mode 100644 app/assets/javascripts/jquery.validate.js create mode 100644 app/assets/javascripts/jquery.validate.min.js create mode 100644 app/assets/javascripts/select2.js create mode 100644 app/assets/javascripts/select2.min.js create mode 100644 app/assets/stylesheets/admin.scss create mode 100644 app/assets/stylesheets/admins/common.scss create mode 100644 app/assets/stylesheets/admins/daily-school-statistics.scss create mode 100644 app/assets/stylesheets/admins/school-statistics.scss create mode 100644 app/assets/stylesheets/admins/sidebar.scss create mode 100644 app/assets/stylesheets/admins/users.scss create mode 100644 app/assets/stylesheets/application.scss create mode 100644 app/assets/stylesheets/bootstrap-datepicker.scss create mode 100644 app/assets/stylesheets/bootstrap-datepicker.standalone.scss create mode 100644 app/assets/stylesheets/select2-bootstrap4.min.scss create mode 100644 app/assets/stylesheets/select2.min.scss create mode 100644 app/controllers/admins/base_controller.rb create mode 100644 app/controllers/admins/daily_school_statistics_controller.rb create mode 100644 app/controllers/admins/dashboards_controller.rb create mode 100644 app/controllers/admins/school_statistics_controller.rb create mode 100644 app/controllers/admins/users_controller.rb create mode 100644 app/controllers/concerns/admins/error_rescue_handler.rb create mode 100644 app/controllers/concerns/admins/paginate_helper.rb create mode 100644 app/controllers/concerns/admins/render_helper.rb create mode 100644 app/jobs/create_student_work_job.rb create mode 100644 app/libs/sidebar_util.rb create mode 100644 app/models/old_message_detail.rb create mode 100644 app/models/school_daily_active_user.rb create mode 100644 app/models/school_daily_report.rb create mode 100644 app/models/school_report.rb create mode 100644 app/queries/admins/user_query.rb create mode 100644 app/services/admins/school_daily_statistic_service.rb create mode 100644 app/services/admins/statistic_school_contrast_data_service.rb create mode 100644 app/services/admins/statistic_school_data_grow_service.rb create mode 100644 app/services/admins/update_user_service.rb create mode 100644 app/tasks/statistic_school_daily_report_task.rb create mode 100644 app/tasks/statistic_school_report_task.rb create mode 100644 app/views/admins/daily_school_statistics/export.xlsx.axlsx create mode 100644 app/views/admins/daily_school_statistics/index.html.erb create mode 100644 app/views/admins/daily_school_statistics/index.js.erb create mode 100644 app/views/admins/daily_school_statistics/shared/_list.html.erb create mode 100644 app/views/admins/dashboards/index.html.erb create mode 100644 app/views/admins/kaminari/_first_page.html.erb create mode 100644 app/views/admins/kaminari/_gap.html.erb create mode 100644 app/views/admins/kaminari/_last_page.html.erb create mode 100644 app/views/admins/kaminari/_next_page.html.erb create mode 100644 app/views/admins/kaminari/_page.html.erb create mode 100644 app/views/admins/kaminari/_paginator.html.erb create mode 100644 app/views/admins/kaminari/_prev_page.html.erb create mode 100644 app/views/admins/school_statistics/contrast.js.erb create mode 100644 app/views/admins/school_statistics/index.html.erb create mode 100644 app/views/admins/school_statistics/index.js.erb create mode 100644 app/views/admins/school_statistics/shared/_contrast_list.html.erb create mode 100644 app/views/admins/school_statistics/shared/_list.html.erb create mode 100644 app/views/admins/shared/404.html.erb create mode 100644 app/views/admins/shared/422.html.erb create mode 100644 app/views/admins/shared/500.html.erb create mode 100644 app/views/admins/shared/_alert.html.erb create mode 100644 app/views/admins/shared/_breadcrumb.html.erb create mode 100644 app/views/admins/shared/_flash_notice.html.erb create mode 100644 app/views/admins/shared/_no_data_for_table.html.erb create mode 100644 app/views/admins/shared/_paginate.html.erb create mode 100644 app/views/admins/shared/_sidebar.html.erb create mode 100644 app/views/admins/shared/after_render_js_hook.js.erb create mode 100644 app/views/admins/shared/delete.js.erb create mode 100644 app/views/admins/shared/error.js.erb create mode 100644 app/views/admins/users/edit.html.erb create mode 100644 app/views/admins/users/index.html.erb create mode 100644 app/views/admins/users/index.js.erb create mode 100644 app/views/admins/users/shared/_reward_grade_modal.html.erb create mode 100644 app/views/admins/users/shared/_user_list.html.erb create mode 100644 app/views/admins/users/show.html.erb create mode 100644 config/admins/sidebar.yml create mode 100644 config/configuration.yml.example create mode 100644 config/initializers/simple_form.rb create mode 100644 config/initializers/simple_form_bootstrap.rb create mode 100644 config/locales/kaminari/zh-CN.yml create mode 100644 config/locales/school_daily_reports/zh-CN.yml create mode 100644 config/locales/simple_form.en.yml create mode 100644 db/migrate/20190822022306_add_exercise_user_update.rb create mode 100644 db/migrate/20190823015610_add_message_count_for_boards.rb create mode 100644 db/migrate/20190823024643_modify_course_introduction_for_boards.rb create mode 100644 db/migrate/20190823063747_modify_contents_for_old_message_details.rb create mode 100644 db/migrate/20190823090957_rechange_exercise_1936_scores.rb create mode 100644 db/migrate/20190824032658_migrate_subject_shixun_count.rb create mode 100644 lib/templates/erb/scaffold/_form.html.erb create mode 100644 public/javascripts/educoder/province-data.json delete mode 100644 public/react/src/modules/courses/exercise/new/NullEditor.js delete mode 100644 public/react/src/modules/courses/exercise/new/SingleEditor.js create mode 100644 public/react/src/modules/courses/gradinforms/Bullsubdirectory.js create mode 100644 public/react/src/modules/modals/WordNumberTextarea.css create mode 100644 public/react/src/modules/modals/WordNumberTextarea.js delete mode 100644 public/react/src/modules/paths/PathDetail/DetailTop.js create mode 100644 spec/jobs/create_student_work_job_spec.rb create mode 100644 spec/models/old_message_detail_spec.rb diff --git a/Gemfile b/Gemfile index a8bf18746..7cda93671 100644 --- a/Gemfile +++ b/Gemfile @@ -48,13 +48,11 @@ gem 'rqrcode_png' gem 'acts-as-taggable-on', '~> 6.0' group :development, :test do -#group :'development.rb.example', :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails', '~> 3.8' end group :development do -#group :'development.rb.example' do gem 'awesome_print' gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' @@ -78,6 +76,10 @@ gem 'faraday', '~> 0.15.4' # view gem 'active_decorator' +gem 'bootstrap', '~> 4.3.1' +gem 'jquery-rails' +gem 'simple_form' +gem 'font-awesome-sass', '4.7.0' # i18n gem 'rails-i18n', '~> 5.1' diff --git a/Gemfile.lock b/Gemfile.lock index 9c80af8a9..a33cd2ef6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,6 +59,8 @@ GEM archive-zip (0.11.0) io-like (~> 0.3.0) arel (9.0.0) + autoprefixer-rails (9.6.1) + execjs awesome_print (1.8.0) axlsx (3.0.0.pre) htmlentities (~> 4.3, >= 4.3.4) @@ -71,6 +73,10 @@ GEM bindex (0.5.0) bootsnap (1.3.1) msgpack (~> 1.0) + bootstrap (4.3.1) + autoprefixer-rails (>= 9.1.0) + popper_js (>= 1.14.3, < 2) + sassc-rails (>= 2.0.0) builder (3.2.3) bulk_insert (1.7.0) activerecord (>= 3.2.0) @@ -105,6 +111,8 @@ GEM faraday (0.15.4) multipart-post (>= 1.2, < 3) ffi (1.9.25) + font-awesome-sass (4.7.0) + sass (>= 3.2) globalid (0.4.1) activesupport (>= 4.2.0) grape-entity (0.7.1) @@ -120,6 +128,10 @@ GEM jbuilder (2.7.0) activesupport (>= 4.2.0) multi_json (>= 1.2) + jquery-rails (4.3.5) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) jwt (2.1.0) kaminari (1.1.1) activesupport (>= 4.1.0) @@ -165,6 +177,7 @@ GEM multi_xml (~> 0.5) rack (>= 1.2, < 3) pdfkit (0.8.4.1) + popper_js (1.14.5) public_suffix (3.0.2) puma (3.12.0) rack (2.0.5) @@ -266,6 +279,15 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + sassc (2.0.1) + ffi (~> 1.9) + rake + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt searchkick (3.1.3) activemodel (>= 4.2) elasticsearch (>= 5) @@ -278,6 +300,9 @@ GEM rack (>= 1.5.0) rack-protection (>= 1.5.0) redis (>= 3.3.5, < 5) + simple_form (4.1.0) + actionpack (>= 5.0) + activemodel (>= 5.0) simple_xlsx_reader (1.0.4) nokogiri rubyzip @@ -336,14 +361,17 @@ DEPENDENCIES axlsx (~> 3.0.0.pre) axlsx_rails (~> 0.5.2) bootsnap (>= 1.1.0) + bootstrap (~> 4.3.1) bulk_insert byebug capybara (>= 2.15, < 4.0) chromedriver-helper faraday (~> 0.15.4) + font-awesome-sass (= 4.7.0) gitlab! grape-entity (~> 0.7.1) jbuilder (~> 2.5) + jquery-rails kaminari (~> 1.1, >= 1.1.1) listen (>= 3.0.5, < 3.2) mysql2 (>= 0.4.4, < 0.6.0) @@ -366,6 +394,7 @@ DEPENDENCIES searchkick selenium-webdriver sidekiq + simple_form simple_xlsx_reader sinatra spreadsheet diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..436d23490404beedcf7cd776bbf8ed0282794ca4 GIT binary patch literal 2816 zcmV+b3;*h z9X5>^<7$31Vk&Jk9U(C~4J{H4GP+bHNYZSSwF!t4;u2sN`TEDZn|&i3x^Yub)y}T%6a@(eVlW!=TM(dw;`*4J!i!1EniHj_)1T zu3dXaTU*=5ej05UPM$n@EImE_rYk+3?*)Q`g9EFos>~z@$7?tR1}qlKV^?}?S2c`A z<24Np4b>B9v>~Xeso7;T8UwEMIIlVwjmChwy1E0G%W!T~RaHHIrN?^Jp|Y~_X_D;k z@9(@!b{p*P@Bf-4OG-)}y3*slYLJ_odppVRNV2%NIH#(r>Q9{5GtX64R`zg3MaAQy z+?$e;GIuO}C8;O*BVUr>4XI2y;fBO?t#K|!IBk&%(Hv9WXJ&z~Qo)9G~W?d=_Pb#-+wyzs(v@4WNQ zClwVH?n{f_VzInrGMR4c>+7SR{Z9e#mXEv=fWHH{mE>Fin*gK&m<6C`w8kVe0K7)> zaR9FZc-IG=;Q*!qxE(;PLRJC*i;w*t32^|v1pt(klsu|jB5%L__MQn)po-Sk)_SF` z!otFp{>mV^kmMU8eJDh=kn9%uY6U%rWFJW<$@@sU{nZCZt`Nzfq31r)jwIKTj28I; zlIKTA?lw!kPL2y9ceUt##v(N%DRn zYBNcz$cHHC_xdmq(JFEurhJSHl3Xw9+{XKa9>c4pe+s^(UNYUrt0WnslCIOJ_oj7| zJdccwj8wJJIGxV!qc zl2s%Zkvu^1Q3dTzF}`ey6^@-dcRr=2)n>DOsMF~tEUXnC9&T_r9PMhl%F4>J{gFj7 z%E!rTBk3a9MKVY+L8QuR6ZG5MQ$RA2q(t&*l8fmvo}IGswNkU-Tk0iSX}owqtR~y9s{rh*hTrPhv5}N4j?7VpB(4qZmw4|h@ z`C}B@4xnD-n*el){Hx;mHGm}miX=(u^+lj0N$&!>nt#lk?3i2COwNooeL7r=D@9wj*oKn8#g$d@k}U0q$L)n3`<%achgq!p9+Yws@Y^p#K80Yv7H?75UEryzDNJ3#wS0ayjut*KBp4g?USCN(@f{Ko*A zedwsqFc=J4&h9m16d4R455ORR!vNN+@RS1BJ&GKMJ45nT00#iLU+S+UN%~mi*8%vY zAN~71fX!M&Ns`WzGz0h@WaF5B}@o17j4qP&{`@lJq%w>4;Xk9}SSt3pFGHh=J_5yU_;O!L9)CpcntYeCX3^ z$WHnW0Pp+2A1R(w0ffPGZUaCl0KLc$fAO78=l?YJZD&$a((GXk#)!1Ev{=eFzhUU_ z@2~Pl7CmR4lk)Z1DC(_VqOFwOiP5maOa2V;B*`-(A42)e8!fqSnkG?Jx*^_Y-pyY9 zc*Uz9{}A~?#zmL99sTJDcL4!y91 zYVeRljc29DuXCsO)r{vQ88c>R2B8}o z8pe9I#sYYm^vR>KN|Ll$SwVSK-0Y*S761oDekuT`2H))fi~wpNA4yslmd5}r0`MMy z?U2vy?EtotTmyOA1%QVD92L)dNqTHmP*9NN^``V$ES5iXbaY%=DGrC@Tv=J!Mw7`j z)0a_BpFTaPu(0rFpMLsjtx~YfW;<3{S^13jtC^XZxxg2>ZqRopUAgjhtH>!Av{6@{ zqV6Xo|4Z_x8f}J!=oB<%c&wFV56K$^Z7NB(+xAECv?$uTU829zy=_KEM~6@j1G&*{ zFc<=YgM)Quvw5S_>Fid}zH4i1`=GM2a=Xc78s=hTW@avKXlOV@@>}H$_Vo0e-Lz@b zI-O1@nM|e#CC%Y**bN4QUtiYgF`c zY;3Gn8?0WhpK|Wpxn`1dI-OtD)z!UTR8(~T^y$;5MMXsg>-G96rKP1Cl)9dM_SubU zvPOc!c<01Kc&Aqcy-s1gFX;Kw;7;y1XR?N#Va5~n>jkZcm`tV{NM3YLsNHV=PfSeA%=GkhlTv`eV2~0M6KBQ5#Qac8 zNkT%xtoZo&DDV30?CfNd$u!g9aCEr!I2?{I!^6W>KgWzR5MQ=U7v)bnqun;&B&oXc zwTvfdzo8tToJ!A@=EhcyHsdj#_l3#xCxXRd8A3Fh&A&=XNr{Y#in`WcMc#;uiwlp5 zi3zKzso9~_QCwV{J9=x?flMWN%LplvWSHR9Qx5d1r4i3?%8~q0KaElT65-Z8YXrV& zLa*k_KSJ9mg^tJ}M5EEDYi@2N@ms}_BS$XV2jA}{qNAh3 zy1ToTf$F}3f`WUMJbJx8AT2Fzj+Tt{^z<0-y3*3p2bG4i*=)yygM-KIKTg66%a$!O z_4oIGqcGq=ZEfwY+}zxZyu7>>jg5_Ona$>3YqhVbso8P#=+VmT?CiS>3JO+Pt=5A| z!*z9a+2Z2jhMkF%m>?u4C(mhXYx{^A7|nQE?V6gJj?A4q*RMaZCZP@0TULF2{b#YU zu{ZDCyZ32~J!pIhE|=@#wr$%UNk~YztEHvo+<4_p!bdD$zI?IGX8ZRTSKfD4tM!eE z{1;{tV?ahmMnY9p)t`HMdhA=aY#H|D*^NDW_H6C$?mlI)SV~e-QYMXd8S#I!wQuj_ SnFiSa0000]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("abaRoutingNumber",function(a){var b=0,c=a.split(""),d=c.length;if(9!==d)return!1;for(var e=0;e9?"0":f,g="JABCDEFGHI".substr(f,1).toString(),i.match(/[ABEH]/)?k===f:i.match(/[KPQS]/)?k===g:k===f||k===g},"Please specify a valid CIF number."),a.validator.addMethod("cnhBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f,g,h=0,i=0;if(b=a.charAt(0),new Array(12).join(b)===a)return!1;for(e=0,f=9,g=0;e<9;++e,--f)h+=+(a.charAt(e)*f);for(c=h%11,c>=10&&(c=0,i=2),h=0,e=0,f=1,g=0;e<9;++e,++f)h+=+(a.charAt(e)*f);return d=h%11,d>=10?d=0:d-=i,String(c).concat(d)===a.substr(-2)},"Please specify a valid CNH number"),a.validator.addMethod("cnpjBR",function(a,b){"use strict";if(this.optional(b))return!0;if(a=a.replace(/[^\d]+/g,""),14!==a.length)return!1;if("00000000000000"===a||"11111111111111"===a||"22222222222222"===a||"33333333333333"===a||"44444444444444"===a||"55555555555555"===a||"66666666666666"===a||"77777777777777"===a||"88888888888888"===a||"99999999999999"===a)return!1;for(var c=a.length-2,d=a.substring(0,c),e=a.substring(c),f=0,g=c-7,h=c;h>=1;h--)f+=d.charAt(c-h)*g--,g<2&&(g=9);var i=f%11<2?0:11-f%11;if(i!==parseInt(e.charAt(0),10))return!1;c+=1,d=a.substring(0,c),f=0,g=c-7;for(var j=c;j>=1;j--)f+=d.charAt(c-j)*g--,g<2&&(g=9);return i=f%11<2?0:11-f%11,i===parseInt(e.charAt(1),10)},"Please specify a CNPJ value number"),a.validator.addMethod("cpfBR",function(a,b){"use strict";if(this.optional(b))return!0;if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var c,d,e,f,g=0;if(c=parseInt(a.substring(9,10),10),d=parseInt(a.substring(10,11),10),e=function(a,b){var c=10*a%11;return 10!==c&&11!==c||(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(f=1;f<=9;f++)g+=parseInt(a.substring(f-1,f),10)*(11-f);if(e(g,c)){for(g=0,f=1;f<=10;f++)g+=parseInt(a.substring(f-1,f),10)*(12-f);return e(g,d)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcard",function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},"Please enter a valid credit card number."),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&(/^(5[12345])/.test(a)||/^(2[234567])/.test(a))?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:!!(128&d)},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=!!e||c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("greaterThan",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-greaterThan-blur").length&&e.addClass("validate-greaterThan-blur").on("blur.validate-greaterThan",function(){a(c).valid()}),b>e.val()},"Please enter a greater value."),a.validator.addMethod("greaterThanEqual",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-greaterThanEqual-blur").length&&e.addClass("validate-greaterThanEqual-blur").on("blur.validate-greaterThanEqual",function(){a(c).valid()}),b>=e.val()},"Please enter a greater value."),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="",q=5;if(l.lengthd)},a.validator.format("Please select no more than {0} files.")),a.validator.addMethod("maxsize",function(b,c,d){if(this.optional(c))return!0;if("file"===a(c).attr("type")&&c.files&&c.files.length)for(var e=0;ed)return!1;return!0},a.validator.format("File size must not exceed {0} bytes each.")),a.validator.addMethod("maxsizetotal",function(b,c,d){if(this.optional(c))return!0;if("file"===a(c).attr("type")&&c.files&&c.files.length)for(var e=0,f=0;fd)return!1;return!0},a.validator.format("Total size of all files must not exceed {0} bytes.")),a.validator.addMethod("mobileNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid mobile number"),a.validator.addMethod("mobileRU",function(a,b){var c=a.replace(/\(|\)|\s+|-/g,"");return this.optional(b)||c.length>9&&/^((\+7|7|8)+([0-9]){10})$/.test(c)},"Please specify a valid mobile number"),a.validator.addMethod("mobileUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("netmask",function(a,b){return this.optional(b)||/^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test(a)},"Please enter a valid netmask."),a.validator.addMethod("nieES",function(a,b){"use strict";if(this.optional(b))return!0;var c,d=new RegExp(/^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi),e="TRWAGMYFPDXBNJZSQVHLCKET",f=a.substr(a.length-1).toUpperCase();return a=a.toString().toUpperCase(),!(a.length>10||a.length<9||!d.test(a))&&(a=a.replace(/^[X]/,"0").replace(/^[Y]/,"1").replace(/^[Z]/,"2"),c=9===a.length?a.substr(0,8):a.substr(0,9),e.charAt(parseInt(c,10)%23)===f)},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a,b){"use strict";return!!this.optional(b)||(a=a.toUpperCase(),!!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")&&(/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):!!/^[KLM]{1}/.test(a)&&a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,1)%23)))},"Please specify a valid NIF number."),a.validator.addMethod("nipPL",function(a){"use strict";if(a=a.replace(/[^0-9]/g,""),10!==a.length)return!1;for(var b=[6,5,7,2,3,4,5,6,7],c=0,d=0;d<9;d++)c+=b[d]*a[d];var e=c%11,f=10===e?0:e;return f===parseInt(a[9],10)},"Please specify a valid NIP number."),a.validator.addMethod("nisBR",function(a){var b,c,d,e,f,g=0;if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;for(c=parseInt(a.substring(10,11),10),b=parseInt(a.substring(0,10),10),e=2;e<12;e++)f=e,10===e&&(f=2),11===e&&(f=3),g+=b%10*f,b=parseInt(b/10,10);return d=g%11,d=d>1?11-d:0,c===d},"Please specify a valid NIS/PIS number"),a.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return!!this.optional(b)||("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonePL",function(a,b){a=a.replace(/\s+/g,"");var c=/^(?:(?:(?:\+|00)?48)|(?:\(\+?48\)))?(?:1[2-8]|2[2-69]|3[2-49]|4[1-68]|5[0-9]|6[0-35-9]|[7-8][1-9]|9[145])\d{7}$/;return this.optional(b)||c.test(a)},"Please specify a valid phone number"),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]\d{2}-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=!e&&"undefined"!=typeof c.caseSensitive&&c.caseSensitive,g=!e&&"undefined"!=typeof c.includeTerritories&&c.includeTerritories,h=!e&&"undefined"!=typeof c.includeMilitary&&c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;b<17;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c 0){ + setTimeout(function(){ + $('.admin-alert-container .alert').alert('close'); + }, 2000); + } +}); + +$(document).on("turbolinks:before-cache", function () { + $('[data-toggle="tooltip"]').tooltip('hide'); + $('[data-toggle="popover"]').popover('hide'); +}); + +$(function () { +}); \ No newline at end of file diff --git a/app/assets/javascripts/admins/daily_school_statistics/index.js b/app/assets/javascripts/admins/daily_school_statistics/index.js new file mode 100644 index 000000000..7c3aa2d4f --- /dev/null +++ b/app/assets/javascripts/admins/daily_school_statistics/index.js @@ -0,0 +1,12 @@ +$(document).on('turbolinks:load', function(){ + if ($('body.admins-daily-school-statistics-index-page').length > 0) { + $('.export-action').on('click', function(){ + var form = $(".daily-school-statistic-list-form .search-form") + var exportLink = $(this); + var keyword = form.find("input[name='keyword']").val(); + + var url = exportLink.data("url").split('?')[0] + "?keyword=" + keyword; + window.open(url); + }); + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/school_statistics/index.js b/app/assets/javascripts/admins/school_statistics/index.js new file mode 100644 index 000000000..f6c364e76 --- /dev/null +++ b/app/assets/javascripts/admins/school_statistics/index.js @@ -0,0 +1,135 @@ +$(document).on('turbolinks:load', function(){ + if ($('body.admins-school-statistics-index-page').length > 0) { + var searchForm = $(".school-statistic-list-form .search-form"); + var growFormUrl = searchForm.data('grow-form-url'); + var contrastFormUrl = searchForm.data('contrast-form-url'); + + var dataTypeInput = searchForm.find("input[name='data_type']"); + var keywordInput = searchForm.find("input[name='keyword']"); + var contrastBtn = searchForm.find(".contrast-btn"); + var growBtn = searchForm.find(".grow-btn"); + var contrastDateContainer = searchForm.find('.contrast-date-container'); + var growDateContainer = searchForm.find('.grow-date-container'); + + // 数据对比日期输入框 + var beginDateInput = searchForm.find("input[name='begin_date']"); + var endDateInput = searchForm.find("input[name='end_date']"); + var otherBeginDateInput = searchForm.find("input[name='other_begin_date']"); + var otherEndDateInput = searchForm.find("input[name='other_end_date']"); + + // 新增数据日期输入框 + var growBeginDateInput = searchForm.find("input[name='grow_begin_date']"); + var growEndDateInput = searchForm.find("input[name='grow_end_date']"); + + // 数据展示切换: 数据对比、新增数据 + searchForm.on('click', ".contrast-btn", function(){ + if(contrastBtn.hasClass("active")) { return } + changeDataType("contrast"); + submitForm(); + }); + searchForm.on('click', ".grow-btn", function(){ + if(growBtn.hasClass("active")) { return } + changeDataType("grow"); + submitForm(); + }); + + // 搜索按钮 + searchForm.on('click', ".search-btn", function(){ + console.log('submit'); + submitForm(); + }); + + $('.school-statistic-list-container').on('change', '.contrast-column-select', function() { + searchForm.find("input[name='contrast_column']").val($('.contrast-column-select').val()); + submitForm(); + }); + + var submitForm = function(){ + if(!validateFrom()) { return } + + var form = searchForm; + var url = dataTypeInput.val() == "contrast" ? contrastFormUrl : growFormUrl; + + $.ajax({ + url: url, + data: form.serialize(), + dataType: "script" + }) + }; + + var validateFrom = function(){ + if (dataTypeInput.val() != "contrast") { return true; } + + // 全部为空时,需要展示空数据页 + if (beginDateInput.val() == "" && endDateInput.val() == "" && + otherBeginDateInput.val() == "" && otherBeginDateInput.val() == "") { + return true; + } + + if (beginDateInput.val() != "" && endDateInput.val() != "" && + otherBeginDateInput.val() != "" && otherBeginDateInput.val() != "") { + return true; + } + + return false; + }; + + var changeDataType = function(dataType){ + if (dataTypeInput.val() == dataType) { return } + + if (dataType == "contrast") { + contrastBtn.addClass("active"); + growBtn.removeClass("active"); + dataTypeInput.val('contrast'); + growDateContainer.hide(); + contrastDateContainer.show(); + + clearGrowDateInput(); + } else { + contrastBtn.removeClass("active"); + growBtn.addClass("active"); + dataTypeInput.val('grow'); + growDateContainer.show(); + contrastDateContainer.hide(); + + clearContrastDateInput(); + } + }; + + var clearGrowDateInput = function() { + searchForm.find("input[name='grow_begin_date']").val(''); + searchForm.find("input[name='grow_end_date']").val(''); + searchForm.find("input[name='grow_date_input']").val(''); + }; + + var clearContrastDateInput = function(){ + searchForm.find("input[name='begin_date']").val(''); + searchForm.find("input[name='end_date']").val(''); + searchForm.find("input[name='other_begin_date']").val(''); + searchForm.find("input[name='other_end_date']").val(''); + searchForm.find("input[name='date_input']").val(''); + searchForm.find("input[name='other_date_input']").val(''); + }; + + var baseOptions = { + autoclose: true, + language: 'zh-CN', + format: 'yyyy-mm-dd', + startDate: '2017-04-01', + endDate: '-1d' + } + + var defineDateRangeSelect = function(element){ + var options = $.extend({inputs: $(element).find('.start-date, .end-date')}, baseOptions); + $(element).datepicker(options); + + $(element).find('.start-date').datepicker().on('changeDate', function(e){ + $(element).find('.end-date').datepicker('setStartDate', e.date); + }) + }; + + defineDateRangeSelect('.grow-date-input-daterange'); + defineDateRangeSelect('.date-input-daterange'); + defineDateRangeSelect('.other-date-input-daterange'); + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/sidebar.js b/app/assets/javascripts/admins/sidebar.js new file mode 100644 index 000000000..432aa700b --- /dev/null +++ b/app/assets/javascripts/admins/sidebar.js @@ -0,0 +1,16 @@ +$(document).on('turbolinks:load', function(){ + $('#sidebarCollapse').on('click', function () { + $(this).toggleClass('active'); + $('#sidebar').toggleClass('active'); + $.cookie('admin_sidebar_collapse', $(this).hasClass('active'), {path: '/admins'}); + }); + + var sidebarController = $('#sidebar').data('current-controller'); + if (sidebarController.length > 0) { + $('#sidebar a.active').removeClass('active'); + $('#sidebar ul.collapse.show').removeClass('show'); + var activeLi = $('#sidebar a[data-controller="' + sidebarController + '"]'); + activeLi.addClass('active'); + activeLi.parent().parent('ul.collapse').addClass('show'); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admins/users/edit.js b/app/assets/javascripts/admins/users/edit.js new file mode 100644 index 000000000..1d66466fb --- /dev/null +++ b/app/assets/javascripts/admins/users/edit.js @@ -0,0 +1,156 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-users-edit-page, body.admins-users-update-page').length > 0) { + var initDepartmentSelect = true; + + // ************** 学校选择 ************* + var matcherFunc = function(params, data){ + if ($.trim(params.term) === '') { + return data; + } + if (typeof data.text === 'undefined') { + return null; + } + + if (data.name && data.name.indexOf(params.term) > -1) { + var modifiedData = $.extend({}, data, true); + return modifiedData; + } + + // Return `null` if the term should not be displayed + return null; + } + + var defineSchoolSelect = function (schools) { + $('.school-select').select2({ + theme: 'bootstrap4', + placeholder: '查询学校/单位', + minimumInputLength: 1, + data: schools, + templateResult: function (item) { + if(!item.id || item.id === '') return item.text; + return item.name; + }, + templateSelection: function(item){ + if (item.id) { + $('#user_school_id').val(item.id); + getDepartmentsData(item.id, defineDepartmentSelect2); + } + return item.name || item.text; + }, + matcher: matcherFunc + }); + }; + + var defineDepartmentSelect2 = function(departments){ + departments.unshift({ id: '-1', name: '未选择' }); // 可不选 + + if (!initDepartmentSelect) { $('.department-select').empty(); } // 为了能够回填部门 + initDepartmentSelect = false; + + $('.department-select').select2({ + theme: 'bootstrap4', + placeholder: '查询学院/部门', + minimumInputLength: 0, + data: departments, + templateResult: function (item) { + if(!item.id || item.id === '') return item.text; + return item.name; + }, + templateSelection: function(item){ + if (item.id) { + $('#user_department_id').val(item.id); + } + return item.name || item.text; + }, + matcher: matcherFunc + }); + }; + + var getDepartmentsData = function(school_id, callback){ + $.ajax({ + url: '/api/schools/' + school_id + '/departments/for_option.json', + dataType: 'json', + type: 'GET', + success: function(data) { + callback(data.departments); + } + }) + } + + // 初始化学校选择器 + $.ajax({ + url: '/api/schools/for_option.json', + dataType: 'json', + type: 'GET', + success: function(data) { + defineSchoolSelect(data.schools); + } + }); + + // **************** 地区选择 **************** + $('.province-city-select').cxSelect({ + url: '/javascripts/educoder/province-data.json', + selects: ['province-select', 'city-select'] + }); + + // *********** 职业选择 ************ + var identityData = [ + { + "v": "teacher", + "n": "教师", + "s": [{"n": "教授", "v": "教授"},{"n": "副教授", "v": "副教授"},{"n": "讲师", "v": "讲师"},{"n": "助教", "v": "助教"}] + }, + { + "v": "student", + "n": "学生", + "s": [] + }, + { + "v": "professional", + "n": "专业人士", + "s": [{"n": "企业管理者", "v": "企业管理者"},{"n": "部门管理者", "v": "部门管理者"},{"n": "高级工程师", "v": "高级工程师"},{"n": "工程师", "v": "工程师"},{"n": "助理工程师", "v": "助理工程师"}] + } + ]; + $('.user-identity-select').cxSelect({ + data: identityData, + jsonValue: 'v', + selects: ['identity-select', 'technical-title-select'] + }); + $('.identity-select').on('change', function(){ + if($(this).val() === 'student'){ + $('.technical-title-select-wrapper').hide(); + $('.form-group.user_student_id').show(); + } else { + $('.technical-title-select-wrapper').show(); + $('.form-group.user_student_id').hide(); + } + }) + + + var $form = $('form.edit_user') + $form.validate({ + errorElement: 'span', + errorClass: 'danger text-danger', + rules: { + "user[password]": { + required: false, + minlength: 5 + }, + "user[password_confirmation]": { + required: false, + minlength: 5, + equalTo: "#user_password" + }, + }, + messages: { + "user[password_confirmation]": { + equalTo: "两次密码输入不一致" + } + } + }) + + $form.submit(function(e){ + if(!$form.valid()){ e.preventDefault(); } + }) + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admins/users/index.js b/app/assets/javascripts/admins/users/index.js new file mode 100644 index 000000000..f0ddf1e0f --- /dev/null +++ b/app/assets/javascripts/admins/users/index.js @@ -0,0 +1,121 @@ +$(document).on('turbolinks:load', function(){ + if ($('body.admins-users-index-page').length > 0) { + + var showSuccessNotify = function() { + $.notify({ + message: '操作成功' + },{ + type: 'success' + }); + } + + // lock user + $('.users-list-container').on('click', '.lock-action', function(){ + var $lockAction = $(this); + var $unlockAction = $lockAction.siblings('.unlock-action'); + + var userId = $lockAction.data('id'); + + $.ajax({ + url: '/admins/users/' + userId + '/lock', + method: 'POST', + dataType: 'json', + success: function() { + showSuccessNotify(); + $lockAction.hide(); + $unlockAction.show(); + } + }); + }); + + // unlock user + $('.users-list-container').on('click', '.unlock-action', function(){ + var $unlockAction = $(this); + var $lockAction = $unlockAction.siblings('.lock-action'); + + var userId = $unlockAction.data('id'); + + $.ajax({ + url: '/admins/users/' + userId + '/unlock', + method: 'POST', + dataType: 'json', + success: function() { + showSuccessNotify(); + $lockAction.show(); + $unlockAction.hide(); + } + }); + }); + + // active user + $('.users-list-container').on('click', '.active-action', function(){ + var $activeAction = $(this); + var $unlockAction = $activeAction.siblings('.unlock-action'); + var $lockAction = $activeAction.siblings('.lock-action'); + + var userId = $activeAction.data('id'); + + $.ajax({ + url: '/admins/users/' + userId + '/unlock', + method: 'POST', + dataType: 'json', + success: function() { + showSuccessNotify(); + $activeAction.hide(); + $lockAction.show(); + $unlockAction.hide(); + } + }); + }); + + // ***************** reward grade modal ***************** + var $rewardGradeModal = $('.admin-users-reward-grade-modal'); + var $form = $rewardGradeModal.find('form.admin-users-reward-grade-form'); + + $form.validate({ + errorElement: 'span', + errorClass: 'danger text-danger', + rules: { + grade: { + required: true, + digits: true + }, + } + }); + + // modal ready fire + $rewardGradeModal.on('show.bs.modal', function (event) { + var $link = $(event.relatedTarget); + + var userId = $link.data('id'); + $rewardGradeModal.find('.modal-body input[name="user_id"]').val(userId); + }); + // modal visited fire + $rewardGradeModal.on('shown.bs.modal', function(){ + $rewardGradeModal.find('.modal-body input[name="grade"]').focus(); + }); + + $('.admin-users-reward-grade-modal .submit-btn').on('click', function(){ + $form.find('.error').html(''); + + if ($form.valid()) { + var userId = $form.find('input[name="user_id"]').val(); + + $.ajax({ + method: 'POST', + dataType: 'json', + url: "/admins/users/" + userId + "/reward_grade", + data: $form.serialize(), + success: function(data) { + showSuccessNotify(); + $('.users-list-container .user-item-' + userId + ' td.grade-content').html(data.grade); + $rewardGradeModal.modal('hide'); + }, + error: function(res) { + $rewardGradeModal.find('.error').html(res.responseJSON.message); + } + }); + } + }); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a3fb4b534..07a3c90d4 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,7 @@ //= require rails-ujs //= require activestorage //= require turbolinks +//= require jquery3 +//= require popper +//= require bootstrap-sprockets //= require_tree . diff --git a/app/assets/javascripts/bootstrap-datepicker.js b/app/assets/javascripts/bootstrap-datepicker.js new file mode 100644 index 000000000..70d91c557 --- /dev/null +++ b/app/assets/javascripts/bootstrap-datepicker.js @@ -0,0 +1,2039 @@ +/*! + * Datepicker for Bootstrap v1.9.0 (https://github.com/uxsolutions/bootstrap-datepicker) + * + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +(function(factory){ + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof exports === 'object') { + factory(require('jquery')); + } else { + factory(jQuery); + } +}(function($, undefined){ + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function isUTCEquals(date1, date2) { + return ( + date1.getUTCFullYear() === date2.getUTCFullYear() && + date1.getUTCMonth() === date2.getUTCMonth() && + date1.getUTCDate() === date2.getUTCDate() + ); + } + function alias(method, deprecationMsg){ + return function(){ + if (deprecationMsg !== undefined) { + $.fn.datepicker.deprecated(deprecationMsg); + } + + return this[method].apply(this, arguments); + }; + } + function isValidDate(d) { + return d && !isNaN(d.getTime()); + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + // Use date arithmetic to allow dates with different times to match + if (0 <= this[i].valueOf() - val && this[i].valueOf() - val < 1000*60*60*24) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + $.data(element, 'datepicker', this); + + this._events = []; + this._secondaryEvents = []; + + this._process_options(options); + + this.dates = new DateArray(); + this.viewDate = this.o.defaultViewDate; + this.focusDate = null; + + this.element = $(element); + this.isInput = this.element.is('input'); + this.inputField = this.isInput ? this.element : this.element.find('input'); + this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .input-group-append, .input-group-prepend, .btn') : false; + if (this.component && this.component.length === 0) + this.component = false; + this.isInline = !this.component && this.element.is('div'); + + this.picker = $(DPGlobal.template); + + // Checking templates and inserting + if (this._check_template(this.o.templates.leftArrow)) { + this.picker.find('.prev').html(this.o.templates.leftArrow); + } + + if (this._check_template(this.o.templates.rightArrow)) { + this.picker.find('.next').html(this.o.templates.rightArrow); + } + + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + if (this.o.calendarWeeks) { + this.picker.find('.datepicker-days .datepicker-switch, thead .datepicker-title, tfoot .today, tfoot .clear') + .attr('colspan', function(i, val){ + return Number(val) + 1; + }); + } + + this._process_options({ + startDate: this._o.startDate, + endDate: this._o.endDate, + daysOfWeekDisabled: this.o.daysOfWeekDisabled, + daysOfWeekHighlighted: this.o.daysOfWeekHighlighted, + datesDisabled: this.o.datesDisabled + }); + + this._allow_update = false; + this.setViewMode(this.o.startView); + this._allow_update = true; + + this.fillDow(); + this.fillMonths(); + + this.update(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _resolveViewName: function(view){ + $.each(DPGlobal.viewModes, function(i, viewMode){ + if (view === i || $.inArray(view, viewMode.names) !== -1){ + view = i; + return false; + } + }); + + return view; + }, + + _resolveDaysOfWeek: function(daysOfWeek){ + if (!$.isArray(daysOfWeek)) + daysOfWeek = daysOfWeek.split(/[,\s]*/); + return $.map(daysOfWeek, Number); + }, + + _check_template: function(tmp){ + try { + // If empty + if (tmp === undefined || tmp === "") { + return false; + } + // If no html, everything ok + if ((tmp.match(/[<>]/g) || []).length <= 0) { + return true; + } + // Checking if html is fine + var jDom = $(tmp); + return jDom.length > 0; + } + catch (ex) { + return false; + } + }, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + // Retrieve view index from any aliases + o.startView = this._resolveViewName(o.startView); + o.minViewMode = this._resolveViewName(o.minViewMode); + o.maxViewMode = this._resolveViewName(o.maxViewMode); + + // Check view is between min and max + o.startView = Math.max(this.o.minViewMode, Math.min(this.o.maxViewMode, o.startView)); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = (o.weekStart + 6) % 7; + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language, o.assumeNearbyYear); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language, o.assumeNearbyYear); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = this._resolveDaysOfWeek(o.daysOfWeekDisabled||[]); + o.daysOfWeekHighlighted = this._resolveDaysOfWeek(o.daysOfWeekHighlighted||[]); + + o.datesDisabled = o.datesDisabled||[]; + if (!$.isArray(o.datesDisabled)) { + o.datesDisabled = o.datesDisabled.split(','); + } + o.datesDisabled = $.map(o.datesDisabled, function(d){ + return DPGlobal.parseDate(d, format, o.language, o.assumeNearbyYear); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return /^auto|left|right|top|bottom$/.test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return /^left|right$/.test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return /^top|bottom$/.test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + if (o.defaultViewDate instanceof Date || typeof o.defaultViewDate === 'string') { + o.defaultViewDate = DPGlobal.parseDate(o.defaultViewDate, format, o.language, o.assumeNearbyYear); + } else if (o.defaultViewDate) { + var year = o.defaultViewDate.year || new Date().getFullYear(); + var month = o.defaultViewDate.month || 0; + var day = o.defaultViewDate.day || 1; + o.defaultViewDate = UTCDate(year, month, day); + } else { + o.defaultViewDate = UTCToday(); + } + }, + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + var events = { + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this), + paste: $.proxy(this.paste, this) + }; + + if (this.o.showOnFocus === true) { + events.focus = $.proxy(this.show, this); + } + + if (this.isInput) { // single input + this._events = [ + [this.element, events] + ]; + } + // component: input + button + else if (this.component && this.inputField.length) { + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.inputField, events], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this), + keydown: $.proxy(this.keydown, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + if (this.o.immediateUpdates) { + // Trigger input updates immediately on changed year/month + this._events.push([this.element, { + 'changeYear changeMonth': $.proxy(function(e){ + this.update(e.date); + }, this) + }]); + } + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [this.picker, '.prev, .next', { + click: $.proxy(this.navArrowsClick, this) + }], + [this.picker, '.day:not(.disabled)', { + click: $.proxy(this.dayCellClick, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + 'mousedown touchstart': $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length || + this.isInline + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + viewMode: this.viewMode, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + if (this.inputField.is(':disabled') || (this.inputField.prop('readonly') && this.o.enableOnReadonly === false)) + return; + if (!this.isInline) + this.picker.appendTo(this.o.container); + this.place(); + this.picker.show(); + this._attachSecondaryEvents(); + this._trigger('show'); + if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) { + $(this.element).blur(); + } + return this; + }, + + hide: function(){ + if (this.isInline || !this.picker.is(':visible')) + return this; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.setViewMode(this.o.startView); + + if (this.o.forceParse && this.inputField.val()) + this.setValue(); + this._trigger('hide'); + return this; + }, + + destroy: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + return this; + }, + + paste: function(e){ + var dateString; + if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.types + && $.inArray('text/plain', e.originalEvent.clipboardData.types) !== -1) { + dateString = e.originalEvent.clipboardData.getData('text/plain'); + } else if (window.clipboardData) { + dateString = window.clipboardData.getData('Text'); + } else { + return; + } + this.setDate(dateString); + this.update(); + e.preventDefault(); + }, + + _utc_to_local: function(utc){ + if (!utc) { + return utc; + } + + var local = new Date(utc.getTime() + (utc.getTimezoneOffset() * 60000)); + + if (local.getTimezoneOffset() !== utc.getTimezoneOffset()) { + local = new Date(utc.getTime() + (local.getTimezoneOffset() * 60000)); + } + + return local; + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && UTCDate(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + var selected_date = this.dates.get(-1); + if (selected_date !== undefined) { + return new Date(selected_date); + } else { + return null; + } + }, + + clearDates: function(){ + this.inputField.val(''); + this.update(); + this._trigger('changeDate'); + + if (this.o.autoclose) { + this.hide(); + } + }, + + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.setDates.apply(this, $.map(args, this._utc_to_local)); + return this; + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + this.inputField.val(formatted); + return this; + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + getStartDate: function(){ + return this.o.startDate; + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + getEndDate: function(){ + return this.o.endDate; + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + return this; + }, + + setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){ + this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted}); + this.update(); + return this; + }, + + setDatesDisabled: function(datesDisabled){ + this._process_options({datesDisabled: datesDisabled}); + this.update(); + return this; + }, + + place: function(){ + if (this.isInline) + return this; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + container = $(this.o.container), + windowWidth = container.width(), + scrollTop = this.o.container === 'body' ? $(document).scrollTop() : container.scrollTop(), + appendOffset = container.offset(); + + var parentsZindex = [0]; + this.element.parents().each(function(){ + var itemZIndex = $(this).css('z-index'); + if (itemZIndex !== 'auto' && Number(itemZIndex) !== 0) parentsZindex.push(Number(itemZIndex)); + }); + var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left - appendOffset.left; + var top = offset.top - appendOffset.top; + + if (this.o.container !== 'body') { + top += scrollTop; + } + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + if (offset.left < 0) { + // component is outside the window on the left side. Move it into visible range + this.picker.addClass('datepicker-orient-left'); + left -= offset.left - visualPadding; + } else if (left + calendarWidth > windowWidth) { + // the calendar passes the widow right edge. Align it to component right side + this.picker.addClass('datepicker-orient-right'); + left += width - calendarWidth; + } else { + if (this.o.rtl) { + // Default to right + this.picker.addClass('datepicker-orient-right'); + } else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + } + } + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + top - calendarHeight; + yorient = top_overflow < 0 ? 'bottom' : 'top'; + } + + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + else + top += height; + + if (this.o.rtl) { + var right = windowWidth - (left + width); + this.picker.css({ + top: top, + right: right, + zIndex: zIndex + }); + } else { + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + } + return this; + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return this; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.inputField.val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language, this.o.assumeNearbyYear); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + !this.dateWithinRange(date) || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.o.updateViewDate) { + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + else + this.viewDate = this.o.defaultViewDate; + } + + if (fromArgs){ + // setting date by clicking + this.setValue(); + this.element.change(); + } + else if (this.dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates) && fromArgs) { + this._trigger('changeDate'); + this.element.change(); + } + } + if (!this.dates.length && oldDates.length) { + this._trigger('clearDate'); + this.element.change(); + } + + this.fill(); + return this; + }, + + fillDow: function(){ + if (this.o.showWeekDays) { + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + html += ' '; + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + } + }, + + fillMonths: function(){ + var localDate = this._utc_to_local(this.viewDate); + var html = ''; + var focused; + for (var i = 0; i < 12; i++){ + focused = localDate && localDate.getMonth() === i ? ' focused' : ''; + html += '' + dates[this.o.language].monthsShort[i] + ''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = UTCToday(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with UTC today, not local today + if (this.o.todayHighlight && isUTCEquals(date, today)) { + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (!this.dateWithinRange(date)){ + cls.push('disabled'); + } + if (this.dateIsDisabled(date)){ + cls.push('disabled', 'disabled-date'); + } + if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){ + cls.push('highlighted'); + } + + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + if (date.valueOf() === this.range[0]){ + cls.push('range-start'); + } + if (date.valueOf() === this.range[this.range.length-1]){ + cls.push('range-end'); + } + } + return cls; + }, + + _fill_yearsView: function(selector, cssClass, factor, year, startYear, endYear, beforeFn){ + var html = ''; + var step = factor / 10; + var view = this.picker.find(selector); + var startVal = Math.floor(year / factor) * factor; + var endVal = startVal + step * 9; + var focusedVal = Math.floor(this.viewDate.getFullYear() / step) * step; + var selected = $.map(this.dates, function(d){ + return Math.floor(d.getUTCFullYear() / step) * step; + }); + + var classes, tooltip, before; + for (var currVal = startVal - step; currVal <= endVal + step; currVal += step) { + classes = [cssClass]; + tooltip = null; + + if (currVal === startVal - step) { + classes.push('old'); + } else if (currVal === endVal + step) { + classes.push('new'); + } + if ($.inArray(currVal, selected) !== -1) { + classes.push('active'); + } + if (currVal < startYear || currVal > endYear) { + classes.push('disabled'); + } + if (currVal === focusedVal) { + classes.push('focused'); + } + + if (beforeFn !== $.noop) { + before = beforeFn(new Date(currVal, 0, 1)); + if (before === undefined) { + before = {}; + } else if (typeof before === 'boolean') { + before = {enabled: before}; + } else if (typeof before === 'string') { + before = {classes: before}; + } + if (before.enabled === false) { + classes.push('disabled'); + } + if (before.classes) { + classes = classes.concat(before.classes.split(/\s+/)); + } + if (before.tooltip) { + tooltip = before.tooltip; + } + } + + html += '' + currVal + ''; + } + + view.find('.datepicker-switch').text(startVal + '-' + endVal); + view.find('td').html(html); + }, + + fill: function(){ + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = dates[this.o.language].today || dates['en'].today || '', + cleartxt = dates[this.o.language].clear || dates['en'].clear || '', + titleFormat = dates[this.o.language].titleFormat || dates['en'].titleFormat, + todayDate = UTCToday(), + titleBtnVisible = (this.o.todayBtn === true || this.o.todayBtn === 'linked') && todayDate >= this.o.startDate && todayDate <= this.o.endDate && !this.weekOfDateIsDisabled(todayDate), + tooltip, + before; + if (isNaN(year) || isNaN(month)) + return; + this.picker.find('.datepicker-days .datepicker-switch') + .text(DPGlobal.formatDate(d, titleFormat, this.o.language)); + this.picker.find('tfoot .today') + .text(todaytxt) + .css('display', titleBtnVisible ? 'table-cell' : 'none'); + this.picker.find('tfoot .clear') + .text(cleartxt) + .css('display', this.o.clearBtn === true ? 'table-cell' : 'none'); + this.picker.find('thead .datepicker-title') + .text(this.o.title) + .css('display', typeof this.o.title === 'string' && this.o.title !== '' ? 'table-cell' : 'none'); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month, 0), + day = prevMonth.getUTCDate(); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + if (prevMonth.getUTCFullYear() < 100){ + nextMonth.setUTCFullYear(prevMonth.getUTCFullYear()); + } + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var weekDay, clsName; + while (prevMonth.valueOf() < nextMonth){ + weekDay = prevMonth.getUTCDay(); + if (weekDay === this.o.weekStart){ + html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - weekDay - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay()) % 7 * 864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + } + } + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + var content = prevMonth.getUTCDate(); + + if (this.o.beforeShowDay !== $.noop){ + before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof before === 'boolean') + before = {enabled: before}; + else if (typeof before === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; + if (before.content) + content = before.content; + } + + //Check if uniqueSort exists (supported by jquery >=1.12 and >=2.2) + //Fallback to unique function for older jquery versions + if ($.isFunction($.uniqueSort)) { + clsName = $.uniqueSort(clsName); + } else { + clsName = $.unique(clsName); + } + + html.push('' + content + ''); + tooltip = null; + if (weekDay === this.o.weekEnd){ + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate() + 1); + } + this.picker.find('.datepicker-days tbody').html(html.join('')); + + var monthsTitle = dates[this.o.language].monthsTitle || dates['en'].monthsTitle || 'Months'; + var months = this.picker.find('.datepicker-months') + .find('.datepicker-switch') + .text(this.o.maxViewMode < 2 ? monthsTitle : year) + .end() + .find('tbody span').removeClass('active'); + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ + months.addClass('disabled'); + } + if (year === startYear){ + months.slice(0, startMonth).addClass('disabled'); + } + if (year === endYear){ + months.slice(endMonth+1).addClass('disabled'); + } + + if (this.o.beforeShowMonth !== $.noop){ + var that = this; + $.each(months, function(i, month){ + var moDate = new Date(year, i, 1); + var before = that.o.beforeShowMonth(moDate); + if (before === undefined) + before = {}; + else if (typeof before === 'boolean') + before = {enabled: before}; + else if (typeof before === 'string') + before = {classes: before}; + if (before.enabled === false && !$(month).hasClass('disabled')) + $(month).addClass('disabled'); + if (before.classes) + $(month).addClass(before.classes); + if (before.tooltip) + $(month).prop('title', before.tooltip); + }); + } + + // Generating decade/years picker + this._fill_yearsView( + '.datepicker-years', + 'year', + 10, + year, + startYear, + endYear, + this.o.beforeShowYear + ); + + // Generating century/decades picker + this._fill_yearsView( + '.datepicker-decades', + 'decade', + 100, + year, + startYear, + endYear, + this.o.beforeShowDecade + ); + + // Generating millennium/centuries picker + this._fill_yearsView( + '.datepicker-centuries', + 'century', + 1000, + year, + startYear, + endYear, + this.o.beforeShowCentury + ); + }, + + updateNavArrows: function(){ + if (!this._allow_update) + return; + + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + prevIsDisabled, + nextIsDisabled, + factor = 1; + switch (this.viewMode){ + case 4: + factor *= 10; + /* falls through */ + case 3: + factor *= 10; + /* falls through */ + case 2: + factor *= 10; + /* falls through */ + case 1: + prevIsDisabled = Math.floor(year / factor) * factor <= startYear; + nextIsDisabled = Math.floor(year / factor) * factor + factor > endYear; + break; + case 0: + prevIsDisabled = year <= startYear && month <= startMonth; + nextIsDisabled = year >= endYear && month >= endMonth; + break; + } + + this.picker.find('.prev').toggleClass('disabled', prevIsDisabled); + this.picker.find('.next').toggleClass('disabled', nextIsDisabled); + }, + + click: function(e){ + e.preventDefault(); + e.stopPropagation(); + + var target, dir, day, year, month; + target = $(e.target); + + // Clicked on the switch + if (target.hasClass('datepicker-switch') && this.viewMode !== this.o.maxViewMode){ + this.setViewMode(this.viewMode + 1); + } + + // Clicked on today button + if (target.hasClass('today') && !target.hasClass('day')){ + this.setViewMode(0); + this._setDate(UTCToday(), this.o.todayBtn === 'linked' ? null : 'view'); + } + + // Clicked on clear button + if (target.hasClass('clear')){ + this.clearDates(); + } + + if (!target.hasClass('disabled')){ + // Clicked on a month, year, decade, century + if (target.hasClass('month') + || target.hasClass('year') + || target.hasClass('decade') + || target.hasClass('century')) { + this.viewDate.setUTCDate(1); + + day = 1; + if (this.viewMode === 1){ + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); + this.viewDate.setUTCMonth(month); + } else { + month = 0; + year = Number(target.text()); + this.viewDate.setUTCFullYear(year); + } + + this._trigger(DPGlobal.viewModes[this.viewMode - 1].e, this.viewDate); + + if (this.viewMode === this.o.minViewMode){ + this._setDate(UTCDate(year, month, day)); + } else { + this.setViewMode(this.viewMode - 1); + this.fill(); + } + } + } + + if (this.picker.is(':visible') && this._focused_from){ + this._focused_from.focus(); + } + delete this._focused_from; + }, + + dayCellClick: function(e){ + var $target = $(e.currentTarget); + var timestamp = $target.data('date'); + var date = new Date(timestamp); + + if (this.o.updateViewDate) { + if (date.getUTCFullYear() !== this.viewDate.getUTCFullYear()) { + this._trigger('changeYear', this.viewDate); + } + + if (date.getUTCMonth() !== this.viewDate.getUTCMonth()) { + this._trigger('changeMonth', this.viewDate); + } + } + this._setDate(date); + }, + + // Clicked on prev or next + navArrowsClick: function(e){ + var $target = $(e.currentTarget); + var dir = $target.hasClass('prev') ? -1 : 1; + if (this.viewMode !== 0){ + dir *= DPGlobal.viewModes[this.viewMode].navStep * 12; + } + this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger(DPGlobal.viewModes[this.viewMode].e, this.viewDate); + this.fill(); + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + + if (ix !== -1){ + if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){ + this.dates.remove(ix); + } + } else if (this.o.multidate === false) { + this.dates.clear(); + this.dates.push(date); + } + else { + this.dates.push(date); + } + + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if ((!which && this.o.updateViewDate) || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + if (!which || which !== 'view') { + this._trigger('changeDate'); + } + this.inputField.trigger('change'); + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } + }, + + moveDay: function(date, dir){ + var newDate = new Date(date); + newDate.setUTCDate(date.getUTCDate() + dir); + + return newDate; + }, + + moveWeek: function(date, dir){ + return this.moveDay(date, dir * 7); + }, + + moveMonth: function(date, dir){ + if (!isValidDate(date)) + return this.o.defaultViewDate; + if (!dir) + return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag === 1){ + test = dir === -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ + return new_date.getUTCMonth() === month; + } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ + return new_date.getUTCMonth() !== new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + new_month = (new_month + 12) % 12; + } + else { + // For magnitudes >1, move one month at a time... + for (var i=0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function(){ + return new_month !== new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()){ + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function(date, dir){ + return this.moveMonth(date, dir*12); + }, + + moveAvailableDate: function(date, dir, fn){ + do { + date = this[fn](date, dir); + + if (!this.dateWithinRange(date)) + return false; + + fn = 'moveDay'; + } + while (this.dateIsDisabled(date)); + + return date; + }, + + weekOfDateIsDisabled: function(date){ + return $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1; + }, + + dateIsDisabled: function(date){ + return ( + this.weekOfDateIsDisabled(date) || + $.grep(this.o.datesDisabled, function(d){ + return isUTCEquals(date, d); + }).length > 0 + ); + }, + + dateWithinRange: function(date){ + return date >= this.o.startDate && date <= this.o.endDate; + }, + + keydown: function(e){ + if (!this.picker.is(':visible')){ + if (e.keyCode === 40 || e.keyCode === 27) { // allow down to re-show picker + this.show(); + e.stopPropagation(); + } + return; + } + var dateChanged = false, + dir, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ + case 27: // escape + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); + e.preventDefault(); + e.stopPropagation(); + break; + case 37: // left + case 38: // up + case 39: // right + case 40: // down + if (!this.o.keyboardNavigation || this.o.daysOfWeekDisabled.length === 7) + break; + dir = e.keyCode === 37 || e.keyCode === 38 ? -1 : 1; + if (this.viewMode === 0) { + if (e.ctrlKey){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear'); + + if (newViewDate) + this._trigger('changeYear', this.viewDate); + } else if (e.shiftKey){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth'); + + if (newViewDate) + this._trigger('changeMonth', this.viewDate); + } else if (e.keyCode === 37 || e.keyCode === 39){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveDay'); + } else if (!this.weekOfDateIsDisabled(focusDate)){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveWeek'); + } + } else if (this.viewMode === 1) { + if (e.keyCode === 38 || e.keyCode === 40) { + dir = dir * 4; + } + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth'); + } else if (this.viewMode === 2) { + if (e.keyCode === 38 || e.keyCode === 40) { + dir = dir * 4; + } + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear'); + } + if (newViewDate){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 13: // enter + if (!this.o.forceParse) + break; + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + if (this.o.keyboardNavigation) { + this._toggle_multidate(focusDate); + dateChanged = true; + } + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + e.stopPropagation(); + if (this.o.autoclose) + this.hide(); + } + break; + case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + this.hide(); + break; + } + if (dateChanged){ + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); + this.inputField.trigger('change'); + } + }, + + setViewMode: function(viewMode){ + this.viewMode = viewMode; + this.picker + .children('div') + .hide() + .filter('.datepicker-' + DPGlobal.viewModes[this.viewMode].clsName) + .show(); + this.updateNavArrows(); + this._trigger('changeViewMode', new Date(this.viewDate)); + } + }; + + var DateRangePicker = function(element, options){ + $.data(element, 'datepicker', this); + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; + }); + delete options.inputs; + + this.keepEmptyValues = options.keepEmptyValues; + delete options.keepEmptyValues; + + datepickerPlugin.call($(this.inputs), options) + .on('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $.data(i, 'datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + clearDates: function(){ + $.each(this.pickers, function(i, p){ + p.clearDates(); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $.data(e.target, 'datepicker'); + + if (dp === undefined) { + return; + } + + var new_date = dp.getUTCDate(), + keep_empty_values = this.keepEmptyValues, + i = $.inArray(e.target, this.inputs), + j = i - 1, + k = i + 1, + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate() && (p === dp || !keep_empty_values)) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[j]){ + // Date being moved earlier/left + while (j >= 0 && new_date < this.dates[j]){ + this.pickers[j--].setUTCDate(new_date); + } + } else if (new_date > this.dates[k]){ + // Date being moved later/right + while (k < l && new_date > this.dates[k]){ + this.pickers[k++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + destroy: function(){ + $.map(this.pickers, function(p){ p.destroy(); }); + $(this.inputs).off('changeDate', this.dateUpdated); + delete this.element.data().datepicker; + }, + remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead') + }; + + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + var datepickerPlugin = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.hasClass('input-daterange') || opts.inputs){ + $.extend(opts, { + inputs: opts.inputs || $this.find('input').toArray() + }); + data = new DateRangePicker(this, opts); + } + else { + data = new Datepicker(this, opts); + } + $this.data('datepicker', data); + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + } + }); + + if ( + internal_return === undefined || + internal_return instanceof Datepicker || + internal_return instanceof DateRangePicker + ) + return this; + + if (this.length > 1) + throw new Error('Using only allowed for the collection of a single element (' + option + ' function)'); + else + return internal_return; + }; + $.fn.datepicker = datepickerPlugin; + + var defaults = $.fn.datepicker.defaults = { + assumeNearbyYear: false, + autoclose: false, + beforeShowDay: $.noop, + beforeShowMonth: $.noop, + beforeShowYear: $.noop, + beforeShowDecade: $.noop, + beforeShowCentury: $.noop, + calendarWeeks: false, + clearBtn: false, + toggleActive: false, + daysOfWeekDisabled: [], + daysOfWeekHighlighted: [], + datesDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keepEmptyValues: false, + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + maxViewMode: 4, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + updateViewDate: true, + weekStart: 0, + disableTouchKeyboard: false, + enableOnReadonly: true, + showOnFocus: true, + zIndexOffset: 10, + container: 'body', + immediateUpdates: false, + title: '', + templates: { + leftArrow: '«', + rightArrow: '»' + }, + showWeekDays: true + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear", + titleFormat: "MM yyyy" + } + }; + + var DPGlobal = { + viewModes: [ + { + names: ['days', 'month'], + clsName: 'days', + e: 'changeMonth' + }, + { + names: ['months', 'year'], + clsName: 'months', + e: 'changeYear', + navStep: 1 + }, + { + names: ['years', 'decade'], + clsName: 'years', + e: 'changeDecade', + navStep: 10 + }, + { + names: ['decades', 'century'], + clsName: 'decades', + e: 'changeCentury', + navStep: 100 + }, + { + names: ['centuries', 'millennium'], + clsName: 'centuries', + e: 'changeMillennium', + navStep: 1000 + } + ], + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\u5e74\u6708\u65e5\[-`{-~\t\n\r]+/g, + parseFormat: function(format){ + if (typeof format.toValue === 'function' && typeof format.toDisplay === 'function') + return format; + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language, assumeNearby){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + if (format.toValue) + return format.toValue(date, format, language); + var fn_map = { + d: 'moveDay', + m: 'moveMonth', + w: 'moveWeek', + y: 'moveYear' + }, + dateAliases = { + yesterday: '-1d', + today: '+0d', + tomorrow: '+1d' + }, + parts, part, dir, i, fn; + if (date in dateAliases){ + date = dateAliases[date]; + } + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/i.test(date)){ + parts = date.match(/([\-+]\d+)([dmwy])/gi); + date = new Date(); + for (i=0; i < parts.length; i++){ + part = parts[i].match(/([\-+]\d+)([dmwy])/i); + dir = Number(part[1]); + fn = fn_map[part[2].toLowerCase()]; + date = Datepicker.prototype[fn](date, dir); + } + return Datepicker.prototype._zero_utc_time(date); + } + + parts = date && date.match(this.nonpunctuation) || []; + + function applyNearbyYear(year, threshold){ + if (threshold === true) + threshold = 10; + + // if year is 2 digits or less, than the user most likely is trying to get a recent century + if (year < 100){ + year += 2000; + // if the new year is more than threshold years in advance, use last century + if (year > ((new Date()).getFullYear()+threshold)){ + year -= 100; + } + } + + return year; + } + + var parsed = {}, + setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], + setters_map = { + yyyy: function(d,v){ + return d.setUTCFullYear(assumeNearby ? applyNearbyYear(v, assumeNearby) : v); + }, + m: function(d,v){ + if (isNaN(d)) + return d; + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() !== v) + d.setUTCDate(d.getUTCDate()-1); + return d; + }, + d: function(d,v){ + return d.setUTCDate(v); + } + }, + val, filtered; + setters_map['yy'] = setters_map['yyyy']; + setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; + setters_map['dd'] = setters_map['d']; + date = UTCToday(); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length !== fparts.length){ + fparts = $(fparts).filter(function(i,p){ + return $.inArray(p, setters_order) !== -1; + }).toArray(); + } + // Process remainder + function match_part(){ + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m.toLowerCase() === p.toLowerCase(); + } + if (parts.length === fparts.length){ + var cnt; + for (i=0, cnt = fparts.length; i < cnt; i++){ + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)){ + switch (part){ + case 'MM': + filtered = $(dates[language].months).filter(match_part); + val = $.inArray(filtered[0], dates[language].months) + 1; + break; + case 'M': + filtered = $(dates[language].monthsShort).filter(match_part); + val = $.inArray(filtered[0], dates[language].monthsShort) + 1; + break; + } + } + parsed[part] = val; + } + var _date, s; + for (i=0; i < setters_order.length; i++){ + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])){ + _date = new Date(date); + setters_map[s](_date, parsed[s]); + if (!isNaN(_date)) + date = _date; + } + } + } + return date; + }, + formatDate: function(date, format, language){ + if (!date) + return ''; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + if (format.toDisplay) + return format.toDisplay(date, format, language); + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + date = []; + var seps = $.extend([], format.separators); + for (var i=0, cnt = format.parts.length; i <= cnt; i++){ + if (seps.length) + date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(''); + }, + headTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+defaults.templates.leftArrow+''+ + ''+ + ''+defaults.templates.rightArrow+''+ + ''+ + '', + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + }; + DPGlobal.template = '
    '+ + '
    '+ + ''+ + DPGlobal.headTemplate+ + ''+ + DPGlobal.footTemplate+ + '
    '+ + '
    '+ + '
    '+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
    '+ + '
    '+ + '
    '+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
    '+ + '
    '+ + '
    '+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
    '+ + '
    '+ + '
    '+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
    '+ + '
    '+ + '
    '; + + $.fn.datepicker.DPGlobal = DPGlobal; + + + /* DATEPICKER NO CONFLICT + * =================== */ + + $.fn.datepicker.noConflict = function(){ + $.fn.datepicker = old; + return this; + }; + + /* DATEPICKER VERSION + * =================== */ + $.fn.datepicker.version = '1.9.0'; + + $.fn.datepicker.deprecated = function(msg){ + var console = window.console; + if (console && console.warn) { + console.warn('DEPRECATED: ' + msg); + } + }; + + + /* DATEPICKER DATA-API + * ================== */ + + $(document).on( + 'focus.datepicker.data-api click.datepicker.data-api', + '[data-provide="datepicker"]', + function(e){ + var $this = $(this); + if ($this.data('datepicker')) + return; + e.preventDefault(); + // component click requires us to explicitly show it + datepickerPlugin.call($this, 'show'); + } + ); + $(function(){ + datepickerPlugin.call($('[data-provide="datepicker-inline"]')); + }); + +})); \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap-notify.js b/app/assets/javascripts/bootstrap-notify.js new file mode 100644 index 000000000..bef308b5f --- /dev/null +++ b/app/assets/javascripts/bootstrap-notify.js @@ -0,0 +1,350 @@ +/* +* Project: Bootstrap Notify = v3.1.3 +* Description: Turns standard Bootstrap alerts into "Growl-like" notifications. +* Author: Mouse0270 aka Robert McIntosh +* License: MIT License +* Website: https://github.com/mouse0270/bootstrap-growl +*/ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + // Create the defaults once + var defaults = { + element: 'body', + position: null, + type: "info", + allow_dismiss: true, + newest_on_top: false, + showProgressbar: false, + placement: { + from: "top", + align: "right" + }, + offset: 20, + spacing: 10, + z_index: 1031, + delay: 1000, + timer: 1000, + url_target: '_blank', + mouse_over: null, + animate: { + enter: 'animated fadeInDown', + exit: 'animated fadeOutUp' + }, + onShow: null, + onShown: null, + onClose: null, + onClosed: null, + icon_type: 'class', + template: '' + }; + + String.format = function() { + var str = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]); + } + return str; + }; + + function Notify ( element, content, options ) { + // Setup Content of Notify + var content = { + content: { + message: typeof content == 'object' ? content.message : content, + title: content.title ? content.title : '', + icon: content.icon ? content.icon : '', + url: content.url ? content.url : '#', + target: content.target ? content.target : '-' + } + }; + + options = $.extend(true, {}, content, options); + this.settings = $.extend(true, {}, defaults, options); + this._defaults = defaults; + if (this.settings.content.target == "-") { + this.settings.content.target = this.settings.url_target; + } + this.animations = { + start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart', + end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend' + } + + if (typeof this.settings.offset == 'number') { + this.settings.offset = { + x: this.settings.offset, + y: this.settings.offset + }; + } + + this.init(); + }; + + $.extend(Notify.prototype, { + init: function () { + var self = this; + + this.buildNotify(); + if (this.settings.content.icon) { + this.setIcon(); + } + if (this.settings.content.url != "#") { + this.styleURL(); + } + this.placement(); + this.bind(); + + this.notify = { + $ele: this.$ele, + update: function(command, update) { + var commands = {}; + if (typeof command == "string") { + commands[command] = update; + }else{ + commands = command; + } + for (var command in commands) { + switch (command) { + case "type": + this.$ele.removeClass('alert-' + self.settings.type); + this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type); + self.settings.type = commands[command]; + this.$ele.addClass('alert-' + commands[command]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[command]); + break; + case "icon": + var $icon = this.$ele.find('[data-notify="icon"]'); + if (self.settings.icon_type.toLowerCase() == 'class') { + $icon.removeClass(self.settings.content.icon).addClass(commands[command]); + }else{ + if (!$icon.is('img')) { + $icon.find('img'); + } + $icon.attr('src', commands[command]); + } + break; + case "progress": + var newDelay = self.settings.delay - (self.settings.delay * (commands[command] / 100)); + this.$ele.data('notify-delay', newDelay); + this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[command]).css('width', commands[command] + '%'); + break; + case "url": + this.$ele.find('[data-notify="url"]').attr('href', commands[command]); + break; + case "target": + this.$ele.find('[data-notify="url"]').attr('target', commands[command]); + break; + default: + this.$ele.find('[data-notify="' + command +'"]').html(commands[command]); + }; + } + var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y); + self.reposition(posX); + }, + close: function() { + self.close(); + } + }; + }, + buildNotify: function () { + var content = this.settings.content; + this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target)); + this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align); + if (!this.settings.allow_dismiss) { + this.$ele.find('[data-notify="dismiss"]').css('display', 'none'); + } + if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) { + this.$ele.find('[data-notify="progressbar"]').remove(); + } + }, + setIcon: function() { + if (this.settings.icon_type.toLowerCase() == 'class') { + this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon); + }else{ + if (this.$ele.find('[data-notify="icon"]').is('img')) { + this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon); + }else{ + this.$ele.find('[data-notify="icon"]').append('Notify Icon'); + } + } + }, + styleURL: function() { + this.$ele.find('[data-notify="url"]').css({ + backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)', + height: '100%', + left: '0px', + position: 'absolute', + top: '0px', + width: '100%', + zIndex: this.settings.z_index + 1 + }); + this.$ele.find('[data-notify="dismiss"]').css({ + position: 'absolute', + right: '10px', + top: '5px', + zIndex: this.settings.z_index + 2 + }); + }, + placement: function() { + var self = this, + offsetAmt = this.settings.offset.y, + css = { + display: 'inline-block', + margin: '0px auto', + position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'), + transition: 'all .5s ease-in-out', + zIndex: this.settings.z_index + }, + hasAnimation = false, + settings = this.settings; + + $('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() { + return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing)); + }); + if (this.settings.newest_on_top == true) { + offsetAmt = this.settings.offset.y; + } + css[this.settings.placement.from] = offsetAmt+'px'; + + switch (this.settings.placement.align) { + case "left": + case "right": + css[this.settings.placement.align] = this.settings.offset.x+'px'; + break; + case "center": + css.left = 0; + css.right = 0; + break; + } + this.$ele.css(css).addClass(this.settings.animate.enter); + $.each(Array('webkit', 'moz', 'o', 'ms', ''), function(index, prefix) { + self.$ele[0].style[prefix+'AnimationIterationCount'] = 1; + }); + + $(this.settings.element).append(this.$ele); + + if (this.settings.newest_on_top == true) { + offsetAmt = (parseInt(offsetAmt)+parseInt(this.settings.spacing)) + this.$ele.outerHeight(); + this.reposition(offsetAmt); + } + + if ($.isFunction(self.settings.onShow)) { + self.settings.onShow.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + } + }, 600); + }, + bind: function() { + var self = this; + + this.$ele.find('[data-notify="dismiss"]').on('click', function() { + self.close(); + }) + + this.$ele.mouseover(function(e) { + $(this).data('data-hover', "true"); + }).mouseout(function(e) { + $(this).data('data-hover', "false"); + }); + this.$ele.data('data-hover', "false"); + + if (this.settings.delay > 0) { + self.$ele.data('notify-delay', self.settings.delay); + var timer = setInterval(function() { + var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer; + if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over == "pause") || self.settings.mouse_over != "pause") { + var percent = ((self.settings.delay - delay) / self.settings.delay) * 100; + self.$ele.data('notify-delay', delay); + self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%'); + } + if (delay <= -(self.settings.timer)) { + clearInterval(timer); + self.close(); + } + }, self.settings.timer); + } + }, + close: function() { + var self = this, + $successors = null, + posX = parseInt(this.$ele.css(this.settings.placement.from)), + hasAnimation = false; + + this.$ele.data('closing', 'true').addClass(this.settings.animate.exit); + self.reposition(posX); + + if ($.isFunction(self.settings.onClose)) { + self.settings.onClose.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + $(this).remove(); + if ($.isFunction(self.settings.onClosed)) { + self.settings.onClosed.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + self.$ele.remove(); + if (self.settings.onClosed) { + self.settings.onClosed(self.$ele); + } + } + }, 600); + }, + reposition: function(posX) { + var self = this, + notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])', + $elements = this.$ele.nextAll(notifies); + if (this.settings.newest_on_top == true) { + $elements = this.$ele.prevAll(notifies); + } + $elements.each(function() { + $(this).css(self.settings.placement.from, posX); + posX = (parseInt(posX)+parseInt(self.settings.spacing)) + $(this).outerHeight(); + }); + } + }); + + $.notify = function ( content, options ) { + var plugin = new Notify( this, content, options ); + return plugin.notify; + }; + $.notifyDefaults = function( options ) { + defaults = $.extend(true, {}, defaults, options); + return defaults; + }; + $.notifyClose = function( command ) { + if (typeof command === "undefined" || command == "all") { + $('[data-notify]').find('[data-notify="dismiss"]').trigger('click'); + }else{ + $('[data-notify-position="'+command+'"]').find('[data-notify="dismiss"]').trigger('click'); + } + }; + +})); \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap-notify.min.js b/app/assets/javascripts/bootstrap-notify.min.js new file mode 100644 index 000000000..f5ad385a0 --- /dev/null +++ b/app/assets/javascripts/bootstrap-notify.min.js @@ -0,0 +1,2 @@ +/* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */ +!function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); \ No newline at end of file diff --git a/app/assets/javascripts/i18n/bootstrap-datepicker.zh-CN.min.js b/app/assets/javascripts/i18n/bootstrap-datepicker.zh-CN.min.js new file mode 100644 index 000000000..8e6920b0c --- /dev/null +++ b/app/assets/javascripts/i18n/bootstrap-datepicker.zh-CN.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["zh-CN"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["周日","周一","周二","周三","周四","周五","周六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今天",monthsTitle:"选择月份",clear:"清除",format:"yyyy-mm-dd",titleFormat:"yyyy年mm月",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/i18n/jquery-validate-message-zh.js b/app/assets/javascripts/i18n/jquery-validate-message-zh.js new file mode 100644 index 000000000..715b5d55d --- /dev/null +++ b/app/assets/javascripts/i18n/jquery-validate-message-zh.js @@ -0,0 +1,33 @@ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( ["jquery", "../jquery.validate"], factory ); + } else { + factory( jQuery ); + } +}(function( $ ) { + + /* + * Translated default messages for the jQuery validation plugin. + * Locale: ZH (Chinese, 中文 (Zhōngwén), 汉语, 漢語) + */ + $.extend($.validator.messages, { + required: "这是必填字段", + remote: "请修正此字段", + email: "请输入有效的电子邮件地址", + url: "请输入有效的网址", + date: "请输入有效的日期", + dateISO: "请输入有效的日期 (YYYY-MM-DD)", + number: "请输入有效的数字", + digits: "只能输入数字", + creditcard: "请输入有效的信用卡号码", + equalTo: "你的输入不相同", + extension: "请输入有效的后缀", + maxlength: $.validator.format("最多可以输入 {0} 个字符"), + minlength: $.validator.format("最少要输入 {0} 个字符"), + rangelength: $.validator.format("请输入长度在 {0} 到 {1} 之间的字符串"), + range: $.validator.format("请输入范围在 {0} 到 {1} 之间的数值"), + max: $.validator.format("请输入不大于 {0} 的数值"), + min: $.validator.format("请输入不小于 {0} 的数值") + }); + +})); \ No newline at end of file diff --git a/app/assets/javascripts/i18n/select2-i18n.zh-CN.js b/app/assets/javascripts/i18n/select2-i18n.zh-CN.js new file mode 100644 index 000000000..061f6df21 --- /dev/null +++ b/app/assets/javascripts/i18n/select2-i18n.zh-CN.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.8 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.cookie.min.js b/app/assets/javascripts/jquery.cookie.min.js new file mode 100644 index 000000000..c0f19d8a3 --- /dev/null +++ b/app/assets/javascripts/jquery.cookie.min.js @@ -0,0 +1,2 @@ +/*! jquery.cookie v1.4.1 | MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}}); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.cxselect.js b/app/assets/javascripts/jquery.cxselect.js new file mode 100644 index 000000000..aeb3c17dd --- /dev/null +++ b/app/assets/javascripts/jquery.cxselect.js @@ -0,0 +1,403 @@ +/*! + * jQuery cxSelect + * @name jquery.cxselect.js + * @version 1.4.1 + * @date 2016-11-02 + * @author ciaoca + * @email ciaoca@gmail.com + * @site https://github.com/ciaoca/cxSelect + * @license Released under the MIT license + */ +(function(factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else { + factory(window.jQuery || window.Zepto || window.$); + }; +}(function($) { + var cxSelect = function() { + var self = this; + var dom, settings, callback; + + // 分配参数 + for (var i = 0, l = arguments.length; i < l; i++) { + if (cxSelect.isJquery(arguments[i]) || cxSelect.isZepto(arguments[i])) { + dom = arguments[i]; + } else if (cxSelect.isElement(arguments[i])) { + dom = $(arguments[i]); + } else if (typeof arguments[i] === 'function') { + callback = arguments[i]; + } else if (typeof arguments[i] === 'object') { + settings = arguments[i]; + }; + }; + + var api = new cxSelect.init(dom, settings); + + if (typeof callback === 'function') { + callback(api); + }; + + return api; + }; + + cxSelect.isElement = function(o){ + if (o && (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') && o instanceof HTMLElement) { + return true; + } else { + return (o && o.nodeType && o.nodeType === 1) ? true : false; + }; + }; + + cxSelect.isJquery = function(o){ + return (o && o.length && (typeof jQuery === 'function' || typeof jQuery === 'object') && o instanceof jQuery) ? true : false; + }; + + cxSelect.isZepto = function(o){ + return (o && o.length && (typeof Zepto === 'function' || typeof Zepto === 'object') && Zepto.zepto.isZ(o)) ? true : false; + }; + + cxSelect.getIndex = function(n, required) { + return required ? n : n - 1; + }; + + cxSelect.getData = function(data, space) { + if (typeof space === 'string' && space.length) { + space = space.split('.'); + for (var i = 0, l = space.length; i < l; i++) { + data = data[space[i]]; + }; + }; + return data; + }; + + cxSelect.init = function(dom, settings) { + var self = this; + + if (!cxSelect.isJquery(dom) && !cxSelect.isZepto(dom)) {return}; + + var theSelect = { + dom: { + box: dom + } + }; + + self.attach = cxSelect.attach.bind(theSelect); + self.detach = cxSelect.detach.bind(theSelect); + self.setOptions = cxSelect.setOptions.bind(theSelect); + self.clear = cxSelect.clear.bind(theSelect); + + theSelect.changeEvent = function() { + cxSelect.selectChange.call(theSelect, this.className); + }; + + theSelect.settings = $.extend({}, $.cxSelect.defaults, settings, { + url: theSelect.dom.box.data('url'), + emptyStyle: theSelect.dom.box.data('emptyStyle'), + required: theSelect.dom.box.data('required'), + firstTitle: theSelect.dom.box.data('firstTitle'), + firstValue: theSelect.dom.box.data('firstValue'), + jsonSpace: theSelect.dom.box.data('jsonSpace'), + jsonName: theSelect.dom.box.data('jsonName'), + jsonValue: theSelect.dom.box.data('jsonValue'), + jsonSub: theSelect.dom.box.data('jsonSub') + }); + + var _dataSelects = theSelect.dom.box.data('selects'); + + if (typeof _dataSelects === 'string' && _dataSelects.length) { + theSelect.settings.selects = _dataSelects.split(','); + }; + + self.setOptions(); + self.attach(); + + // 使用独立接口获取数据 + if (!theSelect.settings.url && !theSelect.settings.data) { + cxSelect.start.apply(theSelect); + + // 设置自定义数据 + } else if ($.isArray(theSelect.settings.data)) { + cxSelect.start.call(theSelect, theSelect.settings.data); + + // 设置 URL,通过 Ajax 获取数据 + } else if (typeof theSelect.settings.url === 'string' && theSelect.settings.url.length) { + $.getJSON(theSelect.settings.url, function(json) { + cxSelect.start.call(theSelect, json); + }); + }; + }; + + // 设置参数 + cxSelect.setOptions = function(opts) { + var self = this; + + if (opts) { + $.extend(self.settings, opts); + }; + + // 初次或重设选择器组 + if (!$.isArray(self.selectArray) || !self.selectArray.length || (opts && opts.selects)) { + self.selectArray = []; + + if ($.isArray(self.settings.selects) && self.settings.selects.length) { + var _tempSelect; + + for (var i = 0, l = self.settings.selects.length; i < l; i++) { + _tempSelect = self.dom.box.find('select.' + self.settings.selects[i]); + + if (!_tempSelect || !_tempSelect.length) {break}; + + self.selectArray.push(_tempSelect); + }; + }; + }; + + if (opts) { + if (!$.isArray(opts.data) && typeof opts.url === 'string' && opts.url.length) { + $.getJSON(self.settings.url, function(json) { + cxSelect.start.call(self, json); + }); + + } else { + cxSelect.start.call(self, opts.data); + }; + }; + }; + + // 绑定 + cxSelect.attach = function() { + var self = this; + + if (!self.attachStatus) { + self.dom.box.on('change', 'select', self.changeEvent); + }; + + if (typeof self.attachStatus === 'boolean') { + cxSelect.start.call(self); + }; + + self.attachStatus = true; + }; + + // 移除绑定 + cxSelect.detach = function() { + var self = this; + self.dom.box.off('change', 'select', self.changeEvent); + self.attachStatus = false; + }; + + // 清空选项 + cxSelect.clear = function(index) { + var self = this; + var _style = { + display: '', + visibility: '' + }; + + index = isNaN(index) ? 0 : index; + + // 清空后面的 select + for (var i = index, l = self.selectArray.length; i < l; i++) { + self.selectArray[i].empty().prop('disabled', true); + + if (self.settings.emptyStyle === 'none') { + _style.display = 'none'; + } else if (self.settings.emptyStyle === 'hidden') { + _style.visibility = 'hidden'; + }; + + self.selectArray[i].css(_style); + }; + }; + + cxSelect.start = function(data) { + var self = this; + + if ($.isArray(data)) { + self.settings.data = cxSelect.getData(data, self.settings.jsonSpace); + }; + + if (!self.selectArray.length) {return}; + + // 保存默认值 + for (var i = 0, l = self.selectArray.length; i < l; i++) { + if (typeof self.selectArray[i].attr('data-value') !== 'string' && self.selectArray[i][0].options.length) { + self.selectArray[i].attr('data-value', self.selectArray[i].val()); + }; + }; + + if (self.settings.data || (typeof self.selectArray[0].data('url') === 'string' && self.selectArray[0].data('url').length)) { + cxSelect.getOptionData.call(self, 0); + } else { + self.selectArray[0].prop('disabled', false).css({ + 'display': '', + 'visibility': '' + }); + }; + }; + + // 获取选项数据 + cxSelect.getOptionData = function(index) { + var self = this; + + if (typeof index !== 'number' || isNaN(index) || index < 0 || index >= self.selectArray.length) {return}; + + var _indexPrev = index - 1; + var _select = self.selectArray[index]; + var _selectData; + var _valueIndex; + var _dataUrl = _select.data('url'); + var _jsonSpace = typeof _select.data('jsonSpace') === 'undefined' ? self.settings.jsonSpace : _select.data('jsonSpace'); + var _query = {}; + var _queryName; + var _selectName; + var _selectValue; + + cxSelect.clear.call(self, index); + + // 使用独立接口 + if (typeof _dataUrl === 'string' && _dataUrl.length) { + if (index > 0) { + for (var i = 0, j = 1; i < index; i++, j++) { + _queryName = self.selectArray[j].data('queryName'); + _selectName = self.selectArray[i].attr('name'); + _selectValue = self.selectArray[i].val(); + + if (typeof _queryName === 'string' && _queryName.length) { + _query[_queryName] = _selectValue; + } else if (typeof _selectName === 'string' && _selectName.length) { + _query[_selectName] = _selectValue; + }; + }; + }; + + $.getJSON(_dataUrl, _query, function(json) { + _selectData = cxSelect.getData(json, _jsonSpace); + + cxSelect.buildOption.call(self, index, _selectData); + }); + + // 使用整合数据 + } else if (self.settings.data && typeof self.settings.data === 'object') { + _selectData = self.settings.data; + + for (var i = 0; i < index; i++) { + _valueIndex = cxSelect.getIndex(self.selectArray[i][0].selectedIndex, typeof self.selectArray[i].data('required') === 'boolean' ? self.selectArray[i].data('required') : self.settings.required); + + if (typeof _selectData[_valueIndex] === 'object' && $.isArray(_selectData[_valueIndex][self.settings.jsonSub]) && _selectData[_valueIndex][self.settings.jsonSub].length) { + _selectData = _selectData[_valueIndex][self.settings.jsonSub]; + } else { + _selectData = null; + break; + }; + }; + + cxSelect.buildOption.call(self, index, _selectData); + }; + }; + + // 构建选项列表 + cxSelect.buildOption = function(index, data) { + var self = this; + + var _select = self.selectArray[index]; + var _required = typeof _select.data('required') === 'boolean' ? _select.data('required') : self.settings.required; + var _firstTitle = typeof _select.data('firstTitle') === 'undefined' ? self.settings.firstTitle : _select.data('firstTitle'); + var _firstValue = typeof _select.data('firstValue') === 'undefined' ? self.settings.firstValue : _select.data('firstValue'); + var _jsonName = typeof _select.data('jsonName') === 'undefined' ? self.settings.jsonName : _select.data('jsonName'); + var _jsonValue = typeof _select.data('jsonValue') === 'undefined' ? self.settings.jsonValue : _select.data('jsonValue'); + + if (!$.isArray(data)) {return}; + + var _html = !_required ? '' : ''; + + // 区分标题、值的数据 + if (typeof _jsonName === 'string' && _jsonName.length) { + // 无值字段时使用标题作为值 + if (typeof _jsonValue !== 'string' || !_jsonValue.length) { + _jsonValue = _jsonName; + }; + + for (var i = 0, l = data.length; i < l; i++) { + _html += ''; + }; + + // 数组即为值的数据 + } else { + for (var i = 0, l = data.length; i < l; i++) { + _html += ''; + }; + }; + + _select.html(_html).prop('disabled', false).css({ + 'display': '', + 'visibility': '' + }); + + // 初次加载设置默认值 + if (typeof _select.attr('data-value') === 'string') { + _select.val(String(_select.attr('data-value'))).removeAttr('data-value'); + + if (_select[0].selectedIndex < 0) { + _select[0].options[0].selected = true; + }; + }; + + if (_required || _select[0].selectedIndex > 0) { + _select.trigger('change'); + }; + + }; + + // 改变选择时的处理 + cxSelect.selectChange = function(name) { + var self = this; + + if (typeof name !== 'string' || !name.length) {return}; + + var index; + + name = name.replace(/\s+/g, ','); + name = ',' + name + ','; + + // 获取当前 select 位置 + for (var i = 0, l = self.selectArray.length; i < l; i++) { + if (name.indexOf(',' + self.settings.selects[i] + ',') > -1) { + index = i; + break; + }; + }; + + if (typeof index === 'number' && index > -1) { + index += 1; + cxSelect.getOptionData.call(self, index); + }; + }; + + $.cxSelect = function() { + return cxSelect.apply(this, arguments); + }; + + // 默认值 + $.cxSelect.defaults = { + selects: [], // 下拉选框组 + url: null, // 列表数据文件路径(URL)或数组数据 + data: null, // 自定义数据 + emptyStyle: null, // 无数据状态显示方式 + required: false, // 是否为必选 + firstTitle: '请选择', // 第一个选项的标题 + firstValue: '', // 第一个选项的值 + jsonSpace: '', // 数据命名空间 + jsonName: 'n', // 数据标题字段名称 + jsonValue: '', // 数据值字段名称 + jsonSub: 's' // 子集数据字段名称 + }; + + $.fn.cxSelect = function(settings, callback) { + this.each(function(i) { + $.cxSelect(this, settings, callback); + }); + return this; + }; +})); diff --git a/app/assets/javascripts/jquery.cxselect.min.js b/app/assets/javascripts/jquery.cxselect.min.js new file mode 100644 index 000000000..9aabba447 --- /dev/null +++ b/app/assets/javascripts/jquery.cxselect.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery cxSelect + * @name jquery.cxselect.js + * @version 1.4.1 + * @date 2016-11-02 + * @author ciaoca + * @email ciaoca@gmail.com + * @site https://github.com/ciaoca/cxSelect + * @license Released under the MIT license + */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(window.jQuery||window.Zepto||window.$)}(function(a){var b=function(){var d,e,f,g,h,i;for(g=0,h=arguments.length;h>g;g++)b.isJquery(arguments[g])||b.isZepto(arguments[g])?d=arguments[g]:b.isElement(arguments[g])?d=a(arguments[g]):"function"==typeof arguments[g]?f=arguments[g]:"object"==typeof arguments[g]&&(e=arguments[g]);return i=new b.init(d,e),"function"==typeof f&&f(i),i};b.isElement=function(a){return a&&("function"==typeof HTMLElement||"object"==typeof HTMLElement)&&a instanceof HTMLElement?!0:a&&a.nodeType&&1===a.nodeType?!0:!1},b.isJquery=function(a){return a&&a.length&&("function"==typeof jQuery||"object"==typeof jQuery)&&a instanceof jQuery?!0:!1},b.isZepto=function(a){return a&&a.length&&("function"==typeof Zepto||"object"==typeof Zepto)&&Zepto.zepto.isZ(a)?!0:!1},b.getIndex=function(a,b){return b?a:a-1},b.getData=function(a,b){if("string"==typeof b&&b.length){b=b.split(".");for(var c=0,d=b.length;d>c;c++)a=a[b[c]]}return a},b.init=function(c,d){var f,g,e=this;(b.isJquery(c)||b.isZepto(c))&&(f={dom:{box:c}},e.attach=b.attach.bind(f),e.detach=b.detach.bind(f),e.setOptions=b.setOptions.bind(f),e.clear=b.clear.bind(f),f.changeEvent=function(){b.selectChange.call(f,this.className)},f.settings=a.extend({},a.cxSelect.defaults,d,{url:f.dom.box.data("url"),emptyStyle:f.dom.box.data("emptyStyle"),required:f.dom.box.data("required"),firstTitle:f.dom.box.data("firstTitle"),firstValue:f.dom.box.data("firstValue"),jsonSpace:f.dom.box.data("jsonSpace"),jsonName:f.dom.box.data("jsonName"),jsonValue:f.dom.box.data("jsonValue"),jsonSub:f.dom.box.data("jsonSub")}),g=f.dom.box.data("selects"),"string"==typeof g&&g.length&&(f.settings.selects=g.split(",")),e.setOptions(),e.attach(),f.settings.url||f.settings.data?a.isArray(f.settings.data)?b.start.call(f,f.settings.data):"string"==typeof f.settings.url&&f.settings.url.length&&a.getJSON(f.settings.url,function(a){b.start.call(f,a)}):b.start.apply(f))},b.setOptions=function(c){var e,f,g,d=this;if(c&&a.extend(d.settings,c),(!a.isArray(d.selectArray)||!d.selectArray.length||c&&c.selects)&&(d.selectArray=[],a.isArray(d.settings.selects)&&d.settings.selects.length))for(f=0,g=d.settings.selects.length;g>f&&(e=d.dom.box.find("select."+d.settings.selects[f]),e&&e.length);f++)d.selectArray.push(e);c&&(!a.isArray(c.data)&&"string"==typeof c.url&&c.url.length?a.getJSON(d.settings.url,function(a){b.start.call(d,a)}):b.start.call(d,c.data))},b.attach=function(){var a=this;a.attachStatus||a.dom.box.on("change","select",a.changeEvent),"boolean"==typeof a.attachStatus&&b.start.call(a),a.attachStatus=!0},b.detach=function(){var a=this;a.dom.box.off("change","select",a.changeEvent),a.attachStatus=!1},b.clear=function(a){var d,e,b=this,c={display:"",visibility:""};for(a=isNaN(a)?0:a,d=a,e=b.selectArray.length;e>d;d++)b.selectArray[d].empty().prop("disabled",!0),"none"===b.settings.emptyStyle?c.display="none":"hidden"===b.settings.emptyStyle&&(c.visibility="hidden"),b.selectArray[d].css(c)},b.start=function(c){var e,f,d=this;if(a.isArray(c)&&(d.settings.data=b.getData(c,d.settings.jsonSpace)),d.selectArray.length){for(e=0,f=d.selectArray.length;f>e;e++)"string"!=typeof d.selectArray[e].attr("data-value")&&d.selectArray[e][0].options.length&&d.selectArray[e].attr("data-value",d.selectArray[e].val());d.settings.data||"string"==typeof d.selectArray[0].data("url")&&d.selectArray[0].data("url").length?b.getOptionData.call(d,0):d.selectArray[0].prop("disabled",!1).css({display:"",visibility:""})}},b.getOptionData=function(c){var f,g,h,i,j,k,l,m,n,o,p,d=this;if(!("number"!=typeof c||isNaN(c)||0>c||c>=d.selectArray.length))if(f=d.selectArray[c],i=f.data("url"),j="undefined"==typeof f.data("jsonSpace")?d.settings.jsonSpace:f.data("jsonSpace"),k={},b.clear.call(d,c),"string"==typeof i&&i.length){if(c>0)for(o=0,p=1;c>o;o++,p++)l=d.selectArray[p].data("queryName"),m=d.selectArray[o].attr("name"),n=d.selectArray[o].val(),"string"==typeof l&&l.length?k[l]=n:"string"==typeof m&&m.length&&(k[m]=n);a.getJSON(i,k,function(a){g=b.getData(a,j),b.buildOption.call(d,c,g)})}else if(d.settings.data&&"object"==typeof d.settings.data){for(g=d.settings.data,o=0;c>o;o++){if(h=b.getIndex(d.selectArray[o][0].selectedIndex,"boolean"==typeof d.selectArray[o].data("required")?d.selectArray[o].data("required"):d.settings.required),"object"!=typeof g[h]||!a.isArray(g[h][d.settings.jsonSub])||!g[h][d.settings.jsonSub].length){g=null;break}g=g[h][d.settings.jsonSub]}b.buildOption.call(d,c,g)}},b.buildOption=function(b,c){var k,l,m,d=this,e=d.selectArray[b],f="boolean"==typeof e.data("required")?e.data("required"):d.settings.required,g="undefined"==typeof e.data("firstTitle")?d.settings.firstTitle:e.data("firstTitle"),h="undefined"==typeof e.data("firstValue")?d.settings.firstValue:e.data("firstValue"),i="undefined"==typeof e.data("jsonName")?d.settings.jsonName:e.data("jsonName"),j="undefined"==typeof e.data("jsonValue")?d.settings.jsonValue:e.data("jsonValue");if(a.isArray(c)){if(k=f?"":'","string"==typeof i&&i.length)for("string"==typeof j&&j.length||(j=i),l=0,m=c.length;m>l;l++)k+='";else for(l=0,m=c.length;m>l;l++)k+='";e.html(k).prop("disabled",!1).css({display:"",visibility:""}),"string"==typeof e.attr("data-value")&&(e.val(String(e.attr("data-value"))).removeAttr("data-value"),e[0].selectedIndex<0&&(e[0].options[0].selected=!0)),(f||e[0].selectedIndex>0)&&e.trigger("change")}},b.selectChange=function(a){var d,e,f,c=this;if("string"==typeof a&&a.length){for(a=a.replace(/\s+/g,","),a=","+a+",",e=0,f=c.selectArray.length;f>e;e++)if(a.indexOf(","+c.settings.selects[e]+",")>-1){d=e;break}"number"==typeof d&&d>-1&&(d+=1,b.getOptionData.call(c,d))}},a.cxSelect=function(){return b.apply(this,arguments)},a.cxSelect.defaults={selects:[],url:null,data:null,emptyStyle:null,required:!1,firstTitle:"请选择",firstValue:"",jsonSpace:"",jsonName:"n",jsonValue:"",jsonSub:"s"},a.fn.cxSelect=function(b,c){return this.each(function(){a.cxSelect(this,b,c)}),this}}); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.validate.js b/app/assets/javascripts/jquery.validate.js new file mode 100644 index 000000000..d025319db --- /dev/null +++ b/app/assets/javascripts/jquery.validate.js @@ -0,0 +1,1650 @@ +/*! + * jQuery Validation Plugin v1.19.1 + * + * https://jqueryvalidation.org/ + * + * Copyright (c) 2019 Jörn Zaefferer + * Released under the MIT license + */ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( ["jquery"], factory ); + } else if (typeof module === "object" && module.exports) { + module.exports = factory( require( "jquery" ) ); + } else { + factory( jQuery ); + } +}(function( $ ) { + +$.extend( $.fn, { + + // https://jqueryvalidation.org/validate/ + validate: function( options ) { + + // If nothing is selected, return nothing; can't chain anyway + if ( !this.length ) { + if ( options && options.debug && window.console ) { + console.warn( "Nothing selected, can't validate, returning nothing." ); + } + return; + } + + // Check if a validator for this form was already created + var validator = $.data( this[ 0 ], "validator" ); + if ( validator ) { + return validator; + } + + // Add novalidate tag if HTML5. + this.attr( "novalidate", "novalidate" ); + + validator = new $.validator( options, this[ 0 ] ); + $.data( this[ 0 ], "validator", validator ); + + if ( validator.settings.onsubmit ) { + + this.on( "click.validate", ":submit", function( event ) { + + // Track the used submit button to properly handle scripted + // submits later. + validator.submitButton = event.currentTarget; + + // Allow suppressing validation by adding a cancel class to the submit button + if ( $( this ).hasClass( "cancel" ) ) { + validator.cancelSubmit = true; + } + + // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button + if ( $( this ).attr( "formnovalidate" ) !== undefined ) { + validator.cancelSubmit = true; + } + } ); + + // Validate the form on submit + this.on( "submit.validate", function( event ) { + if ( validator.settings.debug ) { + + // Prevent form submit to be able to see console output + event.preventDefault(); + } + + function handle() { + var hidden, result; + + // Insert a hidden input as a replacement for the missing submit button + // The hidden input is inserted in two cases: + // - A user defined a `submitHandler` + // - There was a pending request due to `remote` method and `stopRequest()` + // was called to submit the form in case it's valid + if ( validator.submitButton && ( validator.settings.submitHandler || validator.formSubmitted ) ) { + hidden = $( "" ) + .attr( "name", validator.submitButton.name ) + .val( $( validator.submitButton ).val() ) + .appendTo( validator.currentForm ); + } + + if ( validator.settings.submitHandler && !validator.settings.debug ) { + result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); + if ( hidden ) { + + // And clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + if ( result !== undefined ) { + return result; + } + return false; + } + return true; + } + + // Prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + } ); + } + + return validator; + }, + + // https://jqueryvalidation.org/valid/ + valid: function() { + var valid, validator, errorList; + + if ( $( this[ 0 ] ).is( "form" ) ) { + valid = this.validate().form(); + } else { + errorList = []; + valid = true; + validator = $( this[ 0 ].form ).validate(); + this.each( function() { + valid = validator.element( this ) && valid; + if ( !valid ) { + errorList = errorList.concat( validator.errorList ); + } + } ); + validator.errorList = errorList; + } + return valid; + }, + + // https://jqueryvalidation.org/rules/ + rules: function( command, argument ) { + var element = this[ 0 ], + isContentEditable = typeof this.attr( "contenteditable" ) !== "undefined" && this.attr( "contenteditable" ) !== "false", + settings, staticRules, existingRules, data, param, filtered; + + // If nothing is selected, return empty object; can't chain anyway + if ( element == null ) { + return; + } + + if ( !element.form && isContentEditable ) { + element.form = this.closest( "form" )[ 0 ]; + element.name = this.attr( "name" ); + } + + if ( element.form == null ) { + return; + } + + if ( command ) { + settings = $.data( element.form, "validator" ).settings; + staticRules = settings.rules; + existingRules = $.validator.staticRules( element ); + switch ( command ) { + case "add": + $.extend( existingRules, $.validator.normalizeRule( argument ) ); + + // Remove messages from rules, but allow them to be set separately + delete existingRules.messages; + staticRules[ element.name ] = existingRules; + if ( argument.messages ) { + settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages ); + } + break; + case "remove": + if ( !argument ) { + delete staticRules[ element.name ]; + return existingRules; + } + filtered = {}; + $.each( argument.split( /\s/ ), function( index, method ) { + filtered[ method ] = existingRules[ method ]; + delete existingRules[ method ]; + } ); + return filtered; + } + } + + data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.classRules( element ), + $.validator.attributeRules( element ), + $.validator.dataRules( element ), + $.validator.staticRules( element ) + ), element ); + + // Make sure required is at front + if ( data.required ) { + param = data.required; + delete data.required; + data = $.extend( { required: param }, data ); + } + + // Make sure remote is at back + if ( data.remote ) { + param = data.remote; + delete data.remote; + data = $.extend( data, { remote: param } ); + } + + return data; + } +} ); + +// Custom selectors +$.extend( $.expr.pseudos || $.expr[ ":" ], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support + + // https://jqueryvalidation.org/blank-selector/ + blank: function( a ) { + return !$.trim( "" + $( a ).val() ); + }, + + // https://jqueryvalidation.org/filled-selector/ + filled: function( a ) { + var val = $( a ).val(); + return val !== null && !!$.trim( "" + val ); + }, + + // https://jqueryvalidation.org/unchecked-selector/ + unchecked: function( a ) { + return !$( a ).prop( "checked" ); + } +} ); + +// Constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +// https://jqueryvalidation.org/jQuery.validator.format/ +$.validator.format = function( source, params ) { + if ( arguments.length === 1 ) { + return function() { + var args = $.makeArray( arguments ); + args.unshift( source ); + return $.validator.format.apply( this, args ); + }; + } + if ( params === undefined ) { + return source; + } + if ( arguments.length > 2 && params.constructor !== Array ) { + params = $.makeArray( arguments ).slice( 1 ); + } + if ( params.constructor !== Array ) { + params = [ params ]; + } + $.each( params, function( i, n ) { + source = source.replace( new RegExp( "\\{" + i + "\\}", "g" ), function() { + return n; + } ); + } ); + return source; +}; + +$.extend( $.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + pendingClass: "pending", + validClass: "valid", + errorElement: "label", + focusCleanup: false, + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: ":hidden", + ignoreTitle: false, + onfocusin: function( element ) { + this.lastActive = element; + + // Hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup ) { + if ( this.settings.unhighlight ) { + this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + } + this.hideThese( this.errorsFor( element ) ); + } + }, + onfocusout: function( element ) { + if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { + this.element( element ); + } + }, + onkeyup: function( element, event ) { + + // Avoid revalidate the field when pressing one of the following keys + // Shift => 16 + // Ctrl => 17 + // Alt => 18 + // Caps lock => 20 + // End => 35 + // Home => 36 + // Left arrow => 37 + // Up arrow => 38 + // Right arrow => 39 + // Down arrow => 40 + // Insert => 45 + // Num lock => 144 + // AltGr key => 225 + var excludedKeys = [ + 16, 17, 18, 20, 35, 36, 37, + 38, 39, 40, 45, 144, 225 + ]; + + if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { + return; + } else if ( element.name in this.submitted || element.name in this.invalid ) { + this.element( element ); + } + }, + onclick: function( element ) { + + // Click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) { + this.element( element ); + + // Or option elements, check parent select in that case + } else if ( element.parentNode.name in this.submitted ) { + this.element( element.parentNode ); + } + }, + highlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); + } else { + $( element ).addClass( errorClass ).removeClass( validClass ); + } + }, + unhighlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); + } else { + $( element ).removeClass( errorClass ).addClass( validClass ); + } + } + }, + + // https://jqueryvalidation.org/jQuery.validator.setDefaults/ + setDefaults: function( settings ) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + equalTo: "Please enter the same value again.", + maxlength: $.validator.format( "Please enter no more than {0} characters." ), + minlength: $.validator.format( "Please enter at least {0} characters." ), + rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ), + range: $.validator.format( "Please enter a value between {0} and {1}." ), + max: $.validator.format( "Please enter a value less than or equal to {0}." ), + min: $.validator.format( "Please enter a value greater than or equal to {0}." ), + step: $.validator.format( "Please enter a multiple of {0}." ) + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $( this.settings.errorLabelContainer ); + this.errorContext = this.labelContainer.length && this.labelContainer || $( this.currentForm ); + this.containers = $( this.settings.errorContainer ).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var currentForm = this.currentForm, + groups = ( this.groups = {} ), + rules; + $.each( this.settings.groups, function( key, value ) { + if ( typeof value === "string" ) { + value = value.split( /\s/ ); + } + $.each( value, function( index, name ) { + groups[ name ] = key; + } ); + } ); + rules = this.settings.rules; + $.each( rules, function( key, value ) { + rules[ key ] = $.validator.normalizeRule( value ); + } ); + + function delegate( event ) { + var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false"; + + // Set form expando on contenteditable + if ( !this.form && isContentEditable ) { + this.form = $( this ).closest( "form" )[ 0 ]; + this.name = $( this ).attr( "name" ); + } + + // Ignore the element if it belongs to another form. This will happen mainly + // when setting the `form` attribute of an input to the id of another form. + if ( currentForm !== this.form ) { + return; + } + + var validator = $.data( this.form, "validator" ), + eventType = "on" + event.type.replace( /^validate/, "" ), + settings = validator.settings; + if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { + settings[ eventType ].call( validator, this, event ); + } + } + + $( this.currentForm ) + .on( "focusin.validate focusout.validate keyup.validate", + ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + + "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + + "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + + "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate ) + + // Support: Chrome, oldIE + // "select" is provided as event.target when clicking a option + .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate ); + + if ( this.settings.invalidHandler ) { + $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); + } + }, + + // https://jqueryvalidation.org/Validator.form/ + form: function() { + this.checkForm(); + $.extend( this.submitted, this.errorMap ); + this.invalid = $.extend( {}, this.errorMap ); + if ( !this.valid() ) { + $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); + } + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) { + this.check( elements[ i ] ); + } + return this.valid(); + }, + + // https://jqueryvalidation.org/Validator.element/ + element: function( element ) { + var cleanElement = this.clean( element ), + checkElement = this.validationTargetFor( cleanElement ), + v = this, + result = true, + rs, group; + + if ( checkElement === undefined ) { + delete this.invalid[ cleanElement.name ]; + } else { + this.prepareElement( checkElement ); + this.currentElements = $( checkElement ); + + // If this element is grouped, then validate all group elements already + // containing a value + group = this.groups[ checkElement.name ]; + if ( group ) { + $.each( this.groups, function( name, testgroup ) { + if ( testgroup === group && name !== checkElement.name ) { + cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) ); + if ( cleanElement && cleanElement.name in v.invalid ) { + v.currentElements.push( cleanElement ); + result = v.check( cleanElement ) && result; + } + } + } ); + } + + rs = this.check( checkElement ) !== false; + result = result && rs; + if ( rs ) { + this.invalid[ checkElement.name ] = false; + } else { + this.invalid[ checkElement.name ] = true; + } + + if ( !this.numberOfInvalids() ) { + + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + + // Add aria-invalid status for screen readers + $( element ).attr( "aria-invalid", !rs ); + } + + return result; + }, + + // https://jqueryvalidation.org/Validator.showErrors/ + showErrors: function( errors ) { + if ( errors ) { + var validator = this; + + // Add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = $.map( this.errorMap, function( message, name ) { + return { + message: message, + element: validator.findByName( name )[ 0 ] + }; + } ); + + // Remove items from success list + this.successList = $.grep( this.successList, function( element ) { + return !( element.name in errors ); + } ); + } + if ( this.settings.showErrors ) { + this.settings.showErrors.call( this, this.errorMap, this.errorList ); + } else { + this.defaultShowErrors(); + } + }, + + // https://jqueryvalidation.org/Validator.resetForm/ + resetForm: function() { + if ( $.fn.resetForm ) { + $( this.currentForm ).resetForm(); + } + this.invalid = {}; + this.submitted = {}; + this.prepareForm(); + this.hideErrors(); + var elements = this.elements() + .removeData( "previousValue" ) + .removeAttr( "aria-invalid" ); + + this.resetElements( elements ); + }, + + resetElements: function( elements ) { + var i; + + if ( this.settings.unhighlight ) { + for ( i = 0; elements[ i ]; i++ ) { + this.settings.unhighlight.call( this, elements[ i ], + this.settings.errorClass, "" ); + this.findByName( elements[ i ].name ).removeClass( this.settings.validClass ); + } + } else { + elements + .removeClass( this.settings.errorClass ) + .removeClass( this.settings.validClass ); + } + }, + + numberOfInvalids: function() { + return this.objectLength( this.invalid ); + }, + + objectLength: function( obj ) { + /* jshint unused: false */ + var count = 0, + i; + for ( i in obj ) { + + // This check allows counting elements with empty error + // message as invalid elements + if ( obj[ i ] !== undefined && obj[ i ] !== null && obj[ i ] !== false ) { + count++; + } + } + return count; + }, + + hideErrors: function() { + this.hideThese( this.toHide ); + }, + + hideThese: function( errors ) { + errors.not( this.containers ).text( "" ); + this.addWrapper( errors ).hide(); + }, + + valid: function() { + return this.size() === 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if ( this.settings.focusInvalid ) { + try { + $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] ) + .filter( ":visible" ) + .trigger( "focus" ) + + // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger( "focusin" ); + } catch ( e ) { + + // Ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep( this.errorList, function( n ) { + return n.element.name === lastActive.name; + } ).length === 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // Select all valid inputs inside the form (no submit or reset buttons) + return $( this.currentForm ) + .find( "input, select, textarea, [contenteditable]" ) + .not( ":submit, :reset, :image, :disabled" ) + .not( this.settings.ignore ) + .filter( function() { + var name = this.name || $( this ).attr( "name" ); // For contenteditable + var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false"; + + if ( !name && validator.settings.debug && window.console ) { + console.error( "%o has no name assigned", this ); + } + + // Set form expando on contenteditable + if ( isContentEditable ) { + this.form = $( this ).closest( "form" )[ 0 ]; + this.name = name; + } + + // Ignore elements that belong to other/nested forms + if ( this.form !== validator.currentForm ) { + return false; + } + + // Select only the first element for each name, and only those with rules specified + if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) { + return false; + } + + rulesCache[ name ] = true; + return true; + } ); + }, + + clean: function( selector ) { + return $( selector )[ 0 ]; + }, + + errors: function() { + var errorClass = this.settings.errorClass.split( " " ).join( "." ); + return $( this.settings.errorElement + "." + errorClass, this.errorContext ); + }, + + resetInternals: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $( [] ); + this.toHide = $( [] ); + }, + + reset: function() { + this.resetInternals(); + this.currentElements = $( [] ); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor( element ); + }, + + elementValue: function( element ) { + var $element = $( element ), + type = element.type, + isContentEditable = typeof $element.attr( "contenteditable" ) !== "undefined" && $element.attr( "contenteditable" ) !== "false", + val, idx; + + if ( type === "radio" || type === "checkbox" ) { + return this.findByName( element.name ).filter( ":checked" ).val(); + } else if ( type === "number" && typeof element.validity !== "undefined" ) { + return element.validity.badInput ? "NaN" : $element.val(); + } + + if ( isContentEditable ) { + val = $element.text(); + } else { + val = $element.val(); + } + + if ( type === "file" ) { + + // Modern browser (chrome & safari) + if ( val.substr( 0, 12 ) === "C:\\fakepath\\" ) { + return val.substr( 12 ); + } + + // Legacy browsers + // Unix-based path + idx = val.lastIndexOf( "/" ); + if ( idx >= 0 ) { + return val.substr( idx + 1 ); + } + + // Windows-based path + idx = val.lastIndexOf( "\\" ); + if ( idx >= 0 ) { + return val.substr( idx + 1 ); + } + + // Just the file name + return val; + } + + if ( typeof val === "string" ) { + return val.replace( /\r/g, "" ); + } + return val; + }, + + check: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + + var rules = $( element ).rules(), + rulesCount = $.map( rules, function( n, i ) { + return i; + } ).length, + dependencyMismatch = false, + val = this.elementValue( element ), + result, method, rule, normalizer; + + // Prioritize the local normalizer defined for this element over the global one + // if the former exists, otherwise user the global one in case it exists. + if ( typeof rules.normalizer === "function" ) { + normalizer = rules.normalizer; + } else if ( typeof this.settings.normalizer === "function" ) { + normalizer = this.settings.normalizer; + } + + // If normalizer is defined, then call it to retreive the changed value instead + // of using the real one. + // Note that `this` in the normalizer is `element`. + if ( normalizer ) { + val = normalizer.call( element, val ); + + // Delete the normalizer from rules to avoid treating it as a pre-defined method. + delete rules.normalizer; + } + + for ( method in rules ) { + rule = { method: method, parameters: rules[ method ] }; + try { + result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); + + // If a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result === "dependency-mismatch" && rulesCount === 1 ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result === "pending" ) { + this.toHide = this.toHide.not( this.errorsFor( element ) ); + return; + } + + if ( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch ( e ) { + if ( this.settings.debug && window.console ) { + console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); + } + if ( e instanceof TypeError ) { + e.message += ". Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method."; + } + + throw e; + } + } + if ( dependencyMismatch ) { + return; + } + if ( this.objectLength( rules ) ) { + this.successList.push( element ); + } + return true; + }, + + // Return the custom message for the given element and validation method + // specified in the element's HTML5 data attribute + // return the generic message if present and no method specific message is present + customDataMessage: function( element, method ) { + return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + + method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); + }, + + // Return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[ name ]; + return m && ( m.constructor === String ? m : m[ method ] ); + }, + + // Return the first defined argument, allowing empty strings + findDefined: function() { + for ( var i = 0; i < arguments.length; i++ ) { + if ( arguments[ i ] !== undefined ) { + return arguments[ i ]; + } + } + return undefined; + }, + + // The second parameter 'rule' used to be a string, and extended to an object literal + // of the following form: + // rule = { + // method: "method name", + // parameters: "the given method parameters" + // } + // + // The old behavior still supported, kept to maintain backward compatibility with + // old code, and will be removed in the next major release. + defaultMessage: function( element, rule ) { + if ( typeof rule === "string" ) { + rule = { method: rule }; + } + + var message = this.findDefined( + this.customMessage( element.name, rule.method ), + this.customDataMessage( element, rule.method ), + + // 'title' is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[ rule.method ], + "Warning: No message defined for " + element.name + "" + ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message === "function" ) { + message = message.call( this, rule.parameters, element ); + } else if ( theregex.test( message ) ) { + message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); + } + + return message; + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule ); + + this.errorList.push( { + message: message, + element: element, + method: rule.method + } ); + + this.errorMap[ element.name ] = message; + this.submitted[ element.name ] = message; + }, + + addWrapper: function( toToggle ) { + if ( this.settings.wrapper ) { + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + } + return toToggle; + }, + + defaultShowErrors: function() { + var i, elements, error; + for ( i = 0; this.errorList[ i ]; i++ ) { + error = this.errorList[ i ]; + if ( this.settings.highlight ) { + this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + } + this.showLabel( error.element, error.message ); + } + if ( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if ( this.settings.success ) { + for ( i = 0; this.successList[ i ]; i++ ) { + this.showLabel( this.successList[ i ] ); + } + } + if ( this.settings.unhighlight ) { + for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { + this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not( this.invalidElements() ); + }, + + invalidElements: function() { + return $( this.errorList ).map( function() { + return this.element; + } ); + }, + + showLabel: function( element, message ) { + var place, group, errorID, v, + error = this.errorsFor( element ), + elementID = this.idOrName( element ), + describedBy = $( element ).attr( "aria-describedby" ); + + if ( error.length ) { + + // Refresh error/success class + error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); + + // Replace message on existing label + error.html( message ); + } else { + + // Create error element + error = $( "<" + this.settings.errorElement + ">" ) + .attr( "id", elementID + "-error" ) + .addClass( this.settings.errorClass ) + .html( message || "" ); + + // Maintain reference to the element to be placed into the DOM + place = error; + if ( this.settings.wrapper ) { + + // Make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); + } + if ( this.labelContainer.length ) { + this.labelContainer.append( place ); + } else if ( this.settings.errorPlacement ) { + this.settings.errorPlacement.call( this, place, $( element ) ); + } else { + place.insertAfter( element ); + } + + // Link error back to the element + if ( error.is( "label" ) ) { + + // If the error is a label, then associate using 'for' + error.attr( "for", elementID ); + + // If the element is not a child of an associated label, then it's necessary + // to explicitly apply aria-describedby + } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) { + errorID = error.attr( "id" ); + + // Respect existing non-error aria-describedby + if ( !describedBy ) { + describedBy = errorID; + } else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) { + + // Add to end of list if not already present + describedBy += " " + errorID; + } + $( element ).attr( "aria-describedby", describedBy ); + + // If this element is grouped, then assign to all elements in the same group + group = this.groups[ element.name ]; + if ( group ) { + v = this; + $.each( v.groups, function( name, testgroup ) { + if ( testgroup === group ) { + $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm ) + .attr( "aria-describedby", error.attr( "id" ) ); + } + } ); + } + } + } + if ( !message && this.settings.success ) { + error.text( "" ); + if ( typeof this.settings.success === "string" ) { + error.addClass( this.settings.success ); + } else { + this.settings.success( error, element ); + } + } + this.toShow = this.toShow.add( error ); + }, + + errorsFor: function( element ) { + var name = this.escapeCssMeta( this.idOrName( element ) ), + describer = $( element ).attr( "aria-describedby" ), + selector = "label[for='" + name + "'], label[for='" + name + "'] *"; + + // 'aria-describedby' should directly reference the error element + if ( describer ) { + selector = selector + ", #" + this.escapeCssMeta( describer ) + .replace( /\s+/g, ", #" ); + } + + return this + .errors() + .filter( selector ); + }, + + // See https://api.jquery.com/category/selectors/, for CSS + // meta-characters that should be escaped in order to be used with JQuery + // as a literal part of a name/id or any selector. + escapeCssMeta: function( string ) { + return string.replace( /([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g, "\\$1" ); + }, + + idOrName: function( element ) { + return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); + }, + + validationTargetFor: function( element ) { + + // If radio/checkbox, validate first element in group instead + if ( this.checkable( element ) ) { + element = this.findByName( element.name ); + } + + // Always apply ignore filter + return $( element ).not( this.settings.ignore )[ 0 ]; + }, + + checkable: function( element ) { + return ( /radio|checkbox/i ).test( element.type ); + }, + + findByName: function( name ) { + return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" ); + }, + + getLength: function( value, element ) { + switch ( element.nodeName.toLowerCase() ) { + case "select": + return $( "option:selected", element ).length; + case "input": + if ( this.checkable( element ) ) { + return this.findByName( element.name ).filter( ":checked" ).length; + } + } + return value.length; + }, + + depend: function( param, element ) { + return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true; + }, + + dependTypes: { + "boolean": function( param ) { + return param; + }, + "string": function( param, element ) { + return !!$( param, element.form ).length; + }, + "function": function( param, element ) { + return param( element ); + } + }, + + optional: function( element ) { + var val = this.elementValue( element ); + return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; + }, + + startRequest: function( element ) { + if ( !this.pending[ element.name ] ) { + this.pendingRequest++; + $( element ).addClass( this.settings.pendingClass ); + this.pending[ element.name ] = true; + } + }, + + stopRequest: function( element, valid ) { + this.pendingRequest--; + + // Sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + delete this.pending[ element.name ]; + $( element ).removeClass( this.settings.pendingClass ); + if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { + $( this.currentForm ).submit(); + + // Remove the hidden input that was used as a replacement for the + // missing submit button. The hidden input is added by `handle()` + // to ensure that the value of the used submit button is passed on + // for scripted submits triggered by this method + if ( this.submitButton ) { + $( "input:hidden[name='" + this.submitButton.name + "']", this.currentForm ).remove(); + } + + this.formSubmitted = false; + } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) { + $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); + this.formSubmitted = false; + } + }, + + previousValue: function( element, method ) { + method = typeof method === "string" && method || "remote"; + + return $.data( element, "previousValue" ) || $.data( element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, { method: method } ) + } ); + }, + + // Cleans up all forms and elements, removes validator-specific events + destroy: function() { + this.resetForm(); + + $( this.currentForm ) + .off( ".validate" ) + .removeData( "validator" ) + .find( ".validate-equalTo-blur" ) + .off( ".validate-equalTo" ) + .removeClass( "validate-equalTo-blur" ) + .find( ".validate-lessThan-blur" ) + .off( ".validate-lessThan" ) + .removeClass( "validate-lessThan-blur" ) + .find( ".validate-lessThanEqual-blur" ) + .off( ".validate-lessThanEqual" ) + .removeClass( "validate-lessThanEqual-blur" ) + .find( ".validate-greaterThanEqual-blur" ) + .off( ".validate-greaterThanEqual" ) + .removeClass( "validate-greaterThanEqual-blur" ) + .find( ".validate-greaterThan-blur" ) + .off( ".validate-greaterThan" ) + .removeClass( "validate-greaterThan-blur" ); + } + + }, + + classRuleSettings: { + required: { required: true }, + email: { email: true }, + url: { url: true }, + date: { date: true }, + dateISO: { dateISO: true }, + number: { number: true }, + digits: { digits: true }, + creditcard: { creditcard: true } + }, + + addClassRules: function( className, rules ) { + if ( className.constructor === String ) { + this.classRuleSettings[ className ] = rules; + } else { + $.extend( this.classRuleSettings, className ); + } + }, + + classRules: function( element ) { + var rules = {}, + classes = $( element ).attr( "class" ); + + if ( classes ) { + $.each( classes.split( " " ), function() { + if ( this in $.validator.classRuleSettings ) { + $.extend( rules, $.validator.classRuleSettings[ this ] ); + } + } ); + } + return rules; + }, + + normalizeAttributeRule: function( rules, type, method, value ) { + + // Convert the value to a number for number inputs, and for text for backwards compability + // allows type="date" and others to be compared as strings + if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { + value = Number( value ); + + // Support Opera Mini, which returns NaN for undefined minlength + if ( isNaN( value ) ) { + value = undefined; + } + } + + if ( value || value === 0 ) { + rules[ method ] = value; + } else if ( type === method && type !== "range" ) { + + // Exception: the jquery validate 'range' method + // does not test for the html5 'range' type + rules[ method ] = true; + } + }, + + attributeRules: function( element ) { + var rules = {}, + $element = $( element ), + type = element.getAttribute( "type" ), + method, value; + + for ( method in $.validator.methods ) { + + // Support for in both html5 and older browsers + if ( method === "required" ) { + value = element.getAttribute( method ); + + // Some browsers return an empty string for the required attribute + // and non-HTML5 browsers might have required="" markup + if ( value === "" ) { + value = true; + } + + // Force non-HTML5 browsers to return bool + value = !!value; + } else { + value = $element.attr( method ); + } + + this.normalizeAttributeRule( rules, type, method, value ); + } + + // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs + if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) { + delete rules.maxlength; + } + + return rules; + }, + + dataRules: function( element ) { + var rules = {}, + $element = $( element ), + type = element.getAttribute( "type" ), + method, value; + + for ( method in $.validator.methods ) { + value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); + + // Cast empty attributes like `data-rule-required` to `true` + if ( value === "" ) { + value = true; + } + + this.normalizeAttributeRule( rules, type, method, value ); + } + return rules; + }, + + staticRules: function( element ) { + var rules = {}, + validator = $.data( element.form, "validator" ); + + if ( validator.settings.rules ) { + rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {}; + } + return rules; + }, + + normalizeRules: function( rules, element ) { + + // Handle dependency check + $.each( rules, function( prop, val ) { + + // Ignore rule when param is explicitly false, eg. required:false + if ( val === false ) { + delete rules[ prop ]; + return; + } + if ( val.param || val.depends ) { + var keepRule = true; + switch ( typeof val.depends ) { + case "string": + keepRule = !!$( val.depends, element.form ).length; + break; + case "function": + keepRule = val.depends.call( element, element ); + break; + } + if ( keepRule ) { + rules[ prop ] = val.param !== undefined ? val.param : true; + } else { + $.data( element.form, "validator" ).resetElements( $( element ) ); + delete rules[ prop ]; + } + } + } ); + + // Evaluate parameters + $.each( rules, function( rule, parameter ) { + rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter; + } ); + + // Clean number parameters + $.each( [ "minlength", "maxlength" ], function() { + if ( rules[ this ] ) { + rules[ this ] = Number( rules[ this ] ); + } + } ); + $.each( [ "rangelength", "range" ], function() { + var parts; + if ( rules[ this ] ) { + if ( $.isArray( rules[ this ] ) ) { + rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ]; + } else if ( typeof rules[ this ] === "string" ) { + parts = rules[ this ].replace( /[\[\]]/g, "" ).split( /[\s,]+/ ); + rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ]; + } + } + } ); + + if ( $.validator.autoCreateRanges ) { + + // Auto-create ranges + if ( rules.min != null && rules.max != null ) { + rules.range = [ rules.min, rules.max ]; + delete rules.min; + delete rules.max; + } + if ( rules.minlength != null && rules.maxlength != null ) { + rules.rangelength = [ rules.minlength, rules.maxlength ]; + delete rules.minlength; + delete rules.maxlength; + } + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function( data ) { + if ( typeof data === "string" ) { + var transformed = {}; + $.each( data.split( /\s/ ), function() { + transformed[ this ] = true; + } ); + data = transformed; + } + return data; + }, + + // https://jqueryvalidation.org/jQuery.validator.addMethod/ + addMethod: function( name, method, message ) { + $.validator.methods[ name ] = method; + $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ]; + if ( method.length < 3 ) { + $.validator.addClassRules( name, $.validator.normalizeRule( name ) ); + } + }, + + // https://jqueryvalidation.org/jQuery.validator.methods/ + methods: { + + // https://jqueryvalidation.org/required-method/ + required: function( value, element, param ) { + + // Check if dependency is met + if ( !this.depend( param, element ) ) { + return "dependency-mismatch"; + } + if ( element.nodeName.toLowerCase() === "select" ) { + + // Could be an array for select-multiple or a string, both are fine this way + var val = $( element ).val(); + return val && val.length > 0; + } + if ( this.checkable( element ) ) { + return this.getLength( value, element ) > 0; + } + return value !== undefined && value !== null && value.length > 0; + }, + + // https://jqueryvalidation.org/email-method/ + email: function( value, element ) { + + // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address + // Retrieved 2014-01-14 + // If you have a problem with this implementation, report a bug against the above spec + // Or use custom methods to implement your own email validation + return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value ); + }, + + // https://jqueryvalidation.org/url-method/ + url: function( value, element ) { + + // Copyright (c) 2010-2013 Diego Perini, MIT licensed + // https://gist.github.com/dperini/729294 + // see also https://mathiasbynens.be/demo/url-regex + // modified to allow protocol-relative URLs + return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); + }, + + // https://jqueryvalidation.org/date-method/ + date: ( function() { + var called = false; + + return function( value, element ) { + if ( !called ) { + called = true; + if ( this.settings.debug && window.console ) { + console.warn( + "The `date` method is deprecated and will be removed in version '2.0.0'.\n" + + "Please don't use it, since it relies on the Date constructor, which\n" + + "behaves very differently across browsers and locales. Use `dateISO`\n" + + "instead or one of the locale specific methods in `localizations/`\n" + + "and `additional-methods.js`." + ); + } + } + + return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); + }; + }() ), + + // https://jqueryvalidation.org/dateISO-method/ + dateISO: function( value, element ) { + return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); + }, + + // https://jqueryvalidation.org/number-method/ + number: function( value, element ) { + return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); + }, + + // https://jqueryvalidation.org/digits-method/ + digits: function( value, element ) { + return this.optional( element ) || /^\d+$/.test( value ); + }, + + // https://jqueryvalidation.org/minlength-method/ + minlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length >= param; + }, + + // https://jqueryvalidation.org/maxlength-method/ + maxlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length <= param; + }, + + // https://jqueryvalidation.org/rangelength-method/ + rangelength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); + }, + + // https://jqueryvalidation.org/min-method/ + min: function( value, element, param ) { + return this.optional( element ) || value >= param; + }, + + // https://jqueryvalidation.org/max-method/ + max: function( value, element, param ) { + return this.optional( element ) || value <= param; + }, + + // https://jqueryvalidation.org/range-method/ + range: function( value, element, param ) { + return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); + }, + + // https://jqueryvalidation.org/step-method/ + step: function( value, element, param ) { + var type = $( element ).attr( "type" ), + errorMessage = "Step attribute on input type " + type + " is not supported.", + supportedTypes = [ "text", "number", "range" ], + re = new RegExp( "\\b" + type + "\\b" ), + notSupported = type && !re.test( supportedTypes.join() ), + decimalPlaces = function( num ) { + var match = ( "" + num ).match( /(?:\.(\d+))?$/ ); + if ( !match ) { + return 0; + } + + // Number of digits right of decimal point. + return match[ 1 ] ? match[ 1 ].length : 0; + }, + toInt = function( num ) { + return Math.round( num * Math.pow( 10, decimals ) ); + }, + valid = true, + decimals; + + // Works only for text, number and range input types + // TODO find a way to support input types date, datetime, datetime-local, month, time and week + if ( notSupported ) { + throw new Error( errorMessage ); + } + + decimals = decimalPlaces( param ); + + // Value can't have too many decimals + if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) { + valid = false; + } + + return this.optional( element ) || valid; + }, + + // https://jqueryvalidation.org/equalTo-method/ + equalTo: function( value, element, param ) { + + // Bind to the blur event of the target in order to revalidate whenever the target field is updated + var target = $( param ); + if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) { + target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() { + $( element ).valid(); + } ); + } + return value === target.val(); + }, + + // https://jqueryvalidation.org/remote-method/ + remote: function( value, element, param, method ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + + method = typeof method === "string" && method || "remote"; + + var previous = this.previousValue( element, method ), + validator, data, optionDataString; + + if ( !this.settings.messages[ element.name ] ) { + this.settings.messages[ element.name ] = {}; + } + previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ]; + this.settings.messages[ element.name ][ method ] = previous.message; + + param = typeof param === "string" && { url: param } || param; + optionDataString = $.param( $.extend( { data: value }, param.data ) ); + if ( previous.old === optionDataString ) { + return previous.valid; + } + + previous.old = optionDataString; + validator = this; + this.startRequest( element ); + data = {}; + data[ element.name ] = value; + $.ajax( $.extend( true, { + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + context: validator.currentForm, + success: function( response ) { + var valid = response === true || response === "true", + errors, message, submitted; + + validator.settings.messages[ element.name ][ method ] = previous.originalMessage; + if ( valid ) { + submitted = validator.formSubmitted; + validator.resetInternals(); + validator.toHide = validator.errorsFor( element ); + validator.formSubmitted = submitted; + validator.successList.push( element ); + validator.invalid[ element.name ] = false; + validator.showErrors(); + } else { + errors = {}; + message = response || validator.defaultMessage( element, { method: method, parameters: value } ); + errors[ element.name ] = previous.message = message; + validator.invalid[ element.name ] = true; + validator.showErrors( errors ); + } + previous.valid = valid; + validator.stopRequest( element, valid ); + } + }, param ) ); + return "pending"; + } + } + +} ); + +// Ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() + +var pendingRequests = {}, + ajax; + +// Use a prefilter if available (1.5+) +if ( $.ajaxPrefilter ) { + $.ajaxPrefilter( function( settings, _, xhr ) { + var port = settings.port; + if ( settings.mode === "abort" ) { + if ( pendingRequests[ port ] ) { + pendingRequests[ port ].abort(); + } + pendingRequests[ port ] = xhr; + } + } ); +} else { + + // Proxy ajax + ajax = $.ajax; + $.ajax = function( settings ) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if ( mode === "abort" ) { + if ( pendingRequests[ port ] ) { + pendingRequests[ port ].abort(); + } + pendingRequests[ port ] = ajax.apply( this, arguments ); + return pendingRequests[ port ]; + } + return ajax.apply( this, arguments ); + }; +} +return $; +})); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.validate.min.js b/app/assets/javascripts/jquery.validate.min.js new file mode 100644 index 000000000..7bc947fbc --- /dev/null +++ b/app/assets/javascripts/jquery.validate.min.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.19.1 - 6/15/2019 + * https://jqueryvalidation.org/ + * Copyright (c) 2019 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!(c.settings.submitHandler&&!c.settings.debug)||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0],k="undefined"!=typeof this.attr("contenteditable")&&"false"!==this.attr("contenteditable");if(null!=j&&(!j.form&&k&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}}),a.extend(a.expr.pseudos||a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){var c=a(b).val();return null!==c&&!!a.trim(""+c)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");if(!this.form&&c&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name")),d===this.form){var e=a.data(this.form,"validator"),f="on"+b.type.replace(/^validate/,""),g=e.settings;g[f]&&!a(this).is(g.ignore)&&g[f].call(e,this,b)}}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.currentForm,e=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){e[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").trigger("focus").trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name"),e="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),e&&(this.form=a(this).closest("form")[0],this.name=d),this.form===b.currentForm&&(!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0))})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type,g="undefined"!=typeof e.attr("contenteditable")&&"false"!==e.attr("contenteditable");return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=g?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);"function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f&&(j=f.call(b,j),delete g.normalizer);for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+""),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur").find(".validate-lessThan-blur").off(".validate-lessThan").removeClass("validate-lessThan-blur").find(".validate-lessThanEqual-blur").off(".validate-lessThanEqual").removeClass("validate-lessThanEqual-blur").find(".validate-greaterThanEqual-blur").off(".validate-greaterThanEqual").removeClass("validate-greaterThanEqual-blur").find(".validate-greaterThan-blur").off(".validate-greaterThan").removeClass("validate-greaterThan-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),""===d&&(d=!0),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:void 0!==b&&null!==b&&b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(a)},date:function(){var a=!1;return function(b,c){return a||(a=!0,this.settings.debug&&window.console&&console.warn("The `date` method is deprecated and will be removed in version '2.0.0'.\nPlease don't use it, since it relies on the Date constructor, which\nbehaves very differently across browsers and locales. Use `dateISO`\ninstead or one of the locale specific methods in `localizations/`\nand `additional-methods.js`.")),this.optional(c)||!/Invalid|NaN/.test(new Date(b).toString())}}(),dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var b,c={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a}); \ No newline at end of file diff --git a/app/assets/javascripts/select2.js b/app/assets/javascripts/select2.js new file mode 100644 index 000000000..d33caac21 --- /dev/null +++ b/app/assets/javascripts/select2.js @@ -0,0 +1,5891 @@ +/*! + * Select2 4.0.8 + * https://select2.github.io + * + * Released under the MIT license + * https://github.com/select2/select2/blob/master/LICENSE.md + */ +;(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = function (root, jQuery) { + if (jQuery === undefined) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if (typeof window !== 'undefined') { + jQuery = require('jquery'); + } + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; + } else { + // Browser globals + factory(jQuery); + } +} (function (jQuery) { + // This is needed so we can catch the AMD loader configuration and use it + // The inner file should be wrapped (by `banner.start.js`) in a function that + // returns the AMD loader references. + var S2 =(function () { + // Restore the Select2 AMD loader so it can be used + // Needed mostly in the language files, where the loader is not inserted + if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { + var S2 = jQuery.fn.select2.amd; + } +var S2;(function () { if (!S2 || !S2.requirejs) { +if (!S2) { S2 = {}; } else { require = S2; } +/** + * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. + * Released under MIT license, http://github.com/requirejs/almond/LICENSE + */ +//Going sloppy to avoid 'use strict' string cost, but strict practices should +//be followed. +/*global setTimeout: false */ + +var requirejs, require, define; +(function (undef) { + var main, req, makeMap, handlers, + defined = {}, + waiting = {}, + config = {}, + defining = {}, + hasOwn = Object.prototype.hasOwnProperty, + aps = [].slice, + jsSuffixRegExp = /\.js$/; + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @returns {String} normalized name + */ + function normalize(name, baseName) { + var nameParts, nameSegment, mapValue, foundMap, lastIndex, + foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, + baseParts = baseName && baseName.split("/"), + map = config.map, + starMap = (map && map['*']) || {}; + + //Adjust any relative paths. + if (name) { + name = name.split('/'); + lastIndex = name.length - 1; + + // If wanting node ID compatibility, strip .js from end + // of IDs. Have to do this here, and not in nameToUrl + // because node allows either .js or non .js to map + // to same file. + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } + + // Starts with a '.' so need the baseName + if (name[0].charAt(0) === '.' && baseParts) { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + name = normalizedBaseParts.concat(name); + } + + //start trimDots + for (i = 0; i < name.length; i++) { + part = name[i]; + if (part === '.') { + name.splice(i, 1); + i -= 1; + } else if (part === '..') { + // If at the start, or previous value is still .., + // keep them so that when converted to a path it may + // still work when converted to a path, even though + // as an ID it is less than ideal. In larger point + // releases, may be better to just kick out an error. + if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { + continue; + } else if (i > 0) { + name.splice(i - 1, 2); + i -= 2; + } + } + } + //end trimDots + + name = name.join('/'); + } + + //Apply map config if available. + if ((baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join("/"); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = map[baseParts.slice(0, j).join('/')]; + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = mapValue[nameSegment]; + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && starMap[nameSegment]) { + foundStarMap = starMap[nameSegment]; + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function makeRequire(relName, forceSync) { + return function () { + //A version of a require function that passes a moduleName + //value for items that may need to + //look up paths relative to the moduleName + var args = aps.call(arguments, 0); + + //If first arg is not require('string'), and there is only + //one arg, it is the array form without a callback. Insert + //a null so that the following concat is correct. + if (typeof args[0] !== 'string' && args.length === 1) { + args.push(null); + } + return req.apply(undef, args.concat([relName, forceSync])); + }; + } + + function makeNormalize(relName) { + return function (name) { + return normalize(name, relName); + }; + } + + function makeLoad(depName) { + return function (value) { + defined[depName] = value; + }; + } + + function callDep(name) { + if (hasProp(waiting, name)) { + var args = waiting[name]; + delete waiting[name]; + defining[name] = true; + main.apply(undef, args); + } + + if (!hasProp(defined, name) && !hasProp(defining, name)) { + throw new Error('No ' + name); + } + return defined[name]; + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + //Creates a parts array for a relName where first part is plugin ID, + //second part is resource ID. Assumes relName has already been normalized. + function makeRelParts(relName) { + return relName ? splitPrefix(relName) : []; + } + + /** + * Makes a name map, normalizing the name, and using a plugin + * for normalization if necessary. Grabs a ref to plugin + * too, as an optimization. + */ + makeMap = function (name, relParts) { + var plugin, + parts = splitPrefix(name), + prefix = parts[0], + relResourceName = relParts[1]; + + name = parts[1]; + + if (prefix) { + prefix = normalize(prefix, relResourceName); + plugin = callDep(prefix); + } + + //Normalize according + if (prefix) { + if (plugin && plugin.normalize) { + name = plugin.normalize(name, makeNormalize(relResourceName)); + } else { + name = normalize(name, relResourceName); + } + } else { + name = normalize(name, relResourceName); + parts = splitPrefix(name); + prefix = parts[0]; + name = parts[1]; + if (prefix) { + plugin = callDep(prefix); + } + } + + //Using ridiculous property names for space reasons + return { + f: prefix ? prefix + '!' + name : name, //fullName + n: name, + pr: prefix, + p: plugin + }; + }; + + function makeConfig(name) { + return function () { + return (config && config.config && config.config[name]) || {}; + }; + } + + handlers = { + require: function (name) { + return makeRequire(name); + }, + exports: function (name) { + var e = defined[name]; + if (typeof e !== 'undefined') { + return e; + } else { + return (defined[name] = {}); + } + }, + module: function (name) { + return { + id: name, + uri: '', + exports: defined[name], + config: makeConfig(name) + }; + } + }; + + main = function (name, deps, callback, relName) { + var cjsModule, depName, ret, map, i, relParts, + args = [], + callbackType = typeof callback, + usingExports; + + //Use name if no relName + relName = relName || name; + relParts = makeRelParts(relName); + + //Call the callback to define the module, if necessary. + if (callbackType === 'undefined' || callbackType === 'function') { + //Pull out the defined dependencies and pass the ordered + //values to the callback. + //Default to [require, exports, module] if no deps + deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; + for (i = 0; i < deps.length; i += 1) { + map = makeMap(deps[i], relParts); + depName = map.f; + + //Fast path CommonJS standard dependencies. + if (depName === "require") { + args[i] = handlers.require(name); + } else if (depName === "exports") { + //CommonJS module spec 1.1 + args[i] = handlers.exports(name); + usingExports = true; + } else if (depName === "module") { + //CommonJS module spec 1.1 + cjsModule = args[i] = handlers.module(name); + } else if (hasProp(defined, depName) || + hasProp(waiting, depName) || + hasProp(defining, depName)) { + args[i] = callDep(depName); + } else if (map.p) { + map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); + args[i] = defined[depName]; + } else { + throw new Error(name + ' missing ' + depName); + } + } + + ret = callback ? callback.apply(defined[name], args) : undefined; + + if (name) { + //If setting exports via "module" is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + if (cjsModule && cjsModule.exports !== undef && + cjsModule.exports !== defined[name]) { + defined[name] = cjsModule.exports; + } else if (ret !== undef || !usingExports) { + //Use the return value from the function. + defined[name] = ret; + } + } + } else if (name) { + //May just be an object definition for the module. Only + //worry about defining if have a module name. + defined[name] = callback; + } + }; + + requirejs = require = req = function (deps, callback, relName, forceSync, alt) { + if (typeof deps === "string") { + if (handlers[deps]) { + //callback in this case is really relName + return handlers[deps](callback); + } + //Just return the module wanted. In this scenario, the + //deps arg is the module name, and second arg (if passed) + //is just the relName. + //Normalize module name, if it contains . or .. + return callDep(makeMap(deps, makeRelParts(callback)).f); + } else if (!deps.splice) { + //deps is a config object, not an array. + config = deps; + if (config.deps) { + req(config.deps, config.callback); + } + if (!callback) { + return; + } + + if (callback.splice) { + //callback is an array, which means it is a dependency list. + //Adjust args if there are dependencies + deps = callback; + callback = relName; + relName = null; + } else { + deps = undef; + } + } + + //Support require(['a']) + callback = callback || function () {}; + + //If relName is a function, it is an errback handler, + //so remove it. + if (typeof relName === 'function') { + relName = forceSync; + forceSync = alt; + } + + //Simulate async callback; + if (forceSync) { + main(undef, deps, callback, relName); + } else { + //Using a non-zero value because of concern for what old browsers + //do, and latest browsers "upgrade" to 4 if lower value is used: + //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: + //If want a value immediately, use require('id') instead -- something + //that works in almond on the global level, but not guaranteed and + //unlikely to work in other AMD implementations. + setTimeout(function () { + main(undef, deps, callback, relName); + }, 4); + } + + return req; + }; + + /** + * Just drops the config on the floor, but returns req in case + * the config return value is used. + */ + req.config = function (cfg) { + return req(cfg); + }; + + /** + * Expose module registry for debugging and tooling + */ + requirejs._defined = defined; + + define = function (name, deps, callback) { + if (typeof name !== 'string') { + throw new Error('See almond README: incorrect module build, no module name'); + } + + //This module may not have dependencies + if (!deps.splice) { + //deps is not an array, so probably means + //an object literal or factory function for + //the value. Adjust args. + callback = deps; + deps = []; + } + + if (!hasProp(defined, name) && !hasProp(waiting, name)) { + waiting[name] = [name, deps, callback]; + } + }; + + define.amd = { + jQuery: true + }; +}()); + +S2.requirejs = requirejs;S2.require = require;S2.define = define; +} +}()); +S2.define("almond", function(){}); + +/* global jQuery:false, $:false */ +S2.define('jquery',[],function () { + var _$ = jQuery || $; + + if (_$ == null && console && console.error) { + console.error( + 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + + 'found. Make sure that you are including jQuery before Select2 on your ' + + 'web page.' + ); + } + + return _$; +}); + +S2.define('select2/utils',[ + 'jquery' +], function ($) { + var Utils = {}; + + Utils.Extend = function (ChildClass, SuperClass) { + var __hasProp = {}.hasOwnProperty; + + function BaseConstructor () { + this.constructor = ChildClass; + } + + for (var key in SuperClass) { + if (__hasProp.call(SuperClass, key)) { + ChildClass[key] = SuperClass[key]; + } + } + + BaseConstructor.prototype = SuperClass.prototype; + ChildClass.prototype = new BaseConstructor(); + ChildClass.__super__ = SuperClass.prototype; + + return ChildClass; + }; + + function getMethods (theClass) { + var proto = theClass.prototype; + + var methods = []; + + for (var methodName in proto) { + var m = proto[methodName]; + + if (typeof m !== 'function') { + continue; + } + + if (methodName === 'constructor') { + continue; + } + + methods.push(methodName); + } + + return methods; + } + + Utils.Decorate = function (SuperClass, DecoratorClass) { + var decoratedMethods = getMethods(DecoratorClass); + var superMethods = getMethods(SuperClass); + + function DecoratedClass () { + var unshift = Array.prototype.unshift; + + var argCount = DecoratorClass.prototype.constructor.length; + + var calledConstructor = SuperClass.prototype.constructor; + + if (argCount > 0) { + unshift.call(arguments, SuperClass.prototype.constructor); + + calledConstructor = DecoratorClass.prototype.constructor; + } + + calledConstructor.apply(this, arguments); + } + + DecoratorClass.displayName = SuperClass.displayName; + + function ctr () { + this.constructor = DecoratedClass; + } + + DecoratedClass.prototype = new ctr(); + + for (var m = 0; m < superMethods.length; m++) { + var superMethod = superMethods[m]; + + DecoratedClass.prototype[superMethod] = + SuperClass.prototype[superMethod]; + } + + var calledMethod = function (methodName) { + // Stub out the original method if it's not decorating an actual method + var originalMethod = function () {}; + + if (methodName in DecoratedClass.prototype) { + originalMethod = DecoratedClass.prototype[methodName]; + } + + var decoratedMethod = DecoratorClass.prototype[methodName]; + + return function () { + var unshift = Array.prototype.unshift; + + unshift.call(arguments, originalMethod); + + return decoratedMethod.apply(this, arguments); + }; + }; + + for (var d = 0; d < decoratedMethods.length; d++) { + var decoratedMethod = decoratedMethods[d]; + + DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); + } + + return DecoratedClass; + }; + + var Observable = function () { + this.listeners = {}; + }; + + Observable.prototype.on = function (event, callback) { + this.listeners = this.listeners || {}; + + if (event in this.listeners) { + this.listeners[event].push(callback); + } else { + this.listeners[event] = [callback]; + } + }; + + Observable.prototype.trigger = function (event) { + var slice = Array.prototype.slice; + var params = slice.call(arguments, 1); + + this.listeners = this.listeners || {}; + + // Params should always come in as an array + if (params == null) { + params = []; + } + + // If there are no arguments to the event, use a temporary object + if (params.length === 0) { + params.push({}); + } + + // Set the `_type` of the first object to the event + params[0]._type = event; + + if (event in this.listeners) { + this.invoke(this.listeners[event], slice.call(arguments, 1)); + } + + if ('*' in this.listeners) { + this.invoke(this.listeners['*'], arguments); + } + }; + + Observable.prototype.invoke = function (listeners, params) { + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].apply(this, params); + } + }; + + Utils.Observable = Observable; + + Utils.generateChars = function (length) { + var chars = ''; + + for (var i = 0; i < length; i++) { + var randomChar = Math.floor(Math.random() * 36); + chars += randomChar.toString(36); + } + + return chars; + }; + + Utils.bind = function (func, context) { + return function () { + func.apply(context, arguments); + }; + }; + + Utils._convertData = function (data) { + for (var originalKey in data) { + var keys = originalKey.split('-'); + + var dataLevel = data; + + if (keys.length === 1) { + continue; + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k]; + + // Lowercase the first letter + // By default, dash-separated becomes camelCase + key = key.substring(0, 1).toLowerCase() + key.substring(1); + + if (!(key in dataLevel)) { + dataLevel[key] = {}; + } + + if (k == keys.length - 1) { + dataLevel[key] = data[originalKey]; + } + + dataLevel = dataLevel[key]; + } + + delete data[originalKey]; + } + + return data; + }; + + Utils.hasScroll = function (index, el) { + // Adapted from the function created by @ShadowScripter + // and adapted by @BillBarry on the Stack Exchange Code Review website. + // The original code can be found at + // http://codereview.stackexchange.com/q/13338 + // and was designed to be used with the Sizzle selector engine. + + var $el = $(el); + var overflowX = el.style.overflowX; + var overflowY = el.style.overflowY; + + //Check both x and y declarations + if (overflowX === overflowY && + (overflowY === 'hidden' || overflowY === 'visible')) { + return false; + } + + if (overflowX === 'scroll' || overflowY === 'scroll') { + return true; + } + + return ($el.innerHeight() < el.scrollHeight || + $el.innerWidth() < el.scrollWidth); + }; + + Utils.escapeMarkup = function (markup) { + var replaceMap = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + // Do not try to escape the markup if it's not a string + if (typeof markup !== 'string') { + return markup; + } + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replaceMap[match]; + }); + }; + + // Append an array of jQuery nodes to a given element. + Utils.appendMany = function ($element, $nodes) { + // jQuery 1.7.x does not support $.fn.append() with an array + // Fall back to a jQuery object collection using $.fn.add() + if ($.fn.jquery.substr(0, 3) === '1.7') { + var $jqNodes = $(); + + $.map($nodes, function (node) { + $jqNodes = $jqNodes.add(node); + }); + + $nodes = $jqNodes; + } + + $element.append($nodes); + }; + + // Cache objects in Utils.__cache instead of $.data (see #4346) + Utils.__cache = {}; + + var id = 0; + Utils.GetUniqueElementId = function (element) { + // Get a unique element Id. If element has no id, + // creates a new unique number, stores it in the id + // attribute and returns the new id. + // If an id already exists, it simply returns it. + + var select2Id = element.getAttribute('data-select2-id'); + if (select2Id == null) { + // If element has id, use it. + if (element.id) { + select2Id = element.id; + element.setAttribute('data-select2-id', select2Id); + } else { + element.setAttribute('data-select2-id', ++id); + select2Id = id.toString(); + } + } + return select2Id; + }; + + Utils.StoreData = function (element, name, value) { + // Stores an item in the cache for a specified element. + // name is the cache key. + var id = Utils.GetUniqueElementId(element); + if (!Utils.__cache[id]) { + Utils.__cache[id] = {}; + } + + Utils.__cache[id][name] = value; + }; + + Utils.GetData = function (element, name) { + // Retrieves a value from the cache by its key (name) + // name is optional. If no name specified, return + // all cache items for the specified element. + // and for a specified element. + var id = Utils.GetUniqueElementId(element); + if (name) { + if (Utils.__cache[id]) { + if (Utils.__cache[id][name] != null) { + return Utils.__cache[id][name]; + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } else { + return Utils.__cache[id]; + } + }; + + Utils.RemoveData = function (element) { + // Removes all cached items for a specified element. + var id = Utils.GetUniqueElementId(element); + if (Utils.__cache[id] != null) { + delete Utils.__cache[id]; + } + }; + + return Utils; +}); + +S2.define('select2/results',[ + 'jquery', + './utils' +], function ($, Utils) { + function Results ($element, options, dataAdapter) { + this.$element = $element; + this.data = dataAdapter; + this.options = options; + + Results.__super__.constructor.call(this); + } + + Utils.Extend(Results, Utils.Observable); + + Results.prototype.render = function () { + var $results = $( + '
      ' + ); + + if (this.options.get('multiple')) { + $results.attr('aria-multiselectable', 'true'); + } + + this.$results = $results; + + return $results; + }; + + Results.prototype.clear = function () { + this.$results.empty(); + }; + + Results.prototype.displayMessage = function (params) { + var escapeMarkup = this.options.get('escapeMarkup'); + + this.clear(); + this.hideLoading(); + + var $message = $( + '
    • ' + ); + + var message = this.options.get('translations').get(params.message); + + $message.append( + escapeMarkup( + message(params.args) + ) + ); + + $message[0].className += ' select2-results__message'; + + this.$results.append($message); + }; + + Results.prototype.hideMessages = function () { + this.$results.find('.select2-results__message').remove(); + }; + + Results.prototype.append = function (data) { + this.hideLoading(); + + var $options = []; + + if (data.results == null || data.results.length === 0) { + if (this.$results.children().length === 0) { + this.trigger('results:message', { + message: 'noResults' + }); + } + + return; + } + + data.results = this.sort(data.results); + + for (var d = 0; d < data.results.length; d++) { + var item = data.results[d]; + + var $option = this.option(item); + + $options.push($option); + } + + this.$results.append($options); + }; + + Results.prototype.position = function ($results, $dropdown) { + var $resultsContainer = $dropdown.find('.select2-results'); + $resultsContainer.append($results); + }; + + Results.prototype.sort = function (data) { + var sorter = this.options.get('sorter'); + + return sorter(data); + }; + + Results.prototype.highlightFirstItem = function () { + var $options = this.$results + .find('.select2-results__option[aria-selected]'); + + var $selected = $options.filter('[aria-selected=true]'); + + // Check if there are any selected options + if ($selected.length > 0) { + // If there are selected options, highlight the first + $selected.first().trigger('mouseenter'); + } else { + // If there are no selected options, highlight the first option + // in the dropdown + $options.first().trigger('mouseenter'); + } + + this.ensureHighlightVisible(); + }; + + Results.prototype.setClasses = function () { + var self = this; + + this.data.current(function (selected) { + var selectedIds = $.map(selected, function (s) { + return s.id.toString(); + }); + + var $options = self.$results + .find('.select2-results__option[aria-selected]'); + + $options.each(function () { + var $option = $(this); + + var item = Utils.GetData(this, 'data'); + + // id needs to be converted to a string when comparing + var id = '' + item.id; + + if ((item.element != null && item.element.selected) || + (item.element == null && $.inArray(id, selectedIds) > -1)) { + $option.attr('aria-selected', 'true'); + } else { + $option.attr('aria-selected', 'false'); + } + }); + + }); + }; + + Results.prototype.showLoading = function (params) { + this.hideLoading(); + + var loadingMore = this.options.get('translations').get('searching'); + + var loading = { + disabled: true, + loading: true, + text: loadingMore(params) + }; + var $loading = this.option(loading); + $loading.className += ' loading-results'; + + this.$results.prepend($loading); + }; + + Results.prototype.hideLoading = function () { + this.$results.find('.loading-results').remove(); + }; + + Results.prototype.option = function (data) { + var option = document.createElement('li'); + option.className = 'select2-results__option'; + + var attrs = { + 'role': 'treeitem', + 'aria-selected': 'false' + }; + + var matches = window.Element.prototype.matches || + window.Element.prototype.msMatchesSelector || + window.Element.prototype.webkitMatchesSelector; + + if ((data.element != null && matches.call(data.element, ':disabled')) || + (data.element == null && data.disabled)) { + delete attrs['aria-selected']; + attrs['aria-disabled'] = 'true'; + } + + if (data.id == null) { + delete attrs['aria-selected']; + } + + if (data._resultId != null) { + option.id = data._resultId; + } + + if (data.title) { + option.title = data.title; + } + + if (data.children) { + attrs.role = 'group'; + attrs['aria-label'] = data.text; + delete attrs['aria-selected']; + } + + for (var attr in attrs) { + var val = attrs[attr]; + + option.setAttribute(attr, val); + } + + if (data.children) { + var $option = $(option); + + var label = document.createElement('strong'); + label.className = 'select2-results__group'; + + var $label = $(label); + this.template(data, label); + + var $children = []; + + for (var c = 0; c < data.children.length; c++) { + var child = data.children[c]; + + var $child = this.option(child); + + $children.push($child); + } + + var $childrenContainer = $('
        ', { + 'class': 'select2-results__options select2-results__options--nested' + }); + + $childrenContainer.append($children); + + $option.append(label); + $option.append($childrenContainer); + } else { + this.template(data, option); + } + + Utils.StoreData(option, 'data', data); + + return option; + }; + + Results.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-results'; + + this.$results.attr('id', id); + + container.on('results:all', function (params) { + self.clear(); + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + self.highlightFirstItem(); + } + }); + + container.on('results:append', function (params) { + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + } + }); + + container.on('query', function (params) { + self.hideMessages(); + self.showLoading(params); + }); + + container.on('select', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('unselect', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('open', function () { + // When the dropdown is open, aria-expended="true" + self.$results.attr('aria-expanded', 'true'); + self.$results.attr('aria-hidden', 'false'); + + self.setClasses(); + self.ensureHighlightVisible(); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expended="false" + self.$results.attr('aria-expanded', 'false'); + self.$results.attr('aria-hidden', 'true'); + self.$results.removeAttr('aria-activedescendant'); + }); + + container.on('results:toggle', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + $highlighted.trigger('mouseup'); + }); + + container.on('results:select', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var data = Utils.GetData($highlighted[0], 'data'); + + if ($highlighted.attr('aria-selected') == 'true') { + self.trigger('close', {}); + } else { + self.trigger('select', { + data: data + }); + } + }); + + container.on('results:previous', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + // If we are already at the top, don't move further + // If no options, currentIndex will be -1 + if (currentIndex <= 0) { + return; + } + + var nextIndex = currentIndex - 1; + + // If none are highlighted, highlight the first + if ($highlighted.length === 0) { + nextIndex = 0; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top; + var nextTop = $next.offset().top; + var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextTop - currentOffset < 0) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:next', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var nextIndex = currentIndex + 1; + + // If we are at the last option, stay there + if (nextIndex >= $options.length) { + return; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top + + self.$results.outerHeight(false); + var nextBottom = $next.offset().top + $next.outerHeight(false); + var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextBottom > currentOffset) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:focus', function (params) { + params.element.addClass('select2-results__option--highlighted'); + }); + + container.on('results:message', function (params) { + self.displayMessage(params); + }); + + if ($.fn.mousewheel) { + this.$results.on('mousewheel', function (e) { + var top = self.$results.scrollTop(); + + var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; + + var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; + var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); + + if (isAtTop) { + self.$results.scrollTop(0); + + e.preventDefault(); + e.stopPropagation(); + } else if (isAtBottom) { + self.$results.scrollTop( + self.$results.get(0).scrollHeight - self.$results.height() + ); + + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + this.$results.on('mouseup', '.select2-results__option[aria-selected]', + function (evt) { + var $this = $(this); + + var data = Utils.GetData(this, 'data'); + + if ($this.attr('aria-selected') === 'true') { + if (self.options.get('multiple')) { + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } else { + self.trigger('close', {}); + } + + return; + } + + self.trigger('select', { + originalEvent: evt, + data: data + }); + }); + + this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + function (evt) { + var data = Utils.GetData(this, 'data'); + + self.getHighlightedResults() + .removeClass('select2-results__option--highlighted'); + + self.trigger('results:focus', { + data: data, + element: $(this) + }); + }); + }; + + Results.prototype.getHighlightedResults = function () { + var $highlighted = this.$results + .find('.select2-results__option--highlighted'); + + return $highlighted; + }; + + Results.prototype.destroy = function () { + this.$results.remove(); + }; + + Results.prototype.ensureHighlightVisible = function () { + var $highlighted = this.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var $options = this.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var currentOffset = this.$results.offset().top; + var nextTop = $highlighted.offset().top; + var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); + + var offsetDelta = nextTop - currentOffset; + nextOffset -= $highlighted.outerHeight(false) * 2; + + if (currentIndex <= 2) { + this.$results.scrollTop(0); + } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { + this.$results.scrollTop(nextOffset); + } + }; + + Results.prototype.template = function (result, container) { + var template = this.options.get('templateResult'); + var escapeMarkup = this.options.get('escapeMarkup'); + + var content = template(result, container); + + if (content == null) { + container.style.display = 'none'; + } else if (typeof content === 'string') { + container.innerHTML = escapeMarkup(content); + } else { + $(container).append(content); + } + }; + + return Results; +}); + +S2.define('select2/keys',[ + +], function () { + var KEYS = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + DELETE: 46 + }; + + return KEYS; +}); + +S2.define('select2/selection/base',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function BaseSelection ($element, options) { + this.$element = $element; + this.options = options; + + BaseSelection.__super__.constructor.call(this); + } + + Utils.Extend(BaseSelection, Utils.Observable); + + BaseSelection.prototype.render = function () { + var $selection = $( + '' + ); + + this._tabindex = 0; + + if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { + this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); + } else if (this.$element.attr('tabindex') != null) { + this._tabindex = this.$element.attr('tabindex'); + } + + $selection.attr('title', this.$element.attr('title')); + $selection.attr('tabindex', this._tabindex); + + this.$selection = $selection; + + return $selection; + }; + + BaseSelection.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-container'; + var resultsId = container.id + '-results'; + + this.container = container; + + this.$selection.on('focus', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('blur', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', function (evt) { + self.trigger('keypress', evt); + + if (evt.which === KEYS.SPACE) { + evt.preventDefault(); + } + }); + + container.on('results:focus', function (params) { + self.$selection.attr('aria-activedescendant', params.data._resultId); + }); + + container.on('selection:update', function (params) { + self.update(params.data); + }); + + container.on('open', function () { + // When the dropdown is open, aria-expanded="true" + self.$selection.attr('aria-expanded', 'true'); + self.$selection.attr('aria-owns', resultsId); + + self._attachCloseHandler(container); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expanded="false" + self.$selection.attr('aria-expanded', 'false'); + self.$selection.removeAttr('aria-activedescendant'); + self.$selection.removeAttr('aria-owns'); + + self.$selection.trigger('focus'); + + self._detachCloseHandler(container); + }); + + container.on('enable', function () { + self.$selection.attr('tabindex', self._tabindex); + }); + + container.on('disable', function () { + self.$selection.attr('tabindex', '-1'); + }); + }; + + BaseSelection.prototype._handleBlur = function (evt) { + var self = this; + + // This needs to be delayed as the active element is the body when the tab + // key is pressed, possibly along with others. + window.setTimeout(function () { + // Don't trigger `blur` if the focus is still in the selection + if ( + (document.activeElement == self.$selection[0]) || + ($.contains(self.$selection[0], document.activeElement)) + ) { + return; + } + + self.trigger('blur', evt); + }, 1); + }; + + BaseSelection.prototype._attachCloseHandler = function (container) { + var self = this; + + $(document.body).on('mousedown.select2.' + container.id, function (e) { + var $target = $(e.target); + + var $select = $target.closest('.select2'); + + var $all = $('.select2.select2-container--open'); + + $all.each(function () { + var $this = $(this); + + if (this == $select[0]) { + return; + } + + var $element = Utils.GetData(this, 'element'); + + $element.select2('close'); + }); + }); + }; + + BaseSelection.prototype._detachCloseHandler = function (container) { + $(document.body).off('mousedown.select2.' + container.id); + }; + + BaseSelection.prototype.position = function ($selection, $container) { + var $selectionContainer = $container.find('.selection'); + $selectionContainer.append($selection); + }; + + BaseSelection.prototype.destroy = function () { + this._detachCloseHandler(this.container); + }; + + BaseSelection.prototype.update = function (data) { + throw new Error('The `update` method must be defined in child classes.'); + }; + + return BaseSelection; +}); + +S2.define('select2/selection/single',[ + 'jquery', + './base', + '../utils', + '../keys' +], function ($, BaseSelection, Utils, KEYS) { + function SingleSelection () { + SingleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(SingleSelection, BaseSelection); + + SingleSelection.prototype.render = function () { + var $selection = SingleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--single'); + + $selection.html( + '' + + '' + + '' + + '' + ); + + return $selection; + }; + + SingleSelection.prototype.bind = function (container, $container) { + var self = this; + + SingleSelection.__super__.bind.apply(this, arguments); + + var id = container.id + '-container'; + + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); + this.$selection.attr('aria-labelledby', id); + + this.$selection.on('mousedown', function (evt) { + // Only respond to left clicks + if (evt.which !== 1) { + return; + } + + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on('focus', function (evt) { + // User focuses on the container + }); + + this.$selection.on('blur', function (evt) { + // User exits the container + }); + + container.on('focus', function (evt) { + if (!container.isOpen()) { + self.$selection.trigger('focus'); + } + }); + }; + + SingleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); // clear tooltip on empty + }; + + SingleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + SingleSelection.prototype.selectionContainer = function () { + return $(''); + }; + + SingleSelection.prototype.update = function (data) { + if (data.length === 0) { + this.clear(); + return; + } + + var selection = data[0]; + + var $rendered = this.$selection.find('.select2-selection__rendered'); + var formatted = this.display(selection, $rendered); + + $rendered.empty().append(formatted); + $rendered.attr('title', selection.title || selection.text); + }; + + return SingleSelection; +}); + +S2.define('select2/selection/multiple',[ + 'jquery', + './base', + '../utils' +], function ($, BaseSelection, Utils) { + function MultipleSelection ($element, options) { + MultipleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(MultipleSelection, BaseSelection); + + MultipleSelection.prototype.render = function () { + var $selection = MultipleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--multiple'); + + $selection.html( + '
          ' + ); + + return $selection; + }; + + MultipleSelection.prototype.bind = function (container, $container) { + var self = this; + + MultipleSelection.__super__.bind.apply(this, arguments); + + this.$selection.on('click', function (evt) { + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on( + 'click', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.options.get('disabled')) { + return; + } + + var $remove = $(this); + var $selection = $remove.parent(); + + var data = Utils.GetData($selection[0], 'data'); + + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } + ); + }; + + MultipleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); + }; + + MultipleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + MultipleSelection.prototype.selectionContainer = function () { + var $container = $( + '
        • ' + + '' + + '×' + + '' + + '
        • ' + ); + + return $container; + }; + + MultipleSelection.prototype.update = function (data) { + this.clear(); + + if (data.length === 0) { + return; + } + + var $selections = []; + + for (var d = 0; d < data.length; d++) { + var selection = data[d]; + + var $selection = this.selectionContainer(); + var formatted = this.display(selection, $selection); + + $selection.append(formatted); + $selection.attr('title', selection.title || selection.text); + + Utils.StoreData($selection[0], 'data', selection); + + $selections.push($selection); + } + + var $rendered = this.$selection.find('.select2-selection__rendered'); + + Utils.appendMany($rendered, $selections); + }; + + return MultipleSelection; +}); + +S2.define('select2/selection/placeholder',[ + '../utils' +], function (Utils) { + function Placeholder (decorated, $element, options) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options); + } + + Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { + var $placeholder = this.selectionContainer(); + + $placeholder.html(this.display(placeholder)); + $placeholder.addClass('select2-selection__placeholder') + .removeClass('select2-selection__choice'); + + return $placeholder; + }; + + Placeholder.prototype.update = function (decorated, data) { + var singlePlaceholder = ( + data.length == 1 && data[0].id != this.placeholder.id + ); + var multipleSelections = data.length > 1; + + if (multipleSelections || singlePlaceholder) { + return decorated.call(this, data); + } + + this.clear(); + + var $placeholder = this.createPlaceholder(this.placeholder); + + this.$selection.find('.select2-selection__rendered').append($placeholder); + }; + + return Placeholder; +}); + +S2.define('select2/selection/allowClear',[ + 'jquery', + '../keys', + '../utils' +], function ($, KEYS, Utils) { + function AllowClear () { } + + AllowClear.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + if (this.placeholder == null) { + if (this.options.get('debug') && window.console && console.error) { + console.error( + 'Select2: The `allowClear` option should be used in combination ' + + 'with the `placeholder` option.' + ); + } + } + + this.$selection.on('mousedown', '.select2-selection__clear', + function (evt) { + self._handleClear(evt); + }); + + container.on('keypress', function (evt) { + self._handleKeyboardClear(evt, container); + }); + }; + + AllowClear.prototype._handleClear = function (_, evt) { + // Ignore the event if it is disabled + if (this.options.get('disabled')) { + return; + } + + var $clear = this.$selection.find('.select2-selection__clear'); + + // Ignore the event if nothing has been selected + if ($clear.length === 0) { + return; + } + + evt.stopPropagation(); + + var data = Utils.GetData($clear[0], 'data'); + + var previousVal = this.$element.val(); + this.$element.val(this.placeholder.id); + + var unselectData = { + data: data + }; + this.trigger('clear', unselectData); + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + + for (var d = 0; d < data.length; d++) { + unselectData = { + data: data[d] + }; + + // Trigger the `unselect` event, so people can prevent it from being + // cleared. + this.trigger('unselect', unselectData); + + // If the event was prevented, don't clear it out. + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + } + + this.$element.trigger('change'); + + this.trigger('toggle', {}); + }; + + AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { + if (container.isOpen()) { + return; + } + + if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { + this._handleClear(evt); + } + }; + + AllowClear.prototype.update = function (decorated, data) { + decorated.call(this, data); + + if (this.$selection.find('.select2-selection__placeholder').length > 0 || + data.length === 0) { + return; + } + + var removeAll = this.options.get('translations').get('removeAllItems'); + + var $remove = $( + '' + + '×' + + '' + ); + Utils.StoreData($remove[0], 'data', data); + + this.$selection.find('.select2-selection__rendered').prepend($remove); + }; + + return AllowClear; +}); + +S2.define('select2/selection/search',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function Search (decorated, $element, options) { + decorated.call(this, $element, options); + } + + Search.prototype.render = function (decorated) { + var $search = $( + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + var $rendered = decorated.call(this); + + this._transferTabIndex(); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('open', function () { + self.$search.trigger('focus'); + }); + + container.on('close', function () { + self.$search.val(''); + self.$search.removeAttr('aria-activedescendant'); + self.$search.trigger('focus'); + }); + + container.on('enable', function () { + self.$search.prop('disabled', false); + + self._transferTabIndex(); + }); + + container.on('disable', function () { + self.$search.prop('disabled', true); + }); + + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + + container.on('results:focus', function (params) { + self.$search.attr('aria-activedescendant', params.id); + }); + + this.$selection.on('focusin', '.select2-search--inline', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('focusout', '.select2-search--inline', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', '.select2-search--inline', function (evt) { + evt.stopPropagation(); + + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + + var key = evt.which; + + if (key === KEYS.BACKSPACE && self.$search.val() === '') { + var $previousChoice = self.$searchContainer + .prev('.select2-selection__choice'); + + if ($previousChoice.length > 0) { + var item = Utils.GetData($previousChoice[0], 'data'); + + self.searchRemoveChoice(item); + + evt.preventDefault(); + } + } + }); + + // Try to detect the IE version should the `documentMode` property that + // is stored on the document. This is only implemented in IE and is + // slightly cleaner than doing a user agent check. + // This property is not available in Edge, but Edge also doesn't have + // this bug. + var msie = document.documentMode; + var disableInputEvents = msie && msie <= 11; + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$selection.on( + 'input.searchcheck', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents) { + self.$selection.off('input.search input.searchcheck'); + return; + } + + // Unbind the duplicated `keyup` event + self.$selection.off('keyup.search'); + } + ); + + this.$selection.on( + 'keyup.search input.search', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents && evt.type === 'input') { + self.$selection.off('input.search input.searchcheck'); + return; + } + + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + + self.handleSearch(evt); + } + ); + }; + + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + + Search.prototype.createPlaceholder = function (decorated, placeholder) { + this.$search.attr('placeholder', placeholder.text); + }; + + Search.prototype.update = function (decorated, data) { + var searchHadFocus = this.$search[0] == document.activeElement; + + this.$search.attr('placeholder', ''); + + decorated.call(this, data); + + this.$selection.find('.select2-selection__rendered') + .append(this.$searchContainer); + + this.resizeSearch(); + if (searchHadFocus) { + this.$search.trigger('focus'); + } + }; + + Search.prototype.handleSearch = function () { + this.resizeSearch(); + + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.searchRemoveChoice = function (decorated, item) { + this.trigger('unselect', { + data: item + }); + + this.$search.val(item.text); + this.handleSearch(); + }; + + Search.prototype.resizeSearch = function () { + this.$search.css('width', '25px'); + + var width = ''; + + if (this.$search.attr('placeholder') !== '') { + width = this.$selection.find('.select2-selection__rendered').innerWidth(); + } else { + var minimumWidth = this.$search.val().length + 1; + + width = (minimumWidth * 0.75) + 'em'; + } + + this.$search.css('width', width); + }; + + return Search; +}); + +S2.define('select2/selection/eventRelay',[ + 'jquery' +], function ($) { + function EventRelay () { } + + EventRelay.prototype.bind = function (decorated, container, $container) { + var self = this; + var relayEvents = [ + 'open', 'opening', + 'close', 'closing', + 'select', 'selecting', + 'unselect', 'unselecting', + 'clear', 'clearing' + ]; + + var preventableEvents = [ + 'opening', 'closing', 'selecting', 'unselecting', 'clearing' + ]; + + decorated.call(this, container, $container); + + container.on('*', function (name, params) { + // Ignore events that should not be relayed + if ($.inArray(name, relayEvents) === -1) { + return; + } + + // The parameters should always be an object + params = params || {}; + + // Generate the jQuery event for the Select2 event + var evt = $.Event('select2:' + name, { + params: params + }); + + self.$element.trigger(evt); + + // Only handle preventable events if it was one + if ($.inArray(name, preventableEvents) === -1) { + return; + } + + params.prevented = evt.isDefaultPrevented(); + }); + }; + + return EventRelay; +}); + +S2.define('select2/translation',[ + 'jquery', + 'require' +], function ($, require) { + function Translation (dict) { + this.dict = dict || {}; + } + + Translation.prototype.all = function () { + return this.dict; + }; + + Translation.prototype.get = function (key) { + return this.dict[key]; + }; + + Translation.prototype.extend = function (translation) { + this.dict = $.extend({}, translation.all(), this.dict); + }; + + // Static functions + + Translation._cache = {}; + + Translation.loadPath = function (path) { + if (!(path in Translation._cache)) { + var translations = require(path); + + Translation._cache[path] = translations; + } + + return new Translation(Translation._cache[path]); + }; + + return Translation; +}); + +S2.define('select2/diacritics',[ + +], function () { + var diacritics = { + '\u24B6': 'A', + '\uFF21': 'A', + '\u00C0': 'A', + '\u00C1': 'A', + '\u00C2': 'A', + '\u1EA6': 'A', + '\u1EA4': 'A', + '\u1EAA': 'A', + '\u1EA8': 'A', + '\u00C3': 'A', + '\u0100': 'A', + '\u0102': 'A', + '\u1EB0': 'A', + '\u1EAE': 'A', + '\u1EB4': 'A', + '\u1EB2': 'A', + '\u0226': 'A', + '\u01E0': 'A', + '\u00C4': 'A', + '\u01DE': 'A', + '\u1EA2': 'A', + '\u00C5': 'A', + '\u01FA': 'A', + '\u01CD': 'A', + '\u0200': 'A', + '\u0202': 'A', + '\u1EA0': 'A', + '\u1EAC': 'A', + '\u1EB6': 'A', + '\u1E00': 'A', + '\u0104': 'A', + '\u023A': 'A', + '\u2C6F': 'A', + '\uA732': 'AA', + '\u00C6': 'AE', + '\u01FC': 'AE', + '\u01E2': 'AE', + '\uA734': 'AO', + '\uA736': 'AU', + '\uA738': 'AV', + '\uA73A': 'AV', + '\uA73C': 'AY', + '\u24B7': 'B', + '\uFF22': 'B', + '\u1E02': 'B', + '\u1E04': 'B', + '\u1E06': 'B', + '\u0243': 'B', + '\u0182': 'B', + '\u0181': 'B', + '\u24B8': 'C', + '\uFF23': 'C', + '\u0106': 'C', + '\u0108': 'C', + '\u010A': 'C', + '\u010C': 'C', + '\u00C7': 'C', + '\u1E08': 'C', + '\u0187': 'C', + '\u023B': 'C', + '\uA73E': 'C', + '\u24B9': 'D', + '\uFF24': 'D', + '\u1E0A': 'D', + '\u010E': 'D', + '\u1E0C': 'D', + '\u1E10': 'D', + '\u1E12': 'D', + '\u1E0E': 'D', + '\u0110': 'D', + '\u018B': 'D', + '\u018A': 'D', + '\u0189': 'D', + '\uA779': 'D', + '\u01F1': 'DZ', + '\u01C4': 'DZ', + '\u01F2': 'Dz', + '\u01C5': 'Dz', + '\u24BA': 'E', + '\uFF25': 'E', + '\u00C8': 'E', + '\u00C9': 'E', + '\u00CA': 'E', + '\u1EC0': 'E', + '\u1EBE': 'E', + '\u1EC4': 'E', + '\u1EC2': 'E', + '\u1EBC': 'E', + '\u0112': 'E', + '\u1E14': 'E', + '\u1E16': 'E', + '\u0114': 'E', + '\u0116': 'E', + '\u00CB': 'E', + '\u1EBA': 'E', + '\u011A': 'E', + '\u0204': 'E', + '\u0206': 'E', + '\u1EB8': 'E', + '\u1EC6': 'E', + '\u0228': 'E', + '\u1E1C': 'E', + '\u0118': 'E', + '\u1E18': 'E', + '\u1E1A': 'E', + '\u0190': 'E', + '\u018E': 'E', + '\u24BB': 'F', + '\uFF26': 'F', + '\u1E1E': 'F', + '\u0191': 'F', + '\uA77B': 'F', + '\u24BC': 'G', + '\uFF27': 'G', + '\u01F4': 'G', + '\u011C': 'G', + '\u1E20': 'G', + '\u011E': 'G', + '\u0120': 'G', + '\u01E6': 'G', + '\u0122': 'G', + '\u01E4': 'G', + '\u0193': 'G', + '\uA7A0': 'G', + '\uA77D': 'G', + '\uA77E': 'G', + '\u24BD': 'H', + '\uFF28': 'H', + '\u0124': 'H', + '\u1E22': 'H', + '\u1E26': 'H', + '\u021E': 'H', + '\u1E24': 'H', + '\u1E28': 'H', + '\u1E2A': 'H', + '\u0126': 'H', + '\u2C67': 'H', + '\u2C75': 'H', + '\uA78D': 'H', + '\u24BE': 'I', + '\uFF29': 'I', + '\u00CC': 'I', + '\u00CD': 'I', + '\u00CE': 'I', + '\u0128': 'I', + '\u012A': 'I', + '\u012C': 'I', + '\u0130': 'I', + '\u00CF': 'I', + '\u1E2E': 'I', + '\u1EC8': 'I', + '\u01CF': 'I', + '\u0208': 'I', + '\u020A': 'I', + '\u1ECA': 'I', + '\u012E': 'I', + '\u1E2C': 'I', + '\u0197': 'I', + '\u24BF': 'J', + '\uFF2A': 'J', + '\u0134': 'J', + '\u0248': 'J', + '\u24C0': 'K', + '\uFF2B': 'K', + '\u1E30': 'K', + '\u01E8': 'K', + '\u1E32': 'K', + '\u0136': 'K', + '\u1E34': 'K', + '\u0198': 'K', + '\u2C69': 'K', + '\uA740': 'K', + '\uA742': 'K', + '\uA744': 'K', + '\uA7A2': 'K', + '\u24C1': 'L', + '\uFF2C': 'L', + '\u013F': 'L', + '\u0139': 'L', + '\u013D': 'L', + '\u1E36': 'L', + '\u1E38': 'L', + '\u013B': 'L', + '\u1E3C': 'L', + '\u1E3A': 'L', + '\u0141': 'L', + '\u023D': 'L', + '\u2C62': 'L', + '\u2C60': 'L', + '\uA748': 'L', + '\uA746': 'L', + '\uA780': 'L', + '\u01C7': 'LJ', + '\u01C8': 'Lj', + '\u24C2': 'M', + '\uFF2D': 'M', + '\u1E3E': 'M', + '\u1E40': 'M', + '\u1E42': 'M', + '\u2C6E': 'M', + '\u019C': 'M', + '\u24C3': 'N', + '\uFF2E': 'N', + '\u01F8': 'N', + '\u0143': 'N', + '\u00D1': 'N', + '\u1E44': 'N', + '\u0147': 'N', + '\u1E46': 'N', + '\u0145': 'N', + '\u1E4A': 'N', + '\u1E48': 'N', + '\u0220': 'N', + '\u019D': 'N', + '\uA790': 'N', + '\uA7A4': 'N', + '\u01CA': 'NJ', + '\u01CB': 'Nj', + '\u24C4': 'O', + '\uFF2F': 'O', + '\u00D2': 'O', + '\u00D3': 'O', + '\u00D4': 'O', + '\u1ED2': 'O', + '\u1ED0': 'O', + '\u1ED6': 'O', + '\u1ED4': 'O', + '\u00D5': 'O', + '\u1E4C': 'O', + '\u022C': 'O', + '\u1E4E': 'O', + '\u014C': 'O', + '\u1E50': 'O', + '\u1E52': 'O', + '\u014E': 'O', + '\u022E': 'O', + '\u0230': 'O', + '\u00D6': 'O', + '\u022A': 'O', + '\u1ECE': 'O', + '\u0150': 'O', + '\u01D1': 'O', + '\u020C': 'O', + '\u020E': 'O', + '\u01A0': 'O', + '\u1EDC': 'O', + '\u1EDA': 'O', + '\u1EE0': 'O', + '\u1EDE': 'O', + '\u1EE2': 'O', + '\u1ECC': 'O', + '\u1ED8': 'O', + '\u01EA': 'O', + '\u01EC': 'O', + '\u00D8': 'O', + '\u01FE': 'O', + '\u0186': 'O', + '\u019F': 'O', + '\uA74A': 'O', + '\uA74C': 'O', + '\u0152': 'OE', + '\u01A2': 'OI', + '\uA74E': 'OO', + '\u0222': 'OU', + '\u24C5': 'P', + '\uFF30': 'P', + '\u1E54': 'P', + '\u1E56': 'P', + '\u01A4': 'P', + '\u2C63': 'P', + '\uA750': 'P', + '\uA752': 'P', + '\uA754': 'P', + '\u24C6': 'Q', + '\uFF31': 'Q', + '\uA756': 'Q', + '\uA758': 'Q', + '\u024A': 'Q', + '\u24C7': 'R', + '\uFF32': 'R', + '\u0154': 'R', + '\u1E58': 'R', + '\u0158': 'R', + '\u0210': 'R', + '\u0212': 'R', + '\u1E5A': 'R', + '\u1E5C': 'R', + '\u0156': 'R', + '\u1E5E': 'R', + '\u024C': 'R', + '\u2C64': 'R', + '\uA75A': 'R', + '\uA7A6': 'R', + '\uA782': 'R', + '\u24C8': 'S', + '\uFF33': 'S', + '\u1E9E': 'S', + '\u015A': 'S', + '\u1E64': 'S', + '\u015C': 'S', + '\u1E60': 'S', + '\u0160': 'S', + '\u1E66': 'S', + '\u1E62': 'S', + '\u1E68': 'S', + '\u0218': 'S', + '\u015E': 'S', + '\u2C7E': 'S', + '\uA7A8': 'S', + '\uA784': 'S', + '\u24C9': 'T', + '\uFF34': 'T', + '\u1E6A': 'T', + '\u0164': 'T', + '\u1E6C': 'T', + '\u021A': 'T', + '\u0162': 'T', + '\u1E70': 'T', + '\u1E6E': 'T', + '\u0166': 'T', + '\u01AC': 'T', + '\u01AE': 'T', + '\u023E': 'T', + '\uA786': 'T', + '\uA728': 'TZ', + '\u24CA': 'U', + '\uFF35': 'U', + '\u00D9': 'U', + '\u00DA': 'U', + '\u00DB': 'U', + '\u0168': 'U', + '\u1E78': 'U', + '\u016A': 'U', + '\u1E7A': 'U', + '\u016C': 'U', + '\u00DC': 'U', + '\u01DB': 'U', + '\u01D7': 'U', + '\u01D5': 'U', + '\u01D9': 'U', + '\u1EE6': 'U', + '\u016E': 'U', + '\u0170': 'U', + '\u01D3': 'U', + '\u0214': 'U', + '\u0216': 'U', + '\u01AF': 'U', + '\u1EEA': 'U', + '\u1EE8': 'U', + '\u1EEE': 'U', + '\u1EEC': 'U', + '\u1EF0': 'U', + '\u1EE4': 'U', + '\u1E72': 'U', + '\u0172': 'U', + '\u1E76': 'U', + '\u1E74': 'U', + '\u0244': 'U', + '\u24CB': 'V', + '\uFF36': 'V', + '\u1E7C': 'V', + '\u1E7E': 'V', + '\u01B2': 'V', + '\uA75E': 'V', + '\u0245': 'V', + '\uA760': 'VY', + '\u24CC': 'W', + '\uFF37': 'W', + '\u1E80': 'W', + '\u1E82': 'W', + '\u0174': 'W', + '\u1E86': 'W', + '\u1E84': 'W', + '\u1E88': 'W', + '\u2C72': 'W', + '\u24CD': 'X', + '\uFF38': 'X', + '\u1E8A': 'X', + '\u1E8C': 'X', + '\u24CE': 'Y', + '\uFF39': 'Y', + '\u1EF2': 'Y', + '\u00DD': 'Y', + '\u0176': 'Y', + '\u1EF8': 'Y', + '\u0232': 'Y', + '\u1E8E': 'Y', + '\u0178': 'Y', + '\u1EF6': 'Y', + '\u1EF4': 'Y', + '\u01B3': 'Y', + '\u024E': 'Y', + '\u1EFE': 'Y', + '\u24CF': 'Z', + '\uFF3A': 'Z', + '\u0179': 'Z', + '\u1E90': 'Z', + '\u017B': 'Z', + '\u017D': 'Z', + '\u1E92': 'Z', + '\u1E94': 'Z', + '\u01B5': 'Z', + '\u0224': 'Z', + '\u2C7F': 'Z', + '\u2C6B': 'Z', + '\uA762': 'Z', + '\u24D0': 'a', + '\uFF41': 'a', + '\u1E9A': 'a', + '\u00E0': 'a', + '\u00E1': 'a', + '\u00E2': 'a', + '\u1EA7': 'a', + '\u1EA5': 'a', + '\u1EAB': 'a', + '\u1EA9': 'a', + '\u00E3': 'a', + '\u0101': 'a', + '\u0103': 'a', + '\u1EB1': 'a', + '\u1EAF': 'a', + '\u1EB5': 'a', + '\u1EB3': 'a', + '\u0227': 'a', + '\u01E1': 'a', + '\u00E4': 'a', + '\u01DF': 'a', + '\u1EA3': 'a', + '\u00E5': 'a', + '\u01FB': 'a', + '\u01CE': 'a', + '\u0201': 'a', + '\u0203': 'a', + '\u1EA1': 'a', + '\u1EAD': 'a', + '\u1EB7': 'a', + '\u1E01': 'a', + '\u0105': 'a', + '\u2C65': 'a', + '\u0250': 'a', + '\uA733': 'aa', + '\u00E6': 'ae', + '\u01FD': 'ae', + '\u01E3': 'ae', + '\uA735': 'ao', + '\uA737': 'au', + '\uA739': 'av', + '\uA73B': 'av', + '\uA73D': 'ay', + '\u24D1': 'b', + '\uFF42': 'b', + '\u1E03': 'b', + '\u1E05': 'b', + '\u1E07': 'b', + '\u0180': 'b', + '\u0183': 'b', + '\u0253': 'b', + '\u24D2': 'c', + '\uFF43': 'c', + '\u0107': 'c', + '\u0109': 'c', + '\u010B': 'c', + '\u010D': 'c', + '\u00E7': 'c', + '\u1E09': 'c', + '\u0188': 'c', + '\u023C': 'c', + '\uA73F': 'c', + '\u2184': 'c', + '\u24D3': 'd', + '\uFF44': 'd', + '\u1E0B': 'd', + '\u010F': 'd', + '\u1E0D': 'd', + '\u1E11': 'd', + '\u1E13': 'd', + '\u1E0F': 'd', + '\u0111': 'd', + '\u018C': 'd', + '\u0256': 'd', + '\u0257': 'd', + '\uA77A': 'd', + '\u01F3': 'dz', + '\u01C6': 'dz', + '\u24D4': 'e', + '\uFF45': 'e', + '\u00E8': 'e', + '\u00E9': 'e', + '\u00EA': 'e', + '\u1EC1': 'e', + '\u1EBF': 'e', + '\u1EC5': 'e', + '\u1EC3': 'e', + '\u1EBD': 'e', + '\u0113': 'e', + '\u1E15': 'e', + '\u1E17': 'e', + '\u0115': 'e', + '\u0117': 'e', + '\u00EB': 'e', + '\u1EBB': 'e', + '\u011B': 'e', + '\u0205': 'e', + '\u0207': 'e', + '\u1EB9': 'e', + '\u1EC7': 'e', + '\u0229': 'e', + '\u1E1D': 'e', + '\u0119': 'e', + '\u1E19': 'e', + '\u1E1B': 'e', + '\u0247': 'e', + '\u025B': 'e', + '\u01DD': 'e', + '\u24D5': 'f', + '\uFF46': 'f', + '\u1E1F': 'f', + '\u0192': 'f', + '\uA77C': 'f', + '\u24D6': 'g', + '\uFF47': 'g', + '\u01F5': 'g', + '\u011D': 'g', + '\u1E21': 'g', + '\u011F': 'g', + '\u0121': 'g', + '\u01E7': 'g', + '\u0123': 'g', + '\u01E5': 'g', + '\u0260': 'g', + '\uA7A1': 'g', + '\u1D79': 'g', + '\uA77F': 'g', + '\u24D7': 'h', + '\uFF48': 'h', + '\u0125': 'h', + '\u1E23': 'h', + '\u1E27': 'h', + '\u021F': 'h', + '\u1E25': 'h', + '\u1E29': 'h', + '\u1E2B': 'h', + '\u1E96': 'h', + '\u0127': 'h', + '\u2C68': 'h', + '\u2C76': 'h', + '\u0265': 'h', + '\u0195': 'hv', + '\u24D8': 'i', + '\uFF49': 'i', + '\u00EC': 'i', + '\u00ED': 'i', + '\u00EE': 'i', + '\u0129': 'i', + '\u012B': 'i', + '\u012D': 'i', + '\u00EF': 'i', + '\u1E2F': 'i', + '\u1EC9': 'i', + '\u01D0': 'i', + '\u0209': 'i', + '\u020B': 'i', + '\u1ECB': 'i', + '\u012F': 'i', + '\u1E2D': 'i', + '\u0268': 'i', + '\u0131': 'i', + '\u24D9': 'j', + '\uFF4A': 'j', + '\u0135': 'j', + '\u01F0': 'j', + '\u0249': 'j', + '\u24DA': 'k', + '\uFF4B': 'k', + '\u1E31': 'k', + '\u01E9': 'k', + '\u1E33': 'k', + '\u0137': 'k', + '\u1E35': 'k', + '\u0199': 'k', + '\u2C6A': 'k', + '\uA741': 'k', + '\uA743': 'k', + '\uA745': 'k', + '\uA7A3': 'k', + '\u24DB': 'l', + '\uFF4C': 'l', + '\u0140': 'l', + '\u013A': 'l', + '\u013E': 'l', + '\u1E37': 'l', + '\u1E39': 'l', + '\u013C': 'l', + '\u1E3D': 'l', + '\u1E3B': 'l', + '\u017F': 'l', + '\u0142': 'l', + '\u019A': 'l', + '\u026B': 'l', + '\u2C61': 'l', + '\uA749': 'l', + '\uA781': 'l', + '\uA747': 'l', + '\u01C9': 'lj', + '\u24DC': 'm', + '\uFF4D': 'm', + '\u1E3F': 'm', + '\u1E41': 'm', + '\u1E43': 'm', + '\u0271': 'm', + '\u026F': 'm', + '\u24DD': 'n', + '\uFF4E': 'n', + '\u01F9': 'n', + '\u0144': 'n', + '\u00F1': 'n', + '\u1E45': 'n', + '\u0148': 'n', + '\u1E47': 'n', + '\u0146': 'n', + '\u1E4B': 'n', + '\u1E49': 'n', + '\u019E': 'n', + '\u0272': 'n', + '\u0149': 'n', + '\uA791': 'n', + '\uA7A5': 'n', + '\u01CC': 'nj', + '\u24DE': 'o', + '\uFF4F': 'o', + '\u00F2': 'o', + '\u00F3': 'o', + '\u00F4': 'o', + '\u1ED3': 'o', + '\u1ED1': 'o', + '\u1ED7': 'o', + '\u1ED5': 'o', + '\u00F5': 'o', + '\u1E4D': 'o', + '\u022D': 'o', + '\u1E4F': 'o', + '\u014D': 'o', + '\u1E51': 'o', + '\u1E53': 'o', + '\u014F': 'o', + '\u022F': 'o', + '\u0231': 'o', + '\u00F6': 'o', + '\u022B': 'o', + '\u1ECF': 'o', + '\u0151': 'o', + '\u01D2': 'o', + '\u020D': 'o', + '\u020F': 'o', + '\u01A1': 'o', + '\u1EDD': 'o', + '\u1EDB': 'o', + '\u1EE1': 'o', + '\u1EDF': 'o', + '\u1EE3': 'o', + '\u1ECD': 'o', + '\u1ED9': 'o', + '\u01EB': 'o', + '\u01ED': 'o', + '\u00F8': 'o', + '\u01FF': 'o', + '\u0254': 'o', + '\uA74B': 'o', + '\uA74D': 'o', + '\u0275': 'o', + '\u0153': 'oe', + '\u01A3': 'oi', + '\u0223': 'ou', + '\uA74F': 'oo', + '\u24DF': 'p', + '\uFF50': 'p', + '\u1E55': 'p', + '\u1E57': 'p', + '\u01A5': 'p', + '\u1D7D': 'p', + '\uA751': 'p', + '\uA753': 'p', + '\uA755': 'p', + '\u24E0': 'q', + '\uFF51': 'q', + '\u024B': 'q', + '\uA757': 'q', + '\uA759': 'q', + '\u24E1': 'r', + '\uFF52': 'r', + '\u0155': 'r', + '\u1E59': 'r', + '\u0159': 'r', + '\u0211': 'r', + '\u0213': 'r', + '\u1E5B': 'r', + '\u1E5D': 'r', + '\u0157': 'r', + '\u1E5F': 'r', + '\u024D': 'r', + '\u027D': 'r', + '\uA75B': 'r', + '\uA7A7': 'r', + '\uA783': 'r', + '\u24E2': 's', + '\uFF53': 's', + '\u00DF': 's', + '\u015B': 's', + '\u1E65': 's', + '\u015D': 's', + '\u1E61': 's', + '\u0161': 's', + '\u1E67': 's', + '\u1E63': 's', + '\u1E69': 's', + '\u0219': 's', + '\u015F': 's', + '\u023F': 's', + '\uA7A9': 's', + '\uA785': 's', + '\u1E9B': 's', + '\u24E3': 't', + '\uFF54': 't', + '\u1E6B': 't', + '\u1E97': 't', + '\u0165': 't', + '\u1E6D': 't', + '\u021B': 't', + '\u0163': 't', + '\u1E71': 't', + '\u1E6F': 't', + '\u0167': 't', + '\u01AD': 't', + '\u0288': 't', + '\u2C66': 't', + '\uA787': 't', + '\uA729': 'tz', + '\u24E4': 'u', + '\uFF55': 'u', + '\u00F9': 'u', + '\u00FA': 'u', + '\u00FB': 'u', + '\u0169': 'u', + '\u1E79': 'u', + '\u016B': 'u', + '\u1E7B': 'u', + '\u016D': 'u', + '\u00FC': 'u', + '\u01DC': 'u', + '\u01D8': 'u', + '\u01D6': 'u', + '\u01DA': 'u', + '\u1EE7': 'u', + '\u016F': 'u', + '\u0171': 'u', + '\u01D4': 'u', + '\u0215': 'u', + '\u0217': 'u', + '\u01B0': 'u', + '\u1EEB': 'u', + '\u1EE9': 'u', + '\u1EEF': 'u', + '\u1EED': 'u', + '\u1EF1': 'u', + '\u1EE5': 'u', + '\u1E73': 'u', + '\u0173': 'u', + '\u1E77': 'u', + '\u1E75': 'u', + '\u0289': 'u', + '\u24E5': 'v', + '\uFF56': 'v', + '\u1E7D': 'v', + '\u1E7F': 'v', + '\u028B': 'v', + '\uA75F': 'v', + '\u028C': 'v', + '\uA761': 'vy', + '\u24E6': 'w', + '\uFF57': 'w', + '\u1E81': 'w', + '\u1E83': 'w', + '\u0175': 'w', + '\u1E87': 'w', + '\u1E85': 'w', + '\u1E98': 'w', + '\u1E89': 'w', + '\u2C73': 'w', + '\u24E7': 'x', + '\uFF58': 'x', + '\u1E8B': 'x', + '\u1E8D': 'x', + '\u24E8': 'y', + '\uFF59': 'y', + '\u1EF3': 'y', + '\u00FD': 'y', + '\u0177': 'y', + '\u1EF9': 'y', + '\u0233': 'y', + '\u1E8F': 'y', + '\u00FF': 'y', + '\u1EF7': 'y', + '\u1E99': 'y', + '\u1EF5': 'y', + '\u01B4': 'y', + '\u024F': 'y', + '\u1EFF': 'y', + '\u24E9': 'z', + '\uFF5A': 'z', + '\u017A': 'z', + '\u1E91': 'z', + '\u017C': 'z', + '\u017E': 'z', + '\u1E93': 'z', + '\u1E95': 'z', + '\u01B6': 'z', + '\u0225': 'z', + '\u0240': 'z', + '\u2C6C': 'z', + '\uA763': 'z', + '\u0386': '\u0391', + '\u0388': '\u0395', + '\u0389': '\u0397', + '\u038A': '\u0399', + '\u03AA': '\u0399', + '\u038C': '\u039F', + '\u038E': '\u03A5', + '\u03AB': '\u03A5', + '\u038F': '\u03A9', + '\u03AC': '\u03B1', + '\u03AD': '\u03B5', + '\u03AE': '\u03B7', + '\u03AF': '\u03B9', + '\u03CA': '\u03B9', + '\u0390': '\u03B9', + '\u03CC': '\u03BF', + '\u03CD': '\u03C5', + '\u03CB': '\u03C5', + '\u03B0': '\u03C5', + '\u03CE': '\u03C9', + '\u03C2': '\u03C3', + '\u2019': '\'' + }; + + return diacritics; +}); + +S2.define('select2/data/base',[ + '../utils' +], function (Utils) { + function BaseAdapter ($element, options) { + BaseAdapter.__super__.constructor.call(this); + } + + Utils.Extend(BaseAdapter, Utils.Observable); + + BaseAdapter.prototype.current = function (callback) { + throw new Error('The `current` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.query = function (params, callback) { + throw new Error('The `query` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.bind = function (container, $container) { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.destroy = function () { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.generateResultId = function (container, data) { + var id = container.id + '-result-'; + + id += Utils.generateChars(4); + + if (data.id != null) { + id += '-' + data.id.toString(); + } else { + id += '-' + Utils.generateChars(4); + } + return id; + }; + + return BaseAdapter; +}); + +S2.define('select2/data/select',[ + './base', + '../utils', + 'jquery' +], function (BaseAdapter, Utils, $) { + function SelectAdapter ($element, options) { + this.$element = $element; + this.options = options; + + SelectAdapter.__super__.constructor.call(this); + } + + Utils.Extend(SelectAdapter, BaseAdapter); + + SelectAdapter.prototype.current = function (callback) { + var data = []; + var self = this; + + this.$element.find(':selected').each(function () { + var $option = $(this); + + var option = self.item($option); + + data.push(option); + }); + + callback(data); + }; + + SelectAdapter.prototype.select = function (data) { + var self = this; + + data.selected = true; + + // If data.element is a DOM node, use it instead + if ($(data.element).is('option')) { + data.element.selected = true; + + this.$element.trigger('change'); + + return; + } + + if (this.$element.prop('multiple')) { + this.current(function (currentData) { + var val = []; + + data = [data]; + data.push.apply(data, currentData); + + for (var d = 0; d < data.length; d++) { + var id = data[d].id; + + if ($.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + self.$element.trigger('change'); + }); + } else { + var val = data.id; + + this.$element.val(val); + this.$element.trigger('change'); + } + }; + + SelectAdapter.prototype.unselect = function (data) { + var self = this; + + if (!this.$element.prop('multiple')) { + return; + } + + data.selected = false; + + if ($(data.element).is('option')) { + data.element.selected = false; + + this.$element.trigger('change'); + + return; + } + + this.current(function (currentData) { + var val = []; + + for (var d = 0; d < currentData.length; d++) { + var id = currentData[d].id; + + if (id !== data.id && $.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + + self.$element.trigger('change'); + }); + }; + + SelectAdapter.prototype.bind = function (container, $container) { + var self = this; + + this.container = container; + + container.on('select', function (params) { + self.select(params.data); + }); + + container.on('unselect', function (params) { + self.unselect(params.data); + }); + }; + + SelectAdapter.prototype.destroy = function () { + // Remove anything added to child elements + this.$element.find('*').each(function () { + // Remove any custom data set by Select2 + Utils.RemoveData(this); + }); + }; + + SelectAdapter.prototype.query = function (params, callback) { + var data = []; + var self = this; + + var $options = this.$element.children(); + + $options.each(function () { + var $option = $(this); + + if (!$option.is('option') && !$option.is('optgroup')) { + return; + } + + var option = self.item($option); + + var matches = self.matches(params, option); + + if (matches !== null) { + data.push(matches); + } + }); + + callback({ + results: data + }); + }; + + SelectAdapter.prototype.addOptions = function ($options) { + Utils.appendMany(this.$element, $options); + }; + + SelectAdapter.prototype.option = function (data) { + var option; + + if (data.children) { + option = document.createElement('optgroup'); + option.label = data.text; + } else { + option = document.createElement('option'); + + if (option.textContent !== undefined) { + option.textContent = data.text; + } else { + option.innerText = data.text; + } + } + + if (data.id !== undefined) { + option.value = data.id; + } + + if (data.disabled) { + option.disabled = true; + } + + if (data.selected) { + option.selected = true; + } + + if (data.title) { + option.title = data.title; + } + + var $option = $(option); + + var normalizedData = this._normalizeItem(data); + normalizedData.element = option; + + // Override the option's data with the combined data + Utils.StoreData(option, 'data', normalizedData); + + return $option; + }; + + SelectAdapter.prototype.item = function ($option) { + var data = {}; + + data = Utils.GetData($option[0], 'data'); + + if (data != null) { + return data; + } + + if ($option.is('option')) { + data = { + id: $option.val(), + text: $option.text(), + disabled: $option.prop('disabled'), + selected: $option.prop('selected'), + title: $option.prop('title') + }; + } else if ($option.is('optgroup')) { + data = { + text: $option.prop('label'), + children: [], + title: $option.prop('title') + }; + + var $children = $option.children('option'); + var children = []; + + for (var c = 0; c < $children.length; c++) { + var $child = $($children[c]); + + var child = this.item($child); + + children.push(child); + } + + data.children = children; + } + + data = this._normalizeItem(data); + data.element = $option[0]; + + Utils.StoreData($option[0], 'data', data); + + return data; + }; + + SelectAdapter.prototype._normalizeItem = function (item) { + if (item !== Object(item)) { + item = { + id: item, + text: item + }; + } + + item = $.extend({}, { + text: '' + }, item); + + var defaults = { + selected: false, + disabled: false + }; + + if (item.id != null) { + item.id = item.id.toString(); + } + + if (item.text != null) { + item.text = item.text.toString(); + } + + if (item._resultId == null && item.id && this.container != null) { + item._resultId = this.generateResultId(this.container, item); + } + + return $.extend({}, defaults, item); + }; + + SelectAdapter.prototype.matches = function (params, data) { + var matcher = this.options.get('matcher'); + + return matcher(params, data); + }; + + return SelectAdapter; +}); + +S2.define('select2/data/array',[ + './select', + '../utils', + 'jquery' +], function (SelectAdapter, Utils, $) { + function ArrayAdapter ($element, options) { + var data = options.get('data') || []; + + ArrayAdapter.__super__.constructor.call(this, $element, options); + + this.addOptions(this.convertToOptions(data)); + } + + Utils.Extend(ArrayAdapter, SelectAdapter); + + ArrayAdapter.prototype.select = function (data) { + var $option = this.$element.find('option').filter(function (i, elm) { + return elm.value == data.id.toString(); + }); + + if ($option.length === 0) { + $option = this.option(data); + + this.addOptions($option); + } + + ArrayAdapter.__super__.select.call(this, data); + }; + + ArrayAdapter.prototype.convertToOptions = function (data) { + var self = this; + + var $existing = this.$element.find('option'); + var existingIds = $existing.map(function () { + return self.item($(this)).id; + }).get(); + + var $options = []; + + // Filter out all items except for the one passed in the argument + function onlyItem (item) { + return function () { + return $(this).val() == item.id; + }; + } + + for (var d = 0; d < data.length; d++) { + var item = this._normalizeItem(data[d]); + + // Skip items which were pre-loaded, only merge the data + if ($.inArray(item.id, existingIds) >= 0) { + var $existingOption = $existing.filter(onlyItem(item)); + + var existingData = this.item($existingOption); + var newData = $.extend(true, {}, item, existingData); + + var $newOption = this.option(newData); + + $existingOption.replaceWith($newOption); + + continue; + } + + var $option = this.option(item); + + if (item.children) { + var $children = this.convertToOptions(item.children); + + Utils.appendMany($option, $children); + } + + $options.push($option); + } + + return $options; + }; + + return ArrayAdapter; +}); + +S2.define('select2/data/ajax',[ + './array', + '../utils', + 'jquery' +], function (ArrayAdapter, Utils, $) { + function AjaxAdapter ($element, options) { + this.ajaxOptions = this._applyDefaults(options.get('ajax')); + + if (this.ajaxOptions.processResults != null) { + this.processResults = this.ajaxOptions.processResults; + } + + AjaxAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(AjaxAdapter, ArrayAdapter); + + AjaxAdapter.prototype._applyDefaults = function (options) { + var defaults = { + data: function (params) { + return $.extend({}, params, { + q: params.term + }); + }, + transport: function (params, success, failure) { + var $request = $.ajax(params); + + $request.then(success); + $request.fail(failure); + + return $request; + } + }; + + return $.extend({}, defaults, options, true); + }; + + AjaxAdapter.prototype.processResults = function (results) { + return results; + }; + + AjaxAdapter.prototype.query = function (params, callback) { + var matches = []; + var self = this; + + if (this._request != null) { + // JSONP requests cannot always be aborted + if ($.isFunction(this._request.abort)) { + this._request.abort(); + } + + this._request = null; + } + + var options = $.extend({ + type: 'GET' + }, this.ajaxOptions); + + if (typeof options.url === 'function') { + options.url = options.url.call(this.$element, params); + } + + if (typeof options.data === 'function') { + options.data = options.data.call(this.$element, params); + } + + function request () { + var $request = options.transport(options, function (data) { + var results = self.processResults(data, params); + + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. + if (!results || !results.results || !$.isArray(results.results)) { + console.error( + 'Select2: The AJAX results did not return an array in the ' + + '`results` key of the response.' + ); + } + } + + callback(results); + }, function () { + // Attempt to detect if a request was aborted + // Only works if the transport exposes a status property + if ('status' in $request && + ($request.status === 0 || $request.status === '0')) { + return; + } + + self.trigger('results:message', { + message: 'errorLoading' + }); + }); + + self._request = $request; + } + + if (this.ajaxOptions.delay && params.term != null) { + if (this._queryTimeout) { + window.clearTimeout(this._queryTimeout); + } + + this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); + } else { + request(); + } + }; + + return AjaxAdapter; +}); + +S2.define('select2/data/tags',[ + 'jquery' +], function ($) { + function Tags (decorated, $element, options) { + var tags = options.get('tags'); + + var createTag = options.get('createTag'); + + if (createTag !== undefined) { + this.createTag = createTag; + } + + var insertTag = options.get('insertTag'); + + if (insertTag !== undefined) { + this.insertTag = insertTag; + } + + decorated.call(this, $element, options); + + if ($.isArray(tags)) { + for (var t = 0; t < tags.length; t++) { + var tag = tags[t]; + var item = this._normalizeItem(tag); + + var $option = this.option(item); + + this.$element.append($option); + } + } + } + + Tags.prototype.query = function (decorated, params, callback) { + var self = this; + + this._removeOldTags(); + + if (params.term == null || params.page != null) { + decorated.call(this, params, callback); + return; + } + + function wrapper (obj, child) { + var data = obj.results; + + for (var i = 0; i < data.length; i++) { + var option = data[i]; + + var checkChildren = ( + option.children != null && + !wrapper({ + results: option.children + }, true) + ); + + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; + + if (checkText || checkChildren) { + if (child) { + return false; + } + + obj.data = data; + callback(obj); + + return; + } + } + + if (child) { + return true; + } + + var tag = self.createTag(params); + + if (tag != null) { + var $option = self.option(tag); + $option.attr('data-select2-tag', true); + + self.addOptions([$option]); + + self.insertTag(data, tag); + } + + obj.results = data; + + callback(obj); + } + + decorated.call(this, params, wrapper); + }; + + Tags.prototype.createTag = function (decorated, params) { + var term = $.trim(params.term); + + if (term === '') { + return null; + } + + return { + id: term, + text: term + }; + }; + + Tags.prototype.insertTag = function (_, data, tag) { + data.unshift(tag); + }; + + Tags.prototype._removeOldTags = function (_) { + var tag = this._lastTag; + + var $options = this.$element.find('option[data-select2-tag]'); + + $options.each(function () { + if (this.selected) { + return; + } + + $(this).remove(); + }); + }; + + return Tags; +}); + +S2.define('select2/data/tokenizer',[ + 'jquery' +], function ($) { + function Tokenizer (decorated, $element, options) { + var tokenizer = options.get('tokenizer'); + + if (tokenizer !== undefined) { + this.tokenizer = tokenizer; + } + + decorated.call(this, $element, options); + } + + Tokenizer.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + this.$search = container.dropdown.$search || container.selection.$search || + $container.find('.select2-search__field'); + }; + + Tokenizer.prototype.query = function (decorated, params, callback) { + var self = this; + + function createAndSelect (data) { + // Normalize the data object so we can use it for checks + var item = self._normalizeItem(data); + + // Check if the data object already exists as a tag + // Select it if it doesn't + var $existingOptions = self.$element.find('option').filter(function () { + return $(this).val() === item.id; + }); + + // If an existing option wasn't found for it, create the option + if (!$existingOptions.length) { + var $option = self.option(item); + $option.attr('data-select2-tag', true); + + self._removeOldTags(); + self.addOptions([$option]); + } + + // Select the item, now that we know there is an option for it + select(item); + } + + function select (data) { + self.trigger('select', { + data: data + }); + } + + params.term = params.term || ''; + + var tokenData = this.tokenizer(params, this.options, createAndSelect); + + if (tokenData.term !== params.term) { + // Replace the search term if we have the search box + if (this.$search.length) { + this.$search.val(tokenData.term); + this.$search.trigger('focus'); + } + + params.term = tokenData.term; + } + + decorated.call(this, params, callback); + }; + + Tokenizer.prototype.tokenizer = function (_, params, options, callback) { + var separators = options.get('tokenSeparators') || []; + var term = params.term; + var i = 0; + + var createTag = this.createTag || function (params) { + return { + id: params.term, + text: params.term + }; + }; + + while (i < term.length) { + var termChar = term[i]; + + if ($.inArray(termChar, separators) === -1) { + i++; + + continue; + } + + var part = term.substr(0, i); + var partParams = $.extend({}, params, { + term: part + }); + + var data = createTag(partParams); + + if (data == null) { + i++; + continue; + } + + callback(data); + + // Reset the term to not include the tokenized portion + term = term.substr(i + 1) || ''; + i = 0; + } + + return { + term: term + }; + }; + + return Tokenizer; +}); + +S2.define('select2/data/minimumInputLength',[ + +], function () { + function MinimumInputLength (decorated, $e, options) { + this.minimumInputLength = options.get('minimumInputLength'); + + decorated.call(this, $e, options); + } + + MinimumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (params.term.length < this.minimumInputLength) { + this.trigger('results:message', { + message: 'inputTooShort', + args: { + minimum: this.minimumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MinimumInputLength; +}); + +S2.define('select2/data/maximumInputLength',[ + +], function () { + function MaximumInputLength (decorated, $e, options) { + this.maximumInputLength = options.get('maximumInputLength'); + + decorated.call(this, $e, options); + } + + MaximumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (this.maximumInputLength > 0 && + params.term.length > this.maximumInputLength) { + this.trigger('results:message', { + message: 'inputTooLong', + args: { + maximum: this.maximumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MaximumInputLength; +}); + +S2.define('select2/data/maximumSelectionLength',[ + +], function (){ + function MaximumSelectionLength (decorated, $e, options) { + this.maximumSelectionLength = options.get('maximumSelectionLength'); + + decorated.call(this, $e, options); + } + + MaximumSelectionLength.prototype.query = + function (decorated, params, callback) { + var self = this; + + this.current(function (currentData) { + var count = currentData != null ? currentData.length : 0; + if (self.maximumSelectionLength > 0 && + count >= self.maximumSelectionLength) { + self.trigger('results:message', { + message: 'maximumSelected', + args: { + maximum: self.maximumSelectionLength + } + }); + return; + } + decorated.call(self, params, callback); + }); + }; + + return MaximumSelectionLength; +}); + +S2.define('select2/dropdown',[ + 'jquery', + './utils' +], function ($, Utils) { + function Dropdown ($element, options) { + this.$element = $element; + this.options = options; + + Dropdown.__super__.constructor.call(this); + } + + Utils.Extend(Dropdown, Utils.Observable); + + Dropdown.prototype.render = function () { + var $dropdown = $( + '' + + '' + + '' + ); + + $dropdown.attr('dir', this.options.get('dir')); + + this.$dropdown = $dropdown; + + return $dropdown; + }; + + Dropdown.prototype.bind = function () { + // Should be implemented in subclasses + }; + + Dropdown.prototype.position = function ($dropdown, $container) { + // Should be implemented in subclasses + }; + + Dropdown.prototype.destroy = function () { + // Remove the dropdown from the DOM + this.$dropdown.remove(); + }; + + return Dropdown; +}); + +S2.define('select2/dropdown/search',[ + 'jquery', + '../utils' +], function ($, Utils) { + function Search () { } + + Search.prototype.render = function (decorated) { + var $rendered = decorated.call(this); + + var $search = $( + '' + + '' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + $rendered.prepend($search); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + this.$search.on('keydown', function (evt) { + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + }); + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$search.on('input', function (evt) { + // Unbind the duplicated `keyup` event + $(this).off('keyup'); + }); + + this.$search.on('keyup input', function (evt) { + self.handleSearch(evt); + }); + + container.on('open', function () { + self.$search.attr('tabindex', 0); + + self.$search.trigger('focus'); + + window.setTimeout(function () { + self.$search.trigger('focus'); + }, 0); + }); + + container.on('close', function () { + self.$search.attr('tabindex', -1); + + self.$search.val(''); + self.$search.trigger('blur'); + }); + + container.on('focus', function () { + if (!container.isOpen()) { + self.$search.trigger('focus'); + } + }); + + container.on('results:all', function (params) { + if (params.query.term == null || params.query.term === '') { + var showSearch = self.showSearch(params); + + if (showSearch) { + self.$searchContainer.removeClass('select2-search--hide'); + } else { + self.$searchContainer.addClass('select2-search--hide'); + } + } + }); + }; + + Search.prototype.handleSearch = function (evt) { + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.showSearch = function (_, params) { + return true; + }; + + return Search; +}); + +S2.define('select2/dropdown/hidePlaceholder',[ + +], function () { + function HidePlaceholder (decorated, $element, options, dataAdapter) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options, dataAdapter); + } + + HidePlaceholder.prototype.append = function (decorated, data) { + data.results = this.removePlaceholder(data.results); + + decorated.call(this, data); + }; + + HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + HidePlaceholder.prototype.removePlaceholder = function (_, data) { + var modifiedData = data.slice(0); + + for (var d = data.length - 1; d >= 0; d--) { + var item = data[d]; + + if (this.placeholder.id === item.id) { + modifiedData.splice(d, 1); + } + } + + return modifiedData; + }; + + return HidePlaceholder; +}); + +S2.define('select2/dropdown/infiniteScroll',[ + 'jquery' +], function ($) { + function InfiniteScroll (decorated, $element, options, dataAdapter) { + this.lastParams = {}; + + decorated.call(this, $element, options, dataAdapter); + + this.$loadingMore = this.createLoadingMore(); + this.loading = false; + } + + InfiniteScroll.prototype.append = function (decorated, data) { + this.$loadingMore.remove(); + this.loading = false; + + decorated.call(this, data); + + if (this.showLoadingMore(data)) { + this.$results.append(this.$loadingMore); + this.loadMoreIfNeeded(); + } + }; + + InfiniteScroll.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('query', function (params) { + self.lastParams = params; + self.loading = true; + }); + + container.on('query:append', function (params) { + self.lastParams = params; + self.loading = true; + }); + + this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); + }; + + InfiniteScroll.prototype.loadMoreIfNeeded = function () { + var isLoadMoreVisible = $.contains( + document.documentElement, + this.$loadingMore[0] + ); + + if (this.loading || !isLoadMoreVisible) { + return; + } + + var currentOffset = this.$results.offset().top + + this.$results.outerHeight(false); + var loadingMoreOffset = this.$loadingMore.offset().top + + this.$loadingMore.outerHeight(false); + + if (currentOffset + 50 >= loadingMoreOffset) { + this.loadMore(); + } + }; + + InfiniteScroll.prototype.loadMore = function () { + this.loading = true; + + var params = $.extend({}, {page: 1}, this.lastParams); + + params.page++; + + this.trigger('query:append', params); + }; + + InfiniteScroll.prototype.showLoadingMore = function (_, data) { + return data.pagination && data.pagination.more; + }; + + InfiniteScroll.prototype.createLoadingMore = function () { + var $option = $( + '
        • ' + ); + + var message = this.options.get('translations').get('loadingMore'); + + $option.html(message(this.lastParams)); + + return $option; + }; + + return InfiniteScroll; +}); + +S2.define('select2/dropdown/attachBody',[ + 'jquery', + '../utils' +], function ($, Utils) { + function AttachBody (decorated, $element, options) { + this.$dropdownParent = options.get('dropdownParent') || $(document.body); + + decorated.call(this, $element, options); + } + + AttachBody.prototype.bind = function (decorated, container, $container) { + var self = this; + + var setupResultsEvents = false; + + decorated.call(this, container, $container); + + container.on('open', function () { + self._showDropdown(); + self._attachPositioningHandler(container); + + if (!setupResultsEvents) { + setupResultsEvents = true; + + container.on('results:all', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:append', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + } + }); + + container.on('close', function () { + self._hideDropdown(); + self._detachPositioningHandler(container); + }); + + this.$dropdownContainer.on('mousedown', function (evt) { + evt.stopPropagation(); + }); + }; + + AttachBody.prototype.destroy = function (decorated) { + decorated.call(this); + + this.$dropdownContainer.remove(); + }; + + AttachBody.prototype.position = function (decorated, $dropdown, $container) { + // Clone all of the container classes + $dropdown.attr('class', $container.attr('class')); + + $dropdown.removeClass('select2'); + $dropdown.addClass('select2-container--open'); + + $dropdown.css({ + position: 'absolute', + top: -999999 + }); + + this.$container = $container; + }; + + AttachBody.prototype.render = function (decorated) { + var $container = $(''); + + var $dropdown = decorated.call(this); + $container.append($dropdown); + + this.$dropdownContainer = $container; + + return $container; + }; + + AttachBody.prototype._hideDropdown = function (decorated) { + this.$dropdownContainer.detach(); + }; + + AttachBody.prototype._attachPositioningHandler = + function (decorated, container) { + var self = this; + + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.each(function () { + Utils.StoreData(this, 'select2-scroll-position', { + x: $(this).scrollLeft(), + y: $(this).scrollTop() + }); + }); + + $watchers.on(scrollEvent, function (ev) { + var position = Utils.GetData(this, 'select2-scroll-position'); + $(this).scrollTop(position.y); + }); + + $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, + function (e) { + self._positionDropdown(); + self._resizeDropdown(); + }); + }; + + AttachBody.prototype._detachPositioningHandler = + function (decorated, container) { + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.off(scrollEvent); + + $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); + }; + + AttachBody.prototype._positionDropdown = function () { + var $window = $(window); + + var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); + + var newDirection = null; + + var offset = this.$container.offset(); + + offset.bottom = offset.top + this.$container.outerHeight(false); + + var container = { + height: this.$container.outerHeight(false) + }; + + container.top = offset.top; + container.bottom = offset.top + container.height; + + var dropdown = { + height: this.$dropdown.outerHeight(false) + }; + + var viewport = { + top: $window.scrollTop(), + bottom: $window.scrollTop() + $window.height() + }; + + var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); + var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); + + var css = { + left: offset.left, + top: container.bottom + }; + + // Determine what the parent element is to use for calculating the offset + var $offsetParent = this.$dropdownParent; + + // For statically positioned elements, we need to get the element + // that is determining the offset + if ($offsetParent.css('position') === 'static') { + $offsetParent = $offsetParent.offsetParent(); + } + + var parentOffset = $offsetParent.offset(); + + css.top -= parentOffset.top; + css.left -= parentOffset.left; + + if (!isCurrentlyAbove && !isCurrentlyBelow) { + newDirection = 'below'; + } + + if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { + newDirection = 'above'; + } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { + newDirection = 'below'; + } + + if (newDirection == 'above' || + (isCurrentlyAbove && newDirection !== 'below')) { + css.top = container.top - parentOffset.top - dropdown.height; + } + + if (newDirection != null) { + this.$dropdown + .removeClass('select2-dropdown--below select2-dropdown--above') + .addClass('select2-dropdown--' + newDirection); + this.$container + .removeClass('select2-container--below select2-container--above') + .addClass('select2-container--' + newDirection); + } + + this.$dropdownContainer.css(css); + }; + + AttachBody.prototype._resizeDropdown = function () { + var css = { + width: this.$container.outerWidth(false) + 'px' + }; + + if (this.options.get('dropdownAutoWidth')) { + css.minWidth = css.width; + css.position = 'relative'; + css.width = 'auto'; + } + + this.$dropdown.css(css); + }; + + AttachBody.prototype._showDropdown = function (decorated) { + this.$dropdownContainer.appendTo(this.$dropdownParent); + + this._positionDropdown(); + this._resizeDropdown(); + }; + + return AttachBody; +}); + +S2.define('select2/dropdown/minimumResultsForSearch',[ + +], function () { + function countResults (data) { + var count = 0; + + for (var d = 0; d < data.length; d++) { + var item = data[d]; + + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + } + + return count; + } + + function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { + this.minimumResultsForSearch = options.get('minimumResultsForSearch'); + + if (this.minimumResultsForSearch < 0) { + this.minimumResultsForSearch = Infinity; + } + + decorated.call(this, $element, options, dataAdapter); + } + + MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { + if (countResults(params.data.results) < this.minimumResultsForSearch) { + return false; + } + + return decorated.call(this, params); + }; + + return MinimumResultsForSearch; +}); + +S2.define('select2/dropdown/selectOnClose',[ + '../utils' +], function (Utils) { + function SelectOnClose () { } + + SelectOnClose.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('close', function (params) { + self._handleSelectOnClose(params); + }); + }; + + SelectOnClose.prototype._handleSelectOnClose = function (_, params) { + if (params && params.originalSelect2Event != null) { + var event = params.originalSelect2Event; + + // Don't select an item if the close event was triggered from a select or + // unselect event + if (event._type === 'select' || event._type === 'unselect') { + return; + } + } + + var $highlightedResults = this.getHighlightedResults(); + + // Only select highlighted results + if ($highlightedResults.length < 1) { + return; + } + + var data = Utils.GetData($highlightedResults[0], 'data'); + + // Don't re-select already selected resulte + if ( + (data.element != null && data.element.selected) || + (data.element == null && data.selected) + ) { + return; + } + + this.trigger('select', { + data: data + }); + }; + + return SelectOnClose; +}); + +S2.define('select2/dropdown/closeOnSelect',[ + +], function () { + function CloseOnSelect () { } + + CloseOnSelect.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function (evt) { + self._selectTriggered(evt); + }); + + container.on('unselect', function (evt) { + self._selectTriggered(evt); + }); + }; + + CloseOnSelect.prototype._selectTriggered = function (_, evt) { + var originalEvent = evt.originalEvent; + + // Don't close if the control key is being held + if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { + return; + } + + this.trigger('close', { + originalEvent: originalEvent, + originalSelect2Event: evt + }); + }; + + return CloseOnSelect; +}); + +S2.define('select2/i18n/en',[],function () { + // English + return { + errorLoading: function () { + return 'The results could not be loaded.'; + }, + inputTooLong: function (args) { + var overChars = args.input.length - args.maximum; + + var message = 'Please delete ' + overChars + ' character'; + + if (overChars != 1) { + message += 's'; + } + + return message; + }, + inputTooShort: function (args) { + var remainingChars = args.minimum - args.input.length; + + var message = 'Please enter ' + remainingChars + ' or more characters'; + + return message; + }, + loadingMore: function () { + return 'Loading more results…'; + }, + maximumSelected: function (args) { + var message = 'You can only select ' + args.maximum + ' item'; + + if (args.maximum != 1) { + message += 's'; + } + + return message; + }, + noResults: function () { + return 'No results found'; + }, + searching: function () { + return 'Searching…'; + }, + removeAllItems: function () { + return 'Remove all items'; + } + }; +}); + +S2.define('select2/defaults',[ + 'jquery', + 'require', + + './results', + + './selection/single', + './selection/multiple', + './selection/placeholder', + './selection/allowClear', + './selection/search', + './selection/eventRelay', + + './utils', + './translation', + './diacritics', + + './data/select', + './data/array', + './data/ajax', + './data/tags', + './data/tokenizer', + './data/minimumInputLength', + './data/maximumInputLength', + './data/maximumSelectionLength', + + './dropdown', + './dropdown/search', + './dropdown/hidePlaceholder', + './dropdown/infiniteScroll', + './dropdown/attachBody', + './dropdown/minimumResultsForSearch', + './dropdown/selectOnClose', + './dropdown/closeOnSelect', + + './i18n/en' +], function ($, require, + + ResultsList, + + SingleSelection, MultipleSelection, Placeholder, AllowClear, + SelectionSearch, EventRelay, + + Utils, Translation, DIACRITICS, + + SelectData, ArrayData, AjaxData, Tags, Tokenizer, + MinimumInputLength, MaximumInputLength, MaximumSelectionLength, + + Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, + AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + + EnglishTranslation) { + function Defaults () { + this.reset(); + } + + Defaults.prototype.apply = function (options) { + options = $.extend(true, {}, this.defaults, options); + + if (options.dataAdapter == null) { + if (options.ajax != null) { + options.dataAdapter = AjaxData; + } else if (options.data != null) { + options.dataAdapter = ArrayData; + } else { + options.dataAdapter = SelectData; + } + + if (options.minimumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MinimumInputLength + ); + } + + if (options.maximumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumInputLength + ); + } + + if (options.maximumSelectionLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumSelectionLength + ); + } + + if (options.tags) { + options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); + } + + if (options.tokenSeparators != null || options.tokenizer != null) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Tokenizer + ); + } + + if (options.query != null) { + var Query = require(options.amdBase + 'compat/query'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Query + ); + } + + if (options.initSelection != null) { + var InitSelection = require(options.amdBase + 'compat/initSelection'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + InitSelection + ); + } + } + + if (options.resultsAdapter == null) { + options.resultsAdapter = ResultsList; + + if (options.ajax != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + InfiniteScroll + ); + } + + if (options.placeholder != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + HidePlaceholder + ); + } + + if (options.selectOnClose) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + SelectOnClose + ); + } + } + + if (options.dropdownAdapter == null) { + if (options.multiple) { + options.dropdownAdapter = Dropdown; + } else { + var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); + + options.dropdownAdapter = SearchableDropdown; + } + + if (options.minimumResultsForSearch !== 0) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + MinimumResultsForSearch + ); + } + + if (options.closeOnSelect) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + CloseOnSelect + ); + } + + if ( + options.dropdownCssClass != null || + options.dropdownCss != null || + options.adaptDropdownCssClass != null + ) { + var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + DropdownCSS + ); + } + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + AttachBody + ); + } + + if (options.selectionAdapter == null) { + if (options.multiple) { + options.selectionAdapter = MultipleSelection; + } else { + options.selectionAdapter = SingleSelection; + } + + // Add the placeholder mixin if a placeholder was specified + if (options.placeholder != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + Placeholder + ); + } + + if (options.allowClear) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + AllowClear + ); + } + + if (options.multiple) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionSearch + ); + } + + if ( + options.containerCssClass != null || + options.containerCss != null || + options.adaptContainerCssClass != null + ) { + var ContainerCSS = require(options.amdBase + 'compat/containerCss'); + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + ContainerCSS + ); + } + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + EventRelay + ); + } + + if (typeof options.language === 'string') { + // Check if the language is specified with a region + if (options.language.indexOf('-') > 0) { + // Extract the region information if it is included + var languageParts = options.language.split('-'); + var baseLanguage = languageParts[0]; + + options.language = [options.language, baseLanguage]; + } else { + options.language = [options.language]; + } + } + + if ($.isArray(options.language)) { + var languages = new Translation(); + options.language.push('en'); + + var languageNames = options.language; + + for (var l = 0; l < languageNames.length; l++) { + var name = languageNames[l]; + var language = {}; + + try { + // Try to load it with the original name + language = Translation.loadPath(name); + } catch (e) { + try { + // If we couldn't load it, check if it wasn't the full path + name = this.defaults.amdLanguageBase + name; + language = Translation.loadPath(name); + } catch (ex) { + // The translation could not be loaded at all. Sometimes this is + // because of a configuration problem, other times this can be + // because of how Select2 helps load all possible translation files. + if (options.debug && window.console && console.warn) { + console.warn( + 'Select2: The language file for "' + name + '" could not be ' + + 'automatically loaded. A fallback will be used instead.' + ); + } + + continue; + } + } + + languages.extend(language); + } + + options.translations = languages; + } else { + var baseTranslation = Translation.loadPath( + this.defaults.amdLanguageBase + 'en' + ); + var customTranslation = new Translation(options.language); + + customTranslation.extend(baseTranslation); + + options.translations = customTranslation; + } + + return options; + }; + + Defaults.prototype.reset = function () { + function stripDiacritics (text) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return text.replace(/[^\u0000-\u007E]/g, match); + } + + function matcher (params, data) { + // Always return the object if there is nothing to compare + if ($.trim(params.term) === '') { + return data; + } + + // Do a recursive check for options with children + if (data.children && data.children.length > 0) { + // Clone the data object if there are children + // This is required as we modify the object to remove any non-matches + var match = $.extend(true, {}, data); + + // Check each child of the option + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + var matches = matcher(params, child); + + // If there wasn't a match, remove the object in the array + if (matches == null) { + match.children.splice(c, 1); + } + } + + // If any children matched, return the new object + if (match.children.length > 0) { + return match; + } + + // If there were no matching children, check just the plain object + return matcher(params, match); + } + + var original = stripDiacritics(data.text).toUpperCase(); + var term = stripDiacritics(params.term).toUpperCase(); + + // Check if the text contains the term + if (original.indexOf(term) > -1) { + return data; + } + + // If it doesn't contain the term, don't return anything + return null; + } + + this.defaults = { + amdBase: './', + amdLanguageBase: './i18n/', + closeOnSelect: true, + debug: false, + dropdownAutoWidth: false, + escapeMarkup: Utils.escapeMarkup, + language: EnglishTranslation, + matcher: matcher, + minimumInputLength: 0, + maximumInputLength: 0, + maximumSelectionLength: 0, + minimumResultsForSearch: 0, + selectOnClose: false, + scrollAfterSelect: false, + sorter: function (data) { + return data; + }, + templateResult: function (result) { + return result.text; + }, + templateSelection: function (selection) { + return selection.text; + }, + theme: 'default', + width: 'resolve' + }; + }; + + Defaults.prototype.set = function (key, value) { + var camelKey = $.camelCase(key); + + var data = {}; + data[camelKey] = value; + + var convertedData = Utils._convertData(data); + + $.extend(true, this.defaults, convertedData); + }; + + var defaults = new Defaults(); + + return defaults; +}); + +S2.define('select2/options',[ + 'require', + 'jquery', + './defaults', + './utils' +], function (require, $, Defaults, Utils) { + function Options (options, $element) { + this.options = options; + + if ($element != null) { + this.fromElement($element); + } + + this.options = Defaults.apply(this.options); + + if ($element && $element.is('input')) { + var InputCompat = require(this.get('amdBase') + 'compat/inputData'); + + this.options.dataAdapter = Utils.Decorate( + this.options.dataAdapter, + InputCompat + ); + } + } + + Options.prototype.fromElement = function ($e) { + var excludedData = ['select2']; + + if (this.options.multiple == null) { + this.options.multiple = $e.prop('multiple'); + } + + if (this.options.disabled == null) { + this.options.disabled = $e.prop('disabled'); + } + + if (this.options.language == null) { + if ($e.prop('lang')) { + this.options.language = $e.prop('lang').toLowerCase(); + } else if ($e.closest('[lang]').prop('lang')) { + this.options.language = $e.closest('[lang]').prop('lang'); + } + } + + if (this.options.dir == null) { + if ($e.prop('dir')) { + this.options.dir = $e.prop('dir'); + } else if ($e.closest('[dir]').prop('dir')) { + this.options.dir = $e.closest('[dir]').prop('dir'); + } else { + this.options.dir = 'ltr'; + } + } + + $e.prop('disabled', this.options.disabled); + $e.prop('multiple', this.options.multiple); + + if (Utils.GetData($e[0], 'select2Tags')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-select2-tags` attribute has been changed to ' + + 'use the `data-data` and `data-tags="true"` attributes and will be ' + + 'removed in future versions of Select2.' + ); + } + + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); + Utils.StoreData($e[0], 'tags', true); + } + + if (Utils.GetData($e[0], 'ajaxUrl')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-ajax-url` attribute has been changed to ' + + '`data-ajax--url` and support for the old attribute will be removed' + + ' in future versions of Select2.' + ); + } + + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); + } + + var dataset = {}; + + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + + // Pre-load all of the attributes which are prefixed with `data-` + for (var attr = 0; attr < $e[0].attributes.length; attr++) { + var attributeName = $e[0].attributes[attr].name; + var prefix = 'data-'; + + if (attributeName.substr(0, prefix.length) == prefix) { + // Get the contents of the attribute after `data-` + var dataName = attributeName.substring(prefix.length); + + // Get the data contents from the consistent source + // This is more than likely the jQuery data helper + var dataValue = Utils.GetData($e[0], dataName); + + // camelCase the attribute name to match the spec + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); + + // Store the data attribute contents into the dataset since + dataset[camelDataName] = dataValue; + } + } + + // Prefer the element's `dataset` attribute if it exists + // jQuery 1.x does not correctly handle data attributes with multiple dashes + if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { + dataset = $.extend(true, {}, $e[0].dataset, dataset); + } + + // Prefer our internal data cache if it exists + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); + + data = Utils._convertData(data); + + for (var key in data) { + if ($.inArray(key, excludedData) > -1) { + continue; + } + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], data[key]); + } else { + this.options[key] = data[key]; + } + } + + return this; + }; + + Options.prototype.get = function (key) { + return this.options[key]; + }; + + Options.prototype.set = function (key, val) { + this.options[key] = val; + }; + + return Options; +}); + +S2.define('select2/core',[ + 'jquery', + './options', + './utils', + './keys' +], function ($, Options, Utils, KEYS) { + var Select2 = function ($element, options) { + if (Utils.GetData($element[0], 'select2') != null) { + Utils.GetData($element[0], 'select2').destroy(); + } + + this.$element = $element; + + this.id = this._generateId($element); + + options = options || {}; + + this.options = new Options(options, $element); + + Select2.__super__.constructor.call(this); + + // Set up the tabindex + + var tabindex = $element.attr('tabindex') || 0; + Utils.StoreData($element[0], 'old-tabindex', tabindex); + $element.attr('tabindex', '-1'); + + // Set up containers and adapters + + var DataAdapter = this.options.get('dataAdapter'); + this.dataAdapter = new DataAdapter($element, this.options); + + var $container = this.render(); + + this._placeContainer($container); + + var SelectionAdapter = this.options.get('selectionAdapter'); + this.selection = new SelectionAdapter($element, this.options); + this.$selection = this.selection.render(); + + this.selection.position(this.$selection, $container); + + var DropdownAdapter = this.options.get('dropdownAdapter'); + this.dropdown = new DropdownAdapter($element, this.options); + this.$dropdown = this.dropdown.render(); + + this.dropdown.position(this.$dropdown, $container); + + var ResultsAdapter = this.options.get('resultsAdapter'); + this.results = new ResultsAdapter($element, this.options, this.dataAdapter); + this.$results = this.results.render(); + + this.results.position(this.$results, this.$dropdown); + + // Bind events + + var self = this; + + // Bind the container to all of the adapters + this._bindAdapters(); + + // Register any DOM event handlers + this._registerDomEvents(); + + // Register any internal event handlers + this._registerDataEvents(); + this._registerSelectionEvents(); + this._registerDropdownEvents(); + this._registerResultsEvents(); + this._registerEvents(); + + // Set the initial state + this.dataAdapter.current(function (initialData) { + self.trigger('selection:update', { + data: initialData + }); + }); + + // Hide the original select + $element.addClass('select2-hidden-accessible'); + $element.attr('aria-hidden', 'true'); + + // Synchronize any monitored attributes + this._syncAttributes(); + + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). + $element.data('select2', this); + }; + + Utils.Extend(Select2, Utils.Observable); + + Select2.prototype._generateId = function ($element) { + var id = ''; + + if ($element.attr('id') != null) { + id = $element.attr('id'); + } else if ($element.attr('name') != null) { + id = $element.attr('name') + '-' + Utils.generateChars(2); + } else { + id = Utils.generateChars(4); + } + + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = 'select2-' + id; + + return id; + }; + + Select2.prototype._placeContainer = function ($container) { + $container.insertAfter(this.$element); + + var width = this._resolveWidth(this.$element, this.options.get('width')); + + if (width != null) { + $container.css('width', width); + } + }; + + Select2.prototype._resolveWidth = function ($element, method) { + var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; + + if (method == 'resolve') { + var styleWidth = this._resolveWidth($element, 'style'); + + if (styleWidth != null) { + return styleWidth; + } + + return this._resolveWidth($element, 'element'); + } + + if (method == 'element') { + var elementWidth = $element.outerWidth(false); + + if (elementWidth <= 0) { + return 'auto'; + } + + return elementWidth + 'px'; + } + + if (method == 'style') { + var style = $element.attr('style'); + + if (typeof(style) !== 'string') { + return null; + } + + var attrs = style.split(';'); + + for (var i = 0, l = attrs.length; i < l; i = i + 1) { + var attr = attrs[i].replace(/\s/g, ''); + var matches = attr.match(WIDTH); + + if (matches !== null && matches.length >= 1) { + return matches[1]; + } + } + + return null; + } + + if (method == 'computedstyle') { + var computedStyle = window.getComputedStyle($element[0]); + + return computedStyle.width; + } + + return method; + }; + + Select2.prototype._bindAdapters = function () { + this.dataAdapter.bind(this, this.$container); + this.selection.bind(this, this.$container); + + this.dropdown.bind(this, this.$container); + this.results.bind(this, this.$container); + }; + + Select2.prototype._registerDomEvents = function () { + var self = this; + + this.$element.on('change.select2', function () { + self.dataAdapter.current(function (data) { + self.trigger('selection:update', { + data: data + }); + }); + }); + + this.$element.on('focus.select2', function (evt) { + self.trigger('focus', evt); + }); + + this._syncA = Utils.bind(this._syncAttributes, this); + this._syncS = Utils.bind(this._syncSubtree, this); + + if (this.$element[0].attachEvent) { + this.$element[0].attachEvent('onpropertychange', this._syncA); + } + + var observer = window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + ; + + if (observer != null) { + this._observer = new observer(function (mutations) { + $.each(mutations, self._syncA); + $.each(mutations, self._syncS); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); + } else if (this.$element[0].addEventListener) { + this.$element[0].addEventListener( + 'DOMAttrModified', + self._syncA, + false + ); + this.$element[0].addEventListener( + 'DOMNodeInserted', + self._syncS, + false + ); + this.$element[0].addEventListener( + 'DOMNodeRemoved', + self._syncS, + false + ); + } + }; + + Select2.prototype._registerDataEvents = function () { + var self = this; + + this.dataAdapter.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerSelectionEvents = function () { + var self = this; + var nonRelayEvents = ['toggle', 'focus']; + + this.selection.on('toggle', function () { + self.toggleDropdown(); + }); + + this.selection.on('focus', function (params) { + self.focus(params); + }); + + this.selection.on('*', function (name, params) { + if ($.inArray(name, nonRelayEvents) !== -1) { + return; + } + + self.trigger(name, params); + }); + }; + + Select2.prototype._registerDropdownEvents = function () { + var self = this; + + this.dropdown.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerResultsEvents = function () { + var self = this; + + this.results.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerEvents = function () { + var self = this; + + this.on('open', function () { + self.$container.addClass('select2-container--open'); + }); + + this.on('close', function () { + self.$container.removeClass('select2-container--open'); + }); + + this.on('enable', function () { + self.$container.removeClass('select2-container--disabled'); + }); + + this.on('disable', function () { + self.$container.addClass('select2-container--disabled'); + }); + + this.on('blur', function () { + self.$container.removeClass('select2-container--focus'); + }); + + this.on('query', function (params) { + if (!self.isOpen()) { + self.trigger('open', {}); + } + + this.dataAdapter.query(params, function (data) { + self.trigger('results:all', { + data: data, + query: params + }); + }); + }); + + this.on('query:append', function (params) { + this.dataAdapter.query(params, function (data) { + self.trigger('results:append', { + data: data, + query: params + }); + }); + }); + + this.on('keypress', function (evt) { + var key = evt.which; + + if (self.isOpen()) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { + self.close(); + + evt.preventDefault(); + } else if (key === KEYS.ENTER) { + self.trigger('results:select', {}); + + evt.preventDefault(); + } else if ((key === KEYS.SPACE && evt.ctrlKey)) { + self.trigger('results:toggle', {}); + + evt.preventDefault(); + } else if (key === KEYS.UP) { + self.trigger('results:previous', {}); + + evt.preventDefault(); + } else if (key === KEYS.DOWN) { + self.trigger('results:next', {}); + + evt.preventDefault(); + } + } else { + if (key === KEYS.ENTER || key === KEYS.SPACE || + (key === KEYS.DOWN && evt.altKey)) { + self.open(); + + evt.preventDefault(); + } + } + }); + }; + + Select2.prototype._syncAttributes = function () { + this.options.set('disabled', this.$element.prop('disabled')); + + if (this.options.get('disabled')) { + if (this.isOpen()) { + this.close(); + } + + this.trigger('disable', {}); + } else { + this.trigger('enable', {}); + } + }; + + Select2.prototype._syncSubtree = function (evt, mutations) { + var changed = false; + var self = this; + + // Ignore any mutation events raised for elements that aren't options or + // optgroups. This handles the case when the select element is destroyed + if ( + evt && evt.target && ( + evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' + ) + ) { + return; + } + + if (!mutations) { + // If mutation events aren't supported, then we can only assume that the + // change affected the selections + changed = true; + } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { + for (var n = 0; n < mutations.addedNodes.length; n++) { + var node = mutations.addedNodes[n]; + + if (node.selected) { + changed = true; + } + } + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { + changed = true; + } + + // Only re-pull the data if we think there is a change + if (changed) { + this.dataAdapter.current(function (currentData) { + self.trigger('selection:update', { + data: currentData + }); + }); + } + }; + + /** + * Override the trigger method to automatically trigger pre-events when + * there are events that can be prevented. + */ + Select2.prototype.trigger = function (name, args) { + var actualTrigger = Select2.__super__.trigger; + var preTriggerMap = { + 'open': 'opening', + 'close': 'closing', + 'select': 'selecting', + 'unselect': 'unselecting', + 'clear': 'clearing' + }; + + if (args === undefined) { + args = {}; + } + + if (name in preTriggerMap) { + var preTriggerName = preTriggerMap[name]; + var preTriggerArgs = { + prevented: false, + name: name, + args: args + }; + + actualTrigger.call(this, preTriggerName, preTriggerArgs); + + if (preTriggerArgs.prevented) { + args.prevented = true; + + return; + } + } + + actualTrigger.call(this, name, args); + }; + + Select2.prototype.toggleDropdown = function () { + if (this.options.get('disabled')) { + return; + } + + if (this.isOpen()) { + this.close(); + } else { + this.open(); + } + }; + + Select2.prototype.open = function () { + if (this.isOpen()) { + return; + } + + this.trigger('query', {}); + }; + + Select2.prototype.close = function () { + if (!this.isOpen()) { + return; + } + + this.trigger('close', {}); + }; + + Select2.prototype.isOpen = function () { + return this.$container.hasClass('select2-container--open'); + }; + + Select2.prototype.hasFocus = function () { + return this.$container.hasClass('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container.addClass('select2-container--focus'); + this.trigger('focus', {}); + }; + + Select2.prototype.enable = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("enable")` method has been deprecated and will' + + ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' instead.' + ); + } + + if (args == null || args.length === 0) { + args = [true]; + } + + var disabled = !args[0]; + + this.$element.prop('disabled', disabled); + }; + + Select2.prototype.data = function () { + if (this.options.get('debug') && + arguments.length > 0 && window.console && console.warn) { + console.warn( + 'Select2: Data can no longer be set using `select2("data")`. You ' + + 'should consider setting the value instead using `$element.val()`.' + ); + } + + var data = []; + + this.dataAdapter.current(function (currentData) { + data = currentData; + }); + + return data; + }; + + Select2.prototype.val = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("val")` method has been deprecated and will be' + + ' removed in later Select2 versions. Use $element.val() instead.' + ); + } + + if (args == null || args.length === 0) { + return this.$element.val(); + } + + var newVal = args[0]; + + if ($.isArray(newVal)) { + newVal = $.map(newVal, function (obj) { + return obj.toString(); + }); + } + + this.$element.val(newVal).trigger('change'); + }; + + Select2.prototype.destroy = function () { + this.$container.remove(); + + if (this.$element[0].detachEvent) { + this.$element[0].detachEvent('onpropertychange', this._syncA); + } + + if (this._observer != null) { + this._observer.disconnect(); + this._observer = null; + } else if (this.$element[0].removeEventListener) { + this.$element[0] + .removeEventListener('DOMAttrModified', this._syncA, false); + this.$element[0] + .removeEventListener('DOMNodeInserted', this._syncS, false); + this.$element[0] + .removeEventListener('DOMNodeRemoved', this._syncS, false); + } + + this._syncA = null; + this._syncS = null; + + this.$element.off('.select2'); + this.$element.attr('tabindex', + Utils.GetData(this.$element[0], 'old-tabindex')); + + this.$element.removeClass('select2-hidden-accessible'); + this.$element.attr('aria-hidden', 'false'); + Utils.RemoveData(this.$element[0]); + this.$element.removeData('select2'); + + this.dataAdapter.destroy(); + this.selection.destroy(); + this.dropdown.destroy(); + this.results.destroy(); + + this.dataAdapter = null; + this.selection = null; + this.dropdown = null; + this.results = null; + }; + + Select2.prototype.render = function () { + var $container = $( + '' + + '' + + '' + + '' + ); + + $container.attr('dir', this.options.get('dir')); + + this.$container = $container; + + this.$container.addClass('select2-container--' + this.options.get('theme')); + + Utils.StoreData($container[0], 'element', this.$element); + + return $container; + }; + + return Select2; +}); + +S2.define('jquery-mousewheel',[ + 'jquery' +], function ($) { + // Used to shim jQuery.mousewheel for non-full builds. + return $; +}); + +S2.define('jquery.select2',[ + 'jquery', + 'jquery-mousewheel', + + './select2/core', + './select2/defaults', + './select2/utils' +], function ($, _, Select2, Defaults, Utils) { + if ($.fn.select2 == null) { + // All methods that should return the element + var thisMethods = ['open', 'close', 'destroy']; + + $.fn.select2 = function (options) { + options = options || {}; + + if (typeof options === 'object') { + this.each(function () { + var instanceOptions = $.extend(true, {}, options); + + var instance = new Select2($(this), instanceOptions); + }); + + return this; + } else if (typeof options === 'string') { + var ret; + var args = Array.prototype.slice.call(arguments, 1); + + this.each(function () { + var instance = Utils.GetData(this, 'select2'); + + if (instance == null && window.console && console.error) { + console.error( + 'The select2(\'' + options + '\') method was called on an ' + + 'element that is not using Select2.' + ); + } + + ret = instance[options].apply(instance, args); + }); + + // Check if we should be returning `this` + if ($.inArray(options, thisMethods) > -1) { + return this; + } + + return ret; + } else { + throw new Error('Invalid arguments for Select2: ' + options); + } + }; + } + + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = Defaults; + } + + return Select2; +}); + + // Return the AMD loader configuration so it can be used outside of this file + return { + define: S2.define, + require: S2.require + }; +}()); + + // Autoload the jQuery bindings + // We know that all of the modules exist above this, so we're safe + var select2 = S2.require('jquery.select2'); + + // Hold the AMD module references on the jQuery function that was just loaded + // This allows Select2 to use the internal loader outside of this file, such + // as in the language files. + jQuery.fn.select2.amd = S2; + + // Return the Select2 instance for anyone who is importing it. + return select2; +})); diff --git a/app/assets/javascripts/select2.min.js b/app/assets/javascripts/select2.min.js new file mode 100644 index 000000000..9def5ae32 --- /dev/null +++ b/app/assets/javascripts/select2.min.js @@ -0,0 +1,2 @@ +/*! Select2 4.0.8 | https://github.com/select2/select2/blob/master/LICENSE.md */ +!function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)}(function(u){var e=function(){if(u&&u.fn&&u.fn.select2&&u.fn.select2.amd)var e=u.fn.select2.amd;var t,n,r,h,o,s,f,g,m,v,y,_,i,a,w;function b(e,t){return i.call(e,t)}function l(e,t){var n,r,i,o,s,a,l,c,u,d,p,h=t&&t.split("/"),f=y.map,g=f&&f["*"]||{};if(e){for(s=(e=e.split("/")).length-1,y.nodeIdCompat&&w.test(e[s])&&(e[s]=e[s].replace(w,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),u=0;u":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t]},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
            ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h('
          • '),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=(e.id,e.id+"-results");this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex)}),e.on("disable",function(){n.$selection.attr("tabindex","-1")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){n(this);this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r),n.attr("title",t.title||t.text)}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,a){function n(e,t){n.__super__.constructor.apply(this,arguments)}return a.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
              '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.options.get("disabled")){var t=i(this).parent(),n=a.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
            • ×
            • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,s,a){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){r.$search.attr("aria-activedescendant",e.id)}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===a.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.query=function(n,r,i){var o=this;this.current(function(e){var t=null!=e?e.length:0;0=o.maximumSelectionLength?o.trigger("results:message",{message:"maximumSelected",args:{maximum:o.maximumSelectionLength}}):n.call(o,r,i)})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(i,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=i('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){i(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
            • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=n.get("dropdownParent")||f(document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this,i=!1;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),i||(i=!0,t.on("results:all",function(){r._positionDropdown(),r._resizeDropdown()}),t.on("results:append",function(){r._positionDropdown(),r._resizeDropdown()}))}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h=p.offset();d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1 .content { + flex: 1; + font-size: 14px; + + .box { + padding: 20px; + border-radius: 5px; + background: #fff; + } + } + + /* 面包屑 */ + .breadcrumb { + padding-left: 5px; + font-size: 20px; + background: unset; + } + + /* 内容表格 */ + table { + table-layout: fixed; + + td { + vertical-align: middle; + } + + tr { + &.no-data { + &:hover { + color: darkgrey; + background: unset; + } + + & > td { + text-align: center; + height: 300px; + } + } + } + } + + .action-container { + .action { + padding: 0 3px; + } + } + + /* 分页 */ + .paginate-container { + margin-top: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .paginate-total { + margin-bottom: 10px; + color: darkgrey; + } + + .pagination { + margin-bottom: 0px; + } + } + + /* 搜索表单 */ + .search-form-container { + display: flex; + margin-bottom: 20px; + + .search-form { + flex: 1; + + * { font-size: 14px; } + + select, input { + margin-right: 10px; + font-size: 14px; + } + } + } + + .global-error { + color: grey; + min-height: 300px; + + &-code { + font-size: 80px; + } + + &-text { + font-size: 24px; + } + } +} + diff --git a/app/assets/stylesheets/admins/daily-school-statistics.scss b/app/assets/stylesheets/admins/daily-school-statistics.scss new file mode 100644 index 000000000..d54881b77 --- /dev/null +++ b/app/assets/stylesheets/admins/daily-school-statistics.scss @@ -0,0 +1,5 @@ +.admins-daily-school-statistics-index-page { + .daily-school-statistic-list-container { + text-align: center; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/admins/school-statistics.scss b/app/assets/stylesheets/admins/school-statistics.scss new file mode 100644 index 000000000..2bbcef5a3 --- /dev/null +++ b/app/assets/stylesheets/admins/school-statistics.scss @@ -0,0 +1,43 @@ +.admins-school-statistics-index-page { + .school-statistic-list-form { + .time-select { + flex: 1; + } + + .type-box { + .btn { margin: 0 5px; } + } + + .search-input { + width: 220px; + } + + .contrast-date-container { + display: flex; + align-items: center; + } + } + + .school-statistic-list-container { + .contrast-column-select { + position: absolute; + right: 30px; + top: 15px; + width: 130px; + } + + .relative { + position: relative; + } + + .right-border::after { + position: absolute; + top: 10px; + right: 0; + content: ''; + width: 0; + height: 20px; + border-right: 1px solid #000; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/admins/sidebar.scss b/app/assets/stylesheets/admins/sidebar.scss new file mode 100644 index 000000000..be7da93ac --- /dev/null +++ b/app/assets/stylesheets/admins/sidebar.scss @@ -0,0 +1,215 @@ +#sidebar { + min-width: 200px; + max-width: 200px; + background: #272822; + color: #fff; + transition: all 0.5s; + overflow-y: scroll; + + &::-webkit-scrollbar { + display:none + } + + &.active { + min-width: 60px; + max-width: 60px; + text-align: center; + + .sidebar-header { + padding: 10px; + display: flex; + flex-direction: column; + + &-logo { + padding-left: 5px; + overflow: hidden; + margin-bottom: 10px; + } + } + + ul li a { + padding: 10px; + text-align: center; + font-size: 0.85em; + display: flex; + justify-content: center; + + span { display: none } + + i { + margin-right: 0; + display: block; + font-size: 1.8em; + margin-bottom: 5px; + width: 30px; + height: 20px; + } + } + + .dropdown-toggle::after { + top: auto; + bottom: 10px; + right: 50%; + -webkit-transform: translateX(50%); + -ms-transform: translateX(50%); + transform: translateX(50%); + } + + ul ul a { + padding: 10px !important; + + span { display: none } + + i { + margin-left: 0px; + display: block; + font-size: 0.8em; + width: 30px; + height: 10px; + } + } + } + + .sidebar-header { + padding: 20px; + background: #272822; + display: flex; + flex-direction: row; + justify-content: space-between; + } + + #sidebarCollapse { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + text-align: right; + + &.active { + width: 40px; + height: 30px; + background: #3f3f3f; + border: 1px solid grey; + border-radius: 3px; + + i.fold { display: none; } + i.unfold { display: block; } + } + + i.fold { + display: block; + } + i.unfold { display: none; } + } + + a, a:hover, a:focus { + color: inherit; + text-decoration: none; + transition: all 0.3s; + } + + & > ul > li > a > i { + width: 14px; + height: 14px; + } + + ul { + &.components { + padding: 20px 0; + border-bottom: 1px solid #3f3f3f; + } + + p { + color: #fff; + padding: 10px; + } + + li > a { + padding: 10px; + font-size: 1em; + display: block; + text-align: left; + + i { + margin-right: 10px; + font-size: 1em; + margin-bottom: 5px; + } + } + + li a { + &:hover, &.active { + color: #fff; + background: #276891; + } + } + + li.active > a, a[aria-expanded="true"] { + color: #fff; + //background: #276891; + } + + ul a { + font-size: 0.9em !important; + padding-left: 30px !important; + background: #3f3f3f; + } + } +} + +@media (max-width: 768px) { + #sidebar { + &.active { + padding: 10px 5px; + min-width: 40px; + max-width: 40px; + text-align: center; + margin-left: 0; + transform: none; + + .sidebar-header { + padding: 0px; + + .sidebar-header-logo { + display: none; + } + + #sidebarCollapse { + width: 30px; + height: 20px; + } + } + + ul li a { + padding: 10px; + font-size: 0.85em; + + i { + margin-right: 0; + display: block; + margin-bottom: 5px; + } + } + + & > ul > li > a > i { + font-size: 1.8em; + } + + ul ul a { + padding: 10px !important; + } + } + + .sidebar-header { + } + } + + .dropdown-toggle::after { + top: auto; + bottom: 10px; + right: 50%; + -webkit-transform: translateX(50%); + -ms-transform: translateX(50%); + transform: translateX(50%); + } +} diff --git a/app/assets/stylesheets/admins/users.scss b/app/assets/stylesheets/admins/users.scss new file mode 100644 index 000000000..925917bcc --- /dev/null +++ b/app/assets/stylesheets/admins/users.scss @@ -0,0 +1,36 @@ +.admins-users-index-page { + .user-list-form { + } + + .users-list-container { + text-align: center; + } +} +.admins-users-edit-page, .admins-users-update-page { + .user-edit-container { + .user-info { + &-content { + padding-top: 5px; + padding-bottom: 5px; + height: 80px; + } + + &-name { + flex: 2; + font-size: 16px; + } + + &-auth { + flex: 1; + + i.fa { + margin-right: 10px; + font-size: 16px; + width: 16px; + height: 16px; + text-align: center; + } + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 000000000..b0692d898 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1 @@ +@import "bootstrap"; \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap-datepicker.scss b/app/assets/stylesheets/bootstrap-datepicker.scss new file mode 100644 index 000000000..c8a35bb38 --- /dev/null +++ b/app/assets/stylesheets/bootstrap-datepicker.scss @@ -0,0 +1,477 @@ +/*! + * Datepicker for Bootstrap v1.9.0 (https://github.com/uxsolutions/bootstrap-datepicker) + * + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker-rtl { + direction: rtl; +} +.datepicker-rtl.dropdown-menu { + left: auto; +} +.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #fff; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #08c, #0044cc); + background-image: -ms-linear-gradient(to bottom, #08c, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #08c, #0044cc); + background-image: -o-linear-gradient(to bottom, #08c, #0044cc); + background-image: linear-gradient(to bottom, #08c, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover, +.datepicker table tr td span.focused { + background: #eee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #08c, #0044cc); + background-image: -ms-linear-gradient(to bottom, #08c, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #08c, #0044cc); + background-image: -o-linear-gradient(to bottom, #08c, #0044cc); + background-image: linear-gradient(to bottom, #08c, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eee; +} +.datepicker .prev.disabled, +.datepicker .next.disabled { + visibility: hidden; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} +/*# sourceMappingURL=bootstrap-datepicker.css.map */ \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap-datepicker.standalone.scss b/app/assets/stylesheets/bootstrap-datepicker.standalone.scss new file mode 100644 index 000000000..0e7014851 --- /dev/null +++ b/app/assets/stylesheets/bootstrap-datepicker.standalone.scss @@ -0,0 +1,510 @@ +/*! + * Datepicker for Bootstrap v1.9.0 (https://github.com/uxsolutions/bootstrap-datepicker) + * + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker-rtl { + direction: rtl; +} +.datepicker-rtl.dropdown-menu { + left: auto; +} +.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #fff; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #08c, #0044cc); + background-image: -ms-linear-gradient(to bottom, #08c, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #08c, #0044cc); + background-image: -o-linear-gradient(to bottom, #08c, #0044cc); + background-image: linear-gradient(to bottom, #08c, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover, +.datepicker table tr td span.focused { + background: #eee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #08c, #0044cc); + background-image: -ms-linear-gradient(to bottom, #08c, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #08c, #0044cc); + background-image: -o-linear-gradient(to bottom, #08c, #0044cc); + background-image: linear-gradient(to bottom, #08c, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eee; +} +.datepicker .prev.disabled, +.datepicker .next.disabled { + visibility: hidden; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 20px; + padding: 4px 5px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 20px; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 4px 5px; +} +/*# sourceMappingURL=bootstrap-datepicker.standalone.css.map */ \ No newline at end of file diff --git a/app/assets/stylesheets/select2-bootstrap4.min.scss b/app/assets/stylesheets/select2-bootstrap4.min.scss new file mode 100644 index 000000000..f84b4090a --- /dev/null +++ b/app/assets/stylesheets/select2-bootstrap4.min.scss @@ -0,0 +1 @@ +.select2-container--bootstrap4 .select2-selection--single{height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--single .select2-selection__placeholder{color:#757575;line-height:calc(1.5em + .75rem)}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow{position:absolute;top:50%;right:3px;width:20px}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow b{top:60%;border-color:#343a40 transparent transparent;border-style:solid;border-width:5px 4px 0;width:0;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute}.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered{line-height:calc(1.5em + .75rem)}.select2-search--dropdown .select2-search__field{border:1px solid #ced4da;border-radius:.25rem}.select2-results__message{color:#6c757d}.select2-container--bootstrap4 .select2-selection--multiple{min-height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__rendered{-webkit-box-sizing:border-box;box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice{color:#343a40;border:1px solid #bdc6d0;border-radius:.2rem;padding:0 5px 0 0;cursor:pointer;float:left;margin-top:.3em;margin-right:5px}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove{color:#bdc6d0;font-weight:700;margin-left:3px;margin-right:1px;padding-right:3px;padding-left:3px;float:left}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove:hover{color:#343a40}.select2-container{display:block}.select2-container :focus{outline:0}.input-group .select2-container--bootstrap4{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.input-group-prepend~.select2-container--bootstrap4 .select2-selection{border-top-left-radius:0;border-bottom-left-radius:0}.select2-container--bootstrap4 .select2-selection{border:1px solid #ced4da;border-radius:.25rem;width:100%}.select2-container--bootstrap4.select2-container--focus .select2-selection{border-color:#17a2b8;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.25);box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.select2-container--bootstrap4.select2-container--focus.select2-container--open .select2-selection{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-selection,.select2-container--bootstrap4.select2-container--disabled .select2-selection{background-color:#e9ecef;cursor:not-allowed;border-color:#ced4da;-webkit-box-shadow:none;box-shadow:none}.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-search__field,.select2-container--bootstrap4.select2-container--disabled .select2-search__field{background-color:transparent}form.was-validated select:invalid~.select2-container--bootstrap4 .select2-selection,select.is-invalid~.select2-container--bootstrap4 .select2-selection{border-color:#dc3545}form.was-validated select:valid~.select2-container--bootstrap4 .select2-selection,select.is-valid~.select2-container--bootstrap4 .select2-selection{border-color:#28a745}.select2-container--bootstrap4 .select2-dropdown{border-color:#ced4da;border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above{border-top:1px solid #ced4da;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.select2-container--bootstrap4 .select2-dropdown .select2-results__option[aria-selected=true]{background-color:#e9ecef}.select2-container--bootstrap4 .select2-results__option--highlighted,.select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true]{background-color:#007bff;color:#f8f9fa}.select2-container--bootstrap4 .select2-results__option[role=group]{padding:0}.select2-container--bootstrap4 .select2-results>.select2-results__options{max-height:15em;overflow-y:auto}.select2-container--bootstrap4 .select2-results__group{padding:6px;display:list-item;color:#6c757d}.select2-container--bootstrap4 .select2-selection__clear{width:1.2em;height:1.2em;line-height:1.15em;padding-left:.3em;margin-top:.5em;border-radius:100%;background-color:#6c757d;color:#f8f9fa;float:right;margin-right:.3em}.select2-container--bootstrap4 .select2-selection__clear:hover{background-color:#343a40} \ No newline at end of file diff --git a/app/assets/stylesheets/select2.min.scss b/app/assets/stylesheets/select2.min.scss new file mode 100644 index 000000000..dc2315ae0 --- /dev/null +++ b/app/assets/stylesheets/select2.min.scss @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index e62dcf6ed..46b3964c2 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -33,7 +33,7 @@ class AccountsController < ApplicationController uid_logger("start register: verifi_code is #{verifi_code}, code is #{code}, time is #{Time.now.to_i - verifi_code.try(:created_at).to_i}") # check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60) # todo 上线前请删除万能验证码"513231" - unless code == "513231" && request.host == "47.96.87.25" + unless code == "513231" && request.subdomain == "pre-newweb" return normal_status(-2, "验证码不正确") if verifi_code.try(:code) != code.strip return normal_status(-2, "验证码已失效") if !verifi_code&.effective? end diff --git a/app/controllers/admins/base_controller.rb b/app/controllers/admins/base_controller.rb new file mode 100644 index 000000000..6f89e7afa --- /dev/null +++ b/app/controllers/admins/base_controller.rb @@ -0,0 +1,38 @@ +class Admins::BaseController < ApplicationController + include Admins::PaginateHelper + include Admins::RenderHelper + include Admins::ErrorRescueHandler + + layout 'admin' + + before_action :require_login, :require_admin! + + after_action :rebind_event_if_ajax_render_partial + + private + + def require_login + return if User.current.logged? + + redirect_to "/login?back_url=#{CGI::escape(request.fullpath)}" + end + + def require_admin! + return if current_user.blank? || !current_user.logged? + return if current_user.admin_or_business? + + render_forbidden + end + + # 触发after ajax render partial hooks,执行一些因为局部刷新后失效的绑定事件 + def rebind_event_if_ajax_render_partial + return if request.format.symbol != :js + return if response.content_type != 'text/javascript' + + path = Rails.root.join('app/views/admins/shared/after_render_js_hook.js.erb') + return unless File.exists?(path) + + append_js = ERB.new(File.open(path).read).result + response.body += append_js + end +end \ No newline at end of file diff --git a/app/controllers/admins/daily_school_statistics_controller.rb b/app/controllers/admins/daily_school_statistics_controller.rb new file mode 100644 index 000000000..eb9c75fbc --- /dev/null +++ b/app/controllers/admins/daily_school_statistics_controller.rb @@ -0,0 +1,36 @@ +class Admins::DailySchoolStatisticsController < Admins::BaseController + def index + params[:sort_by] = params[:sort_by].presence || :teacher_count + params[:sort_direction] = params[:sort_direction].presence || :desc + + total_count, statistics = Admins::SchoolDailyStatisticService.call(params) + + @statistics = paginate statistics, total_count: total_count + + respond_to do |format| + format.html { load_statistic_total } + format.js + end + end + + def export + params[:per_page] = 10000 + _count, @schools = Admins::SchoolDailyStatisticService.call(params) + + filename = ['学校统计总表', params[:keyword], Time.zone.now.strftime('%Y%m%d%H%M%S')].join('-') << '.xlsx' + render xlsx: 'export', filename: filename + end + + private + + def load_statistic_total + @teacher_total = User.joins(:user_extension).where(user_extensions: { identity: :teacher }).count + @student_total = User.joins(:user_extension).where(user_extensions: { identity: :student }).count + @course_total = Course.count + @active_course_total = Course.where(is_end: false).count + @shixun_homework_total = HomeworkCommon.where(homework_type: 4).count + @other_homework_total = HomeworkCommon.where(homework_type: [1, 3]).count + @shixun_total = Shixun.count + @shixun_evaluate_total = SchoolReport.sum(:shixun_evaluate_count) + end +end \ No newline at end of file diff --git a/app/controllers/admins/dashboards_controller.rb b/app/controllers/admins/dashboards_controller.rb new file mode 100644 index 000000000..5d5d6b184 --- /dev/null +++ b/app/controllers/admins/dashboards_controller.rb @@ -0,0 +1,4 @@ +class Admins::DashboardsController < Admins::BaseController + def index + end +end \ No newline at end of file diff --git a/app/controllers/admins/school_statistics_controller.rb b/app/controllers/admins/school_statistics_controller.rb new file mode 100644 index 000000000..fdd10c70f --- /dev/null +++ b/app/controllers/admins/school_statistics_controller.rb @@ -0,0 +1,49 @@ +class Admins::SchoolStatisticsController < Admins::BaseController + before_action :contrast_column_select_options, only: [:contrast] + + def index + params[:data_type] ||= 'grow' + params[:sort_by] = params[:sort_by].presence || :teacher_increase_count + params[:sort_direction] = params[:sort_direction].presence || :desc + + service = Admins::StatisticSchoolDataGrowService.new(params) + @grow_summary = service.grow_summary + + total_count, statistics = service.call + + @statistics = paginate statistics, total_count: total_count + end + + def contrast + params[:contrast_column] = params[:contrast_column].presence || :teacher_increase_count + params[:sort_direction] ||= :desc + params[:sort_by] = :percentage + + # 无对比日期时直接返回无数据页面 + if useless_contrast_date_parameter? + @total_count = 0 + @statistics = paginate([]) + return + end + + total_count, statistics = Admins::StatisticSchoolContrastDataService.call(params) + + @statistics = paginate statistics, total_count: total_count + rescue Admins::StatisticSchoolContrastDataService::ParameterError + render_unprocessable_entity('参数错误') + end + + private + + def useless_contrast_date_parameter? + params[:begin_date].blank? && params[:end_date].blank? && + params[:other_begin_date].blank? &¶ms[:other_end_date].blank? + end + + def contrast_column_select_options + @select_options = + Admins::StatisticSchoolContrastDataService::CONTRAST_COLUMN_LIST.map do |column| + [I18n.t("school_daily_report.#{column}"), column] + end + end +end diff --git a/app/controllers/admins/users_controller.rb b/app/controllers/admins/users_controller.rb new file mode 100644 index 000000000..cdb64c71f --- /dev/null +++ b/app/controllers/admins/users_controller.rb @@ -0,0 +1,62 @@ +class Admins::UsersController < Admins::BaseController + def index + params[:sort_by] = params[:sort_by].presence || 'created_on' + params[:sort_direction] = params[:sort_direction].presence || 'desc' + + users = Admins::UserQuery.call(params) + @users = paginate users.includes(user_extension: :school) + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + + Admins::UpdateUserService.call(@user, update_params) + flash[:success] = '保存成功' + redirect_to edit_admins_user_path(@user) + rescue ActiveRecord::RecordInvalid + flash.now[:danger] = '保存失败' + render 'edit' + rescue Admins::UpdateUserService::Error => ex + flash.now[:danger] = ex.message + render 'edit' + end + + def destroy + User.find(params[:id]).destroy! + + render_delete_success + end + + def lock + User.find(params[:user_id]).lock! + + render_ok + end + + def unlock + User.find(params[:user_id]).activate! + + render_ok + end + + def reward_grade + user = User.find(params[:user_id]) + return render_unprocessable_entity('金币数量必须大于0') if params[:grade].to_i <= 0 + + RewardGradeService.call(user, container_id: user.id, container_type: 'Feedback', score: params[:grade].to_i, not_unique: true) + + render_ok(grade: user.grade) + end + + private + + def update_params + params.require(:user).permit(%i[lastname nickname gender identity technical_title student_id + mail phone location location_city school_id department_id admin business is_test + password professional_certification authentication]) + end +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5ba0db363..177eb1a42 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -40,7 +40,7 @@ class ApplicationController < ActionController::Base if @user_course_identity > Course::STUDENT && @course.is_public == 0 tip_exception(401, "..") unless User.current.logged? check_account - tip_exception(409, "您没有权限进入") + tip_exception(@course.excellent ? 410 : 409, "您没有权限进入") end uid_logger("###############user_course_identity:#{@user_course_identity}") end @@ -108,6 +108,7 @@ class ApplicationController < ActionController::Base def find_course return normal_status(2, '缺少course_id参数!') if params[:course_id].blank? @course = Course.find(params[:course_id]) + tip_exception(404, "") if @course.is_delete == 1 && !current_user.admin? rescue Exception => e tip_exception(e.message) end @@ -244,7 +245,8 @@ class ApplicationController < ActionController::Base # 测试版前端需求 - if request.host == "47.96.87.25" + logger.info("subdomain:#{request.subdomain}") + if request.subdomain == "pre-newweb" if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除 User.current = User.find 81403 elsif params[:debug] == 'student' @@ -568,7 +570,7 @@ class ApplicationController < ActionController::Base end def strf_date(date) - date.blank? ? '' : date.strftime("%Y-%m-%d") + date.blank? ? '' : date.to_date.strftime("%Y-%m-%d") end def logger_error(error) diff --git a/app/controllers/concerns/admins/error_rescue_handler.rb b/app/controllers/concerns/admins/error_rescue_handler.rb new file mode 100644 index 000000000..b1e29d5ce --- /dev/null +++ b/app/controllers/concerns/admins/error_rescue_handler.rb @@ -0,0 +1,24 @@ +module Admins::ErrorRescueHandler + extend ActiveSupport::Concern + + included do + rescue_from Exception, Educoder::TipException do |e| + raise e if Rails.env.development? + + Util.logger_error e + internal_server_error + end + + rescue_from ActionView::MissingTemplate, ActiveRecord::RecordNotFound, with: :render_not_found + rescue_from ActionController::ParameterMissing do + render_unprocessable_entity('参数缺失') + end + # form validation error + rescue_from ActiveModel::ValidationError do |ex| + render_unprocessable_entity(ex.model.errors.full_messages.join(',')) + end + rescue_from ActiveRecord::RecordInvalid do |ex| + render_unprocessable_entity(ex.record.errors.full_messages.join(',')) + end + end +end \ No newline at end of file diff --git a/app/controllers/concerns/admins/paginate_helper.rb b/app/controllers/concerns/admins/paginate_helper.rb new file mode 100644 index 000000000..da7652584 --- /dev/null +++ b/app/controllers/concerns/admins/paginate_helper.rb @@ -0,0 +1,24 @@ +module Admins::PaginateHelper + extend ActiveSupport::Concern + + def offset + (page - 1) * per_page + end + + def page + params[:page].to_i <= 0 ? 1 : params[:page].to_i + end + + def per_page + params[:per_page].to_i <= 0 || params[:per_page].to_i > 100 ? 20 : params[:per_page].to_i + end + alias_method :limit, :per_page + + def paginate(relations, total_count: nil) + if relations.is_a?(Array) + Kaminari.paginate_array(relations, limit: limit, offset: offset, total_count: total_count) + else + relations.page(page).per(per_page) + end + end +end \ No newline at end of file diff --git a/app/controllers/concerns/admins/render_helper.rb b/app/controllers/concerns/admins/render_helper.rb new file mode 100644 index 000000000..c77c0ad32 --- /dev/null +++ b/app/controllers/concerns/admins/render_helper.rb @@ -0,0 +1,47 @@ +module Admins::RenderHelper + extend ActiveSupport::Concern + + def render_forbidden + respond_to do |format| + format.html { redirect_to '/403' } + format.json { super } + end + end + + def render_not_found + respond_to do |format| + format.html { render 'admins/shared/404' } + format.js { render_js_error('资源未找到') } + format.json { render status: 404, json: { message: '资源未找到' } } + end + end + + def render_unprocessable_entity(message) + respond_to do |format| + format.html { render 'admins/shared/422' } + format.js { render_js_error(message) } + format.json { render status: 422, json: { message: message } } + end + end + alias_method :render_error, :render_unprocessable_entity + + def internal_server_error + respond_to do |format| + format.html { render 'admins/shared/500' } + format.js { render_js_error(message) } + format.json { render status: 500, json: { message: '系统错误' } } + end + end + + def render_js_template(template, **opts) + render({ template: template, formats: :js }.merge(opts)) + end + + def render_delete_success + render_js_template 'admins/shared/delete' + end + + def render_js_error(message) + render_js_template 'admins/shared/error', locals: { message: message } + end +end \ No newline at end of file diff --git a/app/controllers/concerns/git_helper.rb b/app/controllers/concerns/git_helper.rb index 6a3765401..eeb4671a4 100644 --- a/app/controllers/concerns/git_helper.rb +++ b/app/controllers/concerns/git_helper.rb @@ -34,8 +34,7 @@ module GitHelper rescue Exception => e Rails.logger.error(e.message) - Rails.logger.error("#####__________文档内容获取异常") - # raise Educoder::TipException.new("文档内容获取异常") + raise Educoder::TipException.new("文档内容获取异常") end end diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 14276b4dd..e29fa8d7d 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -16,23 +16,24 @@ class CoursesController < ApplicationController before_action :check_account, only: [:new, :create, :apply_to_join_course, :join_excellent_course] before_action :check_auth, except: [:index, :show, :students, :teachers, :board_list, :mine, :all_course_groups, :left_banner, :top_banner, :apply_to_join_course, :exit_course] - before_action :set_course, :user_course_identity, only: [:show, :update, :destroy, :settings, :set_invite_code_halt, - :set_public_or_private, :search_teacher_candidate, :teachers, :apply_teachers, - :top_banner, :left_banner, :add_teacher_popup, :add_teacher, - :graduation_group_list, :create_graduation_group, :join_graduation_group, - :course_group_list, :set_course_group, :change_course_admin, :change_course_teacher, - :delete_course_teacher, :teacher_application_review, :students, :all_course_groups, - :transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search, - :base_info, :get_historical_courses, :create_group_by_importing_file, - :attahcment_category_list,:export_member_scores_excel, :duplicate_course, - :switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course, - :informs, :update_informs, :join_excellent_course, :online_learning, - :update_task_position, :tasks_list] + before_action :set_course, only: [:show, :update, :destroy, :settings, :set_invite_code_halt, + :set_public_or_private, :search_teacher_candidate, :teachers, :apply_teachers, + :top_banner, :left_banner, :add_teacher_popup, :add_teacher, + :graduation_group_list, :create_graduation_group, :join_graduation_group, + :course_group_list, :set_course_group, :change_course_admin, :change_course_teacher, + :delete_course_teacher, :teacher_application_review, :students, :all_course_groups, + :transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search, + :base_info, :get_historical_courses, :create_group_by_importing_file, + :attahcment_category_list,:export_member_scores_excel, :duplicate_course, + :switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course, + :informs, :update_informs, :new_informs, :online_learning, :update_task_position, :tasks_list, :join_excellent_course] + before_action :user_course_identity, except: [:join_excellent_course, :index, :create, :new, :apply_to_join_course, + :search_course_list, :get_historical_course_students, :mine, :search_slim, :board_list] before_action :teacher_allowed, only: [:update, :destroy, :settings, :search_teacher_candidate, :transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup, :add_teacher] before_action :admin_allowed, only: [:set_invite_code_halt, :set_public_or_private, :change_course_admin, - :set_course_group, :create_group_by_importing_file, :update_informs, + :set_course_group, :create_group_by_importing_file, :update_informs, :new_informs, :update_task_position, :tasks_list] before_action :teacher_or_admin_allowed, only: [:graduation_group_list, :create_graduation_group, :join_graduation_group, :change_course_teacher, :export_member_scores_excel, :course_group_list, @@ -41,6 +42,7 @@ class CoursesController < ApplicationController before_action :find_board, only: :board_list before_action :validate_page_size, only: :mine before_action :course_tasks, only: [:tasks_list, :update_task_position] + before_action :validate_inform_params, only: [:update_informs, :new_informs] if RUBY_PLATFORM =~ /linux/ require 'simple_xlsx_reader' @@ -117,6 +119,7 @@ class CoursesController < ApplicationController # Get /courses/:id/settings # Edit Page def settings + @course_modules = @course.course_modules.where.not(module_type: 'activity') end # POST /courses @@ -156,6 +159,8 @@ class CoursesController < ApplicationController @course.subject.subject_members.where.not(user_id: current_user.id).each do |s_member| CourseMember.create!(course_id: @course.id, user_id: s_member.user_id, role: 2) end + + Inform.create(container: @course, description: @subject.learning_notes, name: "学习须知") end course_module_types = params[:course_module_types] @@ -177,7 +182,7 @@ class CoursesController < ApplicationController extra_params = Hash.new extra_params[:school_id] = @school.id - if @course.is_end && (course_params[:end_date].blank? || course_params[:end_date].to_date > Date.today) + if @course.is_end && (course_params[:end_date].blank? || course_params[:end_date].to_date >= Date.today) extra_params[:is_end] = 0 elsif !@course.is_end && !course_params[:end_date].blank? && course_params[:end_date].to_date < Date.today extra_params[:is_end] = 1 @@ -230,14 +235,20 @@ class CoursesController < ApplicationController end def informs - + @informs = @course.informs end - def update_informs - tip_exception("公告内容不能为空") if params[:description].blank? - inform = @course.inform || Inform.new(container: @course) + def new_informs + inform = Inform.new(container: @course) + inform.name = params[:name] inform.description = params[:description] inform.save! + normal_status("创建成功") + end + + def update_informs + inform = @course.informs.find_by(id: params[:inform_id]) + inform.update_attributes!(name: params[:name], description: params[:description]) normal_status("更新成功") end @@ -545,7 +556,7 @@ class CoursesController < ApplicationController course_member = CourseMember.find_by!(id: params[:course_member_id].to_i, course_id: @course.id) tip_exception("删除失败") if course_member.CREATOR? or course_member.STUDENT? - course_student = CourseMember.find_by(id: course_member.user_id, course_id: @course.id, role: %i[STUDENT]) + course_student = CourseMember.find_by(user_id: course_member.user_id, course_id: @course.id, role: %i[STUDENT]) course_member.destroy! course_student.update_attributes(is_active: 1) if course_student.present? && !course_student.is_active normal_status(0, "删除成功") @@ -696,12 +707,12 @@ class CoursesController < ApplicationController if order == 1 # REDO:Extension - @students = @students.includes(user: :user_extension).order("user_extensions.student_id, course_members.id") + @students = @students.includes(user: :user_extension).order("user_extensions.student_id, users.login") elsif order == 2 - @students = @students.includes(:course_group).order("course_groups.position, course_members.id") + @students = @students.includes(:course_group).order("course_groups.position, users.login") else # REDO:Extension - @students = @students.includes(user: :user_extension).order("user_extensions.student_id, course_members.id") + @students = @students.includes(user: :user_extension).order("user_extensions.student_id, users.login") end if course_group_id.present? @@ -768,8 +779,10 @@ class CoursesController < ApplicationController students.each do |student| course_member = CourseMember.find_by(id: student[:course_member_id].to_i, course_id: @course.id) if course_member.present? + member_teacher = CourseMember.find_by(user_id: course_member.user_id, course_id: @course.id, role: %i[CREATOR PROFESSOR ASSISTANT_PROFESSOR]) student_ids << course_member.user_id course_member.destroy! + member_teacher.update_attributes(is_active: 1) if member_teacher.present? end end CourseDeleteStudentDeleteWorksJob.perform_later(@course.id, student_ids) if student_ids.present? @@ -1174,6 +1187,7 @@ class CoursesController < ApplicationController # Use callbacks to share common setup or constraints between actions. def set_course @course = Course.find_by!(id: params[:id]) + tip_exception(404, "") if @course.is_delete == 1 && !current_user.admin? end # Never trust parameters from the scary internet, only allow the white list through. @@ -1191,8 +1205,8 @@ class CoursesController < ApplicationController @subject = @course.present? ? @course.subject : Subject.find_by!(id: params[:subject_id]) tip_exception("开始时间不能为空") if params[:start_date].blank? tip_exception("结束时间不能为空") if params[:end_date].blank? - tip_exception("结束时间必须晚于开始时间") if params[:end_date] <= params[:start_date] - tip_exception("开始时间和结束时间不能与往期开课时间重叠") if @course.nil? && @subject.max_course_end_date && params[:start_date] <= strf_date(@subject.max_course_end_date) + tip_exception("结束时间必须晚于开始时间") if strf_date(params[:end_date]) <= strf_date(params[:start_date]) + tip_exception("开始时间和结束时间不能与往期开课时间重叠") if @course.nil? && @subject.max_course_end_date && strf_date(params[:start_date]) <= strf_date(@subject.max_course_end_date) validate_start_end_date if @course.present? tip_exception("开放课堂必须包含公告栏和在线学习模块") unless params[:course_module_types].include?("announcement") && params[:course_module_types].include?("online_learning") end @@ -1204,8 +1218,8 @@ class CoursesController < ApplicationController def validate_start_end_date prev_course = @subject.courses.where("id < #{@course.id}").last next_course = @subject.courses.where("id > #{@course.id}").first - tip_exception("开始时间不能与往期开课时间重叠") if prev_course && params[:start_date] <= strf_date(prev_course.end_date) - tip_exception("结束时间不能与后期开课时间重叠") if next_course && params[:end_date] >= strf_date(next_course.start_date) + tip_exception("开始时间不能与往期开课时间重叠") if prev_course && strf_date(params[:start_date]) <= strf_date(prev_course.end_date) + tip_exception("结束时间不能与后期开课时间重叠") if next_course && strf_date(params[:end_date]) >= strf_date(next_course.start_date) end # 超级管理员和课堂管理员的权限判断 @@ -1252,6 +1266,11 @@ class CoursesController < ApplicationController end end + def validate_inform_params + tip_exception("公告标题不能为空") if params[:name].blank? + tip_exception("公告内容不能为空") if params[:description].blank? + end + # def find_container # case params[:container_type] # when 'shixun_homework', 'common_homework', 'group_homework' diff --git a/app/controllers/exercise_answers_controller.rb b/app/controllers/exercise_answers_controller.rb index babdd50f7..3fc27c8f2 100644 --- a/app/controllers/exercise_answers_controller.rb +++ b/app/controllers/exercise_answers_controller.rb @@ -8,7 +8,7 @@ class ExerciseAnswersController < ApplicationController begin q_type = @exercise_question.question_type #试卷的类型 choice_id = params[:exercise_choice_id].present? ? params[:exercise_choice_id] : "" - answer_text = params[:answer_text].present? ? params[:answer_text] : "" #为字符串 + answer_text = params[:answer_text].present? ? params[:answer_text].strip : "" #为字符串 if q_type < Exercise::SUBJECTIVE && (q_type != Exercise::MULTIPLE) && choice_id.blank? normal_status(-1,"请选择序号") else diff --git a/app/controllers/exercise_questions_controller.rb b/app/controllers/exercise_questions_controller.rb index 75ada699d..e6f17778a 100644 --- a/app/controllers/exercise_questions_controller.rb +++ b/app/controllers/exercise_questions_controller.rb @@ -178,6 +178,7 @@ class ExerciseQuestionsController < ApplicationController def update ActiveRecord::Base.transaction do begin + standard_answer_change = false # 更新试卷题目的内容 question_options = { :question_title => params[:question_title], @@ -227,95 +228,102 @@ class ExerciseQuestionsController < ApplicationController if standard_answer.present? if @exercise_question.question_type <= Exercise::JUDGMENT #选择题/判断题,标准答案为一个或多个 exercise_standard_choices = @exercise_answers_array.pluck(:exercise_choice_id) #问题以前的全部标准答案选项位置 - common_standard_choices = standard_answer & exercise_standard_choices # 传入的标准答案的选项位置和以前的并集,即表示不用做更改的 - old_left_standard_choices = exercise_standard_choices - common_standard_choices # 以前的差集共同的,剩余的表示需要删掉 - new_left_standard_choices = standard_answer - common_standard_choices # 传入的标准答案差集共同的,剩余的表示需要新建 - if old_left_standard_choices.count > 0 - @exercise_answers_array.standard_by_ids(old_left_standard_choices).destroy_all - end - if new_left_standard_choices.count > 0 #新建标准答案 - new_left_standard_choices.each do |s| - standard_option = { - :exercise_question_id => @exercise_question.id, - :exercise_choice_id => s.to_i #即为选择的位置参数 - } - question_standard_answer = ExerciseStandardAnswer.new(standard_option) - question_standard_answer.save + if exercise_standard_choices.sort != standard_answer.sort #表示答案有更改的 + standard_answer_change = true + common_standard_choices = standard_answer & exercise_standard_choices # 传入的标准答案的选项位置和以前的并集,即表示不用做更改的 + old_left_standard_choices = exercise_standard_choices - common_standard_choices # 以前的差集共同的,剩余的表示需要删掉 + new_left_standard_choices = standard_answer - common_standard_choices # 传入的标准答案差集共同的,剩余的表示需要新建 + if old_left_standard_choices.count > 0 + @exercise_answers_array.standard_by_ids(old_left_standard_choices).destroy_all end + if new_left_standard_choices.count > 0 #新建标准答案 + new_left_standard_choices.each do |s| + standard_option = { + :exercise_question_id => @exercise_question.id, + :exercise_choice_id => s.to_i #即为选择的位置参数 + } + question_standard_answer = ExerciseStandardAnswer.new(standard_option) + question_standard_answer.save + end - end - if standard_answer.count > 1 && @exercise_question.question_type == Exercise::SINGLE #当标准答案数大于1,且不为多选时,修改为多选 - @exercise_question.update_attribute("question_type",Exercise::MULTIPLE) - elsif standard_answer.count == 1 && @exercise_question.question_type == Exercise::MULTIPLE - @exercise_question.update_attribute("question_type",Exercise::SINGLE) + end + if standard_answer.count > 1 && @exercise_question.question_type == Exercise::SINGLE #当标准答案数大于1,且不为多选时,修改为多选 + @exercise_question.update_attribute("question_type",Exercise::MULTIPLE) + elsif standard_answer.count == 1 && @exercise_question.question_type == Exercise::MULTIPLE + @exercise_question.update_attribute("question_type",Exercise::SINGLE) + end end elsif @exercise_question.question_type == Exercise::COMPLETION #填空题 old_ex_answer = @exercise_question.exercise_standard_answers #当前问题的全部标准答案 - old_ex_answer_choice_ids = old_ex_answer.pluck(:exercise_choice_id).uniq #全部的答案数组序号 - new_ex_answer_choice_ids = standard_answer.map {|a| a[:choice_id]}.uniq #新传入的答案数组序号 - - #删除多余的选项 - if old_ex_answer_choice_ids.count > new_ex_answer_choice_ids.count #有减少的填空 - delete_ex_answer_choice_ids = old_ex_answer_choice_ids - new_ex_answer_choice_ids - old_ex_answer.standard_by_ids(delete_ex_answer_choice_ids).destroy_all - end - standard_answer.each do |aa| - null_choice_id = aa[:choice_id] - null_choice_text = aa[:answer_text] - null_choice_text_count = null_choice_text.count #当前传入的答案数量 - null_choice_text_count_array = (1..null_choice_text_count).to_a + old_ex_answer_choice_texts = old_ex_answer.pluck(:answer_text).uniq.sort + new_ex_answer_choice_texts = standard_answer.pluck(:answer_text).sum.uniq.sort + if old_ex_answer_choice_texts != new_ex_answer_choice_texts #填空题标准答案有更改时,才会更新标准答案 + new_ex_answer_choice_ids = standard_answer.map {|a| a[:choice_id]}.uniq #新传入的答案数组序号 + old_ex_answer_choice_ids = old_ex_answer.pluck(:exercise_choice_id).uniq #全部的答案数组序号 + standard_answer_change = true + #删除多余的选项 + if old_ex_answer_choice_ids.count > new_ex_answer_choice_ids.count #有减少的填空 + delete_ex_answer_choice_ids = old_ex_answer_choice_ids - new_ex_answer_choice_ids + old_ex_answer.standard_by_ids(delete_ex_answer_choice_ids).destroy_all + end + standard_answer.each do |aa| + null_choice_id = aa[:choice_id] + null_choice_text = aa[:answer_text] + null_choice_text_count = null_choice_text.count #当前传入的答案数量 + null_choice_text_count_array = (1..null_choice_text_count).to_a - ex_answer_pre = old_ex_answer.standard_by_ids(null_choice_id) #当前问题的全部答案 - ex_answer_pre_count = ex_answer_pre.count - ex_answer_pre_count_array = (1..ex_answer_pre_count).to_a + ex_answer_pre = old_ex_answer.standard_by_ids(null_choice_id) #当前问题的全部答案 + ex_answer_pre_count = ex_answer_pre.count + ex_answer_pre_count_array = (1..ex_answer_pre_count).to_a - if old_ex_answer_choice_ids.include?(null_choice_id) #以前的填空题答案包含有现在的填空序号 - if null_choice_text_count >= ex_answer_pre_count - new_add_choice = null_choice_text_count_array - ex_answer_pre_count_array - ex_answer_pre_count_array.each do |n| - standard_option = { - :exercise_question_id => @exercise_question.id, - :exercise_choice_id => null_choice_id, - :answer_text => null_choice_text[n-1] - } - ex_answer_pre[n-1].update(standard_option) - end - if new_add_choice.count > 0 #表示有新增的 - new_add_choice.each do |i| + if old_ex_answer_choice_ids.include?(null_choice_id) #以前的填空题答案包含有现在的填空序号 + if null_choice_text_count >= ex_answer_pre_count + new_add_choice = null_choice_text_count_array - ex_answer_pre_count_array + ex_answer_pre_count_array.each do |n| standard_option = { :exercise_question_id => @exercise_question.id, :exercise_choice_id => null_choice_id, - :answer_text => null_choice_text[i-1] + :answer_text => null_choice_text[n-1] } - question_standard_answer = ExerciseStandardAnswer.new(standard_option) - question_standard_answer.save + ex_answer_pre[n-1].update(standard_option) + end + if new_add_choice.count > 0 #表示有新增的 + new_add_choice.each do |i| + standard_option = { + :exercise_question_id => @exercise_question.id, + :exercise_choice_id => null_choice_id, + :answer_text => null_choice_text[i-1] + } + question_standard_answer = ExerciseStandardAnswer.new(standard_option) + question_standard_answer.save + end + end + else + new_delete_choice = ex_answer_pre_count_array - null_choice_text_count_array + null_choice_text.each_with_index do |n,index| + standard_option = { + :exercise_question_id => @exercise_question.id, + :exercise_choice_id => null_choice_id, + :answer_text => n + } + ex_answer_pre[index].update(standard_option) + end + if new_delete_choice.count > 0 #表示填空题的答案有删减的 + new_delete_choice.each do |d| + ex_answer_pre[d-1].destroy + end end end else - new_delete_choice = ex_answer_pre_count_array - null_choice_text_count_array - null_choice_text.each_with_index do |n,index| + null_choice_text.each do |n| standard_option = { :exercise_question_id => @exercise_question.id, :exercise_choice_id => null_choice_id, :answer_text => n } - ex_answer_pre[index].update(standard_option) + question_standard_answer = ExerciseStandardAnswer.new(standard_option) + question_standard_answer.save end - if new_delete_choice.count > 0 #表示填空题的答案有删减的 - new_delete_choice.each do |d| - ex_answer_pre[d-1].destroy - end - end - end - else - null_choice_text.each do |n| - standard_option = { - :exercise_question_id => @exercise_question.id, - :exercise_choice_id => null_choice_id, - :answer_text => n - } - question_standard_answer = ExerciseStandardAnswer.new(standard_option) - question_standard_answer.save end end end @@ -348,20 +356,25 @@ class ExerciseQuestionsController < ApplicationController #当试卷已发布时(试卷的总状态),当标准答案修改时,如有已提交的学生,需重新计算分数. - if @exercise.exercise_status >= Exercise::PUBLISHED - ex_users_committed = @exercise.exercise_users.exercise_user_committed - if ex_users_committed.size > 0 - ex_users_committed.each do |ex_user| - user = ex_user.user - objective_score = calculate_student_score(@exercise,user)[:total_score] - subjective_score = ex_user.subjective_score - total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score - total_score = objective_score + total_score_subjective_score - ex_user.update_attributes(objective_score:objective_score,score:total_score) - end - end + if standard_answer_change && @exercise.exercise_status >= Exercise::PUBLISHED + # ex_users_committed = @exercise.exercise_users.exercise_user_committed + # if ex_users_committed.size > 0 + # ex_users_committed.each do |ex_user| + # update_objective_score = update_single_score(@exercise_question,ex_user.user_id,standard_answer) + # if update_objective_score != 0 + # objective_score = ex_user.objective_score + # new_objective_score = objective_score + update_objective_score + # total_score = ex_user.score + update_objective_score + # total_score = total_score < 0.0 ? 0.0 : total_score + # ex_user.update_attributes(objective_score:new_objective_score,score:total_score) + # end + # end + # end + normal_status(3,"修改了标准答案\n是否重新计算学生答题的成绩?") + else + normal_status(0,"试卷更新成功!") end - normal_status(0,"试卷更新成功!") + rescue Exception => e uid_logger_error(e.message) tip_exception("页面调用失败!") @@ -370,6 +383,31 @@ class ExerciseQuestionsController < ApplicationController end end + def update_scores + ActiveRecord::Base.transaction do + begin + standard_answer = params[:standard_answers] + ex_users_committed = @exercise.exercise_users.exercise_user_committed + if ex_users_committed.size > 0 + ex_users_committed.each do |ex_user| + update_objective_score = update_single_score(@exercise_question,ex_user.user_id,standard_answer) + if update_objective_score != 0 + objective_score = ex_user.objective_score + new_objective_score = objective_score + update_objective_score + total_score = ex_user.score + update_objective_score + total_score = total_score < 0.0 ? 0.0 : total_score + ex_user.update_attributes(objective_score:new_objective_score,score:total_score) + end + end + end + normal_status(0,"学生成绩更新成功") + rescue Exception => e + uid_logger_error(e.message) + tip_exception("答案删除失败!") + end + end + end + # 试卷问题的上下移动 def up_down ActiveRecord::Base.transaction do diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index c9162448b..5db704fce 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -688,7 +688,8 @@ class ExercisesController < ApplicationController def publish tip_exception("缺少截止时间参数") if params[:end_time].blank? tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now) - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) ActiveRecord::Base.transaction do begin check_ids = Exercise.where(id: params[:check_ids]) diff --git a/app/controllers/graduation_tasks_controller.rb b/app/controllers/graduation_tasks_controller.rb index 0885eeae2..727bdb77e 100644 --- a/app/controllers/graduation_tasks_controller.rb +++ b/app/controllers/graduation_tasks_controller.rb @@ -322,6 +322,8 @@ class GraduationTasksController < ApplicationController def publish_task tip_exception("缺少截止时间参数") if params[:end_time].blank? tip_exception("截止时间必须晚于当前时间") if params[:end_time] <= strf_time(Time.now) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day ActiveRecord::Base.transaction do begin @@ -397,8 +399,8 @@ class GraduationTasksController < ApplicationController tip_exception("发布时间不能早于当前时间") if params[:publish_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S") tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S") tip_exception("截止时间不能早于发布时间") if params[:publish_time] > params[:end_time] - tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && - params[:end_time] > @course.end_date.end_of_day + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day @task.publish_time = params[:publish_time] @task.end_time = params[:end_time] @@ -410,7 +412,8 @@ class GraduationTasksController < ApplicationController elsif @task.status < 2 tip_exception("截止时间不能为空") if params[:end_time].blank? tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S") - tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day @task.end_time = params[:end_time] end @@ -421,8 +424,8 @@ class GraduationTasksController < ApplicationController if params[:allow_late].to_i == 1 tip_exception("补交结束时间不能为空") if params[:late_time].blank? tip_exception("补交结束时间不能早于截止时间") if params[:late_time] <= @task.end_time - tip_exception("补交结束时间不能晚于课堂结束时间") if @course.end_date.present? && params[:late_time] > - @course.end_date.end_of_day + tip_exception("补交结束时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:late_time] > @course.end_date.end_of_day tip_exception("迟交扣分应为正整数") if params[:late_penalty] && params[:late_penalty].to_i < 0 @task.allow_late = true diff --git a/app/controllers/homework_commons_controller.rb b/app/controllers/homework_commons_controller.rb index eb3576f24..081e2fdf1 100644 --- a/app/controllers/homework_commons_controller.rb +++ b/app/controllers/homework_commons_controller.rb @@ -447,7 +447,8 @@ class HomeworkCommonsController < ApplicationController tip_exception("发布时间不能早于当前时间") if params[:publish_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S") tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S") tip_exception("截止时间不能早于发布时间") if params[:publish_time] > params[:end_time] - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day @homework.unified_setting = 1 @homework.homework_group_settings.destroy_all @@ -469,7 +470,8 @@ class HomeworkCommonsController < ApplicationController tip_exception("发布时间不能早于当前时间") if setting[:publish_time] <= strf_time(Time.now) tip_exception("截止时间不能早于当前时间") if setting[:end_time] <= strf_time(Time.now) tip_exception("截止时间不能早于发布时间") if setting[:publish_time] > setting[:end_time] - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && setting[:end_time] > @course.end_date.end_of_day + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && setting[:end_time] > @course.end_date.end_of_day publish_time = setting[:publish_time] == "" ? Time.now : setting[:publish_time] @@ -503,7 +505,8 @@ class HomeworkCommonsController < ApplicationController if @homework.end_time > Time.now && @homework.unified_setting tip_exception("截止时间不能为空") if params[:end_time].blank? tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now) - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) @homework.end_time = params[:end_time] @@ -521,7 +524,8 @@ class HomeworkCommonsController < ApplicationController tip_exception("截止时间不能早于等于当前时间") if setting[:end_time] <= strf_time(Time.now) tip_exception("截止时间不能早于发布时间") if setting[:publish_time] > setting[:end_time] - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && setting[:end_time] > strf_time(@course.end_date.end_of_day) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && setting[:end_time] > strf_time(@course.end_date.end_of_day) group_settings.none_published.update_all(publish_time: setting[:publish_time]) group_settings.none_end.update_all(end_time: setting[:end_time]) @@ -539,7 +543,7 @@ class HomeworkCommonsController < ApplicationController current_late_penalty = @homework.late_penalty if params[:allow_late] tip_exception("补交结束时间必须晚于截止时间") if params[:late_time] <= strf_time(@homework.end_time) - tip_exception("补交结束时间不能晚于课堂结束时间") if @course.end_date.present? && params[:late_time] > + tip_exception("补交结束时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if @course.end_date.present? && params[:late_time] > strf_time(@course.end_date.end_of_day) tip_exception("迟交扣分不能小于0") if params[:late_penalty] && params[:late_penalty].to_i < 0 @@ -647,7 +651,7 @@ class HomeworkCommonsController < ApplicationController tip_exception("匿评开启时间不能早于截止时间") if params[:evaluation_start] < strf_time(@homework.end_time) tip_exception("匿评结束时间不能为空") if params[:evaluation_end].blank? tip_exception("匿评截止时间必须晚于匿评开启时间") if params[:evaluation_end] <= params[:evaluation_start] - tip_exception("匿评截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:evaluation_end] > + tip_exception("匿评截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if @course.end_date.present? && params[:evaluation_end] > strf_time(@course.end_date.end_of_day) tip_exception("匿评数必须为正整数") if params[:evaluation_num].blank? || params[:evaluation_num].to_i < 1 tip_exception("缺评扣分不能为空") if params[:absence_penalty].blank? @@ -675,7 +679,7 @@ class HomeworkCommonsController < ApplicationController tip_exception("匿评结束时间不能为空") if @homework.anonymous_comment && params[:evaluation_end].blank? tip_exception("匿评截止时间必须晚于匿评开启时间") if @homework.anonymous_comment && params[:evaluation_end] <= strf_time(@homework_detail_manual.evaluation_start) - tip_exception("匿评截止时间不能晚于课堂结束时间") if @homework.anonymous_comment && + tip_exception("匿评截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if @homework.anonymous_comment && @course.end_date.present? && params[:evaluation_end] > strf_time(@course.end_date.end_of_day) @homework_detail_manual.evaluation_end = !@homework.anonymous_comment ? nil : params[:evaluation_end] @@ -701,7 +705,7 @@ class HomeworkCommonsController < ApplicationController tip_exception("匿评申诉结束时间不能为空") if @homework.anonymous_appeal && params[:appeal_time].blank? tip_exception("匿评开启时间不能早于匿评截止时间") if @homework.anonymous_appeal && params[:appeal_time] <= strf_time(@homework_detail_manual.evaluation_end) - tip_exception("匿评申诉结束不能晚于课堂结束时间") if @homework.anonymous_appeal && + tip_exception("匿评申诉结束不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if @homework.anonymous_appeal && @course.end_date.present? && params[:appeal_time] > strf_time(@course.end_date.end_of_day) @homework_detail_manual.appeal_time = @homework.anonymous_appeal ? params[:appeal_time] : nil @@ -992,13 +996,15 @@ class HomeworkCommonsController < ApplicationController @total_count = @subjects.size if reorder != "myshixun_count" - @subjects = @subjects.page(page).per(limit).includes(:shixuns, user: [user_extension: :school]) + # @subjects = @subjects.page(page).per(limit).includes(:shixuns, user: [user_extension: :school]) + @subjects = @subjects.page(page).per(limit).includes(:shixuns) else @subjects = @subjects[offset, limit] unless @subjects.blank? subject_ids = @subjects.pluck(:id) order_ids = subject_ids.size > 0 ? subject_ids.join(',') : -1 - @subjects = Subject.where(id: subject_ids).order("field(id,#{order_ids})").includes(:shixuns, user: [user_extension: :school]) + # @subjects = Subject.where(id: subject_ids).order("field(id,#{order_ids})").includes(:shixuns, user: [user_extension: :school]) + @subjects = Subject.where(id: subject_ids).order("field(id,#{order_ids})").includes(:shixuns) end end end @@ -1052,7 +1058,8 @@ class HomeworkCommonsController < ApplicationController tip_exception("请至少选择一个分班") if params[:group_ids].blank? && @course.course_groups.size != 0 tip_exception("缺少截止时间参数") if params[:end_time].blank? tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now) - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) homeworks = @course.homework_commons.where(id: params[:homework_ids]) homeworks = homeworks.includes(:homework_group_settings, :homework_detail_manual) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 90da7d8e5..2302272bf 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -44,7 +44,7 @@ class MessagesController < ApplicationController @current_user = current_user || nil @messages = @message.children.preload_messages.includes(:message_detail, :praise_treads) - @messages = @messages.ordered(sort: 1) unless @message.parent_id.nil? + # @messages = @messages.ordered(sort: 1) unless @message.parent_id.nil? @user_course_identity = current_user.course_identity(@message.board.course) case @user_course_identity @@ -52,6 +52,7 @@ class MessagesController < ApplicationController @messages = @messages.visible end + @messages = @messages.reorder("messages.created_on desc") @messages = @messages.page(@page).per(@page_size) end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index ba3030424..0f301dd4f 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -246,7 +246,8 @@ class PollsController < ApplicationController def publish tip_exception("缺少截止时间参数") if params[:end_time].blank? tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now) - tip_exception("截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) + tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if + @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) ActiveRecord::Base.transaction do begin check_ids = Poll.where(id: params[:check_ids]) diff --git a/app/controllers/shixuns_controller.rb b/app/controllers/shixuns_controller.rb index bfd386e1c..d9ab31b8d 100644 --- a/app/controllers/shixuns_controller.rb +++ b/app/controllers/shixuns_controller.rb @@ -109,7 +109,7 @@ class ShixunsController < ApplicationController can_fork = current_user.is_certification_teacher || current_user.admin? unless can_fork @can_fork = {can_fork: "已经职业认证的教师才能fork实训", - certi_url: edu_setting('old_edu_host') + "/account/professional_certification"} + certi_url: "/account/certification"} end @current_myshixun = @shixun.current_myshixun(current_user) if @shixun.fork_from diff --git a/app/controllers/subjects_controller.rb b/app/controllers/subjects_controller.rb index 6c5481787..a62707536 100644 --- a/app/controllers/subjects_controller.rb +++ b/app/controllers/subjects_controller.rb @@ -87,14 +87,6 @@ class SubjectsController < ApplicationController @courses = @subject.courses if @subject.excellent @members = @subject.subject_members.includes(:user) - shixuns = @subject.shixuns.published.pluck(:id) - challenge_ids = Challenge.where(shixun_id: shixuns).pluck(:id) - # 实训路径中的所有实训标签 - @tags = ChallengeTag.where(challenge_id: challenge_ids).pluck(:name).uniq - # 用户获取的实训标签 - # @user_tags = @subject.shixuns.map(&:user_tags_name).flatten.uniq - @user_tags = user_shixun_tags challenge_ids, @user.id - @my_subject_progress = @subject.my_subject_progress # 访问数变更 @subject.increment!(:visits) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 47f50a05f..0f536433d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -386,6 +386,95 @@ module ApplicationHelper m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t end + # =========== Admin Helpers Begin =========== + def sidebar_item_group(url, text, **opts) + link_opts = url.start_with?('/') ? {} : { 'data-toggle': 'collapse', 'aria-expanded': false } + content = + link_to url, link_opts do + content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) + + content_tag(:span, text) + end + + content += + content_tag(:ul, id: url[1..-1], class: 'collapse list-unstyled', "data-parent": '#sidebar') do + yield + end + + raw content + end + + def sidebar_item(url, text, **opts) + content = + link_to url, 'data-controller': opts[:controller] do + content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) + + content_tag(:span, text) + end + + raw content + end + + def admin_sidebar_controller + key = params[:controller].to_s.gsub(/\//, '-') + SidebarUtil.controller_name(key) || key + end + + def define_admin_breadcrumbs(&block) + content_for(:setup_admin_breadcrumb, &block) + end + + def add_admin_breadcrumb(text, url = nil) + @_admin_breadcrumbs ||= [] + @_admin_breadcrumbs << OpenStruct.new(text: text, url: url) + end + + def display_text(str, default = '--') + str.presence || default + end + + def overflow_hidden_span(text, width: 300) + opts = { class: 'd-inline-block text-truncate', style: "max-width: #{width}px" } + opts.merge!('data-toggle': 'tooltip', title: text) if text != '--' + + content_tag(:span, text, opts) + end + + def sort_tag(content = '', **opts) + options = {} + options[:sort_by] = opts.delete(:name) + is_current_sort = params[:sort_by].to_s == options[:sort_by] + options[:sort_direction] = is_current_sort && params[:sort_direction].to_s == 'desc' ? 'asc' : 'desc' + + path = opts.delete(:path) + "?" + params.slice(:action, :controller).merge(options).to_unsafe_h.to_query + arrow_class = case params[:sort_direction].to_s + when 'desc' then 'fa-sort-amount-desc' + when 'asc' then 'fa-sort-amount-asc' + else '' + end + opts[:style] = "#{opts[:style]} ;position: relative;" + + content_tag(:span, opts) do + link_to path, remote: true do + content = content_tag(:span) { yield } if block_given? + + content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}", style: 'position: absolute;top:0;') if is_current_sort + raw content + end + end + end + + def javascript_void_link(name, **opts) + raw link_to(name, 'javascript:void(0)', opts) + end + + def delete_link(name, url, **opts) + klass = ['action delete-action', opts.delete(:class)].compact.join(' ') + + refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}" + url = url + (url.index('?') ? '&' : '?') + refresh_url_data + + raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts)) + end + # =========== Admin Helpers End =========== end diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index 6a4353aa2..c5d376baf 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -269,6 +269,6 @@ module CoursesHelper myshixun = Myshixun.where(user_id: user_id, shixun_id: subject&.shixuns).order("updated_at desc").first return "" unless myshixun stage_shixun = subject&.stage_shixuns.where(shixun_id: myshixun.shixun_id).take - progress = stage_shixun&.position.to_s + "-" + stage_shixun&.position.to_s + " " + myshixun.shixun&.name + progress = stage_shixun&.stage&.position.to_s + "-" + stage_shixun&.position.to_s + " " + myshixun.shixun&.name end end diff --git a/app/helpers/exercises_helper.rb b/app/helpers/exercises_helper.rb index baf594a42..2c417ea74 100644 --- a/app/helpers/exercises_helper.rb +++ b/app/helpers/exercises_helper.rb @@ -32,7 +32,7 @@ module ExercisesHelper ques_score = 0.0 end else - ques_score = answers_content.select(:score).pluck(:score).sum + ques_score = answers_content.score_reviewed.select(:score).pluck(:score).sum end if ques_score >= q.question_score #满分作答为正确 @@ -83,12 +83,13 @@ module ExercisesHelper @ex_sub_array = @ex_sub_array.sort_by {|k| k[:q_position]} end - #试卷的统计结果页面计算各题的 + #试卷的统计结果页面计算各题的。选择题和判断题原来是按:已回答了本题的人数来计算的。现需要按已提交试卷的人数来计算。2019-8-23 def exercise_commit_result(questions,user_ids) question_infos = [] percent = 0.0 + commit_user_ids = user_ids.size questions.includes(:exercise_choices).each do |ex| - ex_total_score = user_ids.count * ex&.question_score.to_f #该试卷的已回答的总分 + ex_total_score = commit_user_ids * ex&.question_score.to_f #该试卷的已回答的总分 # ex_answers = ex.exercise_answers if ex.question_type != Exercise::PRACTICAL ques_title = ex.question_title @@ -103,7 +104,7 @@ module ExercisesHelper end effictive_users_count = effictive_users.size #有效回答数,可能有重复的用户id,这里仅统计是否回答这个问题的全部人数 - + # if ex.question_type > Exercise::COMPLETION #当为主观题和实训题时, ex_answered_scores = effictive_users.score_reviewed.pluck(:score).sum #该问题的全部得分 percent = (ex_total_score == 0.0 ? 0.0 : (ex_answered_scores / ex_total_score.to_f).round(3) * 100) #正确率 @@ -113,8 +114,8 @@ module ExercisesHelper if ex.question_type <= Exercise::JUDGMENT #选择题和判断题 ex_choices = ex.exercise_choices standard_answer = ex.exercise_standard_answers.pluck(:exercise_choice_id).sort #标准答案的位置 - right_users_count = 0 - #该问题的正确率 + # right_users_count = 0 + # 该问题的正确率 if ex.question_type == Exercise::MULTIPLE #多选题 right_user_ids = user_ids standard_answer.each do |choice_position| @@ -122,19 +123,12 @@ module ExercisesHelper right_user_ids = right_user_ids & effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.pluck(:user_id) end right_users_count = right_user_ids.size - # user_ids.each do |user_id| - # ex_choice_ids = effictive_users.map{|e| e.exercise_choice_id if e.user_id == user_id}.reject(&:blank?).uniq - # answer_choice_array = ex_choices.map{|a| a.choice_position if ex_choice_ids.include?(a.id)}.reject(&:blank?).uniq - # if answer_choice_array.sort == standard_answer - # right_users_count += 1 - # end - # end else #单选题和判断题 standard_answer_choice_id = ex_choices.select{|ec| ec.choice_position == standard_answer.first}.first&.id right_users_count = effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.size end - percent = effictive_users_count > 0 ? (right_users_count / effictive_users_count.to_f).round(3)*100 : 0.0 + percent = commit_user_ids > 0 ? (right_users_count / commit_user_ids.to_f).round(3)*100 : 0.0 #每个选项的正确率 ex_choices.each do |c| @@ -180,7 +174,8 @@ module ExercisesHelper all_user_count += user_count standard_answer_count += 1 end - percent = effictive_users_count > 0 ? (all_user_count / effictive_users_count.to_f).round(3)*100 : 0.0 + percent = commit_user_ids > 0 ? (all_user_count / commit_user_ids.to_f).round(3)*100 : 0.0 + user_wrong_count = (effictive_users_count - all_user_count ) if effictive_users_count > 0 && user_wrong_count >= 0 @@ -406,7 +401,7 @@ module ExercisesHelper #TODO: 旧版多选题的标准答案是放在一个里面的,新版又做成了一个题有多个标准答案(exercise_choice_id存放的是标准答案的位置..) if q.question_type == 1 && standard_answer.size == 1 - standard_answer = standard_answer.first.to_s.split("").map(&:to_i) + standard_answer = standard_answer.first.to_s.split("").map(&:to_i).sort end if user_answer_content == standard_answer #答案一致,多选或单选才给分,答案不对不给分 @@ -438,8 +433,8 @@ module ExercisesHelper end if q.is_ordered answers_content.each do |u| - i_standard_answer = standard_answer_array.where(exercise_choice_id:u.exercise_choice_id).pluck(:answer_text).reject(&:blank?).map!(&:downcase) #该选项的全部标准答案 - if i_standard_answer.include?(u.answer_text.downcase) #该空的标准答案包含用户的答案才有分数 + i_standard_answer = standard_answer_array.where(exercise_choice_id:u.exercise_choice_id).pluck(:answer_text).reject(&:blank?).map{|a| a.strip.downcase} #该选项的全部标准答案 + if i_standard_answer.include?(u.answer_text.strip.downcase) #该空的标准答案包含用户的答案才有分数 u.update_column('score',q_score_2) score2 = score2 + q_score_2 else @@ -448,9 +443,9 @@ module ExercisesHelper end end else - st_answer_text = standard_answer_array.pluck(:answer_text).reject(&:blank?).map!(&:downcase) + st_answer_text = standard_answer_array.pluck(:answer_text).reject(&:blank?).map{|a| a.strip.downcase} answers_content.each do |u| - u_answer_text = u.answer_text.downcase + u_answer_text = u.answer_text.strip.downcase if st_answer_text.include?(u_answer_text) #只要标准答案包含用户的答案,就有分数。同时,下一次循环时,就会删除该标准答案。防止用户的相同答案获分 u.update_column("score",q_score_2) score2 = score2 + q_score_2 @@ -466,50 +461,59 @@ module ExercisesHelper end elsif q.question_type == 5 #实训题时,主观题这里不评分 q.exercise_shixun_challenges&.each do |exercise_cha| - game = Game.user_games(user.id,exercise_cha.challenge_id)&.first #当前用户的关卡 - if game.present? - exercise_cha_score = 0.0 - answer_status = 0 - # if game.status == 2 && game.final_score >= 0 - if game.final_score > 0 - exercise_cha_score = game.real_score(exercise_cha.question_score) - # exercise_cha_score = exercise_cha.question_score #每一关卡的得分 - answer_status = 1 - end - ex_shixun_answer_content = answers_content&.where(exercise_shixun_challenge_id: exercise_cha.id) - code = nil - if exercise_cha.challenge&.path.present? - cha_path = challenge_path(exercise_cha.challenge&.path) - game_challenge = game.game_codes.search_challenge_path(cha_path)&.first - if game_challenge.present? - game_code = game_challenge - code = game_code.try(:new_code) + user_shixun_score = exercise_cha&.exercise_shixun_answers&.where(user_id:user.id,exercise_question_id:q.id) + if user_shixun_score.present? + score5 += user_shixun_score&.first.score + else + game = Game.user_games(user.id,exercise_cha.challenge_id)&.first #当前用户的关卡 + if game.present? + exercise_cha_score = 0.0 + answer_status = 0 + # if game.status == 2 && game.final_score >= 0 + if game.final_score > 0 + exercise_cha_score = game.real_score(exercise_cha.question_score) + # exercise_cha_score = exercise_cha.question_score #每一关卡的得分 + answer_status = 1 + end + ex_shixun_answer_content = answers_content&.where(exercise_shixun_challenge_id: exercise_cha.id) + code = nil + if exercise_cha.challenge&.path.present? + cha_path = challenge_path(exercise_cha.challenge&.path) + game_challenge = game.game_codes.search_challenge_path(cha_path)&.first + if game_challenge.present? + game_code = game_challenge + code = game_code.try(:new_code) + else + begin + code = git_fle_content(game.myshixun.repo_path,cha_path) + rescue + code = "" + end + end + end + if ex_shixun_answer_content.blank? #把关卡的答案存入试卷的实训里 + ### Todo 实训题的_shixun_details里的代码是不是直接从这里取出就可以了?涉及到code的多个版本库的修改 + sx_option = { + :exercise_question_id => q.id, + :exercise_shixun_challenge_id => exercise_cha.id, + :user_id => user.id, + :score => exercise_cha_score.round(1), + :answer_text => code, + :status => answer_status + } + ExerciseShixunAnswer.create(sx_option) else - code = git_fle_content(game.myshixun.repo_path,cha_path) + ex_shixun_answer_content.first.update_attributes(score:exercise_cha_score.round(1),answer_text:code) end - end - if ex_shixun_answer_content.blank? #把关卡的答案存入试卷的实训里 - ### Todo 实训题的_shixun_details里的代码是不是直接从这里取出就可以了?涉及到code的多个版本库的修改 - sx_option = { - :exercise_question_id => q.id, - :exercise_shixun_challenge_id => exercise_cha.id, - :user_id => user.id, - :score => exercise_cha_score.round(1), - :answer_text => code, - :status => answer_status - } - ExerciseShixunAnswer.create(sx_option) + score5 += exercise_cha_score else - ex_shixun_answer_content.first.update_attributes(score:exercise_cha_score.round(1),answer_text:code) + score5 += 0.0 end - score5 += exercise_cha_score - else - score5 += 0.0 end end end user_scores = answers_content.blank? ? 0.0 : answers_content.score_reviewed.pluck(:score).sum - if user_scores > 0.0 + if user_scores > 0.0 stand_answer = 1 else stand_answer = 0 @@ -535,6 +539,82 @@ module ExercisesHelper } end + #当单个问题的分数更新时,更新用户的总分 + def update_single_score(q,user_id,standard_answer) + score1 = 0.0 #用户的新得分 + origin_score = 0.0 #用户的原来该问题的得分 + answers_content = q&.exercise_answers&.where(user_id: user_id) #学生的答案 + if answers_content.present? #用户回答了该题,才会改分数 + if q.question_type <= 2 #为选择题或判断题时 + origin_score = answers_content.first.score + answer_choice_array = [] + answers_content.each do |a| + answer_choice_array.push(a.exercise_choice.choice_position) #学生答案的位置 + end + user_answer_content = answer_choice_array.sort + + #TODO: 旧版多选题的标准答案是放在一个里面的,新版又做成了一个题有多个标准答案(exercise_choice_id存放的是标准答案的位置..) + if q.question_type == 1 && standard_answer.size == 1 + standard_answer = standard_answer.first.to_s.split("").map(&:to_i) + end + + if user_answer_content == standard_answer.sort #答案一致,多选或单选才给分,答案不对不给分 + if standard_answer.size > 0 + q_score_1 = q.question_score + else + q_score_1 = 0.0 + end + answers_content.update_all(:score => q_score_1) + score1 = q_score_1 + else + answers_content.update_all(:score => -1.0) + end + elsif q.question_type == 3 #填空题 + origin_score = answers_content.score_reviewed.pluck(:score).sum + + standard_answer_count = standard_answer.count + if standard_answer_count > 0 #存在标准答案时才有分数 + q_score_2 = (q.question_score.to_f / standard_answer_count) #每一空的得分 + else + q_score_2 = 0.0 + end + if q.is_ordered + answers_content.each do |u| + i_standard_answer = [] + standard_answer.each do |a| + if a[:choice_id] == u.exercise_choice_id + i_standard_answer += a[:answer_text] + end + end + i_standard_answer = i_standard_answer.map{|a| a.strip.downcase} + if i_standard_answer.include?(u.answer_text.strip.downcase) #该空的标准答案包含用户的答案才有分数 + u.update_column('score',q_score_2) + score1 = score1 + q_score_2 + else + u.update_column('score',-1.0) + score1 += 0.0 + end + end + else + st_answer_text = standard_answer.pluck(:answer_text).sum.map{|a| a.strip.downcase} + answers_content.each do |u| + u_answer_text = u.answer_text.downcase + if st_answer_text.include?(u_answer_text) #只要标准答案包含用户的答案,就有分数。同时,下一次循环时,就会删除该标准答案。防止用户的相同答案获分 + u.update_column("score",q_score_2) + score1 = score1 + q_score_2 + st_answer_text.delete(u_answer_text) + else + u.update_column('score',-1.0) + score1 += 0.0 + end + end + end + end + end + origin_score = origin_score < 0.0 ? 0.0 : origin_score + score1 - origin_score + end + #获取用户的相关信息 def exercise_use_info(ex_user,user_status,exercise) course = exercise.course diff --git a/app/helpers/homework_commons_helper.rb b/app/helpers/homework_commons_helper.rb index c77ae5937..fa563a72c 100644 --- a/app/helpers/homework_commons_helper.rb +++ b/app/helpers/homework_commons_helper.rb @@ -37,25 +37,25 @@ module HomeworkCommonsHelper when 3 if ho_detail_manual.evaluation_end && ho_detail_manual.evaluation_end > Time.now status << "匿评中" - time = how_much_time(ho_detail_manual.evaluation_end) + time = "提交剩余时间:" + how_much_time(ho_detail_manual.evaluation_end) time_status = 3 end when 4 if ho_detail_manual.appeal_time && ho_detail_manual.appeal_time > Time.now status << "申诉中" - time = how_much_time(ho_detail_manual.appeal_time) + time = "申诉剩余时间:" + how_much_time(ho_detail_manual.appeal_time) time_status = 4 end when 2, 5, 6 status << "评阅中" - time = course.end_date.present? ? how_much_time(course.end_date.end_of_day) : "" + time = "评阅剩余时间:" + (course.end_date.present? ? how_much_time(course.end_date.end_of_day) : "") time_status = 5 end # 如果还在补交阶段则显示补交结束时间 if homework_common.end_time && homework_common.end_time < Time.now && homework_common.allow_late && homework_common.late_time && homework_common.late_time > Time.now - time = how_much_time(homework_common.late_time) + time = "补交剩余时间:" + how_much_time(homework_common.late_time) time_status = 2 end else @@ -74,12 +74,12 @@ module HomeworkCommonsHelper when 1 if homework_common.end_time && homework_common.end_time >= Time.now status << "提交中" - time = how_much_time(homework_common.end_time) + time = "提交剩余时间:" + how_much_time(homework_common.end_time) time_status = 1 elsif homework_common.end_time && homework_common.end_time < Time.now time_status = 5 if homework_common.allow_late && (homework_common.late_time.nil? || homework_common.late_time >= Time.now) - time = how_much_time(homework_common.late_time) + time = "补交剩余时间:" + how_much_time(homework_common.late_time) time_status = 2 end status << "评阅中" @@ -114,11 +114,11 @@ module HomeworkCommonsHelper time_status = 0 elsif max_end_time.present? && max_end_time > Time.now #6.14 -hs 添加present? status << "提交中" - time = how_much_time(max_end_time) + time = "提交剩余时间:" + how_much_time(max_end_time) time_status = 1 elsif homework_common.allow_late && (homework_common.late_time.nil? || homework_common.late_time >= Time.now) status << "评阅中" - time = how_much_time(homework_common.late_time) + time = "补交剩余时间:" + how_much_time(homework_common.late_time) time_status = 2 else status << "评阅中" diff --git a/app/helpers/subjects_helper.rb b/app/helpers/subjects_helper.rb index 8aff41d25..75ae9f041 100644 --- a/app/helpers/subjects_helper.rb +++ b/app/helpers/subjects_helper.rb @@ -24,8 +24,8 @@ module SubjectsHelper elsif course.start_date && course.start_date > Date.today {status: 0, time: ""} elsif course.start_date && course.start_date <= Date.today && course.end_date >= Date.today - sum_week = ((course.end_date - course.start_date).to_i / 7.0).ceil - curr_week = ((Date.today - course.start_date).to_i / 7.0).ceil + sum_week = (((course.end_date - course.start_date).to_i + 1) / 7.0).ceil + curr_week = (((Date.today - course.start_date).to_i + 1) / 7.0).ceil {status: 1, time: "进行至第#{curr_week}周,共#{sum_week}周"} else {status: -1, time: ""} diff --git a/app/jobs/create_student_work_job.rb b/app/jobs/create_student_work_job.rb new file mode 100644 index 000000000..1fd9a9b00 --- /dev/null +++ b/app/jobs/create_student_work_job.rb @@ -0,0 +1,21 @@ +class CreateStudentWorkJob < ApplicationJob + queue_as :default + + def perform(homework_id) + homework = HomeworkCommon.find_by(id: homework_id) + course = homework&.course + return if homework.blank? || course.blank? + + attrs = %i[homework_common_id user_id created_at updated_at] + + same_attrs = {homework_common_id: homework.id} + + StudentWork.bulk_insert(*attrs) do |worker| + student_ids = course.students.pluck(:user_id) + + student_ids.each do |user_id| + worker.add same_attrs.merge(user_id: user_id) + end + end + end +end diff --git a/app/libs/sidebar_util.rb b/app/libs/sidebar_util.rb new file mode 100644 index 000000000..c58eab5e5 --- /dev/null +++ b/app/libs/sidebar_util.rb @@ -0,0 +1,11 @@ +class SidebarUtil + class << self + def controller_name(name) + sidebar_controller_map[name] + end + + def sidebar_controller_map + @_sidebar_controller_map ||= YAML.load_file(Rails.root.join('config/admins', 'sidebar.yml')) + end + end +end \ No newline at end of file diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 3a512278a..e3c55242f 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -19,7 +19,7 @@ class Attachment < ApplicationRecord scope :contains_only_project, -> { where(container_type: 'Project') } scope :contains_course_and_project, -> { contains_only_course.or(contains_only_project) } scope :mine, -> (author_id) { where(author_id: author_id) } - scope :simple_columns, -> { select(:id, :filename, :filesize, :created_on, :cloud_url, :author_id) } + scope :simple_columns, -> { select(:id, :filename, :filesize, :created_on, :cloud_url, :author_id, :content_type) } scope :search_by_container, -> (ids) {where(container_id: ids)} validates_length_of :description, maximum: 100 diff --git a/app/models/course.rb b/app/models/course.rb index baed1efe6..5a2a065ba 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -9,7 +9,7 @@ class Course < ApplicationRecord # 所属实践课程 belongs_to :subject, optional: true - has_one :inform, as: :container, dependent: :destroy + has_many :informs, as: :container, dependent: :destroy has_many :course_infos, dependent: :destroy # 课堂左侧导航栏的模块 @@ -82,6 +82,7 @@ class Course < ApplicationRecord scope :by_keywords, lambda { |keywords| where("name LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank? } + scope :started, -> { where("start_date is null or start_date <= '#{Date.today}'") } acts_as_taggable @@ -185,7 +186,7 @@ class Course < ApplicationRecord end def all_course_module_types - %w[activity announcement online_learning shixun_homework common_homework group_homework graduation exercise poll attachment board course_group] + %w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board] end def get_course_module_by_type(type) diff --git a/app/models/homework_common.rb b/app/models/homework_common.rb index 332aff045..ac11a54b7 100644 --- a/app/models/homework_common.rb +++ b/app/models/homework_common.rb @@ -68,14 +68,14 @@ class HomeworkCommon < ApplicationRecord def category_info case self.homework_type when 'normal' - {category_id: course.common_course_modules.first.try(:id), category_name: course.common_course_modules.first.try(:module_name)} + {category_id: course.common_course_modules.first.try(:id), category_name: course.common_course_modules.first.try(:module_name), main: 1} when 'group' - {category_id: course.group_course_modules.first.try(:id), category_name: course.group_course_modules.first.try(:module_name)} + {category_id: course.group_course_modules.first.try(:id), category_name: course.group_course_modules.first.try(:module_name), main: 1} when 'practice' if self.course_second_category.present? - {category_id: self.course_second_category.try(:id), category_name: self.course_second_category.try(:name)} + {category_id: self.course_second_category.try(:id), category_name: self.course_second_category.try(:name), main: 0} else - {category_id: course.shixun_course_modules.take.try(:id), category_name: course.shixun_course_modules.take.try(:module_name)} + {category_id: course.shixun_course_modules.take.try(:id), category_name: course.shixun_course_modules.take.try(:module_name), main: 1} end end end diff --git a/app/models/inform.rb b/app/models/inform.rb index faf1d48e6..5caf80c5f 100644 --- a/app/models/inform.rb +++ b/app/models/inform.rb @@ -1,3 +1,6 @@ class Inform < ApplicationRecord belongs_to :container, polymorphic: true, optional: true + + validates :name, length: { maximum: 60 } + validates :description, length: { maximum: 5000 } end diff --git a/app/models/old_message_detail.rb b/app/models/old_message_detail.rb new file mode 100644 index 000000000..5c1b94f51 --- /dev/null +++ b/app/models/old_message_detail.rb @@ -0,0 +1,2 @@ +class OldMessageDetail < ApplicationRecord +end diff --git a/app/models/school.rb b/app/models/school.rb index a0c829160..8a28ae4bf 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -10,6 +10,9 @@ class School < ApplicationRecord has_many :ec_major_schools, :dependent => :destroy has_many :ec_majors, :through => :ec_major_schools + has_many :school_daily_reports, dependent: :destroy + has_many :courses + # 学校管理员 def manager?(user) ec_school_users.exists?(user_id: user.id) diff --git a/app/models/school_daily_active_user.rb b/app/models/school_daily_active_user.rb new file mode 100644 index 000000000..2abe85145 --- /dev/null +++ b/app/models/school_daily_active_user.rb @@ -0,0 +1,3 @@ +class SchoolDailyActiveUser < ApplicationRecord + belongs_to :school_daily_report +end \ No newline at end of file diff --git a/app/models/school_daily_report.rb b/app/models/school_daily_report.rb new file mode 100644 index 000000000..364f319ab --- /dev/null +++ b/app/models/school_daily_report.rb @@ -0,0 +1,5 @@ +class SchoolDailyReport < ApplicationRecord + belongs_to :school + + has_many :school_daily_active_users, dependent: :delete_all +end \ No newline at end of file diff --git a/app/models/school_report.rb b/app/models/school_report.rb new file mode 100644 index 000000000..b1183b3ec --- /dev/null +++ b/app/models/school_report.rb @@ -0,0 +1,3 @@ +class SchoolReport < ApplicationRecord + belongs_to :school +end \ No newline at end of file diff --git a/app/models/subject.rb b/app/models/subject.rb index b8d6eef8b..7b671cfb1 100644 --- a/app/models/subject.rb +++ b/app/models/subject.rb @@ -19,11 +19,11 @@ class Subject < ApplicationRecord has_many :stages, -> { order("stages.position ASC") }, dependent: :destroy # 开放课堂 - has_many :courses, -> { where("is_delete = 0").order("courses.id ASC") } + has_many :courses, -> { where("is_delete = 0").order("courses.created_at ASC") } validates :name, length: { maximum: 60 } - validates :description, length: { maximum: 5000 } - validates :learning_notes, length: { maximum: 500 } + validates :description, length: { maximum: 8000 } + validates :learning_notes, length: { maximum: 2000 } scope :visible, lambda{where(status: 2)} scope :published, lambda{where(status: 1)} @@ -39,9 +39,15 @@ class Subject < ApplicationRecord courses.pluck(:end_date).max end - # 挑战过路径的成员数 + # 是否有已开课的课堂 + def has_course_start? + courses.where("start_date <= '#{Date.today}' and end_date >= '#{Date.today}'").count > 0 + end + + # 挑战过路径的成员数(金课统计去重后的报名人数) def member_count - shixuns.pluck(:myshixuns_count).sum + excellent && CourseMember.where(role: 4, course_id: courses.pluck(:id)).pluck(:user_id).uniq.length > shixuns.pluck(:myshixuns_count).sum ? + CourseMember.where(role: 4, course_id: courses.pluck(:id)).pluck(:user_id).uniq.length : shixuns.pluck(:myshixuns_count).sum end def all_score diff --git a/app/models/user.rb b/app/models/user.rb index 449a86e8b..5ba8667f5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -146,6 +146,8 @@ class User < ApplicationRecord attr_accessor :password, :password_confirmation + delegate :gender, :department_id, :school_id, :location, :location_city, :technical_title, to: :user_extension, allow_nil: true + before_save :update_hashed_password # @@ -232,8 +234,9 @@ class User < ApplicationRecord user_extension&.school&.name || '' end - def school_id - user_extension&.school_id + # 用户的学院名称 + def department_name + user_extension&.department&.name || '' end # 课堂的老师(创建者、老师、助教) @@ -440,6 +443,10 @@ class User < ApplicationRecord name.gsub(/\s+/, '').strip #6.11 -hs end + def only_real_name + "#{lastname}#{firstname}" + end + # 用户是否选题毕设课题 def selected_topic?(topic) student_graduation_topics.where(graduation_topic_id: topic.id).last.try(:status) diff --git a/app/queries/admins/user_query.rb b/app/queries/admins/user_query.rb new file mode 100644 index 000000000..5a633f059 --- /dev/null +++ b/app/queries/admins/user_query.rb @@ -0,0 +1,40 @@ +class Admins::UserQuery < ApplicationQuery + include CustomSortable + + attr_reader :params + + sort_columns :created_on, :last_login_on, :experience, :grade, default_by: :created_on, default_direction: :desc + + def initialize(params) + @params = params + end + + def call + users = User.where(type: 'User') + + # 状态 + status = params[:status] + users = users.where(status: status) if status.present? + + # 职业 + users = users.joins(:user_extension).where(user_extensions: { identity: params[:identity] }) if params[:identity].present? + + # 授权类型 + if params[:auto_trial].present? + users = users.joins(user_extension: :school).where(schools: { auto_users_trial: params[:auto_trial].to_i == 1 }) + end + + # 关键字检索 + keyword = params[:keyword].to_s.strip.presence + if keyword + sql = 'CONCAT(lastname, firstname) LIKE :keyword OR login LIKE :keyword OR mail LIKE :keyword OR phone LIKE :keyword' + users = users.where(sql, keyword: keyword) + end + + # 学校名称 + school_name = params[:school_name].to_s.strip.presence + users = users.joins(user_extension: :school).where('schools.name LIKE ?', "%#{school_name}%") if school_name + + custom_sort(users, params[:sort_by], params[:sort_direction]) + end +end \ No newline at end of file diff --git a/app/services/admins/school_daily_statistic_service.rb b/app/services/admins/school_daily_statistic_service.rb new file mode 100644 index 000000000..64bb97864 --- /dev/null +++ b/app/services/admins/school_daily_statistic_service.rb @@ -0,0 +1,123 @@ +class Admins::SchoolDailyStatisticService < ApplicationService + include CustomSortable + + attr_reader :params + + sort_columns :student_count, :teacher_count, :homework_count, :other_homework_count, + :course_count, :active_course_count, :nearly_course_time, :shixun_count, :shixun_evaluate_count, + default_by: :teacher_count, default_direction: :desc + + def initialize(params) + @params = params + end + + def call + schools = School.group('schools.id') + + keyword = params[:keyword].try(:to_s).try(:strip) + if keyword.present? + schools = schools.where("schools.name LIKE :keyword OR schools.id LIKE :keyword", keyword: "%#{keyword}%") + end + + count = schools.count.count + + # 根据排序字段进行查询 + schools = query_by_sort_column(schools, params[:sort_by]) + schools = custom_sort(schools, params[:sort_by], params[:sort_direction]) + + schools = schools.limit(page_size).offset(offset) + # 查询并组装其它数据 + schools = package_other_data(schools) + + [count, schools] + end + + def package_other_data(schools) + ids = schools.map(&:id) + + student_map = UserExtension.where(school_id: ids, identity: :student).group(:school_id).count + teacher_map = UserExtension.where(school_id: ids, identity: :teacher).group(:school_id).count + + homeworks = HomeworkCommon.joins(:course) + shixun_homework_map = homeworks.where(homework_type: 4, courses: { school_id: ids }).group('school_id').count + other_homework_map = homeworks.where(homework_type: [1, 3], courses: { school_id: ids }).group('school_id').count + + courses = Course.where(is_delete: 0, school_id: ids).group('school_id') + course_map = courses.count + nearly_course_time_map = courses.joins(:course_acts).maximum('course_activities.updated_at') + active_course_map = courses.where(is_end: false).count + + shixun_map = Shixun.joins(user: :user_extension).where(user_extensions: { identity: :teacher, school_id: ids }) + .where(fork_from: nil).group('school_id').count + + reports = SchoolReport.where(school_id: ids) + evaluate_count_map = reports.each_with_object({}) { |report, obj| obj[report.school_id] = report.shixun_evaluate_count } + + schools.map do |school| + { + id: school.id, + name: school.name, + teacher_count: teacher_map[school.id], + student_count: student_map[school.id], + homework_count: shixun_homework_map[school.id], + other_homework_count: other_homework_map[school.id], + course_count: course_map[school.id], + nearly_course_time: nearly_course_time_map[school.id], + active_course_count: active_course_map[school.id], + shixun_count: shixun_map.fetch(school.id, 0), + shixun_evaluate_count: evaluate_count_map.fetch(school.id, 0) + } + end + end + + private + def query_by_sort_column(schools, sort_by_column) + base_query_column = 'schools.id, schools.name' + + case sort_by_column.to_s + when 'teacher_count' then + schools.joins('LEFT JOIN user_extensions ue ON ue.school_id = schools.id AND ue.identity = 0') + .select("#{base_query_column}, COUNT(*) teacher_count") + when 'student_count' then + schools.joins('LEFT JOIN user_extensions ue ON ue.school_id = schools.id AND ue.identity = 1') + .select("#{base_query_column}, COUNT(*) student_count") + when 'homework_count' then + schools.joins('LEFT JOIN courses ON courses.school_id = schools.id') + .joins('LEFT JOIN homework_commons hc ON hc.course_id = courses.id AND hc.homework_type = 4') + .select("#{base_query_column}, COUNT(*) homework_count") + when 'other_homework_count' then + schools.joins('LEFT JOIN courses ON courses.school_id = schools.id') + .joins('LEFT JOIN homework_commons hc ON hc.course_id = courses.id AND hc.homework_type IN (1, 3)') + .select("#{base_query_column}, COUNT(*) other_homework_count") + when 'course_count' then + schools.joins('LEFT JOIN courses cs ON cs.school_id = schools.id AND cs.is_delete = 0') + .select("#{base_query_column}, COUNT(*) course_count") + when 'shixun_count' then + schools.joins('LEFT JOIN user_extensions ue ON ue.school_id = schools.id AND ue.identity = 0') + .joins('LEFT JOIN users ON users.id = ue.user_id') + .joins('LEFT JOIN shixuns sx ON sx.user_id = users.id AND sx.fork_from IS NULL') + .select("#{base_query_column}, COUNT(*) shixun_count") + when 'shixun_evaluate_count' then + schools.joins('LEFT JOIN school_reports ON school_reports.school_id = schools.id') + .select("#{base_query_column}, shixun_evaluate_count") + when 'nearly_course_time' then + schools.joins('LEFT JOIN courses cs ON cs.school_id = schools.id AND cs.is_delete = 0') + .joins('LEFT JOIN course_activities acs ON acs.course_id = cs.id') + .select("#{base_query_column}, MAX(acs.updated_at) nearly_course_time") + when 'active_course_count' then + schools.joins('LEFT JOIN courses cs ON cs.school_id = schools.id AND cs.is_delete = 0 AND cs.is_end = false') + .select("#{base_query_column}, COUNT(*) active_course_count") + else + schools.joins('LEFT JOIN user_extensions ue ON ue.school_id = schools.id AND ue.identity = 0') + .select("#{base_query_column}, COUNT(*) teacher_count") + end + end + + def page_size + params[:per_page] || 20 + end + + def offset + (params[:page].to_i.zero? ? 0 : params[:page].to_i - 1) * page_size + end +end \ No newline at end of file diff --git a/app/services/admins/statistic_school_contrast_data_service.rb b/app/services/admins/statistic_school_contrast_data_service.rb new file mode 100644 index 000000000..0496b1371 --- /dev/null +++ b/app/services/admins/statistic_school_contrast_data_service.rb @@ -0,0 +1,80 @@ +class Admins::StatisticSchoolContrastDataService < ApplicationService + ParameterError = Class.new(StandardError) + + PAGE_SIZE = 20 + CONTRAST_COLUMN_LIST = %w( + teacher_increase_count student_increase_count course_increase_count + shixun_increase_count active_user_count shixun_homework_count shixun_evaluate_count + ).freeze + + attr_reader :params, :sort_direction, :contrast_column + + def initialize(params) + @params = params + @sort_direction = params[:sort_direction].to_s + @contrast_column = params[:contrast_column].to_s + end + + def call + validate_parameter! + reports = School.joins(:school_daily_reports).select(select_columns) + + keyword = params[:keyword].try(:to_s).try(:strip) + if keyword.present? + reports = reports.where("schools.name LIKE :keyword OR schools.id LIKE :keyword", keyword: "%#{keyword}%") + end + + count = reports.count('distinct(schools.id)') + + sql = query_report_sql(reports.group('schools.id').to_sql) + reports = SchoolDailyReport.find_by_sql(sql) + + [count, reports] + end + + private + def validate_parameter! + if %i[begin_date end_date other_begin_date other_end_date].any? { |key| params[key].blank? } + raise ParameterError + end + + unless %w(desc asc).include?(sort_direction) + raise ParameterError + end + + unless CONTRAST_COLUMN_LIST.include?(contrast_column) + raise ParameterError + end + end + + def format_date(date) + Time.zone.parse(date).strftime("%Y-%m-%d") + end + + def offset + (params[:page].to_i.zero? ? 0 : params[:page].to_i - 1) * PAGE_SIZE + end + + def select_columns + if contrast_column != 'active_user_count' + "schools.id school_id, schools.name school_name,"\ + "(SUM(IF(date BETWEEN '#{format_date(params[:begin_date])}' AND '#{format_date(params[:end_date])}', #{contrast_column}, 0))) total,"\ + "(SUM(IF(date BETWEEN '#{format_date(params[:other_begin_date])}' AND '#{format_date(params[:other_end_date])}', #{contrast_column}, 0))) other_total" + else + # 活跃用户对比时处理方法不同 + relations = SchoolDailyActiveUser.select('COUNT(distinct user_id)').joins(:school_daily_report) + .where('school_id = schools.id') + total_subquery = relations.where("date BETWEEN '#{format_date(params[:begin_date])}' AND '#{format_date(params[:end_date])}'").to_sql + other_total_subquery = relations.where("date BETWEEN '#{format_date(params[:other_begin_date])}' AND '#{format_date(params[:other_end_date])}'").to_sql + + "schools.id school_id, schools.name school_name, (#{total_subquery}) AS total, (#{other_total_subquery}) AS other_total" + end + end + + def query_report_sql(from_sql) + order_by = "(total = 0 AND other_total != 0) #{sort_direction}, (percentage != 0) #{sort_direction}, percentage #{sort_direction}" + + "SELECT reports.*, (other_total - total) increase, (IF(other_total - total = 0, 0.0, round((other_total - total) / IF(total = 0, 1, total), 5))) percentage "\ + "FROM (#{from_sql}) reports ORDER BY #{order_by} LIMIT #{PAGE_SIZE} OFFSET #{offset}" + end +end \ No newline at end of file diff --git a/app/services/admins/statistic_school_data_grow_service.rb b/app/services/admins/statistic_school_data_grow_service.rb new file mode 100644 index 000000000..8df106666 --- /dev/null +++ b/app/services/admins/statistic_school_data_grow_service.rb @@ -0,0 +1,107 @@ +class Admins::StatisticSchoolDataGrowService < ApplicationService + include CustomSortable + + PAGE_SIZE = 20 + + attr_reader :params + + sort_columns :teacher_increase_count, :student_increase_count, + :course_increase_count, :shixun_increase_count, :uniq_active_user_count, + :shixun_homework_count, :shixun_evaluate_count, + default_by: :teacher_increase_count, default_direction: :desc + + def initialize(params) + @params = params + end + + def call + reports = School.where(nil) + + reports = search_filter(reports) + + count = reports.count + + subquery = SchoolDailyActiveUser.select('COUNT(distinct(user_id))').joins(:school_daily_report) + .where(date_condition_sql).where("school_id is not null and school_id = schools.id").to_sql + reports = reports.joins("LEFT JOIN school_daily_reports sdr ON sdr.school_id = schools.id AND #{date_condition_sql}") + reports = reports.select( + 'schools.id school_id, schools.name school_name,'\ + 'SUM(teacher_increase_count) teacher_increase_count,'\ + 'SUM(student_increase_count) student_increase_count,'\ + 'SUM(course_increase_count) course_increase_count,'\ + 'SUM(shixun_increase_count) shixun_increase_count,'\ + 'SUM(shixun_homework_count) shixun_homework_count,'\ + 'SUM(shixun_evaluate_count) shixun_evaluate_count,'\ + "(#{subquery}) uniq_active_user_count,"\ + 'SUM(active_user_count) active_user_count').group('schools.id') + + reports = custom_sort(reports, params[:sort_by], params[:sort_direction]) + reports = reports.order('school_id asc').limit(PAGE_SIZE).offset(offset) + + [count, reports] + end + + def grow_summary + @_grow_summary ||= begin + reports = School.joins("LEFT JOIN school_daily_reports sdr ON sdr.school_id = schools.id") + .where(date_condition_sql) + + subquery = SchoolDailyActiveUser.select('COUNT(distinct user_id)') + .joins('LEFT JOIN school_daily_reports sdr ON sdr.id = school_daily_active_users.school_daily_report_id') + .where(date_condition_sql).to_sql + reports = search_filter(reports) + reports.select( + 'SUM(teacher_increase_count) teacher_increase_count,'\ + 'SUM(student_increase_count) student_increase_count,'\ + 'SUM(course_increase_count) course_increase_count,'\ + 'SUM(shixun_increase_count) shixun_increase_count,'\ + 'SUM(shixun_homework_count) shixun_homework_count,'\ + 'SUM(shixun_evaluate_count) shixun_evaluate_count,'\ + "(#{subquery}) uniq_active_user_count,"\ + 'SUM(active_user_count) active_user_count' + ).first + end + end + + private + + def search_filter(relations) + keyword = params[:keyword].try(:to_s).try(:strip) + if keyword.present? + relations = relations.where("schools.name LIKE :keyword OR schools.id LIKE :keyword", keyword: "%#{keyword}%") + end + + relations + end + + def date_condition_sql + date = query_date + if date.is_a?(Range) + "date BETWEEN '#{date.min.strftime('%Y-%m-%d')}' AND '#{date.max.strftime('%Y-%m-%d')}'" + else + "date = '#{date.strftime('%Y-%m-%d')}'" + end + end + + def query_date + if params[:grow_begin_date].present? + begin_time = Time.zone.parse(params[:grow_begin_date]) + end_date = if params[:grow_end_date].present? + Time.zone.parse(params[:grow_end_date]) + end + + end_date.blank? || end_date == begin_time ? begin_time : begin_time..end_date + else + yesterday + end + end + + def yesterday + # 每日凌晨5点为节点, 25日凌晨4点、3点、2点等等,未到更新数据时间点,看到的数据是:23日-24日的统计数据 + (Time.zone.now - 5.hours).beginning_of_day - 1.days + end + + def offset + (params[:page].to_i.zero? ? 0 : params[:page].to_i - 1) * PAGE_SIZE + end +end \ No newline at end of file diff --git a/app/services/admins/update_user_service.rb b/app/services/admins/update_user_service.rb new file mode 100644 index 000000000..9531d3718 --- /dev/null +++ b/app/services/admins/update_user_service.rb @@ -0,0 +1,52 @@ +class Admins::UpdateUserService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :user, :params + + def initialize(user, params) + @user = user + @params = params + end + + def call + user.assign_attributes(user_attributes) + user.firstname = '' + user.password = password if params[:password].present? + + if params[:identity].to_s == 'student' + params[:technical_title] = nil + else + params[:student_id] = nil + end + user.user_extension.assign_attributes(user_extension_attributes) + + ActiveRecord::Base.transaction do + user.save! + user.user_extension.save! + + update_gitlab_password if params[:password].present? + end + + user + end + + private + + def user_attributes + params.slice(*%i[lastname nickname mail phone admin business is_test + professional_certification authentication]) + end + + def user_extension_attributes + params.slice(*%i[gender identity technical_title student_id location location_city school_id department_id]) + end + + def update_gitlab_password + return if user.gid.blank? + # 同步修改gitlab密码 + Gitlab.client.edit_user(user.gid, password: params[:password]) + rescue Exception => ex + Util.logger_error(ex) + raise Error, '保存失败' + end +end \ No newline at end of file diff --git a/app/services/homeworks_service.rb b/app/services/homeworks_service.rb index 78be3b8f9..64aec92c6 100644 --- a/app/services/homeworks_service.rb +++ b/app/services/homeworks_service.rb @@ -15,7 +15,8 @@ class HomeworksService homework_detail_manual.save! if homework_detail_manual HomeworkCommonsShixun.create!(homework_common_id: homework.id, shixun_id: shixun.id) HomeworksService.new.create_shixun_homework_cha_setting(homework, shixun) - HomeworksService.new.create_works_list(homework, course) + CreateStudentWorkJob.perform_later(homework.id) + # HomeworksService.new.create_works_list(homework, course) end homework end diff --git a/app/services/reward_grade_service.rb b/app/services/reward_grade_service.rb index 7642e1967..38eda7aaa 100644 --- a/app/services/reward_grade_service.rb +++ b/app/services/reward_grade_service.rb @@ -1,13 +1,14 @@ class RewardGradeService < ApplicationService - attr_reader :user, :attrs + attr_reader :user, :attrs, :not_unique def initialize(user, **attrs) @user = user + @not_unique = attrs.delete(:not_unique) || false @attrs = attrs.slice(*%i[container_id container_type score]) end def call - return if user.grades.exists?(attrs) + return if user.grades.exists?(attrs) && !not_unique ActiveRecord::Base.transaction do grade = user.grades.create!(attrs) diff --git a/app/services/users/course_service.rb b/app/services/users/course_service.rb index 9eb34917e..de70c5b86 100644 --- a/app/services/users/course_service.rb +++ b/app/services/users/course_service.rb @@ -23,11 +23,11 @@ class Users::CourseService def category_scope_courses case params[:category] when 'study' then - user.as_student_courses + user.as_student_courses.started when 'manage' then user.manage_courses else - ids = user.as_student_courses.pluck(:id) + user.manage_courses.pluck(:id) + ids = user.as_student_courses.started.pluck(:id) + user.manage_courses.pluck(:id) Course.where(id: ids) end end diff --git a/app/tasks/statistic_school_daily_report_task.rb b/app/tasks/statistic_school_daily_report_task.rb new file mode 100644 index 000000000..5cd3fda7c --- /dev/null +++ b/app/tasks/statistic_school_daily_report_task.rb @@ -0,0 +1,69 @@ +class StatisticSchoolDailyReportTask + def call + School.find_each do |school| + # 新增教师和学生 + users = User.joins(:user_extension) + .where(user_extensions: { school_id: school.id }) + + teacher_count = users.where(created_on: yesterday, user_extensions: { identity: :teacher }).count + student_count = users.where(created_on: yesterday, user_extensions: { identity: :student }).count + + # 活跃用户 + active_user_ids = users.where(last_login_on: yesterday).pluck(:id) + active_user_count = active_user_ids.size + + + # 新增课堂 + course_count = school.courses.where(created_at: yesterday).count + + # 新增实训 + shixun_count = Shixun.joins(user: :user_extension) + .where(user_extensions: { identity: :teacher, school_id: school.id }) + .where(created_at: yesterday).count + # 新增实训作业数 + shixun_homework_count = HomeworkCommon.joins(:course).where(courses: { school_id: school.id }) + .where(homework_type: 4, created_at: yesterday).count + + # 新增实训评测数量 + shixun_evaluate_count = EvaluateRecord.joins('LEFT JOIN homework_commons_shixuns hcs ON hcs.shixun_id = evaluate_records.shixun_id') + .joins('LEFT JOIN homework_commons hc ON hcs.homework_common_id = hc.id AND hc.homework_type = 4') + .joins('LEFT JOIN members ON members.user_id = evaluate_records.user_id') + .joins('LEFT JOIN courses ON members.course_id = courses.id AND hc.course_id = courses.id') + .where(courses: { school_id: school.id }) + .where(created_at: yesterday).reorder(nil).count + + # 无有效数据时不记录 + data = [teacher_count, student_count, course_count, shixun_count, active_user_count, + shixun_homework_count, shixun_evaluate_count] + next if data.all?(&:zero?) + + create_params = { + school_id: school.id, school_name: school.name, teacher_increase_count: teacher_count, + student_increase_count: student_count, course_increase_count: course_count, + shixun_homework_count: shixun_homework_count, shixun_evaluate_count: shixun_evaluate_count, + shixun_increase_count: shixun_count, active_user_count: active_user_count, date: current_date + } + report = SchoolDailyReport.create!(create_params) + + if active_user_ids.present? + values = '(' + active_user_ids.join(", #{report.id}),(") + ", #{report.id})" + user_sql = "INSERT INTO school_daily_active_users(user_id, school_daily_report_id) VALUES#{values}" + SchoolDailyActiveUser.connection.execute(user_sql) + end + end + end + + private + def current_date + @_current_date ||= Time.zone.now.beginning_of_day - 1.day + end + + def yesterday + @_yesterday ||= begin + # 每日凌晨5点为节点 + end_time = Time.zone.now.beginning_of_day + 5.hour + begin_time = end_time - 1.day + begin_time..end_time + end + end +end diff --git a/app/tasks/statistic_school_report_task.rb b/app/tasks/statistic_school_report_task.rb new file mode 100644 index 000000000..a55eb7a42 --- /dev/null +++ b/app/tasks/statistic_school_report_task.rb @@ -0,0 +1,20 @@ +class StatisticSchoolReportTask + def call + School.find_each do |school| + evaluate_count = Game.joins(:challenge) + .joins('LEFT JOIN members ON members.user_id = games.user_id') + .joins('LEFT JOIN homework_commons_shixuns hcs ON hcs.shixun_id = challenges.shixun_id') + .joins('LEFT JOIN homework_commons hc ON hcs.homework_common_id = hc.id AND hc.homework_type = 4') + .joins('LEFT JOIN courses ON hc.course_id = courses.id AND members.course_id = courses.id') + .where(courses: { school_id: school.id }) + .sum(:evaluate_count) + + report = SchoolReport.find_or_initialize_by(school_id: school.id) + + report.school_name = school.name + report.shixun_evaluate_count = evaluate_count + + report.save + end + end +end diff --git a/app/templates/shared/main.css b/app/templates/shared/main.css index 81b80cdf8..9e9bc4f50 100644 --- a/app/templates/shared/main.css +++ b/app/templates/shared/main.css @@ -760,9 +760,9 @@ html>body #ajax-indicator { position: fixed; } .paddingLeft28{padding-left:28px;} -.ant-modal-header{ - border-radius: 10px; -} +/*.ant-modal-header{*/ + /*border-radius: 10px;*/ +/*}*/ .color656565{ color:#656565; @@ -779,4 +779,7 @@ html>body #ajax-indicator { position: fixed; } border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; +} +.yslminHeigth{ + min-height: 400px; } \ No newline at end of file diff --git a/app/views/admins/daily_school_statistics/export.xlsx.axlsx b/app/views/admins/daily_school_statistics/export.xlsx.axlsx new file mode 100644 index 000000000..1757a8fe2 --- /dev/null +++ b/app/views/admins/daily_school_statistics/export.xlsx.axlsx @@ -0,0 +1,13 @@ +wb = xlsx_package.workbook +wb.add_worksheet(name: '统计总表') do |sheet| + sheet.add_row %w(ID 单位名称 教师总人数 学生总人数 课堂总数 正在进行课堂数 总实训数 实训评测总数 实训作业总数 其它作业总数 动态时间) + + @schools.each do |school| + sheet.add_row([ + school[:id].to_s, school[:name].to_s, (school[:teacher_count] || 0).to_s, (school[:student_count] || 0).to_s, + (school[:course_count] || 0).to_s, (school[:active_course_count] || 0).to_s, + (school[:shixun_count] || 0).to_s,(school[:shixun_evaluate_count] || 0).to_s, (school[:homework_count] || 0).to_s, + (school[:other_homework_count] || 0).to_s, format_time(school[:nearly_course_time]) + ]) + end +end \ No newline at end of file diff --git a/app/views/admins/daily_school_statistics/index.html.erb b/app/views/admins/daily_school_statistics/index.html.erb new file mode 100644 index 000000000..054e06fc6 --- /dev/null +++ b/app/views/admins/daily_school_statistics/index.html.erb @@ -0,0 +1,29 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('统计总表', admins_daily_school_statistics_path) %> +<% end %> + +
              + <%= form_tag(admins_daily_school_statistics_path, method: :get, class: 'form-inline search-form', remote: true) do %> + <%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: 'ID/单位名称搜索') %> + <%= submit_tag('搜索', class: 'btn btn-primary ml-3') %> + <% end %> + + <%#= link_to '导出Excel', export_admins_daily_school_statistics_path(format: :xlsx), class: 'btn btn-outline-primary export-action' %> + <%= javascript_void_link '导出Excel', class: 'btn btn-outline-primary export-action', 'data-url': export_admins_daily_school_statistics_path(format: :xlsx) %> +
              + +
              + 统计总计: + 教师总人数<%= @teacher_total %>人, + 学生总人数<%= @student_total %>人, + 课堂总数<%= @course_total %>个, + 正在进行课堂总数<%= @active_course_total %>个, + 实训总数<%= @shixun_total %>个, + 实训评测总数<%= @shixun_evaluate_total %>个, + 实训作业总数<%= @shixun_homework_total %>个, + 其它作业总数<%= @other_homework_total %>个 +
              + +
              + <%= render partial: 'admins/daily_school_statistics/shared/list', locals: { statistics: @statistics } %> +
              \ No newline at end of file diff --git a/app/views/admins/daily_school_statistics/index.js.erb b/app/views/admins/daily_school_statistics/index.js.erb new file mode 100644 index 000000000..d3e261f64 --- /dev/null +++ b/app/views/admins/daily_school_statistics/index.js.erb @@ -0,0 +1 @@ +$(".daily-school-statistic-list-container").html("<%= j(render partial: 'admins/daily_school_statistics/shared/list', locals: { statistics: @statistics }) %>") \ No newline at end of file diff --git a/app/views/admins/daily_school_statistics/shared/_list.html.erb b/app/views/admins/daily_school_statistics/shared/_list.html.erb new file mode 100644 index 000000000..611acdd2a --- /dev/null +++ b/app/views/admins/daily_school_statistics/shared/_list.html.erb @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + <% if statistics.present? %> + <% statistics.each do |statistic| %> + + + + + + + + + + + + + <% end %> + <% else %> + <%= render 'admins/shared/no_data_for_table' %> + <% end %> + +
              单位名称<%= sort_tag('教师总数', name: 'teacher_count', path: admins_daily_school_statistics_path) %><%= sort_tag('学生总数', name: 'student_count', path: admins_daily_school_statistics_path) %><%= sort_tag('课堂总数', name: 'course_count', path: admins_daily_school_statistics_path) %><%= sort_tag('正在进行课堂数', name: 'active_course_count', path: admins_daily_school_statistics_path) %><%= sort_tag('实训总数', name: 'shixun_count', path: admins_daily_school_statistics_path) %> + <%= sort_tag(name: 'shixun_evaluate_count', path: admins_daily_school_statistics_path) do %> + 实训评测总数 + + <% end %> + <%= sort_tag('实训作业总数', name: 'homework_count', path: admins_daily_school_statistics_path) %><%= sort_tag('其它作业总数', name: 'other_homework_count', path: admins_daily_school_statistics_path) %><%= sort_tag('动态时间', name: 'nearly_course_time', path: admins_daily_school_statistics_path) %>
              + <%= link_to statistic[:name], "/colleges/#{statistic[:id]}/statistics", + target: '_blank', data: { toggle: 'tooltip', title: '点击查看学校统计概况' } %> + <%= statistic[:teacher_count].to_i %><%= statistic[:student_count].to_i %><%= statistic[:course_count].to_i %><%= statistic[:active_course_count].to_i %><%= statistic[:shixun_count].to_i %><%= statistic[:shixun_evaluate_count].to_i %><%= statistic[:homework_count].to_i %><%= statistic[:other_homework_count].to_i %><%= statistic[:nearly_course_time]&.strftime('%Y-%m-%d %H:%M') %>
              + +<%= render partial: 'admins/shared/paginate', locals: { objects: statistics } %> \ No newline at end of file diff --git a/app/views/admins/dashboards/index.html.erb b/app/views/admins/dashboards/index.html.erb new file mode 100644 index 000000000..bff34dbcd --- /dev/null +++ b/app/views/admins/dashboards/index.html.erb @@ -0,0 +1,188 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('概览', admins_path) %> +<% end %> + +
              +
              +
              + +
              +
              +
              +
              +
              +
              +
              Traffic
              + 350,897 +
              +
              +
              + +
              +
              +
              +

              + 3.48% + Since last month +

              +
              +
              +
              +
              +
              +
              +
              +
              +
              New users
              + 2,356 +
              +
              +
              + +
              +
              +
              +

              + 3.48% + Since last week +

              +
              +
              +
              +
              +
              +
              +
              +
              +
              Sales
              + 924 +
              +
              +
              + +
              +
              +
              +

              + 1.10% + Since yesterday +

              +
              +
              +
              +
              +
              +
              +
              +
              +
              Performance
              + 49,65% +
              +
              +
              + +
              +
              +
              +

              + 12% + Since last month +

              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +

              Page visits

              +
              +
              + Test +
              +
              +
              +
              + + + + + + + + + + + + <% 5.times do %> + + + + + + + <% end %> + +
              TestTestTestTest
              /test/4,569340 + 46,53% +
              +
              +
              +
              +
              +
              +
              +
              +
              +

              Test

              +
              +
              + Test +
              +
              +
              +
              + + + + + + + + + + + <% 5.times do %> + + + + + + <% end %> + +
              TestTest
              + Test + + 1,480 + +
              + 60% +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/app/views/admins/kaminari/_first_page.html.erb b/app/views/admins/kaminari/_first_page.html.erb new file mode 100644 index 000000000..148fc3f1c --- /dev/null +++ b/app/views/admins/kaminari/_first_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "First" page + - available local variables + url: url to the first page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
            • + <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote, class: 'page-link' %> +
            • diff --git a/app/views/admins/kaminari/_gap.html.erb b/app/views/admins/kaminari/_gap.html.erb new file mode 100644 index 000000000..59236c8f6 --- /dev/null +++ b/app/views/admins/kaminari/_gap.html.erb @@ -0,0 +1,13 @@ +<%# Non-link tag that stands for skipped pages... + - available local variables + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
            • + <%= link_to 'javascript:void(0)', { class: 'page-link' } do %> + <%= t('views.pagination.truncate').html_safe %> + (current) + <% end %> +
            • diff --git a/app/views/admins/kaminari/_last_page.html.erb b/app/views/admins/kaminari/_last_page.html.erb new file mode 100644 index 000000000..3e8a524a0 --- /dev/null +++ b/app/views/admins/kaminari/_last_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Last" page + - available local variables + url: url to the last page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
            • + <%= link_to_unless(current_page.last?, t('views.pagination.last').html_safe, url, remote: remote, class: 'page-link') %> +
            • diff --git a/app/views/admins/kaminari/_next_page.html.erb b/app/views/admins/kaminari/_next_page.html.erb new file mode 100644 index 000000000..211ddd423 --- /dev/null +++ b/app/views/admins/kaminari/_next_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Next" page + - available local variables + url: url to the next page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
            • diff --git a/app/views/admins/kaminari/_page.html.erb b/app/views/admins/kaminari/_page.html.erb new file mode 100644 index 000000000..10a8374c7 --- /dev/null +++ b/app/views/admins/kaminari/_page.html.erb @@ -0,0 +1,19 @@ +<%# Link showing page number + - available local variables + page: a page object for "this" page + url: url to this page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
            • + <% if page.current? %> + <%= link_to url, {remote: remote, rel: page.rel, class: 'page-link'} do %> + <%= page %> + (current) + <% end %> + <% else %> + <%= link_to page, url, {remote: remote, rel: page.rel, class: 'page-link'} %> + <% end %> +
            • diff --git a/app/views/admins/kaminari/_paginator.html.erb b/app/views/admins/kaminari/_paginator.html.erb new file mode 100644 index 000000000..8d090b129 --- /dev/null +++ b/app/views/admins/kaminari/_paginator.html.erb @@ -0,0 +1,27 @@ +<%# The container tag + - available local variables + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote + paginator: the paginator that renders the pagination tags inside +-%> +<%= paginator.render do -%> + +<% end -%> diff --git a/app/views/admins/kaminari/_prev_page.html.erb b/app/views/admins/kaminari/_prev_page.html.erb new file mode 100644 index 000000000..90c38dd36 --- /dev/null +++ b/app/views/admins/kaminari/_prev_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Previous" page + - available local variables + url: url to the previous page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> + diff --git a/app/views/admins/school_statistics/contrast.js.erb b/app/views/admins/school_statistics/contrast.js.erb new file mode 100644 index 000000000..bc9916c12 --- /dev/null +++ b/app/views/admins/school_statistics/contrast.js.erb @@ -0,0 +1 @@ +$(".school-statistic-list-container").html("<%= j(render partial: 'admins/school_statistics/shared/contrast_list', locals: { statistics: @statistics, select_options: @select_options }) %>") \ No newline at end of file diff --git a/app/views/admins/school_statistics/index.html.erb b/app/views/admins/school_statistics/index.html.erb new file mode 100644 index 000000000..fd0c7cac1 --- /dev/null +++ b/app/views/admins/school_statistics/index.html.erb @@ -0,0 +1,49 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('数据变化报表', admins_school_statistics_path) %> +<% end %> + +
              +
              +
              + + <%= hidden_field_tag :contrast_column, params[:contrast_column] %> + +
              +
              + <%= text_field_tag :grow_begin_date, params[:grow_begin_date], class: 'form-control start-date mx-0', placeholder: '开始时间' %> +
              + <%= text_field_tag :grow_end_date, params[:grow_end_date], class: 'form-control end-date mx-0', placeholder: '结束时间' %> +
              +
              +
              +
              + <%= text_field_tag :begin_date, params[:begin_date], class: 'form-control start-date mx-0', placeholder: '开始时间' %> +
              + <%= text_field_tag :end_date, params[:end_date], class: 'form-control end-date mx-0', placeholder: '结束时间' %> +
              +
              VS
              +
              + <%= text_field_tag :other_begin_date, params[:other_begin_date], class: 'form-control start-date mx-0', placeholder: '开始时间' %> +
              + <%= text_field_tag :other_end_date, params[:other_end_date], class: 'form-control end-date mx-0', placeholder: '结束时间' %> +
              +
              +
              + +
              + <%= hidden_field_tag :data_type, params[:data_type] || 'grow' %> + <%= javascript_void_link '时段对比', class: "btn btn-outline-primary btn-sm contrast-btn #{params[:data_type] == 'contrast' ? 'active' : ''}", + data: { toggle: 'tooltip', trigger: 'hover', title: '请在左侧分别选择需进行对比的两个时段,具体从当日5:00开始计算,下表显示两时段选定指标两时段变化情况对比' } %> + <%= javascript_void_link '数据变化', class: "btn btn-outline-primary btn-sm grow-btn #{params[:data_type] == 'contrast' ? '' : 'active'}", + data: { toggle: 'tooltip', trigger: 'hover', title: '请在左侧选择时间段,具体从当日5:00开始计算,下表显示选定时间段内各项指标数据变化情况' } %> +
              + + <%= text_field_tag :keyword, params[:keyword], placeholder: 'ID/单位名称检索', class: 'form-control mx-3 search-input' %> + + <%= javascript_void_link '搜索', class: 'btn btn-primary search-btn' %> +
              +
              + +
              + <%= render partial: 'admins/school_statistics/shared/list', locals: { statistics: @statistics } %> +
              \ No newline at end of file diff --git a/app/views/admins/school_statistics/index.js.erb b/app/views/admins/school_statistics/index.js.erb new file mode 100644 index 000000000..3f5f7334b --- /dev/null +++ b/app/views/admins/school_statistics/index.js.erb @@ -0,0 +1 @@ +$(".school-statistic-list-container").html("<%= j(render partial: 'admins/school_statistics/shared/list', locals: { statistics: @statistics }) %>") \ No newline at end of file diff --git a/app/views/admins/school_statistics/shared/_contrast_list.html.erb b/app/views/admins/school_statistics/shared/_contrast_list.html.erb new file mode 100644 index 000000000..928340c80 --- /dev/null +++ b/app/views/admins/school_statistics/shared/_contrast_list.html.erb @@ -0,0 +1,72 @@ +
              +
              + 学校数据统计(<%= I18n.t("school_daily_report.#{params[:contrast_column]}") %>变化统计情况) +
              + <%= select_tag :contrast_column, + options_for_select(select_options, params[:contrast_column]), + class: 'form-control contrast-column-select' %> + + <% if statistics.present? %> +
              + 说明:新增数=时段二-时段一;新增百分比=(新增数/时段一)*100%(保留小数点后五位) +
              + <% end %> +
              + +<% if statistics.present? %> + + + + + + + + + + + <% statistics.each do |statistic| %> + + + + + <% + increase = statistic['other_total'] - statistic['total'] + percentage = statistic['total'].zero? ? increase.to_f * 100 : (increase / statistic['total'].to_f) * 100 + %> + <% if increase > 0 %> + + <% if statistic['total'].zero? %> + + <% else %> + + <% end %> + <% elsif increase.zero? %> + + + <% else %> + + + <% end %> + + <% end %> + +
              单位名称时段一
              <%= "(#{params[:begin_date]} 05:00至#{(Time.zone.parse(params[:end_date]) + 1.days).strftime('%Y-%m-%d')} 05:00)" %>
              时段二
              <%= "(#{params[:other_begin_date]} 05:00至#{(Time.zone.parse(params[:other_end_date]) + 1.days).strftime('%Y-%m-%d')} 05:00)" %>
              + <%= sort_tag('变化情况', name: 'percentage', path: contrast_admins_school_statistics_path) %> +
              ( 新 增 数 | 新增百分比) +
              + <%= link_to statistic.school_name, "/colleges/#{statistic.school_id}/statistics", + target: '_blank', data: { toggle: 'tooltip', title: '点击查看学校统计概况' } %> + <%= statistic['total'] %><%= statistic['other_total'] %> + +<%= increase %> + -+<%= percentage.round(5) %>% + <%= increase %> + <%= percentage.round(5) %>% + <%= increase %> + <%= percentage.round(5) %>%
              + + <%= render partial: 'admins/shared/paginate', locals: { objects: statistics } %> +<% else %> +
              + 暂无数据,请选择时间段对比 +
              +<% end %> \ No newline at end of file diff --git a/app/views/admins/school_statistics/shared/_list.html.erb b/app/views/admins/school_statistics/shared/_list.html.erb new file mode 100644 index 000000000..aa043f097 --- /dev/null +++ b/app/views/admins/school_statistics/shared/_list.html.erb @@ -0,0 +1,56 @@ +
              + 统计总计: + <% if params[:grow_begin_date].present? %> + <%= params[:grow_begin_date] %> 05:00至<%= (Time.zone.parse(params[:grow_end_date]) + 1.days).strftime('%Y-%m-%d') %> 05:00 + <% else %> + <%= (Time.current - 5.hour).beginning_of_day.ago(1.days).strftime('%Y-%m-%d') %> 05:00至 + <%= (Time.current - 5.hour).beginning_of_day.strftime('%Y-%m-%d') %> 05:00, + <% end %> + 新增教师<%= @grow_summary.teacher_increase_count || 0 %>人, + 新增学生<%= @grow_summary.student_increase_count || 0 %>人, + 新增课堂<%= @grow_summary.course_increase_count || 0 %>个, + 新增实训<%= @grow_summary.shixun_increase_count || 0 %>个, + 新增实训作业<%= @grow_summary.shixun_homework_count || 0 %>个, + 新增实训评测<%= @grow_summary.shixun_evaluate_count || 0 %>个, + 活跃用户 + <%= @grow_summary.uniq_active_user_count.to_i.zero? ? @grow_summary.active_user_count.to_i : @grow_summary.uniq_active_user_count.to_i %> + 个 +
              + + + + + + + + + + + + + + + + <% if statistics.present? %> + <% statistics.each do |statistic| %> + + + + + + + + + + + <% end %> + <% else %> + <%= render 'admins/shared/no_data_for_table' %> + <% end %> + +
              单位名称<%= sort_tag('新增教师', name: 'teacher_increase_count', path: admins_school_statistics_path) %><%= sort_tag('新增学生', name: 'student_increase_count', path: admins_school_statistics_path) %><%= sort_tag('新增课堂', name: 'course_increase_count', path: admins_school_statistics_path) %><%= sort_tag('新增实训', name: 'shixun_increase_count', path: admins_school_statistics_path) %><%= sort_tag('新增实训作业', name: 'shixun_homework_count', path: admins_school_statistics_path) %><%= sort_tag('新增实训评测', name: 'shixun_evaluate_count', path: admins_school_statistics_path) %><%= sort_tag('活跃用户', name: 'uniq_active_user_count', path: admins_school_statistics_path) %>
              + <%= link_to statistic.school_name, "/colleges/#{statistic.school_id}/statistics", + target: '_blank', data: { toggle: 'tooltip', title: '点击查看学校统计概况' } %> + <%= statistic.teacher_increase_count.to_i %><%= statistic.student_increase_count.to_i %><%= statistic.course_increase_count.to_i %><%= statistic.shixun_increase_count.to_i %><%= statistic.shixun_homework_count.to_i %><%= statistic.shixun_evaluate_count.to_i %><%= statistic.uniq_active_user_count.to_i.zero? ? statistic.active_user_count.to_i : statistic.uniq_active_user_count.to_i %>
              + +<%= render partial: 'admins/shared/paginate', locals: { objects: statistics } %> \ No newline at end of file diff --git a/app/views/admins/shared/404.html.erb b/app/views/admins/shared/404.html.erb new file mode 100644 index 000000000..8e285cb27 --- /dev/null +++ b/app/views/admins/shared/404.html.erb @@ -0,0 +1,6 @@ +
              +
              + 404 +
              +
              资源未找到
              +
              \ No newline at end of file diff --git a/app/views/admins/shared/422.html.erb b/app/views/admins/shared/422.html.erb new file mode 100644 index 000000000..e56889496 --- /dev/null +++ b/app/views/admins/shared/422.html.erb @@ -0,0 +1,6 @@ +
              +
              + 422 +
              +
              <%= @message %>
              +
              \ No newline at end of file diff --git a/app/views/admins/shared/500.html.erb b/app/views/admins/shared/500.html.erb new file mode 100644 index 000000000..f053f58ec --- /dev/null +++ b/app/views/admins/shared/500.html.erb @@ -0,0 +1,6 @@ +
              +
              + 500 +
              +
              系统错误
              +
              \ No newline at end of file diff --git a/app/views/admins/shared/_alert.html.erb b/app/views/admins/shared/_alert.html.erb new file mode 100644 index 000000000..8c568f2a3 --- /dev/null +++ b/app/views/admins/shared/_alert.html.erb @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/views/admins/shared/_breadcrumb.html.erb b/app/views/admins/shared/_breadcrumb.html.erb new file mode 100644 index 000000000..8aea89871 --- /dev/null +++ b/app/views/admins/shared/_breadcrumb.html.erb @@ -0,0 +1,13 @@ +<% if @_admin_breadcrumbs.present? %> + +<% end %> \ No newline at end of file diff --git a/app/views/admins/shared/_flash_notice.html.erb b/app/views/admins/shared/_flash_notice.html.erb new file mode 100644 index 000000000..7bee80b0f --- /dev/null +++ b/app/views/admins/shared/_flash_notice.html.erb @@ -0,0 +1,20 @@ +<% flash.each do |k, v| %> + <% next unless %w(success danger warning info).include?(k.to_s) %> + + +<% end %> +<% flash.now.as_json.each do |k, v| %> + <% next unless %w(success danger warning info).include?(k.to_s) %> + + +<% end %> \ No newline at end of file diff --git a/app/views/admins/shared/_no_data_for_table.html.erb b/app/views/admins/shared/_no_data_for_table.html.erb new file mode 100644 index 000000000..1899f8d2e --- /dev/null +++ b/app/views/admins/shared/_no_data_for_table.html.erb @@ -0,0 +1 @@ +暂无数据 \ No newline at end of file diff --git a/app/views/admins/shared/_paginate.html.erb b/app/views/admins/shared/_paginate.html.erb new file mode 100644 index 000000000..b7e40e879 --- /dev/null +++ b/app/views/admins/shared/_paginate.html.erb @@ -0,0 +1,6 @@ +
              + <% if objects.size.nonzero? %> +
              <%= page_entries_info objects %>
              + <% end %> + <%= paginate objects, views_prefix: 'admins', remote: true %> +
              \ No newline at end of file diff --git a/app/views/admins/shared/_sidebar.html.erb b/app/views/admins/shared/_sidebar.html.erb new file mode 100644 index 000000000..cd64f3d0f --- /dev/null +++ b/app/views/admins/shared/_sidebar.html.erb @@ -0,0 +1,42 @@ +<% sidebar_collapse = request.cookies['admin_sidebar_collapse'].to_s == 'true' %> + \ No newline at end of file diff --git a/app/views/admins/shared/after_render_js_hook.js.erb b/app/views/admins/shared/after_render_js_hook.js.erb new file mode 100644 index 000000000..9ceb13f5c --- /dev/null +++ b/app/views/admins/shared/after_render_js_hook.js.erb @@ -0,0 +1,3 @@ +; +$('[data-toggle="tooltip"]').tooltip(); +$('[data-toggle="popover"]').popover(); \ No newline at end of file diff --git a/app/views/admins/shared/delete.js.erb b/app/views/admins/shared/delete.js.erb new file mode 100644 index 000000000..a10e1ae28 --- /dev/null +++ b/app/views/admins/shared/delete.js.erb @@ -0,0 +1,20 @@ +var deleteRow = $('<%= params[:element] %>'); +var refreshUrl = '<%= params[:refresh_url] %>'; + +var refreshFunc = function(url) { + $.ajax({ + url: url.length > 0 ? url : window.location.href, + method: 'GET', + dataType: "script" + }) +} + +if(deleteRow.length > 0){ + var needRefresh = deleteRow.siblings().length == 0; + + deleteRow.remove(); + + if(needRefresh){ refreshFunc(refreshUrl); } +} else { + refreshFunc(refreshUrl); +} \ No newline at end of file diff --git a/app/views/admins/shared/error.js.erb b/app/views/admins/shared/error.js.erb new file mode 100644 index 000000000..ebb78aec6 --- /dev/null +++ b/app/views/admins/shared/error.js.erb @@ -0,0 +1,7 @@ +$('.admin-alert-container').html('<%= j( render partial: 'admins/shared/alert', locals: { message: message } ) %>'); + +setTimeout(function() { + if ($('.admin-alert-container button.close').length > 0) { + $('.admin-alert-container button.close').trigger('click'); + } +}, 2000) \ No newline at end of file diff --git a/app/views/admins/users/edit.html.erb b/app/views/admins/users/edit.html.erb new file mode 100644 index 000000000..3f9d71b96 --- /dev/null +++ b/app/views/admins/users/edit.html.erb @@ -0,0 +1,137 @@ +<% + define_admin_breadcrumbs do + add_admin_breadcrumb('用户管理', admins_users_path) + add_admin_breadcrumb('用户详情') + end +%> + +
              + + + <%= simple_form_for(@user, url: admins_user_path(@user)) do |f| %> + +
              基本信息
              +
              +
              + <%= f.input :lastname, label: '姓名', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-md-11', value: @user.only_real_name } %> +
              + +
              + <%= f.input :nickname, label: '昵称', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-md-11' } %> + <%= f.input :gender, as: :radio_buttons, label: '性别', collection: [%w(男 0), %w(女 1)], wrapper_html: { class: 'col-md-3' } %> +
              + +
              +
              + <%= f.label :identity, label: '职业' %> + <%= select_tag('user[identity]', [], class: 'form-control identity-select optional', 'data-value': @user.user_extension&.identity, 'data-first-title': '请选择') %> +
              +
              + <%= f.label :technical_title, label: '职称' %> + <%= select_tag('user[technical_title]', [], class: 'form-control technical-title-select optional', 'data-value': @user.technical_title) %> +
              + + <%= f.input :student_id, as: :tel, label: '学号', wrapper_html: { class: 'col-md-2', style: @user.user_extension.student? ? '' : 'display:none;' }, input_html: { class: 'student-id-input' } %> +
              + +
              + <%= f.input :mail, as: :email, label: '邮箱地址', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %> + <%= f.input :phone, as: :tel, label: '手机号', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %> +
              + +
              +
              + <%= f.label :location, label: '省份' %> + <%= select_tag('user[location]', [], class: 'form-control province-select optional', 'data-value': @user.location, 'data-first-title': '请选择') %> +
              +
              + <%= f.label :location_city, label: '城市' %> + <%= select_tag('user[location_city]', [], class: 'form-control city-select optional', 'data-value': @user.location_city) %> +
              +
              + +
              + <%= f.input :school_id, as: :hidden %> + <%= f.input :department_id, as: :hidden %> +
              + <%= f.label :school_name, label: '所属学校/单位' %> + <%= f.select :school_name, [@user.school_id], {}, class: 'form-control school-select optional' %> +
              +
              + <%= f.label :department_name, label: '所属学院/部门' %> + <%= f.select :department_name, [@user.department_id], {}, class: 'form-control department-select optional' %> +
              +
              +
              + +
              管理
              +
              + <% if current_user.admin? %> +
              + <%= f.label :role, label: '角色' %> +
              + <%= f.input :admin, as: :boolean, label: '管理员', checked_value: 1, unchecked_value: 0 %> + <%= f.input :business, as: :boolean, label: '运营人员', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %> + <%= f.input :is_test, as: :boolean, label: '测试账号', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %> +
              +
              + <% end %> + +
              + <%= f.label :role, label: '认证信息' %> +
              + <%= f.input :professional_certification, as: :boolean, label: '职业认证', checked_value: 1, unchecked_value: 0 %> + <%= f.input :authentication, as: :boolean, label: '实名认证', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %> +
              +
              + +
              + <%= f.input :password, as: :password, label: '修改密码', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %> + <%= f.input :password_confirmation, as: :password, label: '确认密码', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %> +
              +
              + +
              + <%= f.button :submit, value: '保存', class: 'btn-primary mr-3 px-4' %> + <%= link_to '取消', 'javascript:history.go(-1)', class: 'btn btn-secondary px-4' %> +
              + <% end %> +
              \ No newline at end of file diff --git a/app/views/admins/users/index.html.erb b/app/views/admins/users/index.html.erb new file mode 100644 index 000000000..5d2af36c3 --- /dev/null +++ b/app/views/admins/users/index.html.erb @@ -0,0 +1,35 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('用户管理', admins_users_path) %> +<% end %> + +
              + <%= form_tag(admins_users_path, method: :get, class: 'form-inline search-form', remote: true) do %> +
              + + <% status_options = [['全部', ''], ['正常', User::STATUS_ACTIVE], ['未激活', User::STATUS_REGISTERED], ['已锁定', User::STATUS_LOCKED]] %> + <%= select_tag(:status, options_for_select(status_options), class: 'form-control') %> +
              + +
              + + <% identity_options = [['全部', '']] + UserExtension.identities.map { |k, v| [I18n.t("user.identity.#{k}"), v] } %> + <%= select_tag(:identity, options_for_select(identity_options), class: 'form-control') %> +
              + +
              + + <% auto_trial_options = [['全部', ''], ['自动授权', 1], ['手动授权', 0]] %> + <%= select_tag(:auto_trial, options_for_select(auto_trial_options), class: 'form-control') %> +
              + + <%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: 'ID/姓名/邮箱/手机号检索') %> + <%= text_field_tag(:school_name, params[:school_name], class: 'form-control col-sm-2', placeholder: '学校/单位检索') %> + <%= submit_tag('搜索', class: 'btn btn-primary ml-3') %> + <% end %> +
              + +
              + <%= render partial: 'admins/users/shared/user_list', locals: { users: @users } %> +
              + +<%= render partial: 'admins/users/shared/reward_grade_modal' %> \ No newline at end of file diff --git a/app/views/admins/users/index.js.erb b/app/views/admins/users/index.js.erb new file mode 100644 index 000000000..4e9e41c71 --- /dev/null +++ b/app/views/admins/users/index.js.erb @@ -0,0 +1 @@ +$('.users-list-container').html("<%= j( render partial: 'admins/users/shared/user_list', locals: { users: @users } ) %>"); \ No newline at end of file diff --git a/app/views/admins/users/shared/_reward_grade_modal.html.erb b/app/views/admins/users/shared/_reward_grade_modal.html.erb new file mode 100644 index 000000000..87c74c499 --- /dev/null +++ b/app/views/admins/users/shared/_reward_grade_modal.html.erb @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/app/views/admins/users/shared/_user_list.html.erb b/app/views/admins/users/shared/_user_list.html.erb new file mode 100644 index 000000000..e23e918fb --- /dev/null +++ b/app/views/admins/users/shared/_user_list.html.erb @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + <% if users.present? %> + <% users.each do |user| %> + + + + + + + + + + + + + <% end %> + <% else %> + <%= render 'admins/shared/no_data_for_table' %> + <% end %> + +
              ID真实姓名邮件地址手机号码单位<%= sort_tag('创建于', name: 'created_on', path: admins_users_path) %><%= sort_tag('最后登录', name: 'last_login_on', path: admins_users_path) %><%= sort_tag('经验值', name: 'experience', path: admins_users_path) %><%= sort_tag('金币', name: 'grade', path: admins_users_path) %>操作
              + <%= link_to "/users/#{user.login}", target: '_blank' do %> + <%= overflow_hidden_span user.login, width: 100 %> + <% end %> + + <%= link_to edit_admins_user_path(user) do %> + <%= overflow_hidden_span user.real_name, width: 100 %> + <% end %> + <%= overflow_hidden_span display_text(user.mail), width: 150 %><%= overflow_hidden_span display_text(user.phone), width: 100 %><%= overflow_hidden_span display_text(user.school_name), width: 150 %><%= display_text(user.created_on&.strftime('%Y-%m-%d %H:%M')) %><%= display_text(user.last_login_on&.strftime('%Y-%m-%d %H:%M')) %><%= user.experience.to_i %><%= user.grade.to_i %> + <%= javascript_void_link('奖励', class: 'action reward-grade-action', data: { toggle: 'modal', target: '.admin-users-reward-grade-modal', id: user.id }) %> + + <%= javascript_void_link '解锁', class: 'action unlock-action', data: { id: user.id, confirm: '确认解锁吗?' }, style: user.locked? ? '' : 'display: none;' %> + + <% if user.registered? %> + <%= javascript_void_link '激活', class: 'action active-action', data: { id: user.id, confirm: '确认激活吗?' } %> + <% end %> + + <% if user.id != current_user.id %> + <%= javascript_void_link '加锁', class: 'action lock-action', data: { id: user.id, confirm: '确认加锁吗?' }, style: user.locked? || user.registered? ? 'display: none;' : '' %> + <% end %> + + <%= delete_link '删除', admins_user_path(user, element: ".user-item-#{user.id}"), class: 'delete-user-action' %> +
              + +<%= render partial: 'admins/shared/paginate', locals: { objects: users } %> \ No newline at end of file diff --git a/app/views/admins/users/show.html.erb b/app/views/admins/users/show.html.erb new file mode 100644 index 000000000..8f1a2f69c --- /dev/null +++ b/app/views/admins/users/show.html.erb @@ -0,0 +1,6 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('用户管理', admins_users_path) %> + <% add_admin_breadcrumb('用户详情') %> +<% end %> + +

              Users Show

              \ No newline at end of file diff --git a/app/views/courses/informs.json.jbuilder b/app/views/courses/informs.json.jbuilder index d584be917..7fc396184 100644 --- a/app/views/courses/informs.json.jbuilder +++ b/app/views/courses/informs.json.jbuilder @@ -1 +1,5 @@ -json.description @course.inform&.description \ No newline at end of file +json.informs @informs do |inform| + json.id inform.id + json.name inform.name + json.description inform.description +end \ No newline at end of file diff --git a/app/views/courses/settings.json.jbuilder b/app/views/courses/settings.json.jbuilder index 376dc6f6d..27dc9aac4 100644 --- a/app/views/courses/settings.json.jbuilder +++ b/app/views/courses/settings.json.jbuilder @@ -9,7 +9,11 @@ json.start_date @course.start_date json.end_date @course.end_date json.is_public @course.is_public json.course_module_types @course.course_modules.where(hidden: 0).pluck(:module_type) -json.course_module_names @course.course_modules.where(hidden: 0).pluck(:module_name) +json.course_modules @course_modules do |module_type| + json.module_type module_type.module_type + json.hidden module_type.hidden + json.module_name module_type.module_name +end json.authentication @course.authentication json.professional_certification @course.professional_certification json.subject_id @course.subject_id diff --git a/app/views/exercises/_shixun_details.json.jbuilder b/app/views/exercises/_shixun_details.json.jbuilder index 29a39a594..bd559d37d 100644 --- a/app/views/exercises/_shixun_details.json.jbuilder +++ b/app/views/exercises/_shixun_details.json.jbuilder @@ -31,8 +31,9 @@ json.shixun_detail do if shixun_challenge.challenge&.path.present? if game.try(:lastest_code).blank? cha_path = challenge_path(shixun_challenge.challenge&.path) - latest_code = git_fle_content(game.myshixun.repo_path,cha_path) - if latest_code.to_s == "true" + begin + latest_code = git_fle_content(game.myshixun.repo_path,cha_path) + rescue latest_code = "" end else diff --git a/app/views/homework_commons/subjects.json.jbuilder b/app/views/homework_commons/subjects.json.jbuilder index 257fcc9d5..f24ff2097 100644 --- a/app/views/homework_commons/subjects.json.jbuilder +++ b/app/views/homework_commons/subjects.json.jbuilder @@ -6,12 +6,12 @@ end json.subject_list @subjects do |subject| json.subject_id subject.id json.subject_name subject.name - json.challenge_tags subject.shixun_tags + # json.challenge_tags subject.shixun_tags json.shixun_count subject.shixuns.unhidden.size json.myshixun_count subject.shixuns.pluck(:myshixuns_count).sum - json.creator subject.user&.full_name - json.creator_login subject.user&.login - json.school subject.user&.school_name + # json.creator subject.user&.full_name + # json.creator_login subject.user&.login + # json.school subject.user&.school_name end json.subjects_count @total_count \ No newline at end of file diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 7cb97bdc7..4a62e8bf8 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -1,522 +1,38 @@ - - EduCoder后台管理 - <%= csrf_meta_tags %> - <%= csp_meta_tag %> + + EduCoder后台管理 + + - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= csrf_meta_tags %> + <%= csp_meta_tag %> - + <%= stylesheet_link_tag 'admin', media: 'all','data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'admin', 'data-turbolinks-track': 'reload' %> + - - - -
              -
              -

              Task Progress Information

              -
              -

              CLEANING BUGS

              -
              -
              - 80% Complete -
              -
              -

              POSTING SOME STUFF

              -
              -
              - 65% Complete -
              -
              -

              BACKUP DATA FROM SERVER

              -
              -
              - 95% Complete -
              -
              -

              RE-DESIGNING WEB APPLICATION

              -
              -
              - 100% Complete -
              -
              -

              - -

              -
              -
              -
              - - -
              -
              -

              Logout Confirmation

              -
              -

              Are you sure want to logout from this awesome system?

              -

              - - Yeah, I'm sure -

              -
              -
              -
              - -
              + <% body_class = [params[:controller].gsub(/\//, '-').gsub('_', '-'), params[:action], 'page'].join('-') %> + + + <%= render partial: 'admins/shared/sidebar' %> - -
              -
              - - - -
              - - - - -
              - -<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - +
              + - - - diff --git a/app/views/student_works/shixun_work.json.jbuilder b/app/views/student_works/shixun_work.json.jbuilder index 39e7eb1b3..1be74ea4c 100644 --- a/app/views/student_works/shixun_work.json.jbuilder +++ b/app/views/student_works/shixun_work.json.jbuilder @@ -11,8 +11,8 @@ json.image_url url_to_avatar(@work_user) json.complete_count @myshixun.passed_count json.challenges_count @shixun.challenges_count -json.efficiency number_with_precision @work.efficiency, precision: 2 -json.max_efficiency number_with_precision @homework.max_efficiency, precision: 2 +json.efficiency @homework.work_efficiency ? number_with_precision(@work.efficiency, precision: 2) : nil +json.max_efficiency @homework.work_efficiency ? number_with_precision(@homework.max_efficiency, precision: 2) : nil json.passed_time @myshixun.passed_time json.total_spend_time @myshixun.total_spend_time json.user_score @myshixun.total_score diff --git a/app/views/subjects/show.json.jbuilder b/app/views/subjects/show.json.jbuilder index ec76b5a2d..997e0c3d6 100644 --- a/app/views/subjects/show.json.jbuilder +++ b/app/views/subjects/show.json.jbuilder @@ -14,6 +14,7 @@ json.allow_add_member @is_manager json.is_creator @is_creator if @subject.excellent + json.has_start @subject.has_course_start? json.courses @courses do |course| json.course_id course.id json.first_category_url module_url(course.none_hidden_course_modules.first, course) @@ -23,26 +24,4 @@ if @subject.excellent json.course_identity @user.course_identity(course) json.course_status subject_course_status course end -end - - -json.members @members do |member| - json.partial! 'subject_member', locals: { user: member.user } - json.role member.role -end - -# 技能标签 -json.tags @tags do |tag| - unless tag.blank? - json.tag_name tag - json.status @user_tags.include?(tag) - end -end - -# 我的进展 -json.progress do - json.my_score @subject.my_subject_score - json.all_score @subject.all_score - json.learned @subject.my_subject_progress - json.time @subject.my_consume_time end \ No newline at end of file diff --git a/config/admins/sidebar.yml b/config/admins/sidebar.yml new file mode 100644 index 000000000..d58b92ad9 --- /dev/null +++ b/config/admins/sidebar.yml @@ -0,0 +1 @@ +admins-users: admins-users \ No newline at end of file diff --git a/config/configuration.yml.example b/config/configuration.yml.example new file mode 100644 index 000000000..2a4f4f017 --- /dev/null +++ b/config/configuration.yml.example @@ -0,0 +1,17 @@ +defaults: &defaults + aliyun_vod: + access_key_id: 'test' + access_key_secret: 'test' + base_url: 'http://vod.cn-shanghai.aliyuncs.com' + cate_id: '-1' + callback_url: 'http://47.96.87.25:48080/api/callbacks/aliyun_vod.json' + signature_key: 'test12345678' + +development: + <<: *defaults + +test: + <<: *defaults + +production: + <<: *defaults \ No newline at end of file diff --git a/config/initializers/aliyun_vod_init.rb b/config/initializers/aliyun_vod_init.rb index d133fffbf..47b1dc6a3 100644 --- a/config/initializers/aliyun_vod_init.rb +++ b/config/initializers/aliyun_vod_init.rb @@ -1,7 +1,21 @@ -config = Rails.application.config_for(:aliyun_vod) -AliyunVod.access_key_id = config['access_key_id'] -AliyunVod.access_key_secret = config['access_key_secret'] -AliyunVod.base_url = config['base_url'] || 'http://vod.cn-shanghai.aliyuncs.com'.freeze -AliyunVod.cate_id = config['cate_id'] -AliyunVod.callback_url = config['callback_url'] -AliyunVod.signature_key = config['signature_key'] + +aliyun_vod_config = {} + +begin + config = Rails.application.config_for(:configuration) + aliyun_vod_config = config['aliyun_vod'] + raise 'oauth wechat config missing' if aliyun_vod_config.blank? +rescue => ex + raise ex if Rails.env.production? + + puts %Q{\033[33m [warning] aliyun vod config or configuration.yml missing, + please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m} + aliyun_vod_config = {} +end + +AliyunVod.access_key_id = aliyun_vod_config['access_key_id'] +AliyunVod.access_key_secret = aliyun_vod_config['access_key_secret'] +AliyunVod.base_url = aliyun_vod_config['base_url'] || 'http://vod.cn-shanghai.aliyuncs.com'.freeze +AliyunVod.cate_id = aliyun_vod_config['cate_id'] +AliyunVod.callback_url = aliyun_vod_config['callback_url'] +AliyunVod.signature_key = aliyun_vod_config['signature_key'] diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index c664f7cdd..5298b4ab6 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -11,4 +11,4 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -# Rails.application.config.assets.precompile += %w( admin.js admin.css ) +Rails.application.config.assets.precompile += %w( admin.js admin.scss ) diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb new file mode 100644 index 000000000..bb43c3364 --- /dev/null +++ b/config/initializers/simple_form.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: true +# +# Uncomment this and change the path if necessary to include your own +# components. +# See https://github.com/plataformatec/simple_form#custom-components to know +# more about custom components. +# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } +# +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Wrappers are used by the form builder to generate a + # complete input. You can remove any component from the + # wrapper, change the order or even add your own to the + # stack. The options given below are used to wrap the + # whole input. + config.wrappers :default, class: :input, + hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b| + ## Extensions enabled by default + # Any of these extensions can be disabled for a + # given input by passing: `f.input EXTENSION_NAME => false`. + # You can make any of these extensions optional by + # renaming `b.use` to `b.optional`. + + # Determines whether to use HTML5 (:email, :url, ...) + # and required attributes + b.use :html5 + + # Calculates placeholders automatically from I18n + # You can also pass a string as f.input placeholder: "Placeholder" + b.use :placeholder + + ## Optional extensions + # They are disabled unless you pass `f.input EXTENSION_NAME => true` + # to the input. If so, they will retrieve the values from the model + # if any exists. If you want to enable any of those + # extensions by default, you can change `b.optional` to `b.use`. + + # Calculates maxlength from length validations for string inputs + # and/or database column lengths + b.optional :maxlength + + # Calculate minlength from length validations for string inputs + b.optional :minlength + + # Calculates pattern from format validations for string inputs + b.optional :pattern + + # Calculates min and max from length validations for numeric inputs + b.optional :min_max + + # Calculates readonly automatically from readonly attributes + b.optional :readonly + + ## Inputs + # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' + b.use :label_input + b.use :hint, wrap_with: { tag: :span, class: :hint } + b.use :error, wrap_with: { tag: :span, class: :error } + + ## full_messages_for + # If you want to display the full error message for the attribute, you can + # use the component :full_error, like: + # + # b.use :full_error, wrap_with: { tag: :span, class: :error } + end + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :default + + # Define the way to render check boxes / radio buttons with labels. + # Defaults to :nested for bootstrap config. + # inline: input + label + # nested: label > input + config.boolean_style = :nested + + # Default class for buttons + config.button_class = 'btn' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # Use :to_sentence to list all errors for each field. + # config.error_method = :first + + # Default tag used for error notification helper. + config.error_notification_tag = :div + + # CSS class to add for error notification helper. + config.error_notification_class = 'error_notification' + + # Series of attempts to detect a default label method for collection. + # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] + + # Series of attempts to detect a default value method for collection. + # config.collection_value_methods = [ :id, :to_s ] + + # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. + # config.collection_wrapper_tag = nil + + # You can define the class to use on all collection wrappers. Defaulting to none. + # config.collection_wrapper_class = nil + + # You can wrap each item in a collection of radio/check boxes with a tag, + # defaulting to :span. + # config.item_wrapper_tag = :span + + # You can define a class to use in all item wrappers. Defaulting to none. + # config.item_wrapper_class = nil + + # How the label text should be generated altogether with the required text. + # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } + + # You can define the class to use on all labels. Default is nil. + # config.label_class = nil + + # You can define the default class to be used on forms. Can be overriden + # with `html: { :class }`. Defaulting to none. + # config.default_form_class = nil + + # You can define which elements should obtain additional classes + # config.generate_additional_classes_for = [:wrapper, :label, :input] + + # Whether attributes are required by default (or not). Default is true. + # config.required_by_default = true + + # Tell browsers whether to use the native HTML5 validations (novalidate form option). + # These validations are enabled in SimpleForm's internal config but disabled by default + # in this configuration, which is recommended due to some quirks from different browsers. + # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, + # change this configuration to true. + config.browser_validations = false + + # Collection of methods to detect if a file type was given. + # config.file_methods = [ :mounted_as, :file?, :public_filename, :attached? ] + + # Custom mappings for input types. This should be a hash containing a regexp + # to match as key, and the input type that will be used when the field name + # matches the regexp as value. + # config.input_mappings = { /count/ => :integer } + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + # config.wrapper_mappings = { string: :prepend } + + # Namespaces where SimpleForm should look for custom input classes that + # override default inputs. + # config.custom_inputs_namespaces << "CustomInputs" + + # Default priority for time_zone inputs. + # config.time_zone_priority = nil + + # Default priority for country inputs. + # config.country_priority = nil + + # When false, do not use translations for labels. + # config.translate_labels = true + + # Automatically discover new inputs in Rails' autoload path. + # config.inputs_discovery = true + + # Cache SimpleForm inputs discovery + # config.cache_discovery = !Rails.env.development? + + # Default class for inputs + # config.input_class = nil + + # Define the default class of the input wrapper of the boolean input. + config.boolean_label_class = 'checkbox' + + # Defines if the default input wrapper class should be included in radio + # collection wrappers. + # config.include_default_input_wrapper_class = true + + # Defines which i18n scope will be used in Simple Form. + # config.i18n_scope = 'simple_form' + + # Defines validation classes to the input_field. By default it's nil. + # config.input_field_valid_class = 'is-valid' + # config.input_field_error_class = 'is-invalid' +end diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb new file mode 100644 index 000000000..ff3909d03 --- /dev/null +++ b/config/initializers/simple_form_bootstrap.rb @@ -0,0 +1,439 @@ +# frozen_string_literal: true + +# Please do not make direct changes to this file! +# This generator is maintained by the community around simple_form-bootstrap: +# https://github.com/rafaelfranca/simple_form-bootstrap +# All future development, tests, and organization should happen there. +# Background history: https://github.com/plataformatec/simple_form/issues/1561 + +# Uncomment this and change the path if necessary to include your own +# components. +# See https://github.com/plataformatec/simple_form#custom-components +# to know more about custom components. +# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } + +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Default class for buttons + config.button_class = 'btn' + + # Define the default class of the input wrapper of the boolean input. + config.boolean_label_class = 'form-check-label' + + # How the label text should be generated altogether with the required text. + config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" } + + # Define the way to render check boxes / radio buttons with labels. + config.boolean_style = :inline + + # You can wrap each item in a collection of radio/check boxes with a tag + config.item_wrapper_tag = :div + + # Defines if the default input wrapper class should be included in radio + # collection wrappers. + config.include_default_input_wrapper_class = false + + # CSS class to add for error notification helper. + config.error_notification_class = 'alert alert-danger' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # :to_sentence to list all errors for each field. + config.error_method = :to_sentence + + # add validation classes to `input_field` + config.input_field_error_class = 'is-invalid' + config.input_field_valid_class = 'is-valid' + + + # vertical forms + # + # vertical default_wrapper + config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'form-control-label' + b.use :input, class: 'form-control', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # vertical input for boolean + config.wrappers :vertical_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb| + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' + bb.use :label, class: 'form-check-label' + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # vertical input for radio buttons and check boxes + config.wrappers :vertical_collection, item_wrapper_class: 'form-check', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| + ba.use :label_text + end + b.use :input, class: 'form-check-input', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # vertical input for inline radio buttons and check boxes + config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| + ba.use :label_text + end + b.use :input, class: 'form-check-input', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # vertical file input + config.wrappers :vertical_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label + b.use :input, class: 'form-control-file', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # vertical multi select + config.wrappers :vertical_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'form-control-label' + b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba| + ba.use :input, class: 'form-control mx-1', error_class: 'is-invalid' + end + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # vertical range input + config.wrappers :vertical_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :readonly + b.optional :step + b.use :label + b.use :input, class: 'form-control-range', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + + # horizontal forms + # + # horizontal default_wrapper + config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'col-sm-3 col-form-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-control', error_class: 'is-invalid' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # horizontal input for boolean + config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper tag: 'label', class: 'col-sm-3' do |ba| + ba.use :label_text + end + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |wr| + wr.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb| + bb.use :input, class: 'form-check-input', error_class: 'is-invalid' + bb.use :label, class: 'form-check-label' + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + end + + # horizontal input for radio buttons and check boxes + config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'col-sm-3 form-control-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-check-input', error_class: 'is-invalid' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # horizontal input for inline radio buttons and check boxes + config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'col-sm-3 form-control-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-check-input', error_class: 'is-invalid' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # horizontal file input + config.wrappers :horizontal_file, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label, class: 'col-sm-3 form-control-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, error_class: 'is-invalid' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # horizontal multi select + config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'col-sm-3 control-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |bb| + bb.use :input, class: 'form-control mx-1', error_class: 'is-invalid' + end + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # horizontal range input + config.wrappers :horizontal_range, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :readonly + b.optional :step + b.use :label, class: 'col-sm-3 form-control-label' + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-control-range', error_class: 'is-invalid' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + + # inline forms + # + # inline default_wrapper + config.wrappers :inline_form, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'sr-only' + + b.use :input, class: 'form-control', error_class: 'is-invalid' + b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # inline input for boolean + config.wrappers :inline_boolean, tag: 'span', class: 'form-check flex-wrap justify-content-start mr-sm-2', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :input, class: 'form-check-input', error_class: 'is-invalid' + b.use :label, class: 'form-check-label' + b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + + # bootstrap custom forms + # + # custom input for boolean + config.wrappers :custom_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox' do |bb| + bb.use :input, class: 'custom-control-input', error_class: 'is-invalid' + bb.use :label, class: 'custom-control-label' + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + config.wrappers :custom_boolean_switch, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox-switch' do |bb| + bb.use :input, class: 'custom-control-input', error_class: 'is-invalid' + bb.use :label, class: 'custom-control-label' + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + end + + # custom input for radio buttons and check boxes + config.wrappers :custom_collection, item_wrapper_class: 'custom-control', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| + ba.use :label_text + end + b.use :input, class: 'custom-control-input', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # custom input for inline radio buttons and check boxes + config.wrappers :custom_collection_inline, item_wrapper_class: 'custom-control custom-control-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| + ba.use :label_text + end + b.use :input, class: 'custom-control-input', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # custom file input + config.wrappers :custom_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label, class: 'form-control-label' + b.wrapper :custom_file_wrapper, tag: 'div', class: 'custom-file' do |ba| + ba.use :input, class: 'custom-file-input', error_class: 'is-invalid' + ba.use :label, class: 'custom-file-label' + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + end + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # custom multi select + config.wrappers :custom_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'form-control-label' + b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba| + ba.use :input, class: 'custom-select mx-1', error_class: 'is-invalid' + end + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # custom range input + config.wrappers :custom_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :readonly + b.optional :step + b.use :label, class: 'form-control-label' + b.use :input, class: 'custom-range', error_class: 'is-invalid' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + + # Input Group - custom component + # see example app and config at https://github.com/rafaelfranca/simple_form-bootstrap + # config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + # b.use :html5 + # b.use :placeholder + # b.optional :maxlength + # b.optional :minlength + # b.optional :pattern + # b.optional :min_max + # b.optional :readonly + # b.use :label, class: 'form-control-label' + # b.wrapper :input_group_tag, tag: 'div', class: 'input-group' do |ba| + # ba.optional :prepend + # ba.use :input, class: 'form-control', error_class: 'is-invalid' + # ba.optional :append + # end + # b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } + # b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + # end + + + # Floating Labels form + # + # floating labels default_wrapper + config.wrappers :floating_labels_form, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :input, class: 'form-control', error_class: 'is-invalid' + b.use :label, class: 'form-control-label' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + # custom multi select + config.wrappers :floating_labels_select, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| + b.use :html5 + b.optional :readonly + b.use :input, class: 'custom-select custom-select-lg', error_class: 'is-invalid' + b.use :label, class: 'form-control-label' + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } + end + + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :vertical_form + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + config.wrapper_mappings = { + boolean: :vertical_boolean, + check_boxes: :vertical_collection_inline, + date: :vertical_multi_select, + datetime: :vertical_multi_select, + file: :vertical_file, + radio_buttons: :vertical_collection_inline, + range: :vertical_range, + time: :vertical_multi_select + } + + # enable custom form wrappers + # config.wrapper_mappings = { + # boolean: :custom_boolean, + # check_boxes: :custom_collection, + # date: :custom_multi_select, + # datetime: :custom_multi_select, + # file: :custom_file, + # radio_buttons: :custom_collection, + # range: :custom_range, + # time: :custom_multi_select + # } +end diff --git a/config/locales/kaminari/zh-CN.yml b/config/locales/kaminari/zh-CN.yml new file mode 100644 index 000000000..738131516 --- /dev/null +++ b/config/locales/kaminari/zh-CN.yml @@ -0,0 +1,17 @@ +zh-CN: + views: + pagination: + first: "« 首页" + last: "尾页 »" + previous: "‹ 上一页" + next: "下一页 ›" + truncate: "…" + helpers: + page_entries_info: + one_page: + display_entries: + zero: "暂无数据" + one: "共1条数据" + other: "共%{count}条数据" + more_pages: + display_entries: "当前%{first} - %{last},共%{total}条数据" \ No newline at end of file diff --git a/config/locales/school_daily_reports/zh-CN.yml b/config/locales/school_daily_reports/zh-CN.yml new file mode 100644 index 000000000..d175ed11b --- /dev/null +++ b/config/locales/school_daily_reports/zh-CN.yml @@ -0,0 +1,9 @@ +zh-CN: + school_daily_report: + teacher_increase_count: 新增教师 + student_increase_count: 新增学生 + course_increase_count: 新增课堂 + shixun_increase_count: 新增实训 + shixun_homework_count: 新增实训作业 + shixun_evaluate_count: 新增实训评测 + active_user_count: 活跃用户 \ No newline at end of file diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml new file mode 100644 index 000000000..237438334 --- /dev/null +++ b/config/locales/simple_form.en.yml @@ -0,0 +1,31 @@ +en: + simple_form: + "yes": 'Yes' + "no": 'No' + required: + text: 'required' + mark: '*' + # You can uncomment the line below if you need to overwrite the whole required html. + # When using html, text and mark won't be used. + # html: '*' + error_notification: + default_message: "Please review the problems below:" + # Examples + # labels: + # defaults: + # password: 'Password' + # user: + # new: + # email: 'E-mail to sign in.' + # edit: + # email: 'E-mail.' + # hints: + # defaults: + # username: 'User name to sign in.' + # password: 'No special characters, please.' + # include_blanks: + # defaults: + # age: 'Rather not say' + # prompts: + # defaults: + # age: 'Select your age' diff --git a/config/locales/tidings/zh-CN.yml b/config/locales/tidings/zh-CN.yml index 347b0139a..e6e8e676d 100644 --- a/config/locales/tidings/zh-CN.yml +++ b/config/locales/tidings/zh-CN.yml @@ -49,9 +49,9 @@ Apply_end: "申请发布实训:%{name}" ApplySubject: System: - "1_end": "你提交的实训课程发布申请:%{name},审核已通过" - "2_end": "你提交的实训课程发布申请:%{name},审核未通过
              原因:%{reason}" - Apply_end: "申请发布实训课程:%{name}" + "1_end": "你提交的实践课程发布申请:%{name},审核已通过" + "2_end": "你提交的实践课程发布申请:%{name},审核未通过
              原因:%{reason}" + Apply_end: "申请发布实践课程:%{name}" TrialAuthorization: System: "1_end": "你提交的试用授权申请,审核已通过" @@ -61,7 +61,7 @@ Course: Delete_end: "你删除了课堂:%s" Shixun_end: "你创建了实训:%s" - Subject_end: "你创建了实训课程:%s" + Subject_end: "你创建了实践课程:%s" ArchiveCourse_end: "你的课堂已经归档:%s" JournalsForMessage: Mentioned_end: "@了你:%s" diff --git a/config/routes.rb b/config/routes.rb index 80c4d4237..5ed8ecd69 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,7 +8,6 @@ Rails.application.routes.draw do get 'attachments/download/:id/:filename', to: 'attachments#show' resources :edu_settings - resources :admin scope '/api' do get 'home/index' get 'home/search' @@ -350,6 +349,7 @@ Rails.application.routes.draw do post 'exit_course' get 'informs' post 'update_informs' + post 'new_informs' get 'online_learning' post 'join_excellent_course' get 'tasks_list' @@ -604,6 +604,7 @@ Rails.application.routes.draw do post :up_down post :delete_answer post :adjust_score + post :update_scores end resource :exercise_answers,only:[:create,:destroy] end @@ -741,6 +742,27 @@ Rails.application.routes.draw do post 'callbacks/aliyun_vod', to: 'callbacks/aliyun_vods#create' end + namespace :admins do + get '/', to: 'dashboards#index' + + resources :daily_school_statistics, only: [:index] do + get :export, on: :collection + end + + resources :school_statistics, only: [:index] do + get :contrast, on: :collection + end + + resources :users, only: [:index, :edit, :update] do + member do + post :reward_grade + post :lock + post :unlock + post :active + end + end + end + #git 认证回调 match 'gitauth/*url', to: 'gits#auth', via: :all diff --git a/db/migrate/20190822022306_add_exercise_user_update.rb b/db/migrate/20190822022306_add_exercise_user_update.rb new file mode 100644 index 000000000..7a8784c30 --- /dev/null +++ b/db/migrate/20190822022306_add_exercise_user_update.rb @@ -0,0 +1,39 @@ +class AddExerciseUserUpdate < ActiveRecord::Migration[5.2] + + # include ExercisesHelper + # def change + # #2019,8,22添加 + # two_months = Time.now - 2.months + # exs = Exercise.all.is_exercise_published.where("publish_time > ?",two_months).includes(:exercise_questions,:exercise_users) + # exs.each do |ex| + # if ex.exercise_questions.where("created_at < ?",Time.now - 1.month).pluck(:question_type).include?(1) #含有多选题,且是1个月前创建的才更新 + # ex_users = ex.exercise_users.exercise_user_committed.where("end_at is not null and end_at > ?",two_months) + # if ex_users.exists? + # ex_users.each do |ex_user| + # calculate_score = calculate_student_score(ex,ex_user.user)[:total_score] + # subjective_score = ex_user.subjective_score + # total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score + # total_score = calculate_score + total_score_subjective_score + # ex_user.update_attributes(score:total_score,objective_score:calculate_score) + # puts ex_user.id + # end + # end + # end + # end + # + # #1936的试卷成绩有问题。 + # # #https://www.educoder.net/courses/2935/exercises/1936/users/pizfnr5ts + # ex_special = Exercise.find_by_id(1936) + # ex_special_users = ex_special.exercise_users.exercise_user_committed.where("end_at is not null and end_at > ?",two_months) + # if ex_special.present? && ex_special_users.exists? + # ex_special_users.each do |ex_user| + # calculate_score = calculate_student_score(ex_special,ex_user.user)[:total_score] + # subjective_score = ex_user.subjective_score + # total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score + # total_score = calculate_score + total_score_subjective_score + # ex_user.update_attributes(score:total_score,objective_score:calculate_score) + # puts ex_user.id + # end + # end + # end +end diff --git a/db/migrate/20190823015610_add_message_count_for_boards.rb b/db/migrate/20190823015610_add_message_count_for_boards.rb new file mode 100644 index 000000000..6f121cfc0 --- /dev/null +++ b/db/migrate/20190823015610_add_message_count_for_boards.rb @@ -0,0 +1,9 @@ +class AddMessageCountForBoards < ActiveRecord::Migration[5.2] + def change + boards = Board.where(:parent_id => 0) + Board.reset_column_information + boards.find_each do |board| + Board.reset_counters board.id, :messages + end + end +end diff --git a/db/migrate/20190823024643_modify_course_introduction_for_boards.rb b/db/migrate/20190823024643_modify_course_introduction_for_boards.rb new file mode 100644 index 000000000..3dfcf09f8 --- /dev/null +++ b/db/migrate/20190823024643_modify_course_introduction_for_boards.rb @@ -0,0 +1,42 @@ +class ModifyCourseIntroductionForBoards < ActiveRecord::Migration[5.2] + def change + platform = PlatformSample.where(:samples_type => "courseGuide").first + content = '大家好! + +欢迎进入在线课堂! + +该课堂对应于一个或多个线下班级,课堂成员可以利用老师发布的邀请码申请加入。 + +在这里,老师和教辅将结合教学内容,把不同类型的实训项目发布给大家,让大家在真实的实战环境中得到锻炼。 + +那么什么是实训项目呢?大家有空自己尝试一下就知道了: + +- Python实训:[Python程序设计入门](https://www.educoder.net/paths/13) | [大学计算机基础——基于Python](https://www.educoder.net/paths/11) + +- Java实训:[Java语言程序设计(基础篇)](https://www.educoder.net/paths/38) + +- C/C++实训:[C/C++程序设计](https://www.educoder.net/paths/3) | [数据结构与算法(C语言)](https://www.educoder.net/paths/4) + +- Matlab实训:[智取MATLAB:基本语法](https://www.educoder.net/shixuns/7bvs54gw/challenges) | [控制结构](https://www.educoder.net/shixuns/q4fowkfa/challenges) | [矩阵进阶](https://www.educoder.net/shixuns/grunzcs3/challenges) + +- HTML/CSS实训:[HTML5+CSS3网页制作(基础篇)](https://www.educoder.net/paths/15) + +- 云计算实训:[Docker企业级实训(基础篇)](https://www.educoder.net/paths/29) + +... ... 等等 + +你们现在就可以去体验哦! + +欢迎提出宝贵建议,平台一定会给您带来更多惊喜! + + +支持团队' + platform.update_column(:contents, content) + messages = Message.where(subject: "新课导语") + messages.find_each do |m| + m.update_column(:is_md, true) + message_detail = m.message_detail + message_detail.update_column(:content, content) if message_detail + end + end +end diff --git a/db/migrate/20190823063747_modify_contents_for_old_message_details.rb b/db/migrate/20190823063747_modify_contents_for_old_message_details.rb new file mode 100644 index 000000000..4607881fe --- /dev/null +++ b/db/migrate/20190823063747_modify_contents_for_old_message_details.rb @@ -0,0 +1,13 @@ +class ModifyContentsForOldMessageDetails < ActiveRecord::Migration[5.2] + def change + messages = Message.where(subject: "新课导语").where.not(parent_id: 0).where("created_on < '2019-08-23 02:00:00'") + messages.find_each do |m| + m.update_column(:is_md, true) + message_detail = m.message_detail + if message_detail + content = OldMessageDetail.find_by_id(message_detail.id)&.content + message_detail.update_column(:content, content) + end + end + end +end diff --git a/db/migrate/20190823090957_rechange_exercise_1936_scores.rb b/db/migrate/20190823090957_rechange_exercise_1936_scores.rb new file mode 100644 index 000000000..f089714ee --- /dev/null +++ b/db/migrate/20190823090957_rechange_exercise_1936_scores.rb @@ -0,0 +1,19 @@ +class RechangeExercise1936Scores < ActiveRecord::Migration[5.2] + include ExercisesHelper + def change + #1936的试卷成绩有问题。 + # #https://www.educoder.net/courses/2935/exercises/1936/users/pizfnr5ts + ex_special = Exercise.find_by_id(1936) + ex_special_users = ex_special&.exercise_users&.exercise_user_committed&.where("end_at is not null and end_at > ?",Time.now - 2.months) + if ex_special.present? && ex_special_users.exists? + ex_special_users.each do |ex_user| + calculate_score = calculate_student_score(ex_special,ex_user.user)[:total_score] + subjective_score = ex_user.subjective_score + total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score + total_score = calculate_score + total_score_subjective_score + ex_user.update_attributes(score:total_score,objective_score:calculate_score) + puts ex_user.id + end + end + end +end diff --git a/db/migrate/20190824032658_migrate_subject_shixun_count.rb b/db/migrate/20190824032658_migrate_subject_shixun_count.rb new file mode 100644 index 000000000..bef29346e --- /dev/null +++ b/db/migrate/20190824032658_migrate_subject_shixun_count.rb @@ -0,0 +1,9 @@ +class MigrateSubjectShixunCount < ActiveRecord::Migration[5.2] + def change + Subject.reset_column_information + Subject.all.each do |subject| + Subject.reset_counters subject.id, :stage_shixuns + Subject.reset_counters subject.id, :shixuns + end + end +end diff --git a/dump.rdb b/dump.rdb index f7b65ded0f6f907e3c844f02d8fb1c6dbac91345..6d0966fe01363d6fdffde4613c80093b36eeed0d 100644 GIT binary patch delta 486 zcmWO0y=zlJ008iN?_J_c`axnPj(JIF#L9(x_kB)nsSr^IC4++>+{1y<6ON|Eg8>GHla8_wP~8{P9c7lLuA86LREQBhY<5Fm@ z*nktNu?%&%A7xcWNWOIhR}=6z>?So*V5*9eBHL@V5YyTqMVLmoFh*Lbw4kz+g?Gs* zO3G4GV+;Q=X~S>niBO5&abbN|DCOiGXMYp delta 365 zcmV~$J4+lv007|GncKbdFvnd`Qi$GWB@hBD^L|)qp`;N4Ns)jd%-+m(r{IGm#i5mr zg@*AOK}eNGLll327%VN!KL|^ZGyzKqQSyC*)|cikZXI8~eEYYyzUp1x^S$p^t0%MH zqSpHt6YYs60AmdRY5>gMg$Qniee@(8!I?;hKk)U&BPodMXiTi?QcJsrD@&YaJl!7e zta!WT-8;LMO%ox7l16AgB`7mk(R3Ls*^fW#X_Zq-`u6~42>dG#QJWT+>7v7O*C9pD zjj_3=+~l~hRyn1SkxV^K%QG|*MCB(O5{D=)Kk3%EL;)r(3^B|MOcrv@5tDX*$+o;z1yz}+uQ@{Bi D4+Cm! diff --git a/lib/tasks/course_end.rake b/lib/tasks/course_end.rake index d8e87eacf..841c30b27 100644 --- a/lib/tasks/course_end.rake +++ b/lib/tasks/course_end.rake @@ -2,7 +2,7 @@ namespace :course do desc "course end" task :end => :environment do - courses = Course.where("end_date <= '#{Date.today}' and is_end = 0") + courses = Course.where("end_date < '#{Date.today}' and is_end = 0") courses.each do |course| course.update_attribute(:is_end, 1) end diff --git a/lib/tasks/excellent_course_exercise.rake b/lib/tasks/excellent_course_exercise.rake index 0d8875806..4309eec26 100644 --- a/lib/tasks/excellent_course_exercise.rake +++ b/lib/tasks/excellent_course_exercise.rake @@ -14,16 +14,18 @@ namespace :excellent_course_exercise do course = Course.find_by(id: course_id) course.exercises.each_with_index do |exercise, index| - # 第一个试卷的参与人数和通过人数都是传的数据,后续的随机 - if index == 0 - members = course.students.order("id asc").limit(participant_count) - update_exercise_user(exercise, members, pass_count) - else - new_participant_count = rand((participant_count - 423)..participant_count) - new_pass_count = rand((new_participant_count - 113)..new_participant_count) + if exercise.exercise_users.where(commit_status: 1).count == 0 + # 第一个试卷的参与人数和通过人数都是传的数据,后续的随机 + if index == 0 + members = course.students.order("id asc").limit(participant_count) + update_exercise_user(exercise, members, pass_count) + else + new_participant_count = rand((participant_count - 423)..participant_count) + new_pass_count = rand((new_participant_count - 113)..new_participant_count) - members = course.students.order("id asc").limit(new_participant_count) - update_exercise_user(exercise, members, new_pass_count) + members = course.students.order("id asc").limit(new_participant_count) + update_exercise_user(exercise, members, new_pass_count) + end end end end @@ -147,7 +149,12 @@ namespace :excellent_course_exercise do game_code = game_challenge code = game_code.try(:new_code) else - code = git_fle_content(game.myshixun.repo_path,cha_path) + begin #8-23,hs + code = git_fle_content(game.myshixun.repo_path,cha_path) + rescue + code = "" + end + # code = git_fle_content(game.myshixun.repo_path,cha_path) end end if ex_shixun_answer_content.blank? #把关卡的答案存入试卷的实训里 diff --git a/lib/tasks/public_course.rake b/lib/tasks/public_course.rake index ff50a4e28..d4f62a6e0 100644 --- a/lib/tasks/public_course.rake +++ b/lib/tasks/public_course.rake @@ -88,6 +88,8 @@ namespace :public_course do homework.update_columns(publish_time: publish_time, end_time: end_time, created_at: created_at, updated_at: updated_at) homework.homework_detail_manual.update_columns(comment_status: 6, created_at: created_at, updated_at: updated_at) + + homework.student_works.where("work_status !=0 and update_time > '#{end_time}'").update_all(update_time: end_time) end when 3 # 试卷 @@ -110,6 +112,23 @@ namespace :public_course do end + task :create_homework_work => :environment do + course = Course.find(course_id) + course.practice_homeworks.each do |homework| + if homework.student_works.count == 0 + str = "" + CourseMember.students(course).each do |student| + str += "," if str != "" + str += "(#{homework.id},#{student.user_id}, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')" + end + if str != "" + sql = "insert into student_works (homework_common_id, user_id, created_at, updated_at) values" + str + ActiveRecord::Base.connection.execute sql + end + end + end + end + def min_swith(time) puts time return time < 9 ? "0#{time}" : time diff --git a/lib/tasks/public_message.rake b/lib/tasks/public_message.rake index ca54fbd7b..9b2f89224 100644 --- a/lib/tasks/public_message.rake +++ b/lib/tasks/public_message.rake @@ -2,9 +2,10 @@ namespace :sync do task :public_message => :environment do subject_id = ENV['args'].split(",")[0] # 对应课程的id - board_id = ENV['args'].split(",")[1] - message_id = ENV['args'].split(",")[2] - status = ENV['args'].split(",")[3] # 表示相应的期数 + shixun_id = ENV['args'].split(",")[1] # 对应课程的id + board_id = ENV['args'].split(",")[2] + message_id = ENV['args'].split(",")[3] + status = ENV['args'].split(",")[4] # 表示相应的期数 if status.to_i == 1 start_time = '2018-12-16' @@ -21,7 +22,6 @@ namespace :sync do shixun_ids = Shixun.find_by_sql("select shixun_id from stage_shixuns where stage_id in (select id from stages where subject_id=#{subject_id}) ").map(&:shixun_id) - discusses = Discuss.where(dis_id: shixun_ids).where("created_at >? and created_at :environment do + subject_id = ENV['args'].split(",")[0] # 对应课程的id + shixun_id = ENV['args'].split(",")[1] # 对应课程的id + board_id = ENV['args'].split(",")[2] + message_id = ENV['args'].split(",")[3] + status = ENV['args'].split(",")[4] # 表示相应的期数 + + if status.to_i == 1 + start_time = '2018-12-16' + end_time = '2019-04-01' + elsif status.to_i == 2 + start_time = '2019-04-07' + end_time = '2019-07-28' + else + # 这种情况是取所有的 + start_time = '2015-01-01' + end_time = '2022-07-28' + end + + if subject_id.to_i == -1 + discusses = Discuss.where("parent_id is null and dis_id=?", shixun_id) + else + shixun_ids = Shixun.find_by_sql("select shixun_id from stage_shixuns where stage_id in (select id from stages where + subject_id=#{subject_id}) ").map(&:shixun_id) + discusses = Discuss.where("parent_id is null").where(dis_id: shixun_ids) + end + + discusses.each do |discuss| + rand_created_on = random_time start_time, end_time + puts discuss.id + # 找到所有的子回复 + replies = Discuss.where(parent_id: discuss.id) + + # 如果有子回复,除了创建父回复外,还需要同步子回复 + new_message = Message.create!(board_id: board_id.to_i, author_id: discuss.user_id, parent_id: message_id, root_id: message_id) + new_message.update_columns(created_on: rand_created_on, updated_on: rand_created_on) + message_detail = MessageDetail.create!(message_id: new_message.id, content: discuss.try(:content)) + message_detail.update_columns(created_at: rand_created_on, updated_at: rand_created_on) + if replies.present? + replies.each do |reply| + puts("reply id si #{reply.id}") + reply_time = random_time(start_time, end_time) + while reply_time > rand_created_on + reply_time = random_time(start_time, end_time) + end + + reply_message = Message.create!(board_id: board_id.to_i, author_id: reply.user_id, parent_id: new_message.id, root_id: message_id) + reply_message.update_columns(created_on: reply_time, updated_on: reply_time) + reply_message_detail = MessageDetail.create!(message_id: reply_message.id, content: reply.try(:content)) + reply_message_detail.update_columns(created_at: rand_created_on, updated_at: rand_created_on) + end + end + end + + + def min_swith(time) + puts time + return time < 9 ? "0#{time}" : time + end + + def random_time(start_time, end_time) + hour = (6..23).to_a.sample(1).first + min = rand(60) + sec = rand(60) + + start_time = Date.parse(start_time) + end_time = Date.parse(end_time) + date = (start_time..end_time).to_a.sample(1).first + + time = "#{date} #{min_swith(hour)}:#{min_swith(min)}:#{min_swith(sec)}" + + puts time + time + end + # 子评论的时间必须小于父评论 + def smaller_time(parent_time, start_time, end_time) + large_time = random_time(start_time, end_time) + while large_time > parent_time + large_time = random_time(start_time, end_time) + end + large_time + end + end + + task :board_count => :environment do + Course.find_each do |course| + puts course.id + + begin + messages_count = Message.find_by_sql("select count(*) as count from messages where board_id in (select id from boards where course_id=#{course.id})").first.try(:count) + + Board.update_column(messages_count: messages_count) + rescue + + end + + end end -end \ No newline at end of file +end diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb new file mode 100644 index 000000000..106b71eef --- /dev/null +++ b/lib/templates/erb/scaffold/_form.html.erb @@ -0,0 +1,15 @@ +<%# frozen_string_literal: true %> +<%%= simple_form_for(@<%= singular_table_name %>) do |f| %> + <%%= f.error_notification %> + <%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %> + +
              + <%- attributes.each do |attribute| -%> + <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> + <%- end -%> +
              + +
              + <%%= f.button :submit %> +
              +<%% end %> diff --git a/public/javascripts/educoder/province-data.json b/public/javascripts/educoder/province-data.json new file mode 100644 index 000000000..8604080fb --- /dev/null +++ b/public/javascripts/educoder/province-data.json @@ -0,0 +1,254 @@ +[ + { + "n": "北京", + "s": [ + { "n": "东城" }, + { "n": "西城" }, + { "n": "朝阳" }, + { "n": "丰台" }, + { "n": "石景山" }, + { "n": "海淀" }, + { "n": "门头沟" }, + { "n": "房山" }, + { "n": "通州" }, + { "n": "顺义" }, + { "n": "昌平" }, + { "n": "大兴" }, + { "n": "平谷" }, + { "n": "怀柔" }, + { "n": "密云" }, + { "n": "延庆" } + ] + }, + { + "n": "上海", + "s": [ + { "n": "崇明" }, { "n": "黄浦" }, { "n": "卢湾" }, { "n": "徐汇" }, { "n": "长宁" }, { "n": "静安" }, { "n": "普陀" }, { "n": "闸北" }, { "n": "虹口" }, { "n": "杨浦" }, { "n": "闵行" }, + { "n": "宝山" }, { "n": "嘉定" }, { "n": "浦东" }, { "n": "金山" }, { "n": "松江" }, { "n": "青浦" }, { "n": "南汇" }, { "n": "奉贤" } + ] + }, + { + "n": "广东", + "s": [ + { "n": "广州" }, { "n": "深圳" }, { "n": "珠海" }, { "n": "东莞" }, { "n": "中山" }, { "n": "佛山" }, { "n": "惠州" }, { "n": "河源" }, { "n": "潮州" }, { "n": "江门" }, { "n": "揭阳" }, { "n": "茂名" }, + { "n": "梅州" }, { "n": "清远" }, { "n": "汕头" }, { "n": "汕尾" }, { "n": "韶关" }, { "n": "顺德" }, { "n": "阳江" }, { "n": "云浮" }, { "n": "湛江" }, { "n": "肇庆" } + ] + }, + { + "n": "江苏", + "s": [ + { "n": "南京" }, { "n": "常熟" }, { "n": "常州" }, { "n": "海门" }, { "n": "淮安" }, { "n": "江都" }, { "n": "江阴" }, { "n": "昆山" }, { "n": "连云港" }, { "n": "南通" }, + { "n": "启东" }, { "n": "沭阳" }, { "n": "宿迁" }, { "n": "苏州" }, { "n": "太仓" }, { "n": "泰州" }, { "n": "同里" }, { "n": "无锡" }, { "n": "徐州" }, { "n": "盐城" }, + { "n": "扬州" }, { "n": "宜兴" }, { "n": "仪征" }, { "n": "张家港" }, { "n": "镇江" }, { "n": "周庄" } + ] + }, + { + "n": "浙江", + "s": [ + { "n": "杭州" }, { "n": "安吉" }, { "n": "慈溪" }, { "n": "定海" }, { "n": "奉化" }, { "n": "海盐" }, { "n": "黄岩" }, { "n": "湖州" }, { "n": "嘉兴" }, { "n": "金华" }, { "n": "临安" }, + { "n": "临海" }, { "n": "丽水" }, { "n": "宁波" }, { "n": "瓯海" }, { "n": "平湖" }, { "n": "千岛湖" }, { "n": "衢州" }, { "n": "江山" }, { "n": "瑞安" }, { "n": "绍兴" }, { "n": "嵊州" }, + { "n": "台州" }, { "n": "温岭" }, { "n": "温州" }, { "n": "余姚" }, { "n": "舟山" } + ] + }, + { + "n": "重庆", + "s": [ + { "n": "万州" }, { "n": "涪陵" }, { "n": "渝中" }, { "n": "大渡口" }, { "n": "江北" }, { "n": "沙坪坝" }, { "n": "九龙坡" }, { "n": "南岸" }, { "n": "北碚" }, { "n": "万盛" }, + { "n": "双挢" }, { "n": "渝北" }, { "n": "巴南" }, { "n": "黔江" }, { "n": "长寿" }, { "n": "綦江" }, { "n": "潼南" }, { "n": "铜梁" }, { "n": "大足" }, { "n": "荣昌" }, { "n": "壁山" }, + { "n": "梁平" }, { "n": "城口" }, { "n": "丰都" }, { "n": "垫江" }, { "n": "武隆" }, { "n": "忠县" }, { "n": "开县" }, { "n": "云阳" }, { "n": "奉节" }, { "n": "巫山" }, { "n": "巫溪" }, + { "n": "石柱" }, { "n": "秀山" }, { "n": "酉阳" }, { "n": "彭水" }, { "n": "江津" }, { "n": "合川" }, { "n": "永川" }, { "n": "南川" } + ] + }, + { + "n": "安徽", + "s": [ + { "n": "合肥" }, { "n": "安庆" }, { "n": "蚌埠" }, { "n": "亳州" }, { "n": "巢湖" }, { "n": "滁州" }, { "n": "阜阳" }, { "n": "贵池" }, { "n": "淮北" }, { "n": "淮化" }, { "n": "淮南" }, + { "n": "黄山" }, { "n": "九华山" }, { "n": "六安" }, { "n": "马鞍山" }, { "n": "宿州" }, { "n": "铜陵" }, { "n": "屯溪" }, { "n": "芜湖" }, { "n": "宣城" } + ] + }, + { + "n": "福建", + "s": [ + { "n": "福州" }, { "n": "厦门" }, { "n": "泉州" }, { "n": "漳州" }, { "n": "龙岩" }, { "n": "南平" }, { "n": "宁德" }, { "n": "莆田" }, { "n": "三明" } + ] + }, + { + "n": "甘肃", + "s": [ + { "n": "兰州" }, { "n": "白银" }, { "n": "定西" }, { "n": "敦煌" }, { "n": "甘南" }, { "n": "金昌" }, { "n": "酒泉" }, { "n": "临夏" }, { "n": "平凉" }, { "n": "天水" }, + { "n": "武都" }, { "n": "武威" }, { "n": "西峰" }, { "n": "张掖" } + ] + }, + { + "n": "广西", + "s": [ + { "n": "南宁" }, { "n": "百色" }, { "n": "北海" }, { "n": "桂林" }, { "n": "防城港" }, { "n": "贵港" }, { "n": "河池" }, { "n": "贺州" }, { "n": "柳州" }, { "n": "钦州" }, { "n": "梧州" }, { "n": "玉林" } + ] + }, + { + "n": "贵州", + "s": [ + { "n": "贵阳" }, { "n": "安顺" }, { "n": "毕节" }, { "n": "都匀" }, { "n": "凯里" }, { "n": "六盘水" }, { "n": "铜仁" }, { "n": "兴义" }, { "n": "玉屏" }, { "n": "遵义" } + ] + }, + { + "n": "海南", + "s": [ + { "n": "海口" }, { "n": "儋县" }, { "n": "陵水" }, { "n": "琼海" }, { "n": "三亚" }, { "n": "通什" }, { "n": "万宁" } + ] + }, + { + "n": "河北", + "s": [ + { "n": "石家庄" }, { "n": "保定" }, { "n": "北戴河" }, { "n": "沧州" }, { "n": "承德" }, { "n": "丰润" }, { "n": "邯郸" }, { "n": "衡水" }, { "n": "廊坊" }, { "n": "南戴河" }, { "n": "秦皇岛" }, + { "n": "唐山" }, { "n": "新城" }, { "n": "邢台" }, { "n": "张家口" } + ] + }, + { + "n": "黑龙江", + "s": [ + { "n": "哈尔滨" }, { "n": "北安" }, { "n": "大庆" }, { "n": "大兴安岭" }, { "n": "鹤岗" }, { "n": "黑河" }, { "n": "佳木斯" }, { "n": "鸡西" }, { "n": "牡丹江" }, { "n": "齐齐哈尔" }, + { "n": "七台河" }, { "n": "双鸭山" }, { "n": "绥化" }, { "n": "伊春" } + ] + }, + { + "n": "河南", + "s": [ + { "n": "郑州" }, { "n": "安阳" }, { "n": "鹤壁" }, { "n": "潢川" }, { "n": "焦作" }, { "n": "济源" }, { "n": "开封" }, { "n": "漯河" }, { "n": "洛阳" }, { "n": "南阳" }, { "n": "平顶山" }, + { "n": "濮阳" }, { "n": "三门峡" }, { "n": "商丘" }, { "n": "新乡" }, { "n": "信阳" }, { "n": "许昌" }, { "n": "周口" }, { "n": "驻马店" } + ] + }, + { + "n": "湖北", + "s": [ + { "n": "武汉" }, { "n": "恩施" }, { "n": "鄂州" }, { "n": "黄冈" }, { "n": "黄石" }, { "n": "荆门" }, { "n": "荆州" }, { "n": "潜江" }, { "n": "十堰" }, { "n": "随州" }, { "n": "武穴" }, + { "n": "仙桃" }, { "n": "咸宁" }, { "n": "襄阳" }, { "n": "襄樊" }, { "n": "孝感" }, { "n": "宜昌" } + ] + }, + { + "n": "湖南", + "s": [ + { "n": "长沙" }, { "n": "常德" }, { "n": "郴州" }, { "n": "衡阳" }, { "n": "怀化" }, { "n": "吉首" }, { "n": "娄底" }, { "n": "邵阳" }, { "n": "湘潭" }, { "n": "益阳" }, { "n": "岳阳" }, + { "n": "永州" }, { "n": "张家界" }, { "n": "株洲" } + ] + }, + { + "n": "江西", + "s": [ + { "n": "南昌" }, { "n": "抚州" }, { "n": "赣州" }, { "n": "吉安" }, { "n": "景德镇" }, { "n": "井冈山" }, { "n": "九江" }, { "n": "庐山" }, { "n": "萍乡" }, + { "n": "上饶" }, { "n": "新余" }, { "n": "宜春" }, { "n": "鹰潭" } + ] + }, + { + "n": "吉林", + "s": [ + { "n": "长春" }, { "n": "吉林" }, { "n": "白城" }, { "n": "白山" }, { "n": "珲春" }, { "n": "辽源" }, { "n": "梅河" }, { "n": "四平" }, { "n": "松原" }, { "n": "通化" }, { "n": "延吉" } + ] + }, + { + "n": "辽宁", + "s": [ + { "n": "沈阳" }, { "n": "鞍山" }, { "n": "本溪" }, { "n": "朝阳" }, { "n": "大连" }, { "n": "丹东" }, { "n": "抚顺" }, { "n": "阜新" }, { "n": "葫芦岛" }, { "n": "锦州" }, + { "n": "辽阳" }, { "n": "盘锦" }, { "n": "铁岭" }, { "n": "营口" } + ] + }, + { + "n": "内蒙古", + "s": [ + { "n": "呼和浩特" }, { "n": "阿拉善盟" }, { "n": "包头" }, { "n": "赤峰" }, { "n": "东胜" }, { "n": "海拉尔" }, { "n": "集宁" }, { "n": "临河" }, { "n": "通辽" }, { "n": "乌海" }, + { "n": "乌兰浩特" }, { "n": "锡林浩特" } + ] + }, + { + "n": "宁夏", + "s": [ + { "n": "银川" }, { "n": "固源" }, { "n": "石嘴山" }, { "n": "吴忠" } + ] + }, + { + "n": "青海", + "s": [ + { "n": "西宁" }, { "n": "德令哈" }, { "n": "格尔木" }, { "n": "共和" }, { "n": "海东" }, { "n": "海晏" }, { "n": "玛沁" }, { "n": "同仁" }, { "n": "玉树" } + ] + }, + { + "n": "山东", + "s": [ + { "n": "济南" }, { "n": "滨州" }, { "n": "兖州" }, { "n": "德州" }, { "n": "东营" }, { "n": "菏泽" }, { "n": "济宁" }, { "n": "莱芜" }, { "n": "聊城" }, { "n": "临沂" }, + { "n": "蓬莱" }, { "n": "青岛" }, { "n": "曲阜" }, { "n": "日照" }, { "n": "泰安" }, { "n": "潍坊" }, { "n": "威海" }, { "n": "烟台" }, { "n": "枣庄" }, { "n": "淄博" } + ] + }, + { + "n": "山西", + "s": [ + { "n": "太原" }, { "n": "长治" }, { "n": "大同" }, { "n": "候马" }, { "n": "晋城" }, { "n": "离石" }, { "n": "临汾" }, { "n": "宁武" }, { "n": "朔州" }, { "n": "忻州" }, + { "n": "阳泉" }, { "n": "榆次" }, { "n": "运城" } + ] + }, + { + "n": "陕西", + "s": [ + { "n": "西安" }, { "n": "安康" }, { "n": "宝鸡" }, { "n": "汉中" }, { "n": "渭南" }, { "n": "商州" }, { "n": "绥德" }, { "n": "铜川" }, { "n": "咸阳" }, { "n": "延安" }, { "n": "榆林" } + ] + }, + { + "n": "四川", + "s": [ + { "n": "成都" }, { "n": "巴中" }, { "n": "达川" }, { "n": "德阳" }, { "n": "都江堰" }, { "n": "峨眉山" }, { "n": "涪陵" }, { "n": "广安" }, { "n": "广元" }, { "n": "九寨沟" }, + { "n": "康定" }, { "n": "乐山" }, { "n": "泸州" }, { "n": "马尔康" }, { "n": "绵阳" }, { "n": "眉山" }, { "n": "南充" }, { "n": "内江" }, { "n": "攀枝花" }, { "n": "遂宁" }, + { "n": "汶川" }, { "n": "西昌" }, { "n": "雅安" }, { "n": "宜宾" }, { "n": "自贡" }, { "n": "资阳" } + ] + }, + { + "n": "天津", + "s": [ + { "n": "天津" }, { "n": "和平" }, { "n": "东丽" }, { "n": "河东" }, { "n": "西青" }, { "n": "河西" }, { "n": "津南" }, { "n": "南开" }, { "n": "北辰" }, { "n": "河北" }, { "n": "武清" }, { "n": "红挢" }, + { "n": "塘沽" }, { "n": "汉沽" }, { "n": "大港" }, { "n": "宁河" }, { "n": "静海" }, { "n": "宝坻" }, { "n": "蓟县" } + ] + }, + { + "n": "新疆", + "s": [ + { "n": "乌鲁木齐" }, { "n": "阿克苏" }, { "n": "阿勒泰" }, { "n": "阿图什" }, { "n": "博乐" }, { "n": "昌吉" }, { "n": "东山" }, { "n": "哈密" }, { "n": "和田" }, { "n": "喀什" }, + { "n": "克拉玛依" }, { "n": "库车" }, { "n": "库尔勒" }, { "n": "奎屯" }, { "n": "石河子" }, { "n": "塔城" }, { "n": "吐鲁番" }, { "n": "伊宁" } + ] + }, + { + "n": "西藏", + "s": [ + { "n": "拉萨" }, { "n": "阿里" }, { "n": "昌都" }, { "n": "林芝" }, { "n": "那曲" }, { "n": "日喀则" }, { "n": "山南" } + ] + }, + { + "n": "云南", + "s": [ + { "n": "昆明" }, { "n": "大理" }, { "n": "保山" }, { "n": "楚雄" }, { "n": "大理" }, { "n": "东川" }, { "n": "个旧" }, { "n": "景洪" }, { "n": "开远" }, { "n": "临沧" }, { "n": "丽江" }, + { "n": "六库" }, { "n": "潞西" }, { "n": "曲靖" }, { "n": "思茅" }, { "n": "文山" }, { "n": "西双版纳" }, { "n": "玉溪" }, { "n": "中甸" }, { "n": "昭通" } + ] + }, + { + "n": "香港特别行政区", + "s": [ + { "n": "香港" }, { "n": "九龙" }, { "n": "新界" } + ] + }, + { + "n": "澳门特别行政区", + "s": [ + { "n": { "n": "澳门" } } + ] + }, + { + "n": "台湾", + "s": [ + { "n": "台北" }, { "n": "基隆" }, { "n": "台南" }, { "n": "台中" }, { "n": "高雄" }, { "n": "屏东" }, { "n": "南投" }, { "n": "云林" }, { "n": "新竹" }, { "n": "彰化" }, { "n": "苗栗" }, + { "n": "嘉义" }, { "n": "花莲" }, { "n": "桃园" }, { "n": "宜兰" }, { "n": "台东" }, { "n": "金门" }, { "n": "马祖" }, { "n": "澎湖" } + ] + }, + { + "n": "海外", + "s": [ + { "n": "美国" }, { "n": "日本" }, { "n": "英国" }, { "n": "法国" }, { "n": "德国" }, { "n": "其他" } + ] + } +] \ No newline at end of file diff --git a/public/react/public/css/edu-all.css b/public/react/public/css/edu-all.css index 97b5d753a..6d180e342 100644 --- a/public/react/public/css/edu-all.css +++ b/public/react/public/css/edu-all.css @@ -118,12 +118,13 @@ em.vertical-line{display: inline-block;width: 2px;background: #999;height: 10px} bottom: 90px;} .tag-green .tag-name{display: block;width: auto; /*background-image: url("/images/educoder/tag1.png");*/ - background: #000000; + background: rgba(000,000,000,0.56); border: 1px solid #fff; border-radius: 3px; - font-size: 14px; - opacity: 0.56; - background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;} + font-size: 12px; + /*opacity: 0.56;*/ + background-size: 100% 100%; + padding: 0px 8px;color: #fff;float: left;} .tag-orange{position: absolute;right: 0px;top:12px;} .tag-orange .tag-name{display: block;width: auto;background-color:#FF6800; background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left; @@ -3443,4 +3444,13 @@ a.singlepublishtwo{ .ant-tooltip{ max-width: 100% !important; -} \ No newline at end of file +} + +.square-main p{ + margin-bottom: 0 !important; +} + +/*.ant-notification{*/ + /*width: auto !important;*/ + /*max-width: 600px !important;*/ +/*}*/ \ No newline at end of file diff --git a/public/react/public/css/iconfont.css b/public/react/public/css/iconfont.css index 123661aa2..2802dd954 100644 --- a/public/react/public/css/iconfont.css +++ b/public/react/public/css/iconfont.css @@ -1,10 +1,10 @@ @font-face {font-family: "iconfont"; - src: url('iconfont.eot?t=1566227129181'); /* IE9 */ - src: url('iconfont.eot?t=1566227129181#iefix') format('embedded-opentype'), /* IE6-IE8 */ - url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), - url('iconfont.woff?t=1566227129181') format('woff'), - url('iconfont.ttf?t=1566227129181') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ - url('iconfont.svg?t=1566227129181#iconfont') format('svg'); /* iOS 4.1- */ + src: url('iconfont.eot?t=1566467441924'); /* IE9 */ + src: url('iconfont.eot?t=1566467441924#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), + url('iconfont.woff?t=1566467441924') format('woff'), + url('iconfont.ttf?t=1566467441924') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1566467441924#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { @@ -259,6 +259,10 @@ content: "\e69f"; } +.icon-kecheng:before { + content: "\e60a"; +} + .icon-shezhi1:before { content: "\e71d"; } @@ -583,10 +587,6 @@ content: "\e64e"; } -.icon-xuexizhongxin:before { - content: "\e6a2"; -} - .icon-wenjian:before { content: "\e64f"; } @@ -803,3 +803,11 @@ content: "\e6b0"; } +.icon-yemian:before { + content: "\e6b1"; +} + +.icon-bianzu:before { + content: "\e6b5"; +} + diff --git a/public/react/scripts/build.js b/public/react/scripts/build.js index b2bac24ec..022e8074f 100644 --- a/public/react/scripts/build.js +++ b/public/react/scripts/build.js @@ -194,7 +194,7 @@ function generateNewIndexJsp() { const newVersion = '1.1.1' let cdnHost = 'https://shixun.educoder.net' cdnHost = 'https://ali-cdn.educoder.net' - // cdnHost = '' + cdnHost = '' var result = data.replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) // .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) diff --git a/public/react/src/App.js b/public/react/src/App.js index 7ec25ebc8..e6f55f5d2 100644 --- a/public/react/src/App.js +++ b/public/react/src/App.js @@ -275,6 +275,11 @@ class App extends Component { }) window.location.href = "/"; }; + ModalshowCancelsy=()=>{ + this.setState({ + mydisplay:true, + }) + }; componentDidMount() { // force an update if the URL changes history.listen(() => { @@ -327,7 +332,7 @@ class App extends Component { this.HideAddcoursestypess(i)}/> - + diff --git a/public/react/src/AppConfig.js b/public/react/src/AppConfig.js index dba12e81b..939d2cdaa 100644 --- a/public/react/src/AppConfig.js +++ b/public/react/src/AppConfig.js @@ -43,8 +43,10 @@ export function initAxiosInterceptors(props) { // proxy = "http://testbdweb.educoder.net" // proxy = "https://testeduplus2.educoder.net" proxy="http://47.96.87.25:48080" - // wy + proxy="https://pre-newweb.educoder.net" + + // wy // proxy="http://192.168.2.63:3001" diff --git a/public/react/src/common/UrlTool.js b/public/react/src/common/UrlTool.js index fc2100694..e1c9f0c74 100644 --- a/public/react/src/common/UrlTool.js +++ b/public/react/src/common/UrlTool.js @@ -1,10 +1,10 @@ const isDev = window.location.port == 3007; -export const TEST_HOST = "http://47.96.87.25:48080" +export const TEST_HOST = "http://pre-newweb.educoder.net" export function getImageUrl(path) { // https://www.educoder.net // https://testbdweb.trustie.net // const local = 'http://localhost:3000' - const local = 'http://47.96.87.25:48080' + const local = 'http://pre-newweb.educoder.net' if (isDev) { return `${local}/${path}` } @@ -12,7 +12,7 @@ export function getImageUrl(path) { } export function setImagesUrl(path){ - const local = 'http://47.96.87.25:48080' + const local = 'http://pre-newweb.educoder.net' let firstStr=path.substr(0,1); // console.log(firstStr); if(firstStr=="/"){ @@ -31,7 +31,7 @@ export function getUrl(path, goTest) { // testbdweb.educoder.net testbdweb.trustie.net // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000' // const local = 'https://testeduplus2.educoder.net' - const local = 'http://47.96.87.25:48080' + const local = 'http://pre-newweb.educoder.net' if (isDev) { return `${local}${path?path:''}` } diff --git a/public/react/src/common/educoder.js b/public/react/src/common/educoder.js index 6f3284db2..8eb34ef55 100644 --- a/public/react/src/common/educoder.js +++ b/public/react/src/common/educoder.js @@ -70,3 +70,5 @@ export { CNotificationHOC as CNotificationHOC } from '../modules/courses/common/ export { default as ModalWrapper } from '../modules/courses/common/ModalWrapper' export { default as NoneData } from '../modules/courses/coursesPublic/NoneData' +export {default as WordNumberTextarea} from '../modules/modals/WordNumberTextarea' + diff --git a/public/react/src/context/TPIContextProvider.js b/public/react/src/context/TPIContextProvider.js index 724f8bc11..0e0d7be1b 100644 --- a/public/react/src/context/TPIContextProvider.js +++ b/public/react/src/context/TPIContextProvider.js @@ -174,7 +174,7 @@ class TPIContextProvider extends Component { } let testPath = '' if (window.location.port == 3007) { - testPath = 'http://47.96.87.25:48080' + testPath = 'http://pre-newweb.educoder.net' } // var url = `${testPath}/api/v1/games/${ game.identifier }/cost_time` var url = `${testPath}/api/tasks/${ game.identifier }/cost_time` diff --git a/public/react/src/index.css b/public/react/src/index.css index ee89ffc11..efd553bf3 100644 --- a/public/react/src/index.css +++ b/public/react/src/index.css @@ -25,9 +25,9 @@ body { .ant-message{ z-index: 20000; } -.ant-modal-header{ - border-radius: 10px; -} +/*.ant-modal-header{*/ + /*border-radius: 10px;*/ +/*}*/ .ant-upload-list-item-info .anticon-loading, .ant-upload-list-item-info .anticon-paper-clip{ color: #29bd8b !important; } diff --git a/public/react/src/modules/courses/boards/BoardsListItem.js b/public/react/src/modules/courses/boards/BoardsListItem.js index cf667dc6d..5abb25adb 100644 --- a/public/react/src/modules/courses/boards/BoardsListItem.js +++ b/public/react/src/modules/courses/boards/BoardsListItem.js @@ -67,7 +67,7 @@ class BoardsListItem extends Component{ {discussMessage.author.name} { discussMessage.total_replies_count != 0 && {discussMessage.total_replies_count} 回复 } - { discussMessage.total_praises_count != 0 && {discussMessage.total_praises_count} 点赞 } + { discussMessage.praises_count != 0 && {discussMessage.praises_count} 点赞 } { discussMessage.visits != 0 && {discussMessage.visits} 浏览 } {moment(discussMessage.created_on).fromNow()} diff --git a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js index 65cda33a9..bd1cafaa1 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js @@ -75,15 +75,15 @@ class CommonWorkDetailIndex extends Component{ }) } goback = () => { - // let workId=this.props.match.params.workId; + let workId=this.props.match.params.workId; // - // if ( window.location.pathname.indexOf('appraise') == -1) { - // let category_id= this.state.category.category_id; - // this.props.toListPage(this.props.match.params, category_id) - // } else { - // this.props.toWorkListPage(this.props.match.params, workId) - // } - this.props.history.goBack() + if ( window.location.pathname.indexOf('appraise') == -1) { + let category_id= this.state.category.category_id; + this.props.toListPage(this.props.match.params, category_id) + } else { + this.props.toWorkListPage(this.props.match.params, workId) + } + // this.props.history.goBack() } // 补交附件 diff --git a/public/react/src/modules/courses/busyWork/NewWork.js b/public/react/src/modules/courses/busyWork/NewWork.js index 410d12a3f..ca79e8ac5 100644 --- a/public/react/src/modules/courses/busyWork/NewWork.js +++ b/public/react/src/modules/courses/busyWork/NewWork.js @@ -9,6 +9,7 @@ import CBreadcrumb from '../common/CBreadcrumb' const confirm = Modal.confirm; const $ = window.$ +const MAX_TITLE_LENGTH = 60; class NewWork extends Component{ constructor(props){ super(props); @@ -17,7 +18,7 @@ class NewWork extends Component{ this.state={ title_value:"", - title_num:60, + title_num: 0, contentFileList: [], answerFileList: [], workLoaded: false, @@ -91,7 +92,7 @@ class NewWork extends Component{ // course_id: data.course_id, // course_name: data.course_name, // category: data.category, - title_num: 60 - parseInt(data.name.length), + title_num: parseInt(data.name.length), workLoaded: true, init_min_num: data.min_num, init_max_num: data.max_num, @@ -125,7 +126,7 @@ class NewWork extends Component{ changeTitle=(e)=>{ console.log(e.target.value.length); this.setState({ - title_num: 60 - parseInt(e.target.value.length) + title_num: parseInt(e.target.value.length) }) } handleSubmit = () => { @@ -398,7 +399,7 @@ class NewWork extends Component{ required: true, message: '请输入标题' }], })( - + )} {excellent===true? - 开放课程 + + + + :""} this.tojoinclass(1)}>加入课堂: ""} + ( + excellent===false? + this.tojoinclass(1)}>加入课堂 + : + this.myyslgradin(1)}>立即加入 + ) + : ""} {coursedata.course_identity === 6&&coursedata.educoder_teacher===true? + excellent===false? this.tojoinclass(1)}>加入课堂: ""} + onClick={() => this.tojoinclass(1)}>加入课堂 + : + this.myyslgradin(1)}>立即加入 + : ""} {coursedata.course_identity === 6&&coursedata.educoder_teacher===true? - this.ActionPoll(5)}> 复制课堂 : ""} + ( + excellent===false? + this.ActionPoll(5)}> 复制课堂 + :"" + ) + : ""} {this.props.isStudent()? this.exitclass()} @@ -613,10 +652,10 @@ class CoursesBanner extends Component { `} - this.setHistoryFun("/courses/"+this.props.match.params.coursesId+"/teachers")}> + this.setHistoryFun("/courses/"+this.props.match.params.coursesId+"/teachers")} className={"pointer"}> 教师 {coursedata.teacher_count} - this.setHistoryFun("/courses/"+this.props.match.params.coursesId+"/students")}> + this.setHistoryFun("/courses/"+this.props.match.params.coursesId+"/students")} className={"pointer"}> 学生 {coursedata.student_count} {coursedata.credit===null?"": diff --git a/public/react/src/modules/courses/coursesDetail/CoursesLeftNav.js b/public/react/src/modules/courses/coursesDetail/CoursesLeftNav.js index 8141c59c2..af2d115fc 100644 --- a/public/react/src/modules/courses/coursesDetail/CoursesLeftNav.js +++ b/public/react/src/modules/courses/coursesDetail/CoursesLeftNav.js @@ -884,7 +884,7 @@ class Coursesleftnav extends Component{ this.showsandians(e,key,item.category_url,1)} className={ item.second_category===undefined?"fl ml20 pd0":item.second_category.length===0?"fl ml20 pd0":this.state.sandiantypes===key?"fl ml20 pd0 ebebeb":"fl ml20 pd0"}> { item.type==="announcement"?: - item.type==="online_learning"?: + item.type==="online_learning"?: item.type==="shixun_homework"?: item.type==="common_homework"?: item.type==="group_homework"?: @@ -1001,7 +1001,7 @@ class Coursesleftnav extends Component{ { item.type==="announcement"?: - item.type==="online_learning"?: + item.type==="online_learning"?: item.type==="shixun_homework"?: item.type==="common_homework"?: item.type==="group_homework"?: diff --git a/public/react/src/modules/courses/coursesPublic/AccessoryModal.js b/public/react/src/modules/courses/coursesPublic/AccessoryModal.js index 5541a01a6..71d92cd0c 100644 --- a/public/react/src/modules/courses/coursesPublic/AccessoryModal.js +++ b/public/react/src/modules/courses/coursesPublic/AccessoryModal.js @@ -1,9 +1,11 @@ import React,{ Component } from "react"; -import { Modal,Checkbox,Upload,Button,Icon,message,notification} from "antd"; -import { WordsBtn,getUrl, getUploadActionUrl} from 'educoder'; +import { Modal,Checkbox,Upload,Button,Icon,message,notification,Input} from "antd"; +import { WordsBtn,getUrl, getUploadActionUrl,WordNumberTextarea} from 'educoder'; import axios from 'axios'; import Modals from '../../modals/Modals'; + const CheckboxGroup = Checkbox.Group; +const { TextArea } = Input; class AccessoryModal extends Component{ constructor(props){ @@ -19,6 +21,7 @@ class AccessoryModal extends Component{ updatas:false, shixunsreplace:false, Errormessage:false, + description:undefined } } @@ -238,6 +241,7 @@ class AccessoryModal extends Component{ ModalSave, loadtype, shixunsreplace, + description }=this.state; let {course_groups}=this.props; const uploadProps = { @@ -258,7 +262,6 @@ class AccessoryModal extends Component{ }, }; - return( @@ -335,23 +338,24 @@ class AccessoryModal extends Component{

              - + this.settextarea(e)} + value={description} + maxlength={100} + /> + { this.state.Errormessage && this.state.Errormessage === true ?

              还未上传附件

              - :
              + : "" } {this.state.updatas===true?请上传附件:""}
              diff --git a/public/react/src/modules/courses/coursesPublic/Addcourses.js b/public/react/src/modules/courses/coursesPublic/Addcourses.js index a4599bffb..f03608c8b 100644 --- a/public/react/src/modules/courses/coursesPublic/Addcourses.js +++ b/public/react/src/modules/courses/coursesPublic/Addcourses.js @@ -48,8 +48,8 @@ class Addcourses extends Component{ } componentDidUpdate = (prevProps) => { - console.log(prevProps); - console.log(this.props); + // console.log(prevProps); + // console.log(this.props); if(prevProps.occupation!==this.props.occupation){ this.setState({ Addcoursestype:false, @@ -222,8 +222,8 @@ class Addcourses extends Component{ student:student } ).then((response) => { - console.log("submittojoinclass"); - console.log(response); + // console.log("submittojoinclass"); + // console.log(response); if(response === undefined){ this.setState({ // Addcoursestype:false, @@ -302,7 +302,7 @@ class Addcourses extends Component{ isSpin:false }); }).catch((error) => { - console.log(error) + console.log(error); this.setState({ Addcoursestype:false, isSpin:false @@ -326,8 +326,8 @@ class Addcourses extends Component{ Addcoursestypes }=this.state; const antIcon = ; - console.log("Addcourses"); - console.log(Addcoursestypes) + // console.log("Addcourses"); + // console.log(Addcoursestypes) return(
              document.querySelector('.TabsWarp')} key={key}> - {item==="公开"?公开:""} + {item==="公开"?公开:""} {item==="已开启补交"?已开启补交:""} {item==="未开启补交"?未开启补交:""} {item==="匿名作品"?匿名作品:""} @@ -55,6 +55,8 @@ class CoursesListType extends Component { {item==="未提交"?未提交:""} {item==="已确认"?已确认:""} {item==="已截止"?已截止:""} + {item==="开放课程"?开放课程:""} + ) diff --git a/public/react/src/modules/courses/coursesPublic/HomeworkModal.js b/public/react/src/modules/courses/coursesPublic/HomeworkModal.js index deb329fb9..89e747004 100644 --- a/public/react/src/modules/courses/coursesPublic/HomeworkModal.js +++ b/public/react/src/modules/courses/coursesPublic/HomeworkModal.js @@ -241,6 +241,7 @@ class HomeworkModal extends Component{ className="task-hide edu-txt-left" name="shixun_homework[]" value={item.id} + key={item.id} > {item.name} diff --git a/public/react/src/modules/courses/coursesPublic/ModulationModal.js b/public/react/src/modules/courses/coursesPublic/ModulationModal.js index 46dc3becf..974077793 100644 --- a/public/react/src/modules/courses/coursesPublic/ModulationModal.js +++ b/public/react/src/modules/courses/coursesPublic/ModulationModal.js @@ -1,6 +1,6 @@ import React,{ Component } from "react"; import { Modal,Checkbox,Upload,Button,Icon,message,Input} from "antd"; - +import { WordNumberTextarea } from 'educoder'; class ModulationModal extends Component{ constructor(props){ @@ -78,12 +78,20 @@ class ModulationModal extends Component{
              - + {/**/} + + this.settextarea(e)} + value={textareaval} + maxlength={100} + /> +
            • 原因不能为空
            • diff --git a/public/react/src/modules/courses/coursesPublic/SelectResource.js b/public/react/src/modules/courses/coursesPublic/SelectResource.js index 84afa9e2c..f9433ddf7 100644 --- a/public/react/src/modules/courses/coursesPublic/SelectResource.js +++ b/public/react/src/modules/courses/coursesPublic/SelectResource.js @@ -351,6 +351,7 @@ class Selectresource extends Component{
            • { return (

              - +

              ) diff --git a/public/react/src/modules/courses/coursesPublic/modal/CheckCodeModal.js b/public/react/src/modules/courses/coursesPublic/modal/CheckCodeModal.js index d38c0a515..b0061445a 100644 --- a/public/react/src/modules/courses/coursesPublic/modal/CheckCodeModal.js +++ b/public/react/src/modules/courses/coursesPublic/modal/CheckCodeModal.js @@ -208,7 +208,7 @@ class CheckCodeModal extends Component{ { candidates && candidates.map( candidate => { return (

              - + 12 }> diff --git a/public/react/src/modules/courses/coursesPublic/modal/SendToCourseModal.js b/public/react/src/modules/courses/coursesPublic/modal/SendToCourseModal.js index 3ad35f6ea..c4c306a12 100644 --- a/public/react/src/modules/courses/coursesPublic/modal/SendToCourseModal.js +++ b/public/react/src/modules/courses/coursesPublic/modal/SendToCourseModal.js @@ -182,7 +182,7 @@ class SendToCourseModal extends Component{ { course_lists && course_lists.map( course => { return (

              - +

              ) diff --git a/public/react/src/modules/courses/coursesPublic/modal/ShixunModal2.js b/public/react/src/modules/courses/coursesPublic/modal/ShixunModal2.js index 127abf5cf..59f157597 100644 --- a/public/react/src/modules/courses/coursesPublic/modal/ShixunModal2.js +++ b/public/react/src/modules/courses/coursesPublic/modal/ShixunModal2.js @@ -286,7 +286,9 @@ class ShixunModal extends Component{
            • { + console.log("componentDidUpdate"); + console.log(prevProps); + console.log(this.props); + if(prevProps.current_user!=this.props.current_user){ + if(this.props.current_user!==undefined){ + // console.log(this.props.current_user.login); + // console.log(prevProps.current_user.login); + this.setState({ + userlogin :this.props.current_user.login, + }) + } + } + } - - }; //开始学习 Startlearning=()=>{ let {userlogin} = this.state; + console.log("userlogin"); + console.log(userlogin); if (userlogin === undefined) { this.setState({ isRender: true @@ -95,46 +113,62 @@ class Elearning extends Component{ }else { let {stages}=this.state; if(stages.length>0){ + var stagesdatas=[]; for(var i=0;i { - - if (response.data.status === -2) { - this.setState({ - - shixunsreplaces:true, - hidestartshixunsreplacevalues:response.data.message+".json" - }) - } else if (response.data.status === -1) { - console.log(response) - }else if(response.data.status===-3){ - this.setState({ - shixunsmessages:response.data.message, - startshixunCombattypes:true, - }) - } else { - console.log("开始学习了"); - window.open("/tasks/" + response.data.game_identifier,'_blank'); - //这个是传过来 调用刷新 - this.Myreload(); - // window.location.href = path - // let path="/tasks/"+response.data.game_identifier; - // this.props.history.push(path); - } - }).catch((error) => { - - }); - break; - } - console.log("这是"+i); + for(var ki=0;ki0){ + this.kaishishixun(stagesdatas[0]); + + }else { + notification.open({ + message:"提示", + description: "实训暂未公开!" + }); + } + console.log("这是"+i); } - - } }; + kaishishixun=(id)=>{ + let url = "/shixuns/" + id + "/shixun_exec.json"; + axios.get(url).then((response) => { + console.log("精品课堂开发学习"); + console.log(response); + // console.log(JSON.stringify(response)); + if (response.data.status === -2) { + this.setState({ + + shixunsreplaces:true, + hidestartshixunsreplacevalues:response.data.message+".json" + }) + } else if (response.data.status === -1) { + console.log(response) + }else if(response.data.status===-3){ + this.setState({ + shixunsmessages:response.data.message, + startshixunCombattypes:true, + }) + } else { + console.log("开始学习了"); + window.open("/tasks/" + response.data.game_identifier,'_blank'); + //这个是传过来 调用刷新 + this.Myreload(); + // window.location.href = path + // let path="/tasks/"+response.data.game_identifier; + // this.props.history.push(path); + } + }).catch((error) => { + + }); + } Startlearningtwo=()=>{ this.setState({ diff --git a/public/react/src/modules/courses/exercise/ExerciseNew.js b/public/react/src/modules/courses/exercise/ExerciseNew.js index b790086b8..cbde30a5e 100644 --- a/public/react/src/modules/courses/exercise/ExerciseNew.js +++ b/public/react/src/modules/courses/exercise/ExerciseNew.js @@ -34,7 +34,7 @@ const { TextArea } = Input; const confirm = Modal.confirm; const $ = window.$ const { Option } = Select; - +const TITLE_MAX_LENGTH = 60; class ExerciceNew extends Component{ constructor(props){ super(props); @@ -48,6 +48,36 @@ class ExerciceNew extends Component{ editMode: !this.props.match.params.Id, } } + + // 已发布试卷编辑保存的确认弹框 + changeScore = (question_id,answerArray) =>{ + this.props.confirm({ + content:'修改了标准答案', + subContent:"是否重新计算学生答题的成绩?", + onOk:()=>{ + this.sureChangeScore(question_id,answerArray) + }, + onCancel:()=>{ + this.addSuccess(); + } + }) + } + + // 已发布试卷修改答案确认修改分数 + sureChangeScore = (question_id,answerArray) =>{ + let url=`/exercise_questions/${question_id}/update_scores.json` + axios.post((url),{ + standard_answers:answerArray + }).then((result)=>{ + if(result){ + this.props.showNotification(`${result.data.message}`); + this.addSuccess(); + } + }).catch((error)=>{ + console.log(error); + }) + } + fetchExercise = () => { const Id = this.props.match.params.Id this.isEdit = !!Id @@ -382,6 +412,7 @@ class ExerciceNew extends Component{ addSuccess: this.addSuccess, addQuestion: this.addQuestion, onEditorCancel: this.onEditorCancel, + changeScore:this.changeScore, editQestion: this.editQestion, onSortDown: this.onSortDown, onSortUp: this.onSortUp, @@ -418,7 +449,8 @@ class ExerciceNew extends Component{

              {this.isEdit ? "编辑" : "新建"}试卷 this.props.history.length == 1 ? this.props.history.push(`/courses/${courseId}/exercises/${left_banner_id}`): this.props.history.goBack()}> + // () => this.props.history.length == 1 ? : this.props.history.goBack() + onClick={() => this.props.history.push(`/courses/${courseId}/exercises/${left_banner_id}`)}> 返回

              @@ -449,7 +481,9 @@ class ExerciceNew extends Component{ max: 20, message: '最大限制为20个字符', }], })( */} - + {/* )} */} @@ -504,7 +538,7 @@ class ExerciceNew extends Component{ { exercise_questions.map((item, index) => { if (item.question_type == 0 || item.question_type == 1) { if (item.isNew) { - return + return } else { return + return } else { return } } else if (item.question_type == 3) { if (item.isNew) { - return + return } else { return } diff --git a/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js b/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js index 6bda01cba..b958faa5c 100644 --- a/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js +++ b/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js @@ -299,9 +299,9 @@ class Testpapersettinghomepage extends Component{

              - {this.props.coursedata.name} + {this.props.coursedata.name} > - 试卷 + 试卷 > 试卷详情

              @@ -322,7 +322,7 @@ class Testpapersettinghomepage extends Component{ - 返回 + 返回

              diff --git a/public/react/src/modules/courses/exercise/new/JudgeEditor.js b/public/react/src/modules/courses/exercise/new/JudgeEditor.js index ab93b5bc5..41bc5bac4 100644 --- a/public/react/src/modules/courses/exercise/new/JudgeEditor.js +++ b/public/react/src/modules/courses/exercise/new/JudgeEditor.js @@ -93,10 +93,12 @@ class SingleEditor extends Component{ question_choices, standard_answers: answerArray, insert_id: question_id_to_insert_after || undefined - }) - .then((response) => { + }).then((response) => { if (response.data.status == 0) { this.props.addSuccess() + }else if(response.data.status == 3){ + // 已发布试卷编辑保存 + this.props.changeScore(question_id,answerArray); } }) .catch(function (error) { diff --git a/public/react/src/modules/courses/exercise/new/NullEditor.js b/public/react/src/modules/courses/exercise/new/NullEditor.js deleted file mode 100644 index dca67032f..000000000 --- a/public/react/src/modules/courses/exercise/new/NullEditor.js +++ /dev/null @@ -1,361 +0,0 @@ -import React,{ Component } from "react"; - -import { - Form, Input, InputNumber, Switch, Radio, - Slider, Button, Upload, Icon, Rate, Checkbox, message, - Row, Col, Select, Modal, Tooltip -} from 'antd'; -import TPMMDEditor from '../../../tpm/challengesnew/TPMMDEditor'; -import axios from 'axios' -import update from 'immutability-helper' -import { qNameArray } from './common' -import NullMDEditor from './NullMDEditor' -import NullChildEditor from './NullChildEditor' -import {getUrl, ActionBtn, DMDEditor} from 'educoder'; -const { TextArea } = Input; -const confirm = Modal.confirm; -const $ = window.$ -const { Option } = Select; - -class NullEditor extends Component{ - constructor(props){ - super(props); - /** - choice_id: 32076 - choice_position: 1 - choice_text: "1" - standard_boolean: true - standard_answer: [{choice_id: 1, answer_text: ["2", "22"]}] - - */ - const {question_choices, standard_answer} = this.props; - let _standard_answers = undefined; - if (standard_answer) { - _standard_answers = [] - - standard_answer.forEach((answers, index) => { - _standard_answers.push([]) - answers.answer_text.forEach((item, itemIndex) => { - _standard_answers[index].push(item) - }) - - }) - } - this.state = { - standard_answers: _standard_answers || [], - question_title: this.props.question_title || '', - question_type: this.props.question_type || 3, - question_score: this.props.question_score || this.props.init_question_score, - is_ordered: !!this.props.is_ordered, - } - } - on_is_ordered_change = (e) => { - this.setState({ is_ordered: e.target.checked}) - - } - - onSave = () => { - const {question_title, question_score, question_type, question_choices, standard_answers, is_ordered } = this.state; - const { question_id_to_insert_after, question_id } = this.props - - let newis_ordered= is_ordered; - - if(newis_ordered===true){ - if(standard_answers.length===1){ - newis_ordered=false - } - } - // TODO check - // const answerArray = standard_answers.map((item, index) => { return item == true ? index+1 : -1 }).filter(item => item != -1); - let answerArray = [] - if(!question_title) { - this.refs['titleEditor'].showError() - this.props.showNotification('题目:不能为空'); return; - - } - const intScore = parseFloat(question_score) - if (intScore == 0) { - this.props.showNotification('分值:必须大于0'); return; - } else if(!question_score || intScore == NaN) { - this.props.showNotification('分值:不能为空'); return; - } - - - let isEmpty = false; - standard_answers.forEach((answers, index) => { - answerArray.push({ - "choice_id": index + 1, - "answer_text":[] - }) - answers.forEach((item, itemIndex) => { - answerArray[index].answer_text.push(item) - if(!item) { - this.refs[`nullChildEditor${index}`].showError(itemIndex) - // this.props.showNotification(`请先输入第${index+1}个填空的第${itemIndex+1}参考答案。`); - this.props.showNotification(`答案:不能为空`); - isEmpty = true; - } - }) - }) - if (isEmpty == true) { - return; - } - if(!question_title) { - this.refs['titleEditor'].showError() - this.props.showNotification('题目:不能为空'); return; - - } - - /** - { - "question_title":"社会主义核心价值观(),(),()...", - "question_type":3, - "question_score":5, - "standard_answers":[ - {"choice_id":1, - "answer_text":["abbc","xxx","sssss"] - }, - {"choice_id":2, - "answer_text":["abbc","xxx","sssss"] - } - ] - "is_ordered":true -} - }*/ - const Id = this.props.match.params.Id - if (question_id) { - const editUrl = `/exercise_questions/${question_id}.json` - axios.put(editUrl, { - question_title, - question_type: 3, - question_score, - question_choices, - standard_answers: answerArray, - insert_id: question_id_to_insert_after || undefined, - is_ordered:newis_ordered, - }) - .then((response) => { - if (response.data.status == 0) { - this.props.addSuccess() - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const url = `/exercises/${Id}/exercise_questions.json` - - axios.post(url, { - question_title, - question_type: 3, - question_score, - question_choices, - standard_answers: answerArray, - insert_id: question_id_to_insert_after || undefined, - is_ordered:newis_ordered, - }) - .then((response) => { - if (response.data.status == 0) { - this.props.addSuccess() - } - }) - .catch(function (error) { - console.log(error); - }); - } - } - onCancel = () => { - this.props.onEditorCancel() - } - componentDidMount = () => { - - } - on_question_score_change = (e) => { - this.setState({ question_score: e }) - } - - - // placeholderCountInRange 被删除的个数 - onPlaceholderChange = (placeholderCountBefore, placeholderCountInRange, totalPlaceholderCount) => { - const { standard_answers } = this.state; - const new_standard_answers = standard_answers.slice() - if (placeholderCountInRange) { - new_standard_answers.splice(placeholderCountBefore, placeholderCountInRange) - } - if (totalPlaceholderCount) { - for(let i = 0; i < totalPlaceholderCount; i++) { - new_standard_answers.splice(placeholderCountBefore + i, 0, [""]) - } - } - this.setState({ standard_answers: new_standard_answers }, () => { - if (this.mdReactObject) { - this.mdReactObject.toShowMode() - } - }) - - } - - onAnswerChange = (index, itemIndex, val) => { - if (this.state.standard_answers[index]) { - this.setState( - (prevState) => ({ - standard_answers : update(prevState.standard_answers - , {[index]: {$splice: [[itemIndex, 1, val]]}}), - }) - ) - } - } - addChildAnswer = (index) => { - this.setState( - (prevState) => ({ - standard_answers : update(prevState.standard_answers - , {[index]: {$push: ['']}}), - }) - ) - } - - deleteChildAnswer = (index, childIndex) => { - if(!this.state.standard_answers[index][childIndex]) { - this.setState( - (prevState) => ({ - standard_answers : update(prevState.standard_answers, - {[index]: - {$splice: [[childIndex, 1]]} - } - ) - }) - ) - return; - } - this.props.confirm({ - content: `确认要删除这个参考答案吗?`, - onOk: () => { - this.setState( - (prevState) => ({ - standard_answers : update(prevState.standard_answers, - {[index]: - {$splice: [[childIndex, 1]]} - } - ) - }) - ) - - } - }) - - } - - toMDMode = (that) => { - if (this.mdReactObject) { - let mdReactObject = this.mdReactObject; - this.mdReactObject = null - if (that != mdReactObject) { - mdReactObject.toShowMode() - } - } - this.mdReactObject = that; - } - - render() { - let { question_title, question_score, question_type, question_choices, standard_answers - , is_ordered } = this.state; - let { question_id, index, exerciseIsPublish, - // question_title, - // question_type, - // question_score, - isNew } = this.props; - - // const { getFieldDecorator } = this.props.form; - - const isAdmin = this.props.isAdmin() - const courseId=this.props.match.params.coursesId; - const isEdit = !!this.props.question_id - const qNumber = `question_${index}`; - - // console.log(this.state.showtype) - // const answerTagArray = standard_answers.map((item, index) => { return item == true ? tagArray[index] : -1 }).filter(item => item != -1); - return( -
              - -

              {/*!question_id ? '新建' : '编辑'*/} - {qNameArray[question_type]} - (客观题,由系统自动评分,允许手动调分,请设置标准答案 ;支持最多5个空,每空得分按照本题的总分平均计算) -

              - - this.setState({ question_title: val})} - onPlaceholderChange={this.onPlaceholderChange} showNullButton={exerciseIsPublish ? false : true} - ref="titleEditor" - > - -
              - { - standard_answers.map((answers, index) => { - - return - // answer.map((item, itemIndex) => { - // return this.onAnswerChange(index, itemIndex, val)} - // > - // }) - }) - } - -
              - -
              - {standard_answers.length>1? - - 多个填空的答案有顺序要求 - (选中,每个填空的答案顺序必须与参考答案一致) - :""} -
              -
              - 分值: - 分 - - - - 取消 - 保存 - - -
              - -
              - ) - } -} -// RouteHOC() -export default (NullEditor); \ No newline at end of file diff --git a/public/react/src/modules/courses/exercise/new/SingleEditor.js b/public/react/src/modules/courses/exercise/new/SingleEditor.js deleted file mode 100644 index b979b62c1..000000000 --- a/public/react/src/modules/courses/exercise/new/SingleEditor.js +++ /dev/null @@ -1,320 +0,0 @@ -import React,{ Component } from "react"; - -import { - Form, Input, InputNumber, Switch, Radio, - Slider, Button, Upload, Icon, Rate, Checkbox, message, - Row, Col, Select, Modal, Tooltip -} from 'antd'; -import TPMMDEditor from '../../../tpm/challengesnew/TPMMDEditor'; -import axios from 'axios' -import update from 'immutability-helper' - -import {getUrl, ActionBtn, DMDEditor, ConditionToolTip} from 'educoder'; -const { TextArea } = Input; -const confirm = Modal.confirm; -const $ = window.$ -const { Option } = Select; - -const tagArray = [ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', - 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' -] -class SingleEditor extends Component{ - constructor(props){ - super(props); - /** - choice_id: 32076 - choice_position: 1 - choice_text: "1" - standard_boolean: true - */ - const {question_choices} = this.props; - let _standard_answers = undefined; - let _question_choices = undefined; - if (question_choices) { - _standard_answers = [] - _question_choices = [] - - question_choices.forEach((item, index) => { - _standard_answers.push(item.standard_boolean) - _question_choices.push(item.choice_text) - }) - } - this.state = { - question_choices: _question_choices || ['', '', '', ''], - standard_answers: _standard_answers || [false, false, false, false], - question_title: this.props.question_title || '', - question_type: this.props.question_type || 0, - question_score: this.props.question_score || this.props.init_question_score, - } - } - addOption = () => { - const { question_choices, standard_answers } = this.state; - question_choices.push('') - standard_answers.push(false) - this.setState({ question_choices, standard_answers }) - } - deleteOption = (index) => { - let {question_choices}=this.state; - if(question_choices[index]===""){ - // repeat code - this.toMDMode(null) - this.setState( - (prevState) => ({ - question_choices : update(prevState.question_choices, {$splice: [[index, 1]]}), - standard_answers : update(prevState.standard_answers, {$splice: [[index, 1]]}) - }) - ) - }else{ - this.props.confirm({ - content: `确认要删除这个选项吗?`, - onOk: () => { - this.toMDMode(null) - this.setState( - (prevState) => ({ - question_choices : update(prevState.question_choices, {$splice: [[index, 1]]}), - standard_answers : update(prevState.standard_answers, {$splice: [[index, 1]]}) - }) - ) - } - }) - } - } - onSave = () => { - const {question_title, question_score, question_type, question_choices, standard_answers } = this.state; - const { question_id_to_insert_after, question_id } = this.props - // TODO check - const answerArray = standard_answers.map((item, index) => { return item == true ? index+1 : -1 }).filter(item => item != -1); - if(!question_title) { - this.refs['titleEditor'].showError() - this.props.showNotification('题目:不能为空'); return; - - - } - - const intScore = parseFloat(question_score) - if (intScore == 0) { - this.props.showNotification('分值:必须大于0'); return; - } else if(!question_score || intScore == NaN) { - this.props.showNotification('分值:不能为空'); return; - } - if(!answerArray || answerArray.length == 0) { - this.props.showNotification('请先点击选择本选择题的正确选项'); return; - } - if(!question_title) { - this.refs['titleEditor'].showError() - this.props.showNotification('题目:不能为空'); return; - - - } - - for(let i = 0; i < question_choices.length; i++) { - if (!question_choices[i]) { - this.refs[`optionEditor${i}`].showError() - this.props.showNotification(`请先输入 ${tagArray[i]} 选项的内容`); return; - } - } - - /** - { - "question_title":"同学朋友间常用的沟通工具是什么?", - "question_type":1, - "question_score":5, - "question_choices":["a答案","b答案","c答案","d答案"], - "standard_answers":[1] - }*/ - const Id = this.props.match.params.Id - if (question_id) { - const editUrl = `/exercise_questions/${question_id}.json` - axios.put(editUrl, { - question_title, - question_type: answerArray.length > 1 ? 1 : 0, - question_score, - question_choices, - standard_answers: answerArray, - insert_id: question_id_to_insert_after || undefined - }) - .then((response) => { - if (response.data.status == 0) { - this.props.addSuccess() - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const url = `/exercises/${Id}/exercise_questions.json` - - axios.post(url, { - question_title, - question_type: answerArray.length > 1 ? 1 : 0, - question_score, - question_choices, - standard_answers: answerArray, - insert_id: question_id_to_insert_after || undefined - }) - .then((response) => { - if (response.data.status == 0) { - this.props.addSuccess() - } - }) - .catch(function (error) { - console.log(error); - }); - } - } - onCancel = () => { - this.props.onEditorCancel() - } - componentDidMount = () => { - - } - onOptionClick = (index) => { - // if (this.props.exerciseIsPublish) { - // return; - // } - let standard_answers = this.state.standard_answers.slice(0) - standard_answers[index] = !standard_answers[index] - this.setState({ standard_answers }) - } - onOptionContentChange = (value, index) => { - let question_choices = this.state.question_choices.slice(0); - question_choices[index] = value; - this.setState({ question_choices }) - } - on_question_score_change = (e) => { - this.setState({ question_score: e }) - } - toMDMode = (that) => { - if (this.mdReactObject) { - let mdReactObject = this.mdReactObject; - this.mdReactObject = null - mdReactObject.toShowMode() - } - this.mdReactObject = that; - - } - toShowMode = () => { - - } - render() { - let { question_title, question_score, question_type, question_choices, standard_answers } = this.state; - let { question_id, index, exerciseIsPublish, - // question_title, - // question_type, - // question_score, - isNew } = this.props; - - // const { getFieldDecorator } = this.props.form; - - const isAdmin = this.props.isAdmin() - const courseId=this.props.match.params.coursesId; - const isEdit = !!this.props.question_id - const qNumber = `question_${index}`; - // TODO show模式 isNew为false isEdit为false - - // [true, false, true] -> [0, 2] - - const answerTagArray = standard_answers.map((item, index) => { return item == true ? tagArray[index] : -1 }).filter(item => item != -1); - return( -
              - -

              - {/* {!question_id ? '新建' : '编辑'} */} - 选择题 - (客观题,由系统自动评分,请设置标准答案) -

              - - this.setState({ question_title: val})} - ref="titleEditor" - - > - - {question_choices.map( (item, index) => { - const bg = standard_answers[index] ? 'check-option-bg' : '' - return
              - {/* 点击设置答案 */} - {/* TODO 加了tooltip后,会丢失掉span的class */} - {/* */} - this.onOptionClick(index)} style={{flex: '0 0 38px'}}> - -
              {tagArray[index]}
              -
              -
              - {/*
              */} -
              - this.onOptionContentChange(value, index)} - initValue={item} - > -
              - {exerciseIsPublish || index===0? - - : - this.deleteOption(index)}> - } - { !exerciseIsPublish && - this.addOption()} - style={{float: 'right', visibility: index == question_choices.length - 1 ? '' : 'hidden', marginTop: '2px'}} - > - } - -
              - }) } - -
              - {/* {!exerciseIsPublish && 新增选项} */} - - {!exerciseIsPublish ? '温馨提示:点击选项标题,可以直接设置答案;选择多个答案即为多选题' : ' '} - { answerTagArray && !!answerTagArray.length ? - - {answerTagArray.join(' ')} - 标准答案: - - : - 请点击正确选项 - } -
              - -
              - 分值: -  分 - - - 取消 - 保存 - - -
              - -
              - ) - } -} -// RouteHOC() -export default (SingleEditor); \ No newline at end of file diff --git a/public/react/src/modules/courses/exercise/question/shixunAnswer.js b/public/react/src/modules/courses/exercise/question/shixunAnswer.js index d8644f8c4..cdffe08b8 100644 --- a/public/react/src/modules/courses/exercise/question/shixunAnswer.js +++ b/public/react/src/modules/courses/exercise/question/shixunAnswer.js @@ -316,7 +316,7 @@ class shixunAnswer extends Component{ 第{item[0].position}关 - + {item[0].name} diff --git a/public/react/src/modules/courses/gradinforms/Bullsubdirectory.js b/public/react/src/modules/courses/gradinforms/Bullsubdirectory.js new file mode 100644 index 000000000..c7f773190 --- /dev/null +++ b/public/react/src/modules/courses/gradinforms/Bullsubdirectory.js @@ -0,0 +1,330 @@ +import React,{ Component } from "react"; +import { Input,Checkbox,Table, Pagination, Modal,Menu, Tooltip,Spin,Button,Form } from "antd"; +import { WordsBtn,on, off, trigger,markdownToHTML,getImageUrl} from 'educoder'; +import './myysleduinforms.css' +import axios from 'axios'; +import TPMMDEditor from "../../tpm/challengesnew/TPMMDEditor"; +import moment from "../new/CoursesNew"; +import Fileslistitem from "../Resource/Fileslistitem"; +// 公告栏 +class Bullsubdirectory extends Component{ + constructor(props){ + super(props); + this.messageRef = React.createRef(); + + this.state={ + description:null, + isSpinysl:false, + whethertoeditysl:false, + addonAfter:0, + eduintits:"", + informs:[], + + + } + } + + componentDidMount() { + console.log("获取到数据"); + console.log(this.props); + let{id,myname,mydescription} =this.props + this.props.form.setFieldsValue({ + id:id, + eduintits:myname, + description:mydescription, + }); + this.setState({ + id:id, + eduintits:myname, + description:mydescription, + + }) + if(myname!=undefined){ + this.setState({ + addonAfter:myname.length + }) + } + + } + + + bianji = (bians)=>{ + this.setState({ + whethertoeditysl:bians, + }) + if(bians===true){ + this.props.getyslbooltrue(); + }else { + this.props.getyslboolfalse(); + } + }; + changeTopicName = (e) => { + console.log("调用了changeTopicName"); + let num = e.target.value.length; + + if(num>60){ + return; + } + this.setState({ + addonAfter: num < 0 ? 0 : num + }); + if(num<=60){ + this.setState({ + eduintits: e.target.value + }) + + this.props.form.setFieldsValue({ + eduintits: e.target.value, + }); + } + + + } + handleSubmit=(e) => { + e.preventDefault(); + this.props.form.validateFields((err, values) => { + if (!err) { + console.log(values.description); + if(values.eduintits === undefined|| values.eduintits === "" || values.eduintits ===null){ + this.props.showNotification(`请输入标题`); + return + + } + if(values.description === undefined|| values.description === "" || values.description ===null){ + this.props.showNotification(`请输入内容`); + return + + } + var id=this.props.match.params.coursesId + var titname=""; + try { + if(values.eduintits.length>0){ + if( values.eduintits.length>60){ + var str=values.eduintits; + titname=str.substring(0,60); + }else { + titname=values.eduintits; + } + }else { + titname=values.eduintits; + } + }catch (e) { + titname=values.eduintits; + } + var url = `/courses/${id}/update_informs.json`; + axios.post(url,{ + inform_id:this.state.id, + name:titname, + description:values.description, + }).then((result) => { + if(result){ + if(result.data){ + if(result.data.status === 0){ + this.props.form.setFieldsValue({ + id:this.state.id, + eduintits:titname, + description:values.description, + }); + this.setState({ + whethertoeditysl:false, + id:this.state.id, + eduintits:titname, + description:values.description, + }); + this.props.getinputdata(); + this.props.getyslboolfalse(); + this.props.showNotification(result.data.message); + }else { + this.props.showNotification(result.data.message); + + } + } + + } + }).catch((error) => { + console.log(error) + }) + }else{ + console.log(err); + } + + }); + } + + + render(){ + let{description,whethertoeditysl,addonAfter,eduintits,informs,isSpinysl} =this.state; + let{myname,mydescription}=this.props; + const {getFieldDecorator} = this.props.form; + + return( + +
              + +
              + { + whethertoeditysl === false? +
              +
              +
              {myname}
              + { + this.props.isAdmin() === true ? + (this.props.yslbool===false? + this.bianji(true)}> + : + "" + ) + + :"" + } +
              +
              +
              +
              +
              + : +
              +
              + + + {getFieldDecorator('eduintits',{ initialValue: this.state.eduintits }, { + rules: [{ + required: true, message: '请在此输入标题,最多60个字符', + }], + })( +
              +
              + * +
              +
              + +
              + +
              + )} +
              + + +
              +
              + + + {getFieldDecorator('description', { initialValue: this.state.description },{ + rules: [{ + required: true, message: '请在此输入内容,最多5000个字符', + }, { + len: 5000, message: '最大限制为5000个字符', + }], + })( + + )} + +
              +
              + + +
              + this.bianji(false)}>取消 + +
              +
              +
              +
              +
              + } +
              + + +
              + + ) + } +} +const Bullsubdirectorys = Form.create({ name: 'bullsubdirectorys' })(Bullsubdirectory); +export default Bullsubdirectorys; diff --git a/public/react/src/modules/courses/gradinforms/Eduinforms.js b/public/react/src/modules/courses/gradinforms/Eduinforms.js index aef54ab13..f847c87d3 100644 --- a/public/react/src/modules/courses/gradinforms/Eduinforms.js +++ b/public/react/src/modules/courses/gradinforms/Eduinforms.js @@ -4,7 +4,12 @@ import { WordsBtn,on, off, trigger,markdownToHTML,getImageUrl} from 'educoder'; import './myysleduinforms.css' import axios from 'axios'; import TPMMDEditor from "../../tpm/challengesnew/TPMMDEditor"; +import Bullsubdirectory from "./Bullsubdirectory"; +import moment from "../new/CoursesNew"; +import Fileslistitem from "../Resource/Fileslistitem"; // 公告栏 +// var isOnComposition = false; +// const isChrome = !!window.chrome && !!window.chrome.webstore class Eduinforms extends Component{ constructor(props){ super(props); @@ -14,33 +19,57 @@ class Eduinforms extends Component{ description:null, isSpin:true, whethertoedit:false, - + addonAfter:0, + eduintits:"", + informs:[], + yslbool:false, } } componentDidMount() { console.log("获取到数据"); console.log(this.props); - let url = `/courses/${this.props.match.params.coursesId}/informs.json`; + + this.getinputdata(); + } + + getyslbooltrue(){ + console.log("调用了getyslbooltrue"); + this.setState({ + yslbool:true, + }); + } + + getyslboolfalse(){ + console.log("调用了getyslboolfalse"); + this.setState({ + yslbool:false, + }); + } + getinputdata=()=>{ + this.setState({ + isSpin:true, + }) + let url = `/courses/${this.props.match.params.coursesId}/informs.json`; // axios.get(url).then((response) => { if(response){ - if(response.data){ - this.setState({ - description:response.data.description, - isSpin:false, - }) - }else { - this.setState({ - description:null, - isSpin:false, - - }) - - } + if(response.data){ + this.setState({ + informs:response.data.informs, + isSpin:false, + }) + }else { + this.setState({ + informs:[], + isSpin:false, + + }) + + } }else { this.setState({ - description:null, + informs:[], isSpin:false, }) @@ -48,14 +77,12 @@ class Eduinforms extends Component{ }).catch((error) => { console.log(error) this.setState({ - description:null, + informs:[], isSpin:false, }) }); - } - componentDidUpdate = (prevProps) => { @@ -64,33 +91,113 @@ class Eduinforms extends Component{ bianji = (bians)=>{ this.setState({ whethertoedit:bians, - }) + description:"", + eduintits:"", + addonAfter:0, + }); + this.props.form.setFieldsValue({ + description:"", + eduintits:"", + }); + if(bians===true){ + this.getyslbooltrue(); + }else { + this.getyslboolfalse(); + } }; + changeTopicName = (e) => { + console.log("调用了changeTopicName"); + let num = e.target.value.length; + if(num>60){ + return; + } + this.setState({ + addonAfter: num < 0 ? 0 : num + }); + if(num<=60){ + this.setState({ + eduintits: e.target.value + }) + this.props.form.setFieldsValue({ + eduintits: e.target.value, + }); + } + }; + // handleComposition=(e)=>{ + // if (e.type === 'compositionend') { + // // composition is end + // isOnComposition = false + // + // if (!isOnComposition && isChrome) { + // // fire onChange + // console.log(!isOnComposition); + // this.changeTopicName(e); + // } + // } else { + // // in composition + // isOnComposition = true + // } + // }; + // handleComposition = (e) => { + // console.log(e.type + ": " + e.target.value); + // if (e.type === 'compositionend') { + // // composition is end + // const value = e.target.value; + // this.setState({ isOnComposition: false },()=>{ + // // this.handleFixedChange(value); + // }); + // } else { + // // in composition + // this.setState({ isOnComposition: true }); + // } + // } handleSubmit=(e) => { e.preventDefault(); this.props.form.validateFields((err, values) => { if (!err) { console.log(values.description); + if(values.eduintits === undefined|| values.eduintits === "" || values.eduintits ===null){ + this.props.showNotification(`请输入标题`); + return + + } if(values.description === undefined|| values.description === "" || values.description ===null){ - this.props.showNotification(`请输入提交内容`); + this.props.showNotification(`请输入内容`); return } var id=this.props.match.params.coursesId - - var url = `/courses/${id}/update_informs.json`; + var titname=""; + try { + if(values.eduintits.length>0){ + if( values.eduintits.length>60){ + var str=values.eduintits; + titname=str.substring(0,60); + }else { + titname=values.eduintits; + } + }else { + titname=values.eduintits; + } + }catch (e) { + titname=values.eduintits; + } + var url = `/courses/${id}/new_informs.json`; axios.post(url,{ + name:titname, description:values.description, }).then((result) => { if(result){ if(result.data){ if(result.data.status === 0){ this.setState({ - description:values.description, whethertoedit:false, - }) + + }); + this.getinputdata(); + this.getyslboolfalse(); this.props.showNotification(result.data.message); }else { this.props.showNotification(result.data.message); @@ -111,22 +218,25 @@ class Eduinforms extends Component{ render(){ - let{description,whethertoedit} =this.state; + let{description,whethertoedit,addonAfter,eduintits,informs,yslbool} =this.state; const {getFieldDecorator} = this.props.form; return(
              -

              +

              公告栏 { this.props.isAdmin()===true? -

            • this.bianji(true)}> - 编辑 -
            • + (this.state.yslbool===false? +
            • this.bianji(true)}> + 发布公告 +
            • + :"") + :"" } @@ -138,36 +248,71 @@ class Eduinforms extends Component{
              { - whethertoedit === false? -
              - { - description === null || description=== undefined ||description === "" ? -
              -
              -

              暂时还没有相关数据哦!

              -
              - : -
              -
              + whethertoedit === false?"" + : +
              -
              -
              - } -
              - : -
              - -
              -
              + + {getFieldDecorator('eduintits', { + rules: [{ + required: true, message: '请在此输入标题,最多60个字符', + }], + })( +
              +
              + * +
              +
              + +
              + +
              + )} +
              + + +
              +
              + - - {getFieldDecorator('description', { - rules: [{ - required: true, message: '请在此输入内容,最多5000个字符', - }, { - max: 5000, message: '最大限制为5000个字符', - }], - })( - - )} - -
              -
              + } + + + {getFieldDecorator('description', { + rules: [{ + required: true, message: '请在此输入内容,最多5000个字符', + }, { + max: 5000, message: '最大限制为5000个字符', + }], + })( + + )} + +
              +
              -
              - this.bianji(false)}>取消 - -
              +
              + this.bianji(false)}>取消 + +
              +
              } +
              + { + informs === null || informs=== undefined ||informs.length === 0 ? +
              +
              +

              暂时还没有相关数据哦!

              +
              + : + +
              + { informs&&informs.map((item, index) => { + return ( + this.getyslbooltrue()} + getyslboolfalse={()=>this.getyslboolfalse()} + getinputdata={()=>this.getinputdata()} > + ) + }) + } +
              + } +
              + +
              @@ -257,3 +428,7 @@ class Eduinforms extends Component{ } const Eduinformss = Form.create({ name: 'eduinforms' })(Eduinforms); export default Eduinformss; +{/*
              */} +{/* {item.name===""?"":item.name===undefined?"":item.name===null?"":
              {item.name}
              }*/} +{/*
              */} +{/*
              */} \ No newline at end of file diff --git a/public/react/src/modules/courses/gradinforms/myysleduinforms.css b/public/react/src/modules/courses/gradinforms/myysleduinforms.css index f3fa2fb1a..1e86607ec 100644 --- a/public/react/src/modules/courses/gradinforms/myysleduinforms.css +++ b/public/react/src/modules/courses/gradinforms/myysleduinforms.css @@ -1,5 +1,5 @@ .yslmt16px{ - padding-top: 25px !important; + padding-top: 12px !important; padding-left: 25px !important; padding-right: 25px !important; padding-bottom: 1px !important; @@ -10,4 +10,72 @@ padding: 25px !important; -} \ No newline at end of file +} + +.bluebkbk{ + border: 1px solid #4CADFF; + width: 79px; + height: 30px; + text-align: center; + line-height: 30px; + border-radius: 2px; +} +.ysleduinwh{ + + padding-right: 25px; + margin-top: 26px; + display: flex; + justify-content:flex-start; +} +.yslduincolorred{ + color: red; + line-height: 40px; + height: 40px; + text-align: center; +} +.yslduinleft{ + width: 100% ; +} +.yslduinlefts{ + width: 25px; + line-height: 40px; + height: 40px; + text-align: center; +} +.mtyslduin25{ + margin-top: 25px; +} + +.newbianji1{ + font-size: 16px !important; + margin-right: 10px; + color: #4CACFF; + margin-bottom: 3px; + display: inline-block; +} + +.ysltitbt{ + float: left; + padding-top: 31px; + padding-left: 25px; + font-size: 16px; + color: #333333; + text-align: left; +} +.markdownysltext{ + font-size: 14px; + color: #999999; +} +.fudonyingxiangysl{ + width: 100%; + height: 66px; +} +.yslbianji{ + padding-top: 31px; +} +.yslmaxheigth80{ + max-height: 80px; +} +.ysldashed{ + border:1px dashed #EEE; +} diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitedit.js b/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitedit.js index fcbe6b9aa..ce704ef6e 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitedit.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitedit.js @@ -77,16 +77,6 @@ class GraduationTasksSubmitedit extends Component{ } - setedit=()=>{ - // let coursesId=this.props.match.params.coursesId; - // let workId=this.props.match.params.work_Id; - // let {workslist}=this.state - // let task_id=workslist&&workslist.task_id; - // window.location.href="/courses/"+coursesId+"/graduation_tasks/"+task_id+"/"+workId+"/works/edit"; - this.goback() - } - - handleSelectChange = (value) => { console.log(value); @@ -103,7 +93,8 @@ class GraduationTasksSubmitedit extends Component{ // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + this.props.history.replace(`/courses/${this.state.workslist.course_id}/graduation_tasks/${this.state.workslist.graduation_id}`); + } @@ -378,6 +369,9 @@ class GraduationTasksSubmitedit extends Component{ } Commoninterface =(fileList,selectmemberslist,workslist)=>{ + let coursesIds=this.props.match.params.coursesId + let workId=this.props.match.params.work_Id; + let userids=[]; for(var list of selectmemberslist){ @@ -424,7 +418,7 @@ class GraduationTasksSubmitedit extends Component{ this.setState({ spinnings:true }) - let workId=this.props.match.params.work_Id; + // if(fileList.length===0){ // this.setState({ @@ -444,8 +438,8 @@ class GraduationTasksSubmitedit extends Component{ spinnings:false }) if(response!== undefined){ - this.setedit() - + // this.goback() + window.location.href=`/courses/${coursesIds}/graduation_tasks/${workId}/appraise` } // if(response.status===200) { // GraduationTasksnewtype=false; diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitnew.js b/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitnew.js index dadc621b2..3c8318796 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitnew.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTasksSubmitnew.js @@ -105,7 +105,7 @@ class GraduationTasksSubmitnew extends Component{ // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + this.props.history.replace(`/courses/${this.state.workslist.course_id}/graduation_tasks/${this.state.workslist.graduation_id}`); } @@ -361,9 +361,7 @@ class GraduationTasksSubmitnew extends Component{ return false; } } - gocannel=()=>{ - this.props.history.goBack() - } + //公用数据 Commoninterface = (fileList,selectmemberslist,workslist)=>{ diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTasksappraise.js b/public/react/src/modules/courses/graduation/tasks/GraduationTasksappraise.js index d01be4676..c03ffc1e0 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTasksappraise.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTasksappraise.js @@ -76,7 +76,9 @@ class GraduationTasksappraise extends Component{ // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + + this.props.history.replace(`/courses/${this.state.datalist.course_id}/graduation_tasks/${this.state.datalist.graduation_id}/${this.state.datalist.task_id}/list`); + } Cancelvisible=()=>{ diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTasksedit.js b/public/react/src/modules/courses/graduation/tasks/GraduationTasksedit.js index 559144ecf..42a128719 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTasksedit.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTasksedit.js @@ -18,7 +18,7 @@ class GraduationTasksedit extends Component{ this.state={ coursename:"", coursesearch:"", - title_num:60, + title_num:0, title_value:"", fileList: [], contents: [{val:"",id:1}], @@ -53,14 +53,14 @@ class GraduationTasksedit extends Component{ // // } let namelength=result.data.task_name.length; - let sixlength=title_num-namelength + // let sixlength=title_num-namelength this.setState({ // fileList:newfilelist, description:result.data.description, tasktype:result.data.task_type, name:result.data.task_name, data:result.data, - title_num:sixlength, + title_num:namelength, attachments:result.data.attachments, }) @@ -88,15 +88,15 @@ class GraduationTasksedit extends Component{ // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + this.props.history.replace(`/courses/${this.state.data.course_id}/graduation_tasks/${this.state.data.graduation_id}`); } // 输入title changeTitle=(e)=>{ - + // title_num:60-parseInt(e.target.value.length), this.setState({ - title_num:60-parseInt(e.target.value.length), + title_num:e.target.value.length, title_value:e.target.value }) @@ -379,7 +379,7 @@ class GraduationTasksedit extends Component{ {getFieldDecorator('name', { rules: [{ required: true, message: "请输入标题" }], - })()} + })()}
              diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTasksnew.js b/public/react/src/modules/courses/graduation/tasks/GraduationTasksnew.js index 1d3c7578f..7c499430a 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTasksnew.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTasksnew.js @@ -19,7 +19,7 @@ class GraduationTasksnew extends Component { this.state = { coursename: "", coursesearch: "", - title_num: 60, + title_num: 0, title_value: "", fileList: [], contents: [{val: "", id: 1}], @@ -113,15 +113,16 @@ class GraduationTasksnew extends Component { // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + // this.props.history.goBack() + this.props.history.replace(`/courses/${this.props.match.params.coursesId}/graduation_tasks/${this.props.match.params.category_id}`); } // 输入title changeTitle = (e) => { - + // title_num: 60 - parseInt(e.target.value.length), this.setState({ - title_num: 60 - parseInt(e.target.value.length), + title_num: e.target.value.length, title_value: e.target.value }) @@ -384,7 +385,7 @@ class GraduationTasksnew extends Component { rules: [{required: true, message: "不能为空"}], })()} + suffix={String(title_num)+"/60"}/>)}
              diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssetting.js b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssetting.js index e679702da..7e5707695 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssetting.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssetting.js @@ -771,7 +771,10 @@ class GraduationTaskssettingapp extends Component{ // }else{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + // this.props.history.goBack() + // this.props.history.replace(this.props.current_user.first_category_url); + this.props.history.replace(`/courses/${this.state.settingdata.course_id}/graduation_tasks/${this.state.settingdata.graduation_id}`); + } isgoback=()=>{ diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettinglist.js b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettinglist.js index 816d53526..9718de28a 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettinglist.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettinglist.js @@ -64,9 +64,10 @@ class GraduationTaskssettinglist extends Component{ // if(courseId===undefined){ // this.props.history.push("/courses"); // }else{ - // this.props.history.push(this.props.current_user.first_category_url); + // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + // this.props.history.goBack() + this.props.history.replace(`/courses/${this.state.taskslistdata.course_id}/graduation_tasks/${this.state.taskslistdata.graduation_id}`); } seacthdata=(teacher_comment,task_status,course_group,cross_comment,order,b_order,search,pages)=>{ diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettingquestions.js b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettingquestions.js index 22ce6fa04..b8296f9ef 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettingquestions.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTaskssettingquestions.js @@ -62,7 +62,8 @@ class GraduationTasksquestions extends Component{ // this.props.history.push(this.props.current_user.first_category_url); // } - this.props.history.goBack() + // this.props.history.goBack() + this.props.history.replace(`/courses/${this.state.questionslist.course_id}/graduation_tasks/${this.state.questionslist.graduation_id}`); } end=()=>{ diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js index b24277a2c..b518c44f3 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js @@ -114,7 +114,7 @@ class GraduateTopicDetail extends Component{

              {tableData && tableData.graduation_topic_name} - this.props.history.goBack()}>返回 + 返回

              diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js index 8c46bfa0e..3845721be 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js @@ -12,6 +12,8 @@ import "../../common/formCommon.css" import '../style.css' import '../../css/Courses.css' import { WordsBtn, City } from 'educoder' + +import {Link} from 'react-router-dom' // import City from './City' // import './board.css' @@ -20,6 +22,7 @@ import { WordsBtn, City } from 'educoder' const confirm = Modal.confirm; const $ = window.$ const { Option } = Select; +const NAME_COUNT=60; // 新建毕设选题 // https://lanhuapp.com/web/#/item/project/board/detail?pid=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&project_id=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&image_id=c6d9b36f-7701-4035-afdb-62404681108c class GraduateTopicNew extends Component{ @@ -38,7 +41,7 @@ class GraduateTopicNew extends Component{ topic_source:[], topic_type:[], attachments:undefined, - addonAfter:60, + addonAfter:0, left_banner_id:undefined, course_name:undefined } @@ -99,7 +102,7 @@ class GraduateTopicNew extends Component{ topic_source:result.data.topic_source, topic_type:result.data.topic_type, attachments:result.data.attachments, - addonAfter:20-parseInt(result.data.selected_data.name.length) + addonAfter:parseInt(result.data.selected_data.name.length) }) this.props.form.setFieldsValue({ tea_id:result.data.selected_data.tea_id, @@ -257,9 +260,9 @@ class GraduateTopicNew extends Component{ // 附件相关 ------------ END changeTopicName=(e)=>{ - let num= 60 - parseInt(e.target.value.length); + // let num= 60 - parseInt(e.target.value.length); this.setState({ - addonAfter:num < 0 ? 0 : num + addonAfter:e.target.value.length }) } render() { @@ -331,7 +334,7 @@ class GraduateTopicNew extends Component{

              {topicId==undefined?"新建":"编辑"}毕设选题

              - this.props.history.goBack()} className="color-grey-6 fr font-16">返回 + 返回
              @@ -366,7 +369,7 @@ class GraduateTopicNew extends Component{ max: 60, message: '最大限制为60个字符', }], })( - + )}
              diff --git a/public/react/src/modules/courses/members/CourseGroupChooser.js b/public/react/src/modules/courses/members/CourseGroupChooser.js index 1a03c978f..ba9c64bb1 100644 --- a/public/react/src/modules/courses/members/CourseGroupChooser.js +++ b/public/react/src/modules/courses/members/CourseGroupChooser.js @@ -75,6 +75,7 @@ function CourseGroupChooser({ course_groups, isAdminOrCreator = true, item, inde return(
            • diff --git a/public/react/src/modules/courses/members/modal/AddStudentModal.js b/public/react/src/modules/courses/members/modal/AddStudentModal.js index 958b4f134..de95bc741 100644 --- a/public/react/src/modules/courses/members/modal/AddStudentModal.js +++ b/public/react/src/modules/courses/members/modal/AddStudentModal.js @@ -237,7 +237,7 @@ class AddStudentModal extends Component{ { users.map( candidate => { return (

              - + 12 }>

              - + {/* "color":"#4c4c4c" */} 12 }> diff --git a/public/react/src/modules/courses/new/Goldsubject.js b/public/react/src/modules/courses/new/Goldsubject.js index da7a12b3f..7214f46a8 100644 --- a/public/react/src/modules/courses/new/Goldsubject.js +++ b/public/react/src/modules/courses/new/Goldsubject.js @@ -214,7 +214,7 @@ class Goldsubject extends Component { datatime: dateString, }); this.props.form.setFieldsValue({ - endtime: moment(dateString, dateFormat).add(1, 'days'), + endtime: moment(dateString, dateFormat), }); } @@ -237,7 +237,7 @@ class Goldsubject extends Component { datatimetwo: dateString, }) this.props.form.setFieldsValue({ - starttime: moment(dateString, dateFormat).add(1, 'days'), + starttime: moment(dateString, dateFormat), }); } @@ -318,14 +318,23 @@ class Goldsubject extends Component { name: values.classroom, class_period: values.period, credit: parseFloat(values.credit), - start_date:values.starttime, - end_date: values.endtime, + start_date:moment(values.starttime).format("YYYY-MM-DD"), + end_date: moment(values.endtime).format("YYYY-MM-DD"), is_public: this.state.is_public, //这是也是带过来的值 course_module_types: values.checkboxgroup, school:values.school }; - console.log("327"); - console.log(datasysl); + try { + console.log("327"); + console.log(datasysl); + // console.log(JSON.stringify(datasysl)); + console.log("88887777"); + console.log(moment(values.starttime).format("YYYY-MM-DD")); + console.log(moment(values.endtime).format("YYYY-MM-DD")); + }catch (e) { + + } + let url = "/courses/" + coursesId + ".json"; axios.put(url, datasysl @@ -392,15 +401,22 @@ class Goldsubject extends Component { name: values.classroom, class_period: values.period, credit: parseFloat(values.credit), - start_date:values.starttime, - end_date: values.endtime, + start_date:moment(values.starttime).format("YYYY-MM-DD"), + end_date: moment(values.endtime).format("YYYY-MM-DD"), is_public: is_public, //这是也是带过来的值 course_module_types: values.checkboxgroup, school:values.school }; - console.log("提交的ysldatas数据"); - console.log(ysldatas); - console.log(JSON.stringify(ysldatas)); + try { + console.log("提交的ysldatas数据"); + console.log(ysldatas); + // console.log(JSON.stringify(ysldatas)); + console.log(moment(values.starttime).format("YYYY-MM-DD")); + console.log(moment(values.endtime).format("YYYY-MM-DD")); + }catch (e) { + + } + axios.post(url, ysldatas ).then((response) => { @@ -442,7 +458,8 @@ class Goldsubject extends Component { var subjectids=this.props.match.params.subjectid; window.location.href=`/paths/${subjectids}` }else{ - this.props.history.goBack(); + // this.props.history.goBack(); + window.location.href=`/courses/${this.props.match.params.coursesId}/informs` } } diff --git a/public/react/src/modules/courses/poll/PollDetailIndex.js b/public/react/src/modules/courses/poll/PollDetailIndex.js index a141db961..e92a0e352 100644 --- a/public/react/src/modules/courses/poll/PollDetailIndex.js +++ b/public/react/src/modules/courses/poll/PollDetailIndex.js @@ -136,7 +136,7 @@ class PollDetailIndex extends Component{ - this.props.history.goBack()}>返回 + 返回

              diff --git a/public/react/src/modules/courses/poll/PollDetailTabThird.js b/public/react/src/modules/courses/poll/PollDetailTabThird.js index b0bc95cea..47fcaf52f 100644 --- a/public/react/src/modules/courses/poll/PollDetailTabThird.js +++ b/public/react/src/modules/courses/poll/PollDetailTabThird.js @@ -108,8 +108,8 @@ class PollDetailTabThird extends Component{ { item.question.answers.map((index,k)=>{ return( -
            • - +
            • + {index.answer_text} { index.answer_text=="其他" ?

              :"" diff --git a/public/react/src/modules/courses/poll/PollInfo.js b/public/react/src/modules/courses/poll/PollInfo.js index dfbfd3a74..2476e0bc3 100644 --- a/public/react/src/modules/courses/poll/PollInfo.js +++ b/public/react/src/modules/courses/poll/PollInfo.js @@ -327,7 +327,7 @@ class PollInfo extends Component{ {poll && poll.polls_name} { - isAdmin || (poll && poll.user_poll_status == 1) ? this.props.history.goBack()}>返回 :'' + isAdmin || (poll && poll.user_poll_status == 1) ? 返回 :'' }

              @@ -445,7 +445,7 @@ class PollInfo extends Component{ item.question.answers && item.question.answers.map((i,k)=>{ return(
            • - + {i.answer_text} { i.answer_text=="其他"? diff --git a/public/react/src/modules/courses/poll/PollNew.js b/public/react/src/modules/courses/poll/PollNew.js index ce602afc3..48bc69c79 100644 --- a/public/react/src/modules/courses/poll/PollNew.js +++ b/public/react/src/modules/courses/poll/PollNew.js @@ -1,6 +1,9 @@ import React, {Component} from "react"; import {Form, Input, Tooltip, Checkbox, Radio, Select, message, Modal, Button} from 'antd' import {WordsBtn, ActionBtn} from 'educoder' + +import {Link} from 'react-router-dom' + import '../css/members.css' import "../common/formCommon.css" @@ -36,7 +39,7 @@ class PollNew extends Component { visible: false, poll_questions: [], user_permission: "", - addonAfter: 60, + addonAfter: 0, addonAftertwo: 100, mysingles: 0, mydoubles: 0, @@ -65,6 +68,8 @@ class PollNew extends Component { cancellation: false, bindingid:undefined, Newdisplay:false, + first_category_url:"", + left_banner_id:"", } // console.log("试卷新建和编辑"); // console.log(JSON.stringify(props)); @@ -72,10 +77,14 @@ class PollNew extends Component { } changeTopicName = (e) => { - let num = 60 - parseInt(e.target.value.length); + console.log("调用了changeTopicName"); + let num = parseInt(e.target.value.length); + if(num>60){ + return; + } this.setState({ addonAfter: num < 0 ? 0 : num - }) + }); this.setState({ polls_nametest: e.target.value }) @@ -124,8 +133,35 @@ class PollNew extends Component { console.log("问卷返回"); console.log(this.props); + try { + if(this.props.current_user!==undefined){ + this.setState({ + first_category_url :this.props.current_user.first_category_url, + }); + console.log("======================="); + console.log(this.props.current_user.first_category_url); + } + }catch (e) { + console.log("12312312312") + console.log(e); + } + }; + componentDidUpdate = (prevProps) => { + // console.log("componentDidUpdate"); + // console.log(prevProps); + // console.log(this.props); + if(prevProps.current_user!=this.props.current_user){ + if(this.props.current_user!==undefined){ + // console.log(this.props.current_user.login); + // console.log(prevProps.current_user.login); + this.setState({ + first_category_url :this.props.current_user.first_category_url, + }) + } + } } + //获取权限 // getPollInfo(){ // // console.log(this.props.match); @@ -144,12 +180,12 @@ class PollNew extends Component { // } //初始化请求网络 Initializatio_data = () => { - // console.log("Initializatio_data 582") + console.log("Initializatio_data 582") //课堂id let coursesId = this.props.match.params.coursesId; //时间id let pollid = this.props.match.params.pollid; - // console.log(pollid); + console.log(pollid); // let coursesId = 557; if (pollid === undefined) { // console.log("没有问卷新建问卷~~~") @@ -215,6 +251,19 @@ class PollNew extends Component { polls_nametest: result.data.poll.polls_name, polls_descriptiontest: result.data.poll.polls_description, }); + + if(result.data){ + if(result.data.poll){ + if(result.data.poll.polls_name){ + let num = parseInt(result.data.poll.polls_name.length); + this.setState({ + addonAfter: num < 0 ? 0 : num + }) + } + } + + } + this.setState({ projects: result.data, pollid: pollid, @@ -229,6 +278,7 @@ class PollNew extends Component { polls_nametest: result.data.poll.polls_name, polls_descriptiontest: result.data.poll.polls_description, questionnair: true, + left_banner_id:result.data.left_banner_id }) // console.log(this.state.polls_nametest) // console.log(this.state.polls_descriptiontest) @@ -2338,10 +2388,13 @@ class PollNew extends Component { } gotohome=()=>{ // const { current_user} = this.props + if(this.state.first_category_url){ + window.location.href=this.state.first_category_url; + }else{ + this.props.history.goBack(); + } - // this.props.history.push(current_user && current_user.first_category_url); // - this.props.history.goBack() // let courseId=this.props.match.params.coursesId; // if(courseId===undefined){ // this.props.history.push("/courses"); @@ -2360,6 +2413,7 @@ class PollNew extends Component { readOnlys, newoption, cancellation, + left_banner_id } = this.state //获取老师权限 // console.log("[`${maps[polls_status && polls_status]}`]]"); @@ -2404,16 +2458,29 @@ class PollNew extends Component {

              this.gotohome()}>{this.props.coursedata.name} > - 问卷 + { + this.props.match.params.news === "new"? + 问卷 + : + 问卷 + } + > {this.props.match.params.news === undefined ? "新建" : this.props.match.params.news === "new" ? "新建" : "编辑"}

              {this.props.match.params.news === undefined ? "新建问卷" : this.props.match.params.news === "new" ? "新建问卷" : "编辑问卷"}

              - this.gotohome()} - className=" fr font-16">返回 + { + this.props.match.params.news === "new" ? + 返回 + : + 返回 + }
              {/**/} @@ -2461,12 +2528,13 @@ class PollNew extends Component { }
            • - + {/*suffix={String(addonAfter)}*/} @@ -2491,7 +2559,7 @@ class PollNew extends Component { readOnly={readOnlys} onInput={this.changeTopicNametwo} value={this.state.polls_descriptiontest} - autoComplete="off" addonAfter={"100"}> + autoComplete="off" suffix={"100"}> { this.state.Newedit === true || this.state.mysave === true ?
              diff --git a/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js b/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js index a37e58a92..64e2e6a99 100644 --- a/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js +++ b/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js @@ -369,9 +369,9 @@ class Listofworksstudentone extends Component { { record.submitstate === "未提交" ?-- : - this.Viewstudenttraininginformation(record)}>{record.operating} + onClick={() => this.Viewstudenttraininginformation(record)}>{record.operating} } diff --git a/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js b/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js index f46d3f6b1..dddd39989 100644 --- a/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js +++ b/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js @@ -113,21 +113,19 @@ class ShixunHomeworkPage extends Component { bindRef = ref => { this.child = ref } ///////////////教师截止 gotohome=()=>{ - // let courseId=this.props.match.params.coursesId; - // if(courseId===undefined){ - // this.props.history.push("/courses"); - // }else{ - // this.props.history.push(this.props.current_user.first_category_url); - // } - this.props.history.goBack() + // console.log(this.props) + let {jobsettingsdatapage}=this.state + + this.props.history.replace(`/courses/${this.props.match.params.coursesId}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.main === 1 ? "shixun_homeworks" :"shixun_homework"}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.category_id === undefined ? "" : jobsettingsdatapage.data.category.category_id}`); } render() { let {tab, teacherdatapage, jobsettingsdatapage} = this.state; const isAdmin = this.props.isAdmin(); // console.log(119) - // console.log(jobsettingsdatapage); - // console.log(teacherdatapage); + console.log(jobsettingsdatapage); + + return (
              @@ -136,11 +134,11 @@ class ShixunHomeworkPage extends Component {

              - this.gotohome()}>{jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.course_name} + {jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.course_name} > {jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.category_name} + href={`/courses/${this.props.match.params.coursesId}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.main === 1 ? "shixun_homeworks" :"shixun_homework"}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.category_id === undefined ? "" : jobsettingsdatapage.data.category.category_id}`}>{jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.category_name} > 作业详情

              @@ -240,14 +238,14 @@ class ShixunHomeworkPage extends Component { this.workshowmodels(this.child)}>代码查重 : "" : ""} {this.state.view_report === true ? + to={`/courses/${this.props.match.params.coursesId}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.main === 1 ? "shixun_homeworks" :"shixun_homework"}/${teacherdatapage&&teacherdatapage.work_id}/shixun_work_report`}> 查看实训报告 : ""} { teacherdatapage === undefined ? "" : teacherdatapage.commit_des === null || teacherdatapage.commit_des === undefined ? "" : {teacherdatapage.commit_des} + href={`/courses/${this.props.match.params.coursesId}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.main === 1 ? "shixun_homeworks" :"shixun_homework"}/${teacherdatapage === undefined ? "" : teacherdatapage.id}/commitsummary/${this.props.match.params.homeworkid}`}>{teacherdatapage.commit_des} } {teacherdatapage === undefined ? "" : { - - this.props.history.goBack() + this.props.history.replace(`/courses/${this.props.match.params.coursesId}/shixun_homeworks/${this.state.data.homework_common_id}/list?tab=0`); } render() { let{data} =this.state; @@ -159,7 +158,7 @@ class ShixunWorkReport extends Component { {/*className="fr color-blue font-16"*/} {/*onClick={()=>this.confirmysl(`/student_works/${homeworkid}/export_shixun_work_report.pdf`)}*/} {/*>导出实训报告数据 : ""}*/} - {/*返回*/} + 返回
              diff --git a/public/react/src/modules/courses/shixunHomework/Shixunworkdetails/ShixunWorkModal.js b/public/react/src/modules/courses/shixunHomework/Shixunworkdetails/ShixunWorkModal.js index 977b459d4..293310a90 100644 --- a/public/react/src/modules/courses/shixunHomework/Shixunworkdetails/ShixunWorkModal.js +++ b/public/react/src/modules/courses/shixunHomework/Shixunworkdetails/ShixunWorkModal.js @@ -288,6 +288,7 @@ class ShixunWorkModal extends Component{ className="fl task-hide edu-txt-left" name="shixun_homework[]" value={item=== undefined?"":item.id} + key={item=== undefined?"":item.id} > diff --git a/public/react/src/modules/courses/shixunHomework/TraineetraininginformationModal.js b/public/react/src/modules/courses/shixunHomework/TraineetraininginformationModal.js index 9b86483df..7babe1e6b 100644 --- a/public/react/src/modules/courses/shixunHomework/TraineetraininginformationModal.js +++ b/public/react/src/modules/courses/shixunHomework/TraineetraininginformationModal.js @@ -236,11 +236,11 @@ class TraineetraininginformationModal extends Component {
            • {this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.username} 通关:{this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.complete_count}/{this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.challenges_count}
            • -
            • 完成效率:{this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.efficiency}
            • + {this.props.viewtrainingdata === undefined ?
            • :this.props.viewtrainingdata.efficiency === undefined ?
            • :this.props.viewtrainingdata.efficiency === null ?
            • :this.props.viewtrainingdata.efficiency === "null" ?
            • :this.props.viewtrainingdata.efficiency === "" ?
            • :
            • 完成效率:{this.props.viewtrainingdata.efficiency}
            • }
            • 通关时间: {this.props.viewtrainingdata === undefined ? "":moment(this.props.viewtrainingdata.passed_time).format('YYYY-MM-DD HH:mm')==="Invalid date"?"--":moment(this.props.viewtrainingdata.passed_time).format('YYYY-MM-DD HH:mm')}
            • -
            • 课堂最高完成效率: {this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.max_efficiency}
            • + {this.props.viewtrainingdata === undefined ?
            • :this.props.viewtrainingdata.max_efficiency === undefined ?
            • :this.props.viewtrainingdata.max_efficiency === null ?
            • :this.props.viewtrainingdata.max_efficiency === "null" ?
            • : this.props.viewtrainingdata.max_efficiency === "" ?
            • :
            • 课堂最高完成效率: {this.props.viewtrainingdata.max_efficiency}
            • }
            • 总耗时: {this.props.viewtrainingdata === undefined ? "" :this.props.viewtrainingdata.total_spend_time}
            • diff --git a/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js b/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js index 9cb2bd973..bd90db42f 100644 --- a/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js +++ b/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js @@ -322,9 +322,14 @@ class Trainingjobsetting extends Component { }; challenge_scoredata.push(datas[i].challenge_score); array.push(object) + console.log("datas[i].challenge_score"); + console.log(i); + console.log(datas[i].challenge_score); } } + console.log("提交的数据"+"pustdate"); + console.log("提交的数据"+"pustdate"); if (this.state.jobsettingsdata.data.unified_setting === true) { if (this.state.unifiedsetting === true) { @@ -560,14 +565,20 @@ class Trainingjobsetting extends Component { if(challenge_scoredata.length>0){ var len = 0; for (var k = 0; k < challenge_scoredata.length; k++) { - len = len + challenge_scoredata[k]; + len = len + parseFloat(challenge_scoredata[k]); + console.log(len); + console.log(challenge_scoredata[k]); + console.log(len); + } var max = latedeductiontwos + len; if (max > 100) { + console.log("max>100"); this.props.showNotification(`总分值+效率占比分之和要等于100,现在分值为` + max); return; } if(max<100){ + console.log("max<100"); this.props.showNotification(`总分值+效率占比分之和要等于100,现在分值为` + max); return; } diff --git a/public/react/src/modules/home/shixunsHome.js b/public/react/src/modules/home/shixunsHome.js index d366426c8..690baf28b 100644 --- a/public/react/src/modules/home/shixunsHome.js +++ b/public/react/src/modules/home/shixunsHome.js @@ -294,7 +294,7 @@ class ShixunsHome extends Component { {/*精选实训 改为 开发社区*/}
              -

              开发社区

              +

              实训项目

              DEVELOPMENT COMMUNITY

              更多 diff --git a/public/react/src/modules/modals/Certifiedprofessional.js b/public/react/src/modules/modals/Certifiedprofessional.js index 5e3df4abb..ca1691809 100644 --- a/public/react/src/modules/modals/Certifiedprofessional.js +++ b/public/react/src/modules/modals/Certifiedprofessional.js @@ -19,19 +19,9 @@ class Certifiedprofessional extends Component { // console.log("加入金品课堂"); // console.log(this.props); + } - componentDidMount() { - // axios.interceptors.response.use((response) => { - // if (response != undefined) - // if (response && response.data.status === -1) { - // - // } - // return response; - // }, (error) => { - // //TODO 这里如果样式变了会出现css不加载的情况 - // - // }); - } + modalCancel=()=>{ this.props.ModalCancelsy(); }; diff --git a/public/react/src/modules/modals/Jointheclass.js b/public/react/src/modules/modals/Jointheclass.js index 7d8ce788a..0ad13c158 100644 --- a/public/react/src/modules/modals/Jointheclass.js +++ b/public/react/src/modules/modals/Jointheclass.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { Modal} from 'antd'; import axios from 'axios'; +import Modals from './Modals'; //加入精品课堂 class Jointheclass extends Component { @@ -11,6 +12,12 @@ class Jointheclass extends Component { componentDidMount() { // console.log("加入精品课堂"); // console.log(this.props); + let type=this.props.yslJointhe===undefined?false:this.props.yslJointhe; + if(type===true){ + this.setState({ + Modalstype:true + }) + } } @@ -19,6 +26,7 @@ class Jointheclass extends Component { }; setDownload=()=>{ + let cousestype=this.props.pathcousestypeid; let id=this.props.Pathcourseid===undefined?this.props.match.params.coursesId:this.props.Pathcourseid let url = `/courses/${id}/join_excellent_course.json`; axios.post(url).then((result) => { @@ -27,6 +35,9 @@ class Jointheclass extends Component { if(result.data.status === 0){ this.props.showNotification(result.data.message); this.props.ysljoinmodalCanceltwo(); + if(cousestype===1){ + window.open(`/courses/${id}/informs`) + } }else { this.props.showNotification(result.data.message); } @@ -44,35 +55,15 @@ class Jointheclass extends Component { // console.log("加入精品课堂2"); //console.log(this.props.Pathcourseid); return( - -
              -

              是否确认该加入课堂?

              -
              - - this.modalCancel()}>取消 - this.setDownload()}>确认 -
              -
              -
              + this.modalCancel()} + modalSave={()=>this.setDownload()} + > ) } } -export default Jointheclass; \ No newline at end of file +export default Jointheclass; + diff --git a/public/react/src/modules/modals/WordNumberTextarea.css b/public/react/src/modules/modals/WordNumberTextarea.css new file mode 100644 index 000000000..9bd8c820c --- /dev/null +++ b/public/react/src/modules/modals/WordNumberTextarea.css @@ -0,0 +1,42 @@ +.WordNumberTextarea { + outline: none; /* 去掉输入字符时的默认样式 */ + appearance:none; + -webkit-appearance:none; + -moz-appearance:none; + background-color: white; + text-shadow: none; + -webkit-writing-mode: horizontal-tb !important; + -webkit-tap-highlight-color:rgba(0,0,0,0); + resize:none; /*禁止拉伸*/ + border: none; /*去掉默认边框*/ + width: 100%; + height:150px; + border:none; + padding: 10px; + display: block; +} + +.WordNumbernote { + padding: 0; + margin: 0; + list-style: none; + text-decoration: none; + box-sizing: border-box; + overflow: hidden; + height: auto; + border: 1px solid rgba(234,234,234,1); + border-radius: 0.125rem; + margin: 0.31rem; + padding: 0.19rem; + backgroud:rgba(234,234,234,1); + padding-bottom: 10px; + padding-right: 10px; + +} +.WordNumberTextarea-count { + display: inline-block; + float: right; + font-size: 0.28rem; + color: #adadad; + padding-right: 0.25rem; +} diff --git a/public/react/src/modules/modals/WordNumberTextarea.js b/public/react/src/modules/modals/WordNumberTextarea.js new file mode 100644 index 000000000..52711de2c --- /dev/null +++ b/public/react/src/modules/modals/WordNumberTextarea.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import './WordNumberTextarea.css'; +class WordNumberTextarea extends Component { + constructor(props) { + super(props); + } +render() { + return( +
              + - } +
              + }
              {tags === undefined ? "" :tags === null ? "": diff --git a/public/react/src/modules/paths/PathDetail/sendPanel.js b/public/react/src/modules/paths/PathDetail/sendPanel.js index ae7dd5b00..450afc75e 100644 --- a/public/react/src/modules/paths/PathDetail/sendPanel.js +++ b/public/react/src/modules/paths/PathDetail/sendPanel.js @@ -180,7 +180,7 @@ class sendPanel extends Component{ { this.props.detailInfoList===undefined?"":this.props.detailInfoList.allow_send===true? - + 发送至 :'' diff --git a/public/react/src/modules/paths/PathNew.js b/public/react/src/modules/paths/PathNew.js index 4616660f0..95e343b13 100644 --- a/public/react/src/modules/paths/PathNew.js +++ b/public/react/src/modules/paths/PathNew.js @@ -107,8 +107,8 @@ class PathNew extends Component{ window.location.href="#part_Des"; return; } - if (des.length > 5000) { - this.props.showSnackbar("实践课程的简介最大限制5000个字符"); + if (des.length > 8000) { + this.props.showSnackbar("实践课程的简介最大限制8000个字符"); window.location.href="#part_Des"; return; } @@ -118,8 +118,8 @@ class PathNew extends Component{ window.location.href="#part_point"; return; } - if(point.length > 500){ - this.props.showSnackbar("实践课程的学习须知最大限制500个字符"); + if(point.length > 2000){ + this.props.showSnackbar("实践课程的学习须知最大限制2000个字符"); window.location.href="#part_point"; return; } @@ -186,10 +186,10 @@ class PathNew extends Component{ }) const Des_editMD = create_editorMD("shixun_introduction","100%","490px" - ,"请在此输入实践课程的简介,最大限制5000个字符","/api/attachments.json", response.data.description,""); + ,"请在此输入实践课程的简介,最大限制8000个字符","/api/attachments.json", response.data.description,""); this.Des_editMD=Des_editMD; const Point_editMD = create_editorMD("shixun_propaedeutics","100%","260px" - ,"请在此输入实践课程的学习须知,最大限制500个字符","/api/attachments.json",response.data.learning_notes,""); + ,"请在此输入实践课程的学习须知,最大限制2000个字符","/api/attachments.json",response.data.learning_notes,""); this.Point_editMD=Point_editMD; } }).catch((error)=>{ @@ -198,9 +198,9 @@ class PathNew extends Component{ } else { this.isEditPage = false - const Des_editMD = create_editorMD("shixun_introduction","100%","490px","请在此输入实践课程的简介,最大限制5000个字符","/api/attachments.json","",""); + const Des_editMD = create_editorMD("shixun_introduction","100%","490px","请在此输入实践课程的简介,最大限制8000个字符","/api/attachments.json","",""); this.Des_editMD=Des_editMD; - const Point_editMD = create_editorMD("shixun_propaedeutics","100%","260px","请在此输入实践课程的学习须知,最大限制500个字符","/api/attachments.json","",""); + const Point_editMD = create_editorMD("shixun_propaedeutics","100%","260px","请在此输入实践课程的学习须知,最大限制2000个字符","/api/attachments.json","",""); this.Point_editMD=Point_editMD; } diff --git a/public/react/src/modules/paths/ShixunPathSearch.js b/public/react/src/modules/paths/ShixunPathSearch.js index b5ba1563d..c0060a528 100644 --- a/public/react/src/modules/paths/ShixunPathSearch.js +++ b/public/react/src/modules/paths/ShixunPathSearch.js @@ -139,8 +139,8 @@ class ShixunPathSearch extends Component{
              {/* this.changeStatus("publish_time")}>全部*/} {/* this.changeStatus("mine")}>我的*/} - this.changeStatus("updated_at")}>最新 - this.changeStatus("myshixun_count")}>最热 + this.changeStatus("updated_at")}>最新 + this.changeStatus("myshixun_count")}>最热 {/*
              */} {/*/!* {
            • - 开发社区 + 实训项目
            • @@ -814,7 +814,7 @@ submittojoinclass=(value)=>{ onBlur={(e)=>this.hideshowSearchOpen(e)} onMouseLeave={()=>this.setevaluatinghides()}> this.onKeywordSearchKeyDowns()} onSearch={(value) => this.onKeywordSearchKeyDown(value)} // onPressEnter={this.onKeywordSearchKeyDown} @@ -860,15 +860,15 @@ submittojoinclass=(value)=>{