├── .meteor ├── .gitignore ├── release ├── platforms ├── dev_bundle ├── .id ├── .finished-upgraders ├── packages └── versions ├── server ├── startup.coffee ├── lib │ ├── future.coffee │ └── syncDownload.coffee ├── publications │ ├── tables.coffee │ ├── problems.coffee │ ├── downloads.coffee │ ├── cfResults.coffee │ ├── meteorUsers.coffee │ ├── submits.coffee │ ├── users.coffee │ └── results.coffee ├── downloads │ ├── updateCf.coffee │ ├── downloadContests.coffee │ └── downloadSubmits.coffee └── calculations │ ├── userCalcs │ ├── levelCalc.coffee │ ├── cfCalc.coffee │ ├── chocoCalc.coffee │ └── submitsByWeekCalc.coffee │ └── resultCalc.coffee ├── client ├── components │ ├── footer │ │ ├── footer.coffee │ │ ├── footer.jade │ │ └── footer.less │ ├── overall │ │ ├── line │ │ │ ├── overallLine.less │ │ │ ├── overallLine.jade │ │ │ └── overallLine.coffee │ │ ├── table │ │ │ ├── overallTable.less │ │ │ ├── overallTable.coffee │ │ │ └── overallTable.jade │ │ └── header │ │ │ ├── overallHeader.coffee │ │ │ ├── overallHeader.jade │ │ │ └── overallHeader.less │ ├── table │ │ ├── header │ │ │ ├── tableHeader.less │ │ │ ├── tableHeader.coffee │ │ │ └── tableHeader.jade │ │ ├── proper │ │ │ ├── tableProper.less │ │ │ ├── tableProper.coffee │ │ │ └── tableProper.jade │ │ ├── table │ │ │ ├── table.less │ │ │ ├── table.coffee │ │ │ └── table.jade │ │ └── row │ │ │ ├── tableRow.jade │ │ │ ├── tableRow.less │ │ │ └── tableRow.coffee │ ├── solvedByWeek │ │ ├── line │ │ │ ├── solvedByWeekLine.less │ │ │ ├── solvedByWeekLine.jade │ │ │ └── solvedByWeekLine.coffee │ │ ├── table │ │ │ ├── solvedByWeekTable.less │ │ │ ├── solvedByWeekTable.coffee │ │ │ └── solvedByWeekTable.jade │ │ └── header │ │ │ ├── solvedByWeekHeader.less │ │ │ ├── solvedByWeekHeader.jade │ │ │ └── solvedByWeekHeader.coffee │ ├── user │ │ ├── user.less │ │ ├── user.coffee │ │ └── user.jade │ ├── loading │ │ └── loading.jade │ ├── userLine │ │ ├── userLine.less │ │ ├── userLine.coffee │ │ └── userLine.jade │ ├── userBadge │ │ ├── userBadge.less │ │ ├── userBadge.coffee │ │ └── userBadge.jade │ ├── userName │ │ ├── userName.less │ │ ├── userName.jade │ │ └── userName.coffee │ ├── userHeader │ │ ├── userHeader.coffee │ │ ├── userHeader.less │ │ └── userHeader.jade │ ├── chocos │ │ ├── chocos.coffee │ │ ├── chocos.less │ │ └── chocos.jade │ ├── cfStatus │ │ ├── cfStatus.less │ │ ├── cfStatus.jade │ │ └── cfStatus.coffee │ ├── okSubmits │ │ ├── okSubmits.less │ │ ├── okSubmits.jade │ │ └── okSubmits.coffee │ ├── application │ │ ├── application.coffee │ │ └── application.jade │ ├── head │ │ └── head.jade │ └── general │ │ └── general.less ├── config │ └── router.coffee └── lib │ └── bootstrap │ ├── custom.bootstrap.json │ ├── custom.bootstrap.import.less │ └── custom.bootstrap.mixins.import.less ├── bundle.sh ├── routes ├── login.coffee ├── userBadge.coffee ├── overallTable.coffee ├── solvedByWeekTable.coffee ├── user.coffee ├── okSubmits.coffee └── table.coffee ├── todo.txt ├── .gitignore ├── public ├── choco.png └── choco-strut.png ├── lib ├── userCalcs │ └── submitsByWeekCalc.coffee ├── isAdmin.coffee ├── levelCalcs.coffee ├── ControllerWithTitle │ └── ControllerWithTitle.coffee └── tinycolor.js ├── methods ├── setCfLogin.coffee └── setBaseLevel.coffee ├── collections ├── downloads.coffee ├── cfResults.coffee ├── submits.coffee ├── problems.coffee ├── users.coffee ├── results.coffee └── tables.coffee └── convert-client-templates-to-server.py /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /server/startup.coffee: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/components/footer/footer.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | meteor build ../openshift --directory -------------------------------------------------------------------------------- /client/components/overall/line/overallLine.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/table/header/tableHeader.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/table/proper/tableProper.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/overall/table/overallTable.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/line/solvedByWeekLine.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/table/solvedByWeekTable.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/lib/future.coffee: -------------------------------------------------------------------------------- 1 | @Future = Npm.require('fibers/future'); 2 | -------------------------------------------------------------------------------- /routes/login.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/login/c', name: 'loginButtons' 2 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | * Кешировать названия контестов в задаче 2 | * DQ breaks total -------------------------------------------------------------------------------- /client/components/user/user.less: -------------------------------------------------------------------------------- 1 | table.chocos { 2 | width: auto; 3 | } 4 | -------------------------------------------------------------------------------- /client/components/loading/loading.jade: -------------------------------------------------------------------------------- 1 | template(name="loading") 2 | +spinner -------------------------------------------------------------------------------- /client/components/userLine/userLine.less: -------------------------------------------------------------------------------- 1 | .cfColor { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /client/components/userBadge/userBadge.less: -------------------------------------------------------------------------------- 1 | table.chocos { 2 | width: auto; 3 | } 4 | -------------------------------------------------------------------------------- /client/components/userName/userName.less: -------------------------------------------------------------------------------- 1 | .userName { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /server/publications/tables.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'tables', -> 2 | Tables.findAll() -------------------------------------------------------------------------------- /server/publications/problems.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'problems', -> 2 | Problems.findAll() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .meteor/meteorite 3 | t 4 | *.kate-swp 5 | mongo.sh 6 | from_client -------------------------------------------------------------------------------- /public/choco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petr-kalinin/informatics-data-v2/master/public/choco.png -------------------------------------------------------------------------------- /server/publications/downloads.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'downloads', -> 2 | Downloads.findAll() 3 | -------------------------------------------------------------------------------- /server/publications/cfResults.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish "lastCfResults", -> 2 | cfResults.findLastResults(20) -------------------------------------------------------------------------------- /client/config/router.coffee: -------------------------------------------------------------------------------- 1 | Router.configure 2 | layoutTemplate: "application" 3 | loadingTemplate: "loading" -------------------------------------------------------------------------------- /public/choco-strut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petr-kalinin/informatics-data-v2/master/public/choco-strut.png -------------------------------------------------------------------------------- /client/components/userHeader/userHeader.coffee: -------------------------------------------------------------------------------- 1 | Template.userHeader.helpers 2 | admin: -> 3 | isAdmin() 4 | 5 | -------------------------------------------------------------------------------- /lib/userCalcs/submitsByWeekCalc.coffee: -------------------------------------------------------------------------------- 1 | @WEEK_ACTIVITY_EXP = 0.55 2 | @LEVEL_RATING_EXP = 2.5 3 | @ACTIVITY_THRESHOLD = 0.1 -------------------------------------------------------------------------------- /client/components/table/header/tableHeader.coffee: -------------------------------------------------------------------------------- 1 | Template.tableHeader1.helpers 2 | colspan: -> 3 | @problems.length -------------------------------------------------------------------------------- /client/components/chocos/chocos.coffee: -------------------------------------------------------------------------------- 1 | Template.chocos.helpers 2 | chocosSet: -> 3 | [0..@number-1] 4 | 5 | -------------------------------------------------------------------------------- /client/components/cfStatus/cfStatus.less: -------------------------------------------------------------------------------- 1 | .cfColor { 2 | font-weight: bold; 3 | } 4 | .cfProgress { 5 | font-weight: bold; 6 | } -------------------------------------------------------------------------------- /client/components/okSubmits/okSubmits.less: -------------------------------------------------------------------------------- 1 | table.okSubmits td { 2 | padding-left : 5px; 3 | padding-right : 5px; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /client/components/application/application.coffee: -------------------------------------------------------------------------------- 1 | Template.application.helpers 2 | user: -> 3 | Meteor.user() 4 | 5 | -------------------------------------------------------------------------------- /client/components/table/table/table.less: -------------------------------------------------------------------------------- 1 | .example { 2 | padding-left:5px; 3 | padding-right:5px; 4 | margin-bottom:5px; 5 | } 6 | -------------------------------------------------------------------------------- /server/publications/meteorUsers.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish "meteorUser", -> 2 | Meteor.users.find({_id: this.userId}, {fields: {'admin': 1}}) -------------------------------------------------------------------------------- /.meteor/dev_bundle: -------------------------------------------------------------------------------- 1 | /home/petr/.meteor/packages/meteor-tool/.1.3.4_4.1gio1g6++os.linux.x86_64+web.browser+web.cordova/mt-os.linux.x86_64/dev_bundle -------------------------------------------------------------------------------- /client/components/solvedByWeek/table/solvedByWeekTable.coffee: -------------------------------------------------------------------------------- 1 | Template.solvedByWeekTable.helpers 2 | users: -> 3 | Users.findByList("" + this) 4 | -------------------------------------------------------------------------------- /client/components/head/head.jade: -------------------------------------------------------------------------------- 1 | head 2 | meta(charset='utf-8') 3 | meta(name="viewport",content="width=device-width, initial-scale=1") 4 | title Сводные таблицы -------------------------------------------------------------------------------- /client/components/table/proper/tableProper.coffee: -------------------------------------------------------------------------------- 1 | Template.tableProper.helpers 2 | url: -> 3 | "http://informatics.mccme.ru/moodle/user/view.php?id=" + @_id 4 | -------------------------------------------------------------------------------- /lib/isAdmin.coffee: -------------------------------------------------------------------------------- 1 | @isAdmin = -> 2 | Meteor.isClient and Meteor.user() and Meteor.user().admin 3 | 4 | @isAdminMethod = -> 5 | Meteor.user() and Meteor.user().admin -------------------------------------------------------------------------------- /client/components/userName/userName.jade: -------------------------------------------------------------------------------- 1 | template(name='userName') 2 | a(href="/user/{{_id}}") 3 | span.userName(style="color: {{color}};") 4 | =name 5 | -------------------------------------------------------------------------------- /client/components/userBadge/userBadge.coffee: -------------------------------------------------------------------------------- 1 | Template.userBadge.helpers 2 | activity: -> 3 | @activity.toFixed(2) 4 | 5 | profileLink: -> 6 | "/user/" + @_id 7 | 8 | -------------------------------------------------------------------------------- /client/components/overall/table/overallTable.coffee: -------------------------------------------------------------------------------- 1 | Template.overallTable.helpers 2 | users: -> 3 | Users.findByList("" + this) 4 | 5 | mainTable: -> 6 | Tables.findById(Tables.main) 7 | -------------------------------------------------------------------------------- /client/components/chocos/chocos.less: -------------------------------------------------------------------------------- 1 | td.chocos { 2 | padding-right:10px; 3 | padding-bottom:5px; 4 | } 5 | .table > tbody > tr.chocos > td { 6 | padding-top: 0px; 7 | padding-bottom: 0px; 8 | border: 0px; 9 | } -------------------------------------------------------------------------------- /methods/setCfLogin.coffee: -------------------------------------------------------------------------------- 1 | Meteor.methods 2 | setCfLogin: (userId, cfLogin) -> 3 | if (!isAdminMethod()) 4 | throw new Meteor.error('Not authorized') 5 | user = Users.findById(userId) 6 | user.setCfLogin(cfLogin) -------------------------------------------------------------------------------- /methods/setBaseLevel.coffee: -------------------------------------------------------------------------------- 1 | Meteor.methods 2 | setBaseLevel: (userId, newLevel) -> 3 | if (!isAdminMethod()) 4 | throw new Meteor.error('Not authorized') 5 | user = Users.findById(userId) 6 | user.setBaseLevel(newLevel) -------------------------------------------------------------------------------- /client/components/overall/header/overallHeader.coffee: -------------------------------------------------------------------------------- 1 | Template.overallHeader.helpers 2 | needRecurse: -> 3 | return @height() >= 3 4 | 5 | needBorder: -> 6 | return @height() == 3 7 | 8 | subTable: -> 9 | return Tables.findById("" + this) 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/components/table/table/table.coffee: -------------------------------------------------------------------------------- 1 | Template.table.helpers 2 | levelsText: -> 3 | levels = (table.name for table in @tables) 4 | levels.join(", ") 5 | 6 | 7 | 8 | #Template.table.events 9 | # 'click .topLeft': (e,t) -> 10 | # Session.set("activeUser", undefined) 11 | -------------------------------------------------------------------------------- /client/components/overall/header/overallHeader.jade: -------------------------------------------------------------------------------- 1 | template(name='overallHeader') 2 | if needRecurse 3 | +each tables 4 | +overallHeader subTable 5 | if needBorder 6 | td.border 7 | else 8 | td 9 | =name 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/components/userHeader/userHeader.less: -------------------------------------------------------------------------------- 1 | table.solvedByWeek, table.solvedByWeek td { 2 | border:1px solid black; 3 | text-align:center; 4 | } 5 | table.solvedByWeek td { 6 | text-align:center; 7 | padding-left:3px; 8 | padding-right:3px; 9 | } 10 | table.solvedByWeek { 11 | border-collapse:true; 12 | } -------------------------------------------------------------------------------- /client/components/overall/header/overallHeader.less: -------------------------------------------------------------------------------- 1 | table.solvedByWeek, table.solvedByWeek td { 2 | border:1px solid black; 3 | text-align:center; 4 | } 5 | table.solvedByWeek td { 6 | text-align:center; 7 | padding-left:3px; 8 | padding-right:3px; 9 | } 10 | table.solvedByWeek { 11 | border-collapse:true; 12 | } -------------------------------------------------------------------------------- /client/components/application/application.jade: -------------------------------------------------------------------------------- 1 | template(name='application') 2 | script(type="text/javascript",src="https://cdn.rawgit.com/davidjbradshaw/iframe-resizer/master/js/iframeResizer.contentWindow.min.js") 3 | .container-fluid 4 | if user 5 | +loginButtons 6 | +yield 7 | //+footer 8 | //+githubRibbon 9 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/header/solvedByWeekHeader.less: -------------------------------------------------------------------------------- 1 | table.solvedByWeek, table.solvedByWeek td { 2 | border:1px solid black; 3 | text-align:center; 4 | } 5 | table.solvedByWeek td { 6 | text-align:center; 7 | padding-left:3px; 8 | padding-right:3px; 9 | } 10 | table.solvedByWeek { 11 | border-collapse:true; 12 | } -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1s28yt71blrakayipfxn 8 | -------------------------------------------------------------------------------- /server/lib/syncDownload.coffee: -------------------------------------------------------------------------------- 1 | Future = Npm.require('fibers/future'); 2 | 3 | @syncDownload = (url) -> 4 | console.log 'Retrieving ', url 5 | fut = new Future(); 6 | request = HTTP.get url, {jar: true, timeout: 10000}, (error, response) -> 7 | console.log 'Done' 8 | fut.return response 9 | return fut.wait(); 10 | 11 | -------------------------------------------------------------------------------- /server/publications/submits.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'submits', -> 2 | Submits.findAll() 3 | 4 | Meteor.publish 'okSubmits', -> 5 | Submits.findByOutcome("OK") 6 | 7 | Meteor.publish 'lastAcSubmits', -> 8 | Submits.findLastByOutcome("AC", 20) 9 | 10 | Meteor.publish 'lastIgSubmits', -> 11 | Submits.findLastByOutcome("IG", 20) 12 | 13 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/header/solvedByWeekHeader.jade: -------------------------------------------------------------------------------- 1 | template(name='solvedByWeekHeader') 2 | tr 3 | if name 4 | +userHeader 5 | +each weekSet 6 | +oneWeekHeader weekNumber=this userList=../userList 7 | 8 | template(name='oneWeekHeader') 9 | td 10 | =weekHeader 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/components/chocos/chocos.jade: -------------------------------------------------------------------------------- 1 | template(name='chocos') 2 | tr.chocos 3 | td.chocos 4 | if number 5 | +each chocosSet 6 | img(src="/choco.png") 7 | else 8 | img(src="/choco-strut.png") 9 | td 10 | span.badge 11 | =number 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/components/table/proper/tableProper.jade: -------------------------------------------------------------------------------- 1 | template(name='tableProper') 2 | table.mainTable 3 | tr 4 | +userHeader 5 | +tableHeader1 6 | //tr 7 | //td 8 | //|   9 | //+tableHeader2 10 | +each users 11 | tr 12 | +userLine 13 | +tableRow user=this tables=../tables 14 | 15 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/line/solvedByWeekLine.jade: -------------------------------------------------------------------------------- 1 | template(name='solvedByWeekLine') 2 | tr 3 | if name 4 | +userLine user 5 | +each weekSet 6 | +oneWeekSolved weekNumber=this userList=../userList user=../.. 7 | 8 | template(name='oneWeekSolved') 9 | td(style="background-color: {{bgColor}};") 10 | =weekSolved 11 | 12 | 13 | -------------------------------------------------------------------------------- /server/publications/users.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'users', -> 2 | Users.findAll() 3 | 4 | Meteor.publish 'basicUsers', -> 5 | Users.collection.find({}, {fields: { 6 | _id: 1, 7 | name: 1, 8 | userList: 1, 9 | level: 1, 10 | startlevel: 1, 11 | active: 1, 12 | ratingSort: 1, 13 | rating: 1, 14 | activity: 1 15 | }}) -------------------------------------------------------------------------------- /client/components/overall/line/overallLine.jade: -------------------------------------------------------------------------------- 1 | template(name='overallLine') 2 | if needRecurse 3 | +each table.tables 4 | +overallLine table=subTable user=../user 5 | if needBorder 6 | td.border 7 | else 8 | +overallResult 9 | 10 | 11 | template(name='overallResult') 12 | td(style="background-color: {{bgColor}};") 13 | =result 14 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /server/publications/results.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publish 'results', -> 2 | Results.findAll() 3 | 4 | Meteor.publish 'resultsForUserList', (userList)-> 5 | Results.findByUserList(userList) 6 | 7 | Meteor.publish 'resultsForUserListAndTable', (userList, table)-> 8 | Results.findByUserListAndTable(userList, table) 9 | 10 | Meteor.publish 'resultsForUser', (user)-> 11 | Results.findByUser(user) 12 | 13 | Meteor.publish 'lastWaResults', -> 14 | Results.findLastWA(20) 15 | -------------------------------------------------------------------------------- /client/components/userLine/userLine.coffee: -------------------------------------------------------------------------------- 1 | Template.userLine.helpers 2 | activity: -> 3 | a = Math.floor(@activity / ACTIVITY_THRESHOLD) * ACTIVITY_THRESHOLD 4 | s = a.toFixed(5) 5 | s.replace(/\.?0+$/gm,"") 6 | 7 | admin: -> 8 | isAdmin() 9 | 10 | Template.userLine.events 11 | "submit .baseLevel": (event) -> 12 | Meteor.call("setBaseLevel", @_id, event.target.newLevel.value) 13 | event.preventDefault() 14 | false 15 | -------------------------------------------------------------------------------- /client/components/cfStatus/cfStatus.jade: -------------------------------------------------------------------------------- 1 | template(name='cfStatus') 2 | a(href="http://codeforces.com/profile/{{cfLogin}}") 3 | span.cfColor(style="color:{{cfColor}};") 4 | =cfRating 5 | | ( 6 | span.cfProgress(title="Взвешенный прирост рейтинга за последнее время",style="color:{{cfProgressColor}}") 7 | =cfProgressFormatted 8 | | / 9 | span(title="Взвешенное количество написанных контестов за последнее время") 10 | =cfActivityFormatted 11 | | ) 12 | -------------------------------------------------------------------------------- /lib/levelCalcs.coffee: -------------------------------------------------------------------------------- 1 | @parseLevel = (level) -> 2 | if level.substr(0,3) == "reg" 3 | if level.length == 3 4 | res = { major: "reg" } 5 | else 6 | res = { major: "reg", minor: level.substr(3) } 7 | return res 8 | last = level.slice(-1) 9 | if (last >= '0') and (last <= '9') 10 | res = { major : level } 11 | else 12 | res = { major : level.slice(0, -1), minor : last } 13 | console.log "level ", level, " parsed ", res 14 | res -------------------------------------------------------------------------------- /client/components/cfStatus/cfStatus.coffee: -------------------------------------------------------------------------------- 1 | Template.cfStatus.helpers 2 | cfProgressFormatted: -> 3 | if @cfProgress > 0 4 | "+" + @cfProgress.toFixed(1) 5 | else 6 | @cfProgress.toFixed(1) 7 | 8 | cfProgressColor: -> 9 | if @cfProgress > 10 10 | "#00aa00" 11 | else if @cfProgress < -10 12 | "#aa0000" 13 | else 14 | "inherit" 15 | 16 | cfActivityFormatted: -> 17 | @cfActivity.toFixed(1) -------------------------------------------------------------------------------- /server/downloads/updateCf.coffee: -------------------------------------------------------------------------------- 1 | updateAllCf = -> 2 | for u in Users.findAll().fetch() 3 | u.updateCfRating() 4 | 5 | SyncedCron.add 6 | name: 'updateCf', 7 | schedule: (parser) -> 8 | return parser.text('every 1 hour'); 9 | # return parser.text('every 5 minutes'); 10 | job: -> 11 | console.log("updating Cf ratings") 12 | updateAllCf() 13 | 14 | #Meteor.startup -> 15 | # for u in Users.findAll().fetch() 16 | # u.updateCfRating() 17 | 18 | -------------------------------------------------------------------------------- /client/components/general/general.less: -------------------------------------------------------------------------------- 1 | .mainTable { 2 | border-left:1px solid black; 3 | border-top:1px solid black; 4 | margin-bottom: 10px; 5 | } 6 | .mainTable td { 7 | border-bottom:1px solid black; 8 | border-right:1px solid black; 9 | padding: 3px; 10 | text-align: center; 11 | min-width: 3ex; 12 | } 13 | .mainTable td.border { 14 | background:black; 15 | padding:0px; 16 | border-right:3px solid black; 17 | min-width: 0px; 18 | } 19 | .head { 20 | text-align:center; 21 | } 22 | -------------------------------------------------------------------------------- /client/components/footer/footer.jade: -------------------------------------------------------------------------------- 1 | template(name='footer') 2 | footer.footer 3 | .container-fluid 4 | p.text-muted 5 | Copyright © Петр Калинин, 6 | a(href="https://www.gnu.org/licenses/agpl-3.0.html") GNU Affero General Public License 3.0 7 | | | 8 | a(href="https://github.com/petr-kalinin/informatics-data") https://github.com/petr-kalinin/informatics-data 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/table/solvedByWeekTable.jade: -------------------------------------------------------------------------------- 1 | template(name='solvedByWeekTable') 2 | h1 3 | | Сданные задачи по неделям 4 | p 5 | | Отображается количество сданных задач по неделям, 0.5 — были попытки сдавать, но ни одной удачной. 6 | p 7 | | Имена школьников — ссылки на странички с результатами кадого конкретного школьника. 8 | table.mainTable 9 | +solvedByWeekHeader userList=this name=true 10 | +each users 11 | +solvedByWeekLine userList=.. name=true user=this 12 | -------------------------------------------------------------------------------- /routes/userBadge.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/userBadge/:id/c', name: 'userBadge' 2 | class @UserBadgeController extends ControllerWithTitle 3 | server: false 4 | template: 'userBadge' 5 | fastRender: false 6 | 7 | waitOn: -> 8 | id = this.params.id 9 | Meteor.subscribe 'users' 10 | 11 | data: -> 12 | id = this.params.id 13 | Users.findById id 14 | 15 | name: -> 16 | 'userBadge' 17 | 18 | title: -> 19 | id = this.params.id 20 | Users.findById(id).name 21 | 22 | -------------------------------------------------------------------------------- /client/components/userName/userName.coffee: -------------------------------------------------------------------------------- 1 | MAX_ACTIVITY = 7 2 | MAX_RATING = 10 * (Math.pow(LEVEL_RATING_EXP, 11) - 1) / (LEVEL_RATING_EXP - 1) 3 | 4 | Template.userName.helpers 5 | color: -> 6 | activity = Math.min(@activity + 1, MAX_ACTIVITY + 1) 7 | rating = Math.min(@rating + 1, MAX_RATING + 1) 8 | h = 11/12 * (1 - Math.log(rating) / Math.log(MAX_RATING + 1)) 9 | v = 0.3 + 0.7 * Math.log(activity) / Math.log(MAX_ACTIVITY + 1) 10 | if @activity < ACTIVITY_THRESHOLD 11 | v = 0 12 | return "#" + tinycolor.fromRatio({h: h, s: 1, v: v}).toHex() 13 | -------------------------------------------------------------------------------- /client/components/userHeader/userHeader.jade: -------------------------------------------------------------------------------- 1 | template(name='userHeader') 2 | td 3 | |   4 | if admin 5 | td 6 | span(title="Базовый уровень") 7 | | БУ 8 | td 9 | span(title="Уровень на начало полугодия") 10 | | УП 11 | td 12 | span(title="Уровень") 13 | | У 14 | td 15 | span(title="Рейтинг") 16 | | Р 17 | td 18 | span(title="Активность") 19 | | A 20 | td 21 | span(title="Codeforces рейтинг") 22 | | CF 23 | 24 | 25 | -------------------------------------------------------------------------------- /routes/overallTable.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/overallTable/:userList', {name: 'overallTable', where: 'server'} 2 | class @OverallTableController extends ControllerWithTitle 3 | server: true 4 | 5 | waitOn: -> 6 | userList = this.params.userList 7 | Meteor.subscribe 'users' 8 | Meteor.subscribe 'meteorUser' 9 | Meteor.subscribe 'tables' 10 | Meteor.subscribe 'problems' 11 | Meteor.subscribe 'results' 12 | 13 | data: -> 14 | userList = this.params.userList 15 | return userList 16 | 17 | name: -> 18 | 'overallTable' 19 | 20 | title: -> 21 | 'Общая таблица' 22 | -------------------------------------------------------------------------------- /client/components/footer/footer.less: -------------------------------------------------------------------------------- 1 | @import "{}/client/lib/bootstrap/custom.bootstrap.import.less"; 2 | html { 3 | position: relative; 4 | min-height: 100%; 5 | } 6 | body { 7 | /* Margin bottom by footer height */ 8 | /*margin-bottom: 55px !important;*/ 9 | } 10 | .footer { 11 | position: absolute; 12 | bottom: 0; 13 | height: 55px; 14 | width:100%; 15 | .container-fluid { 16 | background-color: #f5f5f5; 17 | padding-top:20px; 18 | @media (min-width: @grid-float-breakpoint) { 19 | border-radius: @navbar-border-radius; 20 | } 21 | } 22 | a { 23 | color:lighten(@brand-primary,20%); 24 | } 25 | } 26 | 27 | .footer.container { 28 | width:100%; 29 | } -------------------------------------------------------------------------------- /client/components/table/table/table.jade: -------------------------------------------------------------------------------- 1 | template(name='table') 2 | h1 3 | | Сводная таблица по уровням 4 | =levelsText 5 | p 6 | | Цвета: 7 | span.ac.example Зачтено/Принято 8 | span.ig.example Проигнорировано 9 | span.ok.example OK 10 | span.wa.example Частичное решение и т.п. 11 | p 12 | | Наведите курсор на ячейку таблицы, чтобы узнать название задачи 13 | p 14 | | Двойной щелчок по ячейке таблицы открывает соответствующую задачу и, если по ней были посылки, то последнюю посылку по ней 15 | p 16 | | Имена школьников — ссылки на странички с результатами каждого конкретного школьника. 17 | +tableProper 18 | -------------------------------------------------------------------------------- /client/components/table/header/tableHeader.jade: -------------------------------------------------------------------------------- 1 | template(name='tableHeader1') 2 | +each tables 3 | +each tables 4 | td.border 5 | td(colspan="{{colspan}}") 6 | =name 7 | td.border 8 | td 9 | | = 10 | td.border 11 | td 12 | | = 13 | td 14 | | Попыток 15 | 16 | template(name='tableHeader2') 17 | +each tables 18 | td.border 19 | +each tables 20 | +each problems 21 | td(title="{{name}}") 22 |   23 | td.border 24 | td 25 | | = 26 | td.border 27 | td 28 | | = 29 | td 30 | | Попыток 31 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/header/solvedByWeekHeader.coffee: -------------------------------------------------------------------------------- 1 | Template.solvedByWeekHeader.helpers 2 | weekSet: -> 3 | thisStart = new Date(startDayForWeeks["" + @userList]) 4 | now = new Date() 5 | nowWeek = Math.floor((now - thisStart) / MSEC_IN_WEEK) 6 | [0..nowWeek] 7 | 8 | admin: -> 9 | isAdmin() 10 | 11 | 12 | Template.oneWeekHeader.helpers 13 | weekHeader: -> 14 | thisStart = new Date(startDayForWeeks[@userList]) 15 | startDay = new Date(+thisStart + MSEC_IN_WEEK * @weekNumber) 16 | endDay = new Date(+startDay + MSEC_IN_WEEK - 1) 17 | startDay.getUTCDate() + "-" + endDay.getUTCDate() + "." + (endDay.getUTCMonth()+1) 18 | 19 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | less 8 | standard-minifiers 9 | meteor-base 10 | mobile-experience 11 | mongo 12 | blaze-html-templates 13 | session 14 | jquery 15 | tracker 16 | logging 17 | reload 18 | random 19 | ejson 20 | spacebars 21 | check 22 | coffeescript 23 | mquandalle:jade 24 | mquandalle:jade-compiler 25 | iron:router 26 | nemo64:bootstrap 27 | momentjs:moment 28 | sacha:spin 29 | dburles:collection-helpers 30 | percolate:synced-cron 31 | dandv:http-more 32 | meteorhacks:fast-render 33 | accounts-ui 34 | accounts-password 35 | mrt:accounts-vk 36 | meteorhacks:ssr 37 | -------------------------------------------------------------------------------- /client/components/table/row/tableRow.jade: -------------------------------------------------------------------------------- 1 | template(name='tableRow') 2 | +each tables 3 | +each tables 4 | td.border 5 | +each problems 6 | +result user=../../../user table=this 7 | td.border 8 | +totalResult user=../user table=this 9 | td.border 10 | +totalResult 11 | +attempts 12 | 13 | template(name='result') 14 | td(title="{{user.name}} : {{table.name}}",class="{{class}} res", ondblclick="if (event.ctrlKey) window.open('{{ctrlDbClickUrl}}', '_blank'); else window.open('{{dbClickUrl}}', '_blank');") 15 | =text 16 | 17 | template(name='totalResult') 18 | td(title="Итого по уровню {{table.name}}") 19 | =result 20 | 21 | template(name='attempts') 22 | td(title="Попыток по уровню {{table.name}}") 23 | =result 24 | -------------------------------------------------------------------------------- /lib/ControllerWithTitle/ControllerWithTitle.coffee: -------------------------------------------------------------------------------- 1 | class @ControllerWithTitle extends RouteController 2 | title: -> 3 | "" 4 | 5 | onAfterAction: -> 6 | thisTitle = @title() 7 | if !thisTitle 8 | thisTitle = "Сводные таблицы" 9 | # SEO.set title: thisTitle 10 | 11 | #fastRender: true 12 | 13 | action: -> 14 | if @server 15 | head = SSR.render('head', {}) 16 | #console.log("Before render") 17 | body = SSR.render(@name(), @data()) 18 | #console.log("Done render") 19 | css = '\n' 20 | this.response.end("" + css + head + "
" + body + "
") 21 | else 22 | this.render() -------------------------------------------------------------------------------- /routes/solvedByWeekTable.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/solvedByWeekTable/:userList', {name: 'solvedByWeekTable', where: 'server'} 2 | class @SolvedByWeekTableController extends ControllerWithTitle 3 | server: true 4 | 5 | waitOn: -> 6 | userList = this.params.userList 7 | Meteor.subscribe 'users' 8 | Meteor.subscribe 'meteorUser' 9 | 10 | data: -> 11 | userList = this.params.userList 12 | return userList 13 | 14 | name: -> 15 | 'solvedByWeekTable' 16 | 17 | title: -> 18 | 'Посылки по неделям' 19 | 20 | Router.route '/solvedByWeekTable/:userList/c', name: 'solvedByWeekTableC' 21 | class @SolvedByWeekTableCController extends SolvedByWeekTableController 22 | server: false 23 | 24 | template: 'solvedByWeekTable' 25 | 26 | fastRender: false 27 | -------------------------------------------------------------------------------- /routes/user.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/user/:id', {name: 'user', where: 'server'} 2 | class @UserController extends ControllerWithTitle 3 | server: true 4 | 5 | waitOn: -> 6 | id = this.params.id 7 | Meteor.subscribe 'users' 8 | Meteor.subscribe 'tables' 9 | Meteor.subscribe 'problems' 10 | Meteor.subscribe 'resultsForUser', id 11 | Meteor.subscribe 'meteorUser' 12 | 13 | data: -> 14 | id = this.params.id 15 | Users.findById id 16 | 17 | name: -> 18 | 'user' 19 | 20 | title: -> 21 | id = this.params.id 22 | Users.findById(id).name 23 | 24 | Router.route '/user/:id/c', name: 'userC' 25 | class @UserCController extends UserController 26 | server: false 27 | 28 | template: 'user' 29 | 30 | fastRender: false 31 | -------------------------------------------------------------------------------- /client/components/userLine/userLine.jade: -------------------------------------------------------------------------------- 1 | template(name='userLine') 2 | if name 3 | td 4 | +userName 5 | if admin 6 | td 7 | span(title="Базовый уровень") 8 | form.baseLevel 9 | input(type="text",name="newLevel",value="{{baseLevel}}",size="3") 10 | td 11 | span(title="Уровень на старте полугодия") 12 | =startLevel 13 | td 14 | span(title="Уровень") 15 | =level 16 | td 17 | span(title="Рейтинг") 18 | =rating 19 | td 20 | span(title="Активность") 21 | =activity 22 | td 23 | span(title="Codeforces рейтинг") 24 | if cfRating 25 | +cfStatus 26 | 27 | 28 | -------------------------------------------------------------------------------- /collections/downloads.coffee: -------------------------------------------------------------------------------- 1 | DownloadsCollection = new Mongo.Collection 'downloads' 2 | 3 | Downloads = 4 | findAll: -> 5 | @collection.find {} 6 | 7 | lastDownloadTime: (id) -> 8 | doc = @collection.findOne _id: "last" + id 9 | if !doc 10 | return new Date(0) 11 | doc.time 12 | 13 | setLastDownloadTime: (id, time) -> 14 | cid = "last" + id 15 | @collection.update({_id: cid}, {$set: {_id: cid, time: time}}, {upsert: true}) 16 | 17 | isInProgress: -> 18 | doc = @collection.findOne _id: "inProgress" 19 | doc && doc.value 20 | 21 | setInProgress: (value) -> 22 | cid = "inProgress" 23 | @collection.update({_id: cid}, {$set: {_id: cid, value: value}}, {upsert: true}) 24 | 25 | collection: DownloadsCollection 26 | 27 | @Downloads = Downloads 28 | 29 | -------------------------------------------------------------------------------- /client/components/table/row/tableRow.less: -------------------------------------------------------------------------------- 1 | @ac: #44ff44; 2 | @ok: #ffff00; 3 | @wa: #ff4444; 4 | @ig: #aaaaff; 5 | 6 | .ac { 7 | background: @ac; 8 | } 9 | .ac.inactive { 10 | background: rgb(luma(@ac), luma(@ac), luma(@ac)); 11 | } 12 | .ok { 13 | background: @ok; 14 | } 15 | .ok.inactive { 16 | background: rgb(luma(@ok), luma(@ok), luma(@ok)); 17 | } 18 | .wa { 19 | background: @wa; 20 | } 21 | .wa.inactive { 22 | background: rgb(luma(@wa), luma(@wa), luma(@wa)); 23 | } 24 | .ig { 25 | background: @ig; 26 | } 27 | .ig.inactive { 28 | background: rgb(luma(@ig), luma(@ig), luma(@ig)); 29 | } 30 | 31 | td.res { 32 | font-family: monospace; 33 | text-align: center; 34 | } 35 | .dq { 36 | background: black; 37 | } 38 | 39 | .active, .mainTable td.active { 40 | border-top:3px solid black; 41 | border-bottom:3px solid black; 42 | } 43 | 44 | td.active { 45 | font-weight: bold; 46 | } 47 | -------------------------------------------------------------------------------- /collections/cfResults.coffee: -------------------------------------------------------------------------------- 1 | cfResultsCollection = new Mongo.Collection 'cfResults' 2 | 3 | # fields 4 | # _id 5 | # userId 6 | # contestId 7 | # time 8 | # place 9 | # oldRating 10 | # newRating 11 | 12 | @cfResults = 13 | 14 | addResult: (userId, contestId, time, place, oldRating, newRating) -> 15 | id = contestId + "::" + userId 16 | @collection.update({_id: id}, {$set: {_id: id, userId: userId, contestId: contestId, time: time, place: place, oldRating: oldRating, newRating: newRating}}, {upsert: true}) 17 | 18 | findLastResults: (limit) -> 19 | @collection.find {}, { 20 | sort: { time: -1, place: 1 }, 21 | limit: limit 22 | } 23 | 24 | 25 | collection: cfResultsCollection 26 | 27 | if Meteor.isServer 28 | Meteor.startup -> 29 | cfResults.collection._ensureIndex 30 | time: -1 31 | place: 1 32 | -------------------------------------------------------------------------------- /client/components/overall/table/overallTable.jade: -------------------------------------------------------------------------------- 1 | template(name='overallTable') 2 | h1 3 | | Общая таблица 4 | p 5 | | Цвет ячеек: белая — на уровне не решено ни одной задачи, серая — на уровне решено сколько-то задач, но недостаточно, чтобы пройти уровень, темно-зеленая — уровень пройден, но решены не все задачи, ярко-зеленая — решены вообще все задачи. 6 | p 7 | | Имена школьников — ссылки на странички с результатами каждого конкретного школьника. 8 | table.mainTable 9 | tr 10 | +userHeader name=true 11 | td.border 12 | td 13 | | = 14 | td.border 15 | +overallHeader mainTable 16 | +each users 17 | tr 18 | +userLine 19 | td.border 20 | +overallResult table=mainTable user=this 21 | td.border 22 | +overallLine user=this table=mainTable 23 | -------------------------------------------------------------------------------- /routes/okSubmits.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/okSubmits', {name: 'okSubmits', where: 'server'} 2 | class @OkSubmitsController extends ControllerWithTitle 3 | server: true 4 | 5 | waitOn: -> 6 | Meteor.subscribe 'okSubmits' 7 | Meteor.subscribe 'lastAcSubmits' 8 | Meteor.subscribe 'lastIgSubmits' 9 | Meteor.subscribe 'lastWaResults' 10 | Meteor.subscribe 'users' 11 | Meteor.subscribe 'meteorUser' 12 | Meteor.subscribe 'tables' 13 | Meteor.subscribe 'problems' 14 | Meteor.subscribe 'lastCfResults' 15 | Meteor.subscribe 'downloads' 16 | 17 | data: -> 18 | return 19 | 20 | name: -> 21 | 'okSubmits' 22 | 23 | title: -> 24 | 'OK-посылки' 25 | 26 | Router.route '/okSubmits/c', name: 'okSubmitsC' 27 | class @OkSubmitsCController extends OkSubmitsController 28 | server: false 29 | 30 | template: 'okSubmits' 31 | 32 | fastRender: false 33 | -------------------------------------------------------------------------------- /client/components/overall/line/overallLine.coffee: -------------------------------------------------------------------------------- 1 | Template.overallLine.helpers 2 | needRecurse: -> 3 | return @table.height() >= 3 4 | 5 | needBorder: -> 6 | return @table.height() == 3 7 | 8 | subTable: -> 9 | return Tables.findById("" + this) 10 | 11 | Template.overallResult.helpers 12 | result: -> 13 | res = Results.findByUserAndTable(@user._id, @table._id) 14 | if not res 15 | "" 16 | else 17 | res.solved + (if res.ok > 0 then "+" + res.ok else "") + " / " + res.total 18 | 19 | bgColor: -> 20 | res = Results.findByUserAndTable(@user._id, @table._id) 21 | if not res 22 | return "#ffffff" 23 | if res.solved == res.total 24 | return "#00ff00" 25 | letter = @table._id[@table._id.length-1] 26 | if letter == "В" and res.solved*2 >= res.total 27 | return "#00bb00" 28 | if letter == "Г" and res.solved*3 >= res.total 29 | return "#00bb00" 30 | if res.solved > 0 31 | return "#dddddd" 32 | return "#ffffff" 33 | 34 | 35 | -------------------------------------------------------------------------------- /client/components/user/user.coffee: -------------------------------------------------------------------------------- 1 | Template.user.helpers 2 | tables: -> 3 | main = Tables.findById(Tables.main) 4 | res = [] 5 | for table in main.tables 6 | if table == "1" 7 | res.push (Tables.findById(id).expand() for id in ["1А", "1Б"]) 8 | res.push (Tables.findById(id).expand() for id in ["1В", "1Г"]) 9 | else 10 | result = Results.findByUserAndTable(@_id, table) 11 | if result.lastSubmitId 12 | res.push (Tables.findById(id).expand() for id in Tables.findById(table).tables) 13 | res 14 | 15 | activity: -> 16 | @activity.toFixed(2) 17 | 18 | choco: -> 19 | @choco 20 | 21 | lic40: -> 22 | @userList == "lic40" 23 | 24 | admin: -> 25 | isAdmin() 26 | 27 | 28 | Template.user.events 29 | "submit .baseLevel": (event) -> 30 | Meteor.call("setBaseLevel", @_id, event.target.newLevel.value) 31 | event.preventDefault() 32 | false 33 | 34 | Template.user.events 35 | "submit .cfLogin": (event) -> 36 | Meteor.call("setCfLogin", @_id, event.target.cfLogin.value) 37 | event.preventDefault() 38 | false 39 | 40 | -------------------------------------------------------------------------------- /client/components/solvedByWeek/line/solvedByWeekLine.coffee: -------------------------------------------------------------------------------- 1 | Template.solvedByWeekLine.helpers 2 | weekSet: -> 3 | thisStart = new Date(startDayForWeeks["" + @userList]) 4 | now = new Date() 5 | nowWeek = Math.floor((now - thisStart) / MSEC_IN_WEEK) 6 | [0..nowWeek] 7 | 8 | activity: -> 9 | a = Math.floor(@user.activity / ACTIVITY_THRESHOLD) * ACTIVITY_THRESHOLD 10 | s = a.toFixed(5) 11 | s.replace(/\.?0+$/gm,"") 12 | 13 | admin: -> 14 | isAdmin() 15 | 16 | Template.solvedByWeekLine.events 17 | "submit .baseLevel": (event) -> 18 | Meteor.call("setBaseLevel", @user._id, event.target.newLevel.value) 19 | event.preventDefault() 20 | false 21 | 22 | Template.oneWeekSolved.helpers 23 | weekSolved: -> 24 | num = @user.solvedByWeek[@weekNumber] 25 | res = if num then num else "" 26 | if @user.okByWeek 27 | ok = @user.okByWeek[@weekNumber] 28 | if ok 29 | res = if res then res + "+" + ok else "0+" + ok 30 | res 31 | 32 | bgColor: -> 33 | num = @user.solvedByWeek[@weekNumber] 34 | if !num 35 | "#ffffff" 36 | else if num<=2 37 | "#ddffdd" 38 | else if num<=5 39 | "#bbffbb" 40 | else if num<=8 41 | "#88ff88" 42 | else 43 | "#22ff22" 44 | 45 | -------------------------------------------------------------------------------- /client/components/userBadge/userBadge.jade: -------------------------------------------------------------------------------- 1 | template(name='userBadge') 2 | if this 3 | h1 4 | +userName 5 | blockquote 6 | div 7 | | Уровень: 8 | =level 9 | div 10 | | Рейтинг: 11 | =rating 12 | div 13 | | Активность: 14 | =activity 15 | if cfLogin 16 | div 17 | | Codeforces рейтинг: 18 | +cfStatus 19 | else 20 | | Логин на codeforces неизвестен. Если вы зарегистированы, сообщите логин мне. 21 | a(href="{{profileLink}}" target="_blank") Полные результаты 22 | else 23 | h1 24 | | Неизвестный пользователь 25 | Это может быть ошибкой, а может быть нормально: 26 | ul 27 | li Если вы не занимаетесь в этом курсе, а просто зашли посмотреть, то это нормально. 28 | li Если вы занимаетесь, но не написали мне об этом, то это ошибка, срочно напишите. 29 | li Если вы написали, но не очень давно (менее 2 дней назад), то, возможно, я просто еще не видел вашего письма; 30 | li Если вы еще не решали задач в курсе, то вас пока нет в сводных таблицах и нет здесь. Как только будете решать, эта ошибка пропадет. 31 | li Если вы решали задачи в курсе, но очень давно, и я вас только что добавил в таблицы, то ваши старые результаты появятся в течение суток. 32 | li Иначе срочно напишите мне, возможно, я вас как-то не так добавил. 33 | -------------------------------------------------------------------------------- /collections/submits.coffee: -------------------------------------------------------------------------------- 1 | SubmitsCollection = new Mongo.Collection 'submits' 2 | 3 | # fields 4 | # _id 5 | # time 6 | # user 7 | # problem 8 | # outcome 9 | 10 | Submits = 11 | findById: (id) -> 12 | @collection.findOne _id: id 13 | 14 | findAcByUserAndProblem: (user, problem) -> 15 | @collection.find({user: user, problem: problem, outcome: "AC"}) 16 | 17 | findByUserAndProblem: (user, problem) -> 18 | @collection.find({user: user, problem: problem}, {sort: {time: 1}}) 19 | 20 | findByUser: (user) -> 21 | @collection.find({user: user}, {sort: {time: 1}}) 22 | 23 | findByOutcome: (outcome) -> 24 | @collection.find({outcome: outcome}, {sort: {time: 1}}) 25 | 26 | findLastByOutcome: (outcome, limit) -> 27 | @collection.find({outcome: outcome}, {sort: {time: -1}, limit: limit}) 28 | 29 | addSubmit: (id, time, user, problem, outcome) -> 30 | @collection.update({_id: id}, {_id: id, time: time, user: user, problem: problem, outcome: outcome}, {upsert: true}) 31 | 32 | findAll: -> 33 | @collection.find {} 34 | 35 | collection: SubmitsCollection 36 | 37 | @Submits = Submits 38 | 39 | if Meteor.isServer 40 | Meteor.startup -> 41 | Submits.collection._ensureIndex({ user : 1, problem: 1, time: 1 }); 42 | Submits.collection._ensureIndex({ user : 1, problem: 1, outcome: 1 }); 43 | Submits.collection._ensureIndex({ outcome : 1, time : 1 }); 44 | Submits.collection._ensureIndex({ time : 1 }); -------------------------------------------------------------------------------- /client/lib/bootstrap/custom.bootstrap.json: -------------------------------------------------------------------------------- 1 | {"modules": { 2 | "normalize": true, 3 | "print": false, 4 | 5 | "scaffolding": true, 6 | "type": true, 7 | "code": false, 8 | "grid": true, 9 | "tables": true, 10 | "forms": false, 11 | "buttons": true, 12 | 13 | "glyphicons": true, 14 | "button-groups": false, 15 | "input-groups": true, 16 | "navs": true, 17 | "navbar": true, 18 | "breadcrumbs": false, 19 | "pagination": false, 20 | "pager": false, 21 | "labels": true, 22 | "badges": true, 23 | "jumbotron": false, 24 | "thumbnails": false, 25 | "alerts": false, 26 | "progress-bars": false, 27 | "media": false, 28 | "list-group": true, 29 | "panels": false, 30 | "responsive-embed": false, 31 | "wells": false, 32 | "close": true, 33 | 34 | "component-animations": false, 35 | "dropdowns": true, 36 | "modals": false, 37 | "tooltip": false, 38 | "popovers": false, 39 | "carousel": false, 40 | 41 | "affix": false, 42 | "alert": false, 43 | "button": false, 44 | "collapse": false, 45 | "scrollspy": false, 46 | "tab": false, 47 | "transition": false, 48 | 49 | "utilities": false, 50 | "responsive-utilities": false 51 | }} -------------------------------------------------------------------------------- /server/calculations/userCalcs/levelCalc.coffee: -------------------------------------------------------------------------------- 1 | @calculateLevel = (user, baseLevel, lastDate) -> 2 | #console.log "lastdate=", lastDate 3 | for bigLevel in [1..10] 4 | for smallLevel in ["А", "Б", "В", "Г"] 5 | tableId = bigLevel + smallLevel 6 | level = tableId 7 | table = Tables.findById(tableId) 8 | if not table 9 | continue 10 | probNumber = 0 11 | probAc = 0 12 | for subTableId in table.tables 13 | subTable = Tables.findById(subTableId) 14 | if not subTable 15 | continue 16 | for prob in subTable.problems 17 | if subTable.name[4] != "*" 18 | probNumber++ 19 | result = Results.findByUserAndTable(user, prob) 20 | if not result 21 | continue 22 | if result.solved == 0 23 | continue 24 | submitDate = new Date(result.lastSubmitTime) 25 | #console.log "submitDate=", submitDate 26 | if submitDate >= lastDate 27 | #console.log "skipping submit" 28 | continue 29 | probAc++ 30 | needProblem = probNumber 31 | if smallLevel == "В" 32 | needProblem = probNumber * 0.5 33 | else if smallLevel == "Г" 34 | needProblem = probNumber * 0.3333 35 | if (probAc < needProblem) and ((!baseLevel) or (baseLevel <= level)) 36 | console.log user, level 37 | return level 38 | return "inf" 39 | 40 | Meteor.startup -> 41 | # for u in Users.findAll().fetch() 42 | # #u = Users.findById("62906") 43 | # u.updateLevel() 44 | # console.log u.name, u.level, u.startLevel 45 | -------------------------------------------------------------------------------- /collections/problems.coffee: -------------------------------------------------------------------------------- 1 | ProblemsCollection = new Mongo.Collection 'problems' 2 | 3 | # fields 4 | # _id 5 | # letter 6 | # name 7 | # tables[] 8 | # level 9 | 10 | ProblemsCollection.helpers 11 | addTable: (table) -> 12 | console.log @name, @tables 13 | if table in @tables 14 | return 15 | console.log @_id, @tables, table 16 | @tables.push(table) 17 | console.log @_id, @tables, table 18 | Problems.collection.update({ _id: @_id }, {$set: { tables: @tables }}) 19 | Problems.findById(@_id).updateLevel() 20 | 21 | updateLevel: -> 22 | reg = "" 23 | @level = "" 24 | for table in @tables 25 | console.log table 26 | level = Tables.findById(table).parent 27 | if (level.slice(0,3) == "reg") and (reg < level) 28 | reg = level 29 | else if level > @level 30 | @level = level 31 | if @level == "" 32 | @level = reg 33 | if @level == "" 34 | @level = undefined 35 | console.log @_id, @name, @level 36 | Problems.collection.update({_id: @_id}, {$set: {level: @level}}) 37 | 38 | 39 | Problems = 40 | findById: (id) -> 41 | @collection.findOne _id: id 42 | 43 | findByTable: (table) -> 44 | @collection.find tables: table 45 | 46 | findByLevel: (level) -> 47 | @collection.find level: level 48 | 49 | findAll: -> 50 | @collection.find {} 51 | 52 | addProblem: (id, letter, name) -> 53 | console.log @findById id 54 | if @findById id 55 | return 56 | @collection.insert({_id: id, letter: letter, name: name, tables: []}) 57 | 58 | collection: ProblemsCollection 59 | 60 | @Problems = Problems 61 | 62 | #Meteor.startup -> 63 | # Problems.findById("p89").updateLevel() 64 | # for prob in Problems.findAll().fetch() 65 | # prob.updateLevel() 66 | -------------------------------------------------------------------------------- /server/calculations/userCalcs/cfCalc.coffee: -------------------------------------------------------------------------------- 1 | colors = [[0, "gray"], [1200, "green"], [1400, "#03A89E"], [1600, "blue"], 2 | [1900, "#a0a"], [2200, "#bb0"], [2300, "#FF8C00"], [2400, "red"]]; 3 | 4 | getRating = (user) -> 5 | href = "http://codeforces.com/api/user.info?handles=" + user.cfLogin 6 | text = syncDownload(href).content 7 | #console.log "cf returns ", text 8 | data = JSON.parse(text)["result"] 9 | return data[0]["rating"] 10 | 11 | MSEC_IN_WEEK = 1000*60*60*24*7 12 | EXPONENT = MSEC_IN_WEEK * 4 13 | timeScore = (date) -> 14 | weeks = (new Date() - date)/EXPONENT 15 | return Math.pow(0.5, weeks) 16 | 17 | getActivityAndProgress = (user) -> 18 | href = "http://codeforces.com/api/user.rating?handle=" + user.cfLogin 19 | text = syncDownload(href).content 20 | #console.log "cf returns ", text 21 | data = JSON.parse(text)["result"] 22 | 23 | startDate = new Date("2016-09-01") 24 | change = 0 25 | contests = 0 26 | first = true 27 | 28 | for elem in data 29 | thisDate = new Date(elem["ratingUpdateTimeSeconds"] * 1000) 30 | cfResults.addResult(user._id, elem["contestId"], thisDate, elem["rank"], elem["oldRating"], elem["newRating"]) 31 | if (not first) # very first contest has no meaning as start rating is 1500 32 | change += (elem["newRating"] - elem["oldRating"]) * timeScore(thisDate) 33 | contests += timeScore(thisDate) 34 | console.log (elem["newRating"] - elem["oldRating"]), timeScore(thisDate) 35 | first = false 36 | return activity: contests, progress: change 37 | 38 | colorByRating = (rating) -> 39 | color = "" 40 | for c in colors 41 | if c[0] > rating 42 | break 43 | color = c[1] 44 | return color 45 | 46 | @updateCfRating = (user) -> 47 | if not user.cfLogin 48 | return 49 | rating = getRating(user) 50 | color = colorByRating(rating) 51 | activityAndProgress = getActivityAndProgress(user) 52 | return rating: rating, color: color, activity: activityAndProgress.activity, progress: activityAndProgress.progress 53 | 54 | -------------------------------------------------------------------------------- /routes/table.coffee: -------------------------------------------------------------------------------- 1 | cmp = (a, b) -> 2 | if (a.solved != b.solved) 3 | return b.solved - a.solved 4 | if (a.attempts != b.attempts) 5 | return a.attempts - b.attempts 6 | return 0 7 | 8 | Router.route '/table/:userList/:tableIds', {name: 'table', where: 'server'} 9 | class @TableController extends ControllerWithTitle 10 | server: true 11 | 12 | waitOn: -> 13 | tableIds = this.params.tableIds.split(",") 14 | userList = this.params.userList 15 | Meteor.subscribe 'basicUsers' 16 | Meteor.subscribe 'tables' 17 | Meteor.subscribe 'problems' 18 | Meteor.subscribe 'resultsForUserList', userList 19 | Meteor.subscribe 'meteorUser' 20 | 21 | data: -> 22 | console.log("In data") 23 | #params = this.request.url.split("/") 24 | #tableIds = params[3] 25 | #userList = params[2] 26 | tableIds = this.params.tableIds.split(",") 27 | userList = this.params.userList 28 | tables = (Tables.findById(id).expand() for id in tableIds) 29 | if tables.length == 1 30 | tables = tables[0].tables 31 | users = Users.findByList(userList).fetch() 32 | newUsers = [] 33 | for user in users 34 | solved = 0 35 | attempts = 0 36 | ok = 0 37 | for table in tables 38 | res = Results.findByUserAndTable(user._id, table._id) 39 | solved += res.solved 40 | attempts += res.attempts 41 | ok += res.ok 42 | user.solved = solved 43 | user.attempts = attempts 44 | if (solved != 0) or (attempts != 0) or (ok != 0) 45 | newUsers.push(user) 46 | newUsers.sort(cmp) 47 | console.log("Returning from data") 48 | return {tables: tables, users: newUsers} 49 | 50 | name: -> 51 | 'table' 52 | 53 | title: -> 54 | 'Сводная таблица' 55 | 56 | Router.route '/table/:userList/:tableIds/c', {name: 'tableC'} 57 | class @TableCController extends TableController 58 | server: false 59 | template: 'table' 60 | fastRender: false 61 | -------------------------------------------------------------------------------- /client/components/user/user.jade: -------------------------------------------------------------------------------- 1 | template(name='user') 2 | h1 3 | +userName 4 | blockquote 5 | if admin 6 | div 7 | | Базовый уровень 8 | form.baseLevel 9 | input(type="text",name="newLevel",value="{{baseLevel}}",size="3") 10 | div 11 | | Уровень на начало полугодия: 12 | =startLevel 13 | div 14 | | CF Login 15 | form.cfLogin 16 | input(type="text",name="cfLogin",value="{{cfLogin}}",size="20") 17 | div 18 | | Уровень на начало полугодия: 19 | =startLevel 20 | div 21 | | Уровень: 22 | =level 23 | div 24 | | Рейтинг: 25 | =rating 26 | div 27 | | Активность: 28 | =activity 29 | if cfRating 30 | div 31 | | Codeforces рейтинг: 32 | +cfStatus 33 | if lic40 34 | h2 35 | | Шоколадки 36 | p.small 37 | | Первая строка: шоколадки за полные контесты: первая шоколадка за первый сданный полностью контест и далее по одной шоколадке каждый раз, когда число сданных контестов делится на три. 38 | | Вторая строка: шоколадки за чистые контесты: за каждый контест, полностью сданный с первой попытки, одна шоколадка. 39 | | Третья строка: шоколадки за почти чистые контесты: по шоколадке за каждые два контеста, в которых все задачи сданы не более чем со второй попытки, и при этом хотя бы одна задача сдана не с первой попытки. 40 | table.table.chocos 41 | +each chocos 42 | +chocos number=this 43 | h2 44 | | Посылки по неделям 45 | p.small 46 | | Количество зачтенных посылок за неделю; 0.5, если посылки были, но ни одной зачтенной 47 | table.mainTable 48 | +solvedByWeekHeader userList=userList name=false 49 | +solvedByWeekLine userList=userList user=this name=fale 50 | h2 51 | | Результаты 52 | +each tables 53 | table.mainTable 54 | tr 55 | +tableHeader1 tables=this 56 | tr 57 | +tableRow user=.. tables=this 58 | -------------------------------------------------------------------------------- /server/calculations/userCalcs/chocoCalc.coffee: -------------------------------------------------------------------------------- 1 | class FullContestChocoCalculator 2 | constructor: -> 3 | @fullContests = 0 4 | 5 | processContest: (contest) -> 6 | full = true 7 | for p in contest 8 | if p.solved == 0 9 | full = false 10 | if full 11 | #console.log "+full" 12 | @fullContests++ 13 | 14 | chocos: -> 15 | if @fullContests == 0 16 | 0 17 | else if @fullContests == 1 18 | 1 19 | else 20 | (@fullContests // 3) + 1 21 | 22 | 23 | class CleanContestChocoCalculator 24 | constructor: -> 25 | @cleanContests = 0 26 | 27 | processContest: (contest) -> 28 | clean = true 29 | for p in contest 30 | if (p.attempts > 0) or (p.solved == 0) 31 | clean = false 32 | if clean 33 | #console.log "+clean" 34 | @cleanContests++ 35 | 36 | chocos: -> 37 | @cleanContests 38 | 39 | class HalfCleanContestChocoCalculator 40 | constructor: -> 41 | @hcleanContests = 0 42 | 43 | processContest: (contest) -> 44 | clean = true 45 | half = true 46 | for p in contest 47 | if (p.attempts > 0) or (p.solved == 0) 48 | clean = false 49 | if (p.attempts > 1) or (p.solved == 0) 50 | half = false 51 | if half and (not clean) 52 | #console.log "+half" 53 | @hcleanContests++ 54 | 55 | chocos: -> 56 | @hcleanContests // 2 57 | 58 | @calculateChocos = (userId) -> 59 | chocoCalcs = [new FullContestChocoCalculator(), new CleanContestChocoCalculator(), new HalfCleanContestChocoCalculator()] 60 | tables = Tables.findAll().fetch() 61 | for table in tables 62 | if table.problems.length == 0 # not a single contest 63 | continue 64 | results = [] 65 | for problem in table.problems 66 | results.push(Results.findByUserAndTable(userId, problem)) 67 | #console.log table.name 68 | for calc in chocoCalcs 69 | calc.processContest(results) 70 | res = [] 71 | for calc in chocoCalcs 72 | res.push(calc.chocos()) 73 | return res 74 | 75 | #Meteor.startup -> 76 | # for u in Users.findAll().fetch() 77 | # u.updateChocos() 78 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.2 2 | accounts-oauth@1.1.8 3 | accounts-password@1.1.4 4 | accounts-ui@1.1.6 5 | accounts-ui-unstyled@1.1.8 6 | autoupdate@1.2.4 7 | babel-compiler@5.8.24_1 8 | babel-runtime@0.1.4 9 | base64@1.0.4 10 | binary-heap@1.0.4 11 | blaze@2.1.3 12 | blaze-html-templates@1.0.1 13 | blaze-tools@1.0.4 14 | boilerplate-generator@1.0.4 15 | caching-compiler@1.0.0 16 | caching-html-compiler@1.0.2 17 | callback-hook@1.0.4 18 | check@1.1.0 19 | chuangbo:cookie@1.1.0 20 | coffeescript@1.0.11 21 | dandv:http-more@1.0.7_1 22 | dburles:collection-helpers@1.0.4 23 | ddp@1.2.2 24 | ddp-client@1.2.1 25 | ddp-common@1.2.2 26 | ddp-rate-limiter@1.0.0 27 | ddp-server@1.2.2 28 | deps@1.0.9 29 | diff-sequence@1.0.1 30 | ecmascript@0.1.6 31 | ecmascript-runtime@0.2.6 32 | ejson@1.0.7 33 | email@1.0.8 34 | fastclick@1.0.7 35 | geojson-utils@1.0.4 36 | hot-code-push@1.0.0 37 | html-tools@1.0.5 38 | htmljs@1.0.5 39 | http@1.1.1 40 | id-map@1.0.4 41 | iron:controller@1.0.12 42 | iron:core@1.0.11 43 | iron:dynamic-template@1.0.12 44 | iron:layout@1.0.12 45 | iron:location@1.0.11 46 | iron:middleware-stack@1.0.11 47 | iron:router@1.0.12 48 | iron:url@1.0.11 49 | jquery@1.11.4 50 | launch-screen@1.0.4 51 | less@2.5.1 52 | livedata@1.0.15 53 | localstorage@1.0.5 54 | logging@1.0.8 55 | meteor@1.1.10 56 | meteor-base@1.0.1 57 | meteorhacks:fast-render@2.13.0 58 | meteorhacks:inject-data@2.0.0 59 | meteorhacks:meteorx@1.4.1 60 | meteorhacks:picker@1.0.3 61 | meteorhacks:ssr@2.2.0 62 | minifiers@1.1.7 63 | minimongo@1.0.10 64 | mobile-experience@1.0.1 65 | mobile-status-bar@1.0.6 66 | momentjs:moment@2.12.0 67 | mongo@1.1.3 68 | mongo-id@1.0.1 69 | mquandalle:jade@0.4.9 70 | mquandalle:jade-compiler@0.4.5 71 | mrt:accounts-vk@0.2.0 72 | nemo64:bootstrap@3.3.5_2 73 | nemo64:bootstrap-data@3.3.5 74 | npm-bcrypt@0.7.8_2 75 | npm-mongo@1.4.39_1 76 | oauth@1.1.6 77 | oauth2@1.1.5 78 | observe-sequence@1.0.7 79 | ordered-dict@1.0.4 80 | percolate:synced-cron@1.3.0 81 | promise@0.5.1 82 | random@1.0.5 83 | rate-limit@1.0.0 84 | reactive-dict@1.1.3 85 | reactive-var@1.0.6 86 | reload@1.1.4 87 | retry@1.0.4 88 | routepolicy@1.0.6 89 | sacha:spin@2.3.1 90 | service-configuration@1.0.5 91 | session@1.1.1 92 | sha@1.0.4 93 | spacebars@1.0.7 94 | spacebars-compiler@1.0.7 95 | srp@1.0.4 96 | standard-minifiers@1.0.2 97 | templating@1.1.5 98 | templating-tools@1.0.0 99 | tracker@1.0.9 100 | ui@1.0.8 101 | underscore@1.0.4 102 | url@1.0.5 103 | webapp@1.2.3 104 | webapp-hashing@1.0.5 105 | -------------------------------------------------------------------------------- /convert-client-templates-to-server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import shutil 4 | import re 5 | 6 | os.chdir("client") 7 | 8 | SERVER_PREFIX = "../server/from_client/" 9 | PRIVATE_PREFIX = "../private/from_client/" 10 | 11 | shutil.rmtree(SERVER_PREFIX) 12 | shutil.rmtree(PRIVATE_PREFIX) 13 | os.makedirs(PRIVATE_PREFIX) 14 | 15 | def convert_coffee(source, target): 16 | with open(target, "w") as tf, open(source) as sf: 17 | for line in sf.readlines(): 18 | match = re.match("Template.(\w+).helpers", line) 19 | if match: 20 | name = match.group(1) 21 | tf.write("SSR.compileTemplate '{name}', Assets.getText('from_client/{name}.jade'), language: 'jade'\n\n" 22 | .format(name=match.group(1))) 23 | tf.write(line) 24 | 25 | 26 | def convert_jade(source): 27 | tf = None 28 | if source == "./components/head/head.jade": 29 | with open(SERVER_PREFIX + "head.coffee", 'w') as f: 30 | f.write("SSR.compileTemplate '{name}', Assets.getText('from_client/{name}.jade'), language: 'jade'\n\n" 31 | .format(name='head')) 32 | tf = open(PRIVATE_PREFIX + "head.jade", 'w') 33 | strip_chars = 0 34 | with open(source) as sf: 35 | for line in sf.readlines(): 36 | if not line.strip(): 37 | continue 38 | match = re.match(r"template\(name=['\"](\w+)['\"]\)", line) 39 | if match: 40 | name = match.group(1) 41 | if tf: 42 | tf.close() 43 | tf = open(PRIVATE_PREFIX + name + ".jade", 'w') 44 | strip_chars = 4 45 | else: 46 | assert tf 47 | tf.write(line[strip_chars:]) 48 | if tf: 49 | tf.close() 50 | 51 | 52 | for directory, subdirs, files in os.walk("."): 53 | for d in subdirs: 54 | dname = os.path.join(directory, d) 55 | print(dname) 56 | os.makedirs(SERVER_PREFIX + dname) 57 | for f in files: 58 | fname = os.path.join(directory, f) 59 | print(fname) 60 | unused_path, ext = os.path.splitext(fname) 61 | if ext == ".less": 62 | target = SERVER_PREFIX + fname 63 | shutil.copyfile(fname, target) 64 | elif ext == ".coffee": 65 | target = SERVER_PREFIX + fname 66 | convert_coffee(fname, target) 67 | elif ext == ".jade": 68 | convert_jade(fname) 69 | -------------------------------------------------------------------------------- /client/components/okSubmits/okSubmits.jade: -------------------------------------------------------------------------------- 1 | template(name='okSubmits') 2 | h2 3 | | All: 4 | =allUpdateTime 5 | h2 6 | | UntilIgnored: 7 | =ignoredUpdateTime 8 | h2 9 | | Last: 10 | =lastUpdateTime 11 | h1 12 | | OK 13 | table.okSubmits 14 | +each submits 15 | if needSubmit 16 | tr 17 | td 18 | a(href="{{href}}",target="_blank") 19 | =time 20 | td 21 | a(href="{{userHref}}") 22 | =user 23 | td 24 | =problem 25 | td 26 | =contests 27 | 28 | h1 29 | | WA 30 | table.okSubmits 31 | +each waResults 32 | tr 33 | td 34 | a(href="{{href}}",target="_blank") 35 | =time 36 | | [ x 37 | =attempts 38 | | ] 39 | td 40 | a(href="{{userHref}}") 41 | =user 42 | td 43 | =problem 44 | td 45 | =contests 46 | 47 | h1 48 | | AC 49 | table.okSubmits 50 | +each acSubmits 51 | if needAcSubmit 52 | tr 53 | td 54 | a(href="{{href}}",target="_blank") 55 | =time 56 | td 57 | a(href="{{userHref}}") 58 | =user 59 | td 60 | =problem 61 | td 62 | =contests 63 | 64 | h1 65 | | IG 66 | table.okSubmits 67 | +each igSubmits 68 | if needAcSubmit 69 | tr 70 | td 71 | a(href="{{href}}",target="_blank") 72 | =time 73 | td 74 | a(href="{{userHref}}") 75 | =user 76 | td 77 | =problem 78 | td 79 | =contests 80 | h1 81 | | CF 82 | table.okSubmits 83 | +each lastCfResults 84 | tr 85 | td 86 | =cfTime 87 | td 88 | a(href="{{cfUserHref}}") 89 | =cfUser 90 | td 91 | =contestId 92 | td 93 | a(href="{{cfResultHref}}") 94 | =cfRatingStr 95 | -------------------------------------------------------------------------------- /client/components/okSubmits/okSubmits.coffee: -------------------------------------------------------------------------------- 1 | Template.okSubmits.helpers 2 | allUpdateTime: -> 3 | Downloads.lastDownloadTime("All") 4 | 5 | ignoredUpdateTime: -> 6 | Downloads.lastDownloadTime("UntilIgnored") 7 | 8 | lastUpdateTime: -> 9 | Downloads.lastDownloadTime("Last") 10 | 11 | submits: -> 12 | Submits.findByOutcome("OK") 13 | 14 | acSubmits: -> 15 | Submits.findLastByOutcome("AC", 100) 16 | 17 | igSubmits: -> 18 | Submits.findLastByOutcome("IG", 100) 19 | 20 | waResults: -> 21 | results = Results.findLastWA(100) 22 | 23 | user: -> 24 | user = Users.findById(@user) 25 | star = "" 26 | if user.userList == "stud" 27 | star = "*" 28 | star + user.name + " (" + user.level + ")" 29 | 30 | userHref: -> 31 | "/user/" + @user 32 | 33 | needSubmit: -> 34 | START_DATE = "2017-06-12" 35 | startDate = new Date(START_DATE) 36 | if new Date(@time) < startDate 37 | return false 38 | if Problems.findById(@problem) 39 | return true 40 | return false 41 | 42 | needAcSubmit: -> 43 | if Problems.findById(@problem) 44 | return true 45 | return false 46 | 47 | problem: -> 48 | p = Problems.findById(@problem || @table) 49 | return p.name 50 | 51 | time: -> 52 | @time || @lastSubmitTime 53 | 54 | contests: -> 55 | p = Problems.findById(@problem || @table) 56 | contests = "" 57 | for t in p.tables 58 | table = Tables.findById(t) 59 | if table.tables.length == 0 60 | if contests.length > 0 61 | contests = contests + ", " 62 | contests = contests + table.name 63 | return contests 64 | 65 | href: -> 66 | problem = (@problem || @table).substr(1) 67 | url = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid='+problem+'&submit&user_id=' + @user 68 | return url 69 | 70 | lastCfResults: -> 71 | cfResults.findLastResults(20) 72 | 73 | cfTime: -> 74 | moment(@time).format("YYYY-MM-DD HH:mm") 75 | 76 | cfUser: -> 77 | user = Users.findById(@userId) 78 | user.name + " (" + user.level + ")" 79 | 80 | cfUserHref: -> 81 | "/user/" + @userId 82 | 83 | cfResultHref: -> 84 | user = Users.findById(@userId) 85 | "http://codeforces.com/submissions/" + user.cfLogin + "/contest/" + @contestId 86 | 87 | cfRatingStr: -> 88 | delta = @newRating - @oldRating 89 | if delta > 0 90 | delta = "+" + delta 91 | delta + " (" + @oldRating + " -> " + @newRating + " / " + @place + ")" 92 | -------------------------------------------------------------------------------- /collections/users.coffee: -------------------------------------------------------------------------------- 1 | UsersCollection = new Mongo.Collection 'tableUsers' 2 | 3 | # fields 4 | # _id 5 | # name 6 | # userList 7 | # chocos 8 | # level 9 | # startlevel 10 | # baselevel 11 | # active 12 | # ratingSort 13 | # solvedByWeek 14 | # okByWeek 15 | # rating 16 | # activity 17 | # cfLogin 18 | # cfRating 19 | # cfColor 20 | # cfActivity 21 | # cfProgress 22 | 23 | @startDayForWeeks = 24 | "lic40": "2016-12-28" 25 | "zaoch": "2017-01-01" 26 | @MSEC_IN_WEEK = 7 * 24 * 60 * 60 * 1000 27 | @SEMESTER_START = "2016-06-01" 28 | 29 | UsersCollection.helpers 30 | updateChocos: -> 31 | @chocos = calculateChocos @_id 32 | console.log @name, @chocos 33 | Users.collection.update({_id: @_id}, {$set: {chocos: @chocos}}) 34 | 35 | updateRatingEtc: -> 36 | res = calculateRatingEtc this 37 | console.log @name, res 38 | Users.collection.update({_id: @_id}, {$set: res}) 39 | 40 | updateLevel: -> 41 | @level = calculateLevel @_id, @baseLevel, new Date("2100-01-01") 42 | @startLevel = calculateLevel @_id, @baseLevel, new Date(SEMESTER_START) 43 | Users.collection.update({_id: @_id}, {$set: {level: @level, startLevel : @startLevel}}) 44 | 45 | updateCfRating: -> 46 | res = updateCfRating this 47 | if not res 48 | return 49 | @cfRating = res.rating 50 | @cfColor = res.color 51 | @cfActivity = res.activity 52 | @cfProgress = res.progress 53 | Users.collection.update({_id: @_id}, {$set: {cfRating: @cfRating, cfColor: @cfColor, cfActivity: @cfActivity, cfProgress: @cfProgress}}) 54 | 55 | setBaseLevel: (level) -> 56 | Users.collection.update({_id: @_id}, {$set: {baseLevel: level}}) 57 | @baseLevel = level 58 | if Meteor.isServer 59 | @updateLevel() 60 | @updateRatingEtc() 61 | 62 | setCfLogin: (cfLogin) -> 63 | Users.collection.update({_id: @_id}, {$set: {cfLogin: cfLogin}}) 64 | @cfLogin = cfLogin 65 | if Meteor.isServer 66 | @updateCfRating() 67 | 68 | Users = 69 | findById: (id) -> 70 | @collection.findOne _id: id 71 | 72 | findAll: -> 73 | @collection.find {}, {sort: {active: 1, level: -1, ratingSort: -1}} 74 | 75 | findByList: (list) -> 76 | @collection.find {userList: list}, {sort: {active: -1, level: -1, ratingSort: -1}} 77 | 78 | addUser: (id, name, userList) -> 79 | @collection.update({_id: id}, {$set: {_id: id, name: name, userList: userList}}, {upsert: true}) 80 | 81 | collection: UsersCollection 82 | 83 | @Users = Users 84 | 85 | if Meteor.isServer 86 | Meteor.startup -> 87 | Users.collection._ensureIndex 88 | userList: 1 89 | active: -1 90 | level: -1 91 | ratingSort: -1 92 | -------------------------------------------------------------------------------- /collections/results.coffee: -------------------------------------------------------------------------------- 1 | ResultsCollection = new Mongo.Collection 'results' 2 | 3 | # fields 4 | # _id 5 | # user 6 | # userList 7 | # table 8 | # total 9 | # solved 10 | # ok 11 | # attempts 12 | # lastSubmitId 13 | # lastSubmitTime 14 | # ignored // for problems only 15 | 16 | MAX_CACHE_SIZE = 100000 17 | 18 | Results = 19 | DQconst: -10 20 | 21 | addToCache: (id, data) -> 22 | if @cacheSize >= MAX_CACHE_SIZE 23 | @cacheSize = 0 24 | @cache = {} 25 | @cacheSize++ 26 | @cache[id] = data 27 | 28 | addResult: (user, table, total, solved, ok, attempts, ignored, lastSubmitId, lastSubmitTime) -> 29 | userList = Users.findById(user).userList 30 | id = user + "::" + table 31 | data = {_id: id, user: user, userList: userList, table: table, total: total, solved: solved, ok: ok, attempts: attempts, ignored: ignored, lastSubmitId: lastSubmitId, lastSubmitTime: lastSubmitTime} 32 | @collection.update({_id: id}, 33 | data, 34 | {upsert: true}) 35 | @addToCache(id, data) 36 | 37 | findById: (id) -> 38 | @collection.findOne _id: id 39 | 40 | findAll: -> 41 | @collection.find {} 42 | 43 | findByUserListAndTable: (userList, table) -> 44 | tableList = Tables.findById(table).descendandTables() 45 | @collection.find { 46 | userList: userList, 47 | table: {$in: tableList} 48 | }, sort: { solved: -1, attempts: 1} 49 | 50 | findByUserList: (userList) -> 51 | @collection.find { 52 | userList: userList 53 | }, sort: { solved: -1, attempts: 1} 54 | 55 | findByUser: (userId) -> 56 | @collection.find { 57 | user: userId, 58 | } 59 | 60 | findByUserAndTable: (userId, tableId) -> 61 | key = userId + "::" + tableId 62 | if not (key of @cache) 63 | @addToCache(key, @collection.findOne { 64 | _id: userId + "::" + tableId 65 | }) 66 | return @cache[key] 67 | 68 | findLastWA: (limit) -> 69 | @collection.find { 70 | total: 1, # this is a problem, not a contest 71 | solved: 0, 72 | ok: 0, 73 | ignored: 0, 74 | attempts: {$gte: 1}, 75 | }, { 76 | sort: { lastSubmitTime: -1 }, 77 | limit: limit 78 | } 79 | 80 | 81 | collection: ResultsCollection 82 | 83 | cache: {} 84 | 85 | cacheSize: 0 86 | 87 | @Results = Results 88 | 89 | if Meteor.isServer 90 | Meteor.startup -> 91 | Results.collection._ensureIndex 92 | userList: 1 93 | table : 1 94 | solved: -1 95 | attempts: 1 96 | 97 | Results.collection._ensureIndex 98 | userList: 1 99 | solved: -1 100 | attempts: 1 101 | 102 | Results.collection._ensureIndex 103 | user: 1 104 | table: 1 105 | 106 | Results.collection._ensureIndex 107 | total: 1 108 | solved: 1 109 | ok: 1 110 | ignored: 1 111 | attempts: 1 112 | lastSubmitTime: -1 113 | -------------------------------------------------------------------------------- /collections/tables.coffee: -------------------------------------------------------------------------------- 1 | TablesCollection = new Mongo.Collection 'tables' 2 | 3 | # fields 4 | # _id 5 | # name 6 | # tables[] 7 | # problems[] 8 | # parent 9 | # order 10 | 11 | TablesCollection.helpers 12 | addTable: (id) -> 13 | Tables.collection.update({ _id: @_id }, {$push: { tables: id }}) 14 | Tables.cache = {} 15 | 16 | height: -> 17 | if @tables.length > 0 18 | return Tables.findById(@tables[0]).height() + 1 19 | else 20 | return 1 21 | 22 | expand: -> 23 | expandedTables = [] 24 | for table in @tables 25 | subTable = Tables.findById(table) 26 | subTable.expand() 27 | expandedTables.push(subTable) 28 | @tables = expandedTables 29 | expandedProblems = [] 30 | for problem in @problems 31 | expandedProblem = Problems.findById(problem) 32 | expandedProblems.push(expandedProblem) 33 | @problems = expandedProblems 34 | return this 35 | 36 | descendandTables: -> 37 | result = [@_id] 38 | for table in @table 39 | subTable = Tables.findById(table) 40 | result = result.concat(subTable.descendandTables()) 41 | for problem in @problems 42 | result.push(problem) 43 | 44 | parentFromParent = (level) -> 45 | if level == Tables.main 46 | return undefined 47 | p = parseLevel(level) 48 | if p.minor 49 | return p.major 50 | else 51 | return Tables.main 52 | 53 | Tables = 54 | main: "main" 55 | 56 | findById: (id) -> 57 | if not (id of @cache) 58 | console.log "tables cache miss" 59 | @cache[id] = @collection.findOne({_id: id}) 60 | if not @cache[id] 61 | return @cache[id] 62 | copy = JSON.parse(JSON.stringify(@cache[id])) 63 | return @collection._transform(copy) 64 | 65 | findAll: -> 66 | @collection.find({}, {sort: {_id: 1}}) 67 | 68 | addTable: (id, name, tables, problems, parent, order) -> 69 | @collection.update({_id: id}, 70 | {_id: id, name: name, tables: tables, problems: problems, parent: parent, order: order}, 71 | {upsert: true}) 72 | @cache = {} 73 | for prob in problems 74 | console.log prob, id 75 | Problems.findById(prob).addTable(id) 76 | if parent 77 | if not @findById(parent) 78 | pp = parentFromParent(parent) 79 | Tables.addTable(parent, parent, [], [], pp, order-1) 80 | Tables.findById(parent).addTable(id) 81 | 82 | removeDuplicateChildren: -> 83 | tables = @findAll().fetch() 84 | for table in tables 85 | wasTables = {} 86 | newTables = [] 87 | for subTable in table.tables 88 | if not (subTable of wasTables) 89 | wasTables[subTable] = 1 90 | newTables.push(subTable) 91 | table.tables = newTables 92 | @collection.update({_id: table._id}, table) 93 | @cache = {} 94 | 95 | collection: TablesCollection 96 | 97 | cache: {} 98 | 99 | @Tables = Tables 100 | -------------------------------------------------------------------------------- /server/calculations/resultCalc.coffee: -------------------------------------------------------------------------------- 1 | updateResultsForTable = (userId, tableId, dirtyResults) -> 2 | if dirtyResults and (not ((userId + "::" + tableId) of dirtyResults)) 3 | result = Results.findByUserAndTable(userId, tableId) 4 | if result 5 | return result 6 | total = 0 7 | solved = 0 8 | ok = 0 9 | attempts = 0 10 | lastSubmitId = undefined 11 | lastSubmitTime = undefined 12 | 13 | processRes = (res) -> 14 | total += res.total 15 | solved += res.solved 16 | ok += res.ok 17 | attempts += res.attempts 18 | if (!lastSubmitId) or (res.lastSubmitId and res.lastSubmitTime > lastSubmitTime) 19 | lastSubmitId = res.lastSubmitId 20 | lastSubmitTime = res.lastSubmitTime 21 | 22 | table = Tables.findById(tableId) 23 | for child in table.tables 24 | res = updateResultsForTable(userId, child, dirtyResults) 25 | processRes(res) 26 | for prob in table.problems 27 | res = updateResultsForProblem(userId, prob, dirtyResults) 28 | processRes(res) 29 | 30 | #console.log "updated result ", userId, tableId, total, solved, ok, attempts, lastSubmitTime 31 | Results.addResult(userId, tableId, total, solved, ok, attempts, undefined, lastSubmitId, lastSubmitTime) 32 | return {total: total, solved: solved, ok: ok, attempts: attempts, lastSubmitId: lastSubmitId, lastSubmitTime: lastSubmitTime} 33 | 34 | updateResultsForProblem = (userId, problemId, dirtyResults) -> 35 | if dirtyResults and (not ((userId + "::" + problemId) of dirtyResults)) 36 | result = Results.findByUserAndTable(userId, problemId) 37 | if result 38 | return result 39 | submits = Submits.findByUserAndProblem(userId, problemId).fetch() 40 | solved = 0 41 | ok = 0 42 | attempts = 0 43 | ignored = 0 44 | lastSubmitId = undefined 45 | lastSubmitTime = undefined 46 | for submit in submits 47 | lastSubmitId = submit._id 48 | lastSubmitTime = submit.time 49 | if submit.outcome == "IG" 50 | ignored = 1 51 | ok = 0 52 | continue 53 | # any other result resets ignored flag 54 | ignored = 0 55 | if submit.outcome == "DQ" 56 | ignored = Results.DQconst 57 | solved = -2 58 | ok = 0 59 | break 60 | else if submit.outcome == "AC" 61 | solved = 1 62 | ok = 0 63 | break 64 | else if submit.outcome == "OK" 65 | ok = 1 66 | continue # we might have a future AC 67 | else if submit.outcome != "CE" 68 | attempts++ 69 | #console.log "updated result ", userId, problemId, solved, ok, attempts, ignored, lastSubmitId 70 | Results.addResult(userId, problemId, 1, solved, ok, attempts, ignored, lastSubmitId, lastSubmitTime) 71 | return {total: 1, solved: solved, ok: ok, attempts: attempts, ignored: ignored, lastSubmitId: lastSubmitId, lastSubmitTime: lastSubmitTime} 72 | 73 | @updateResults = (user, dirtyResults) -> 74 | console.log "updating results for user ", user 75 | updateResultsForTable(user, Tables.main, dirtyResults) 76 | 77 | Meteor.startup -> 78 | # updateResults("262028", {}) 79 | # u = Users.findById("262028") 80 | # u.updateChocos() 81 | # u.updateRatingEtc() 82 | # u.updateLevel() 83 | # for u in Users.findAll().fetch() 84 | # updateResults(u._id) 85 | -------------------------------------------------------------------------------- /client/components/table/row/tableRow.coffee: -------------------------------------------------------------------------------- 1 | Template.tableRow.helpers 2 | 3 | Template.result.helpers 4 | text: -> 5 | result = Results.findByUserAndTable(@user._id, @table._id) 6 | if result.ignored == Results.DQconst 7 | "" 8 | else if result.solved > 0 or result.ok > 0 9 | "+" + (if result.attempts > 0 then result.attempts else "") 10 | else if result.attempts > 0 11 | "-" + result.attempts 12 | else 13 | " " 14 | 15 | class: -> 16 | result = Results.findByUserAndTable(@user._id, @table._id) 17 | if result.solved > 0 18 | "ac" 19 | else if result.ignored > 0 20 | "ig" 21 | else if result.ok > 0 22 | "ok" 23 | else if result.ignored == Results.DQconst 24 | "dq" 25 | else if result.attempts > 0 26 | "wa" 27 | else 28 | undefined 29 | 30 | dbClickUrl: -> 31 | problem = @table._id.substr(1) 32 | result = Results.findByUserAndTable(@user._id, @table._id) 33 | if not result 34 | return "" 35 | runId = result.lastSubmitId 36 | runSuff = '' 37 | if runId 38 | if runId.indexOf("p") > 0 39 | runId = runId.substr(0, runId.indexOf("p")) # strip problem suffix 40 | runSuff = '&run_id=' + runId 41 | url = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem + runSuff 42 | 43 | ctrlDbClickUrl: -> 44 | problem = @table._id.substr(1) 45 | result = Results.findByUserAndTable(@user._id, @table._id) 46 | if not result 47 | return "" 48 | url = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem + '&submit&user_id=' + @user._id 49 | 50 | 51 | Template.totalResult.helpers 52 | result: -> 53 | solved = 0 54 | ok = 0 55 | total = 0 56 | if @table 57 | tables = [@table] 58 | else 59 | tables = @tables 60 | for table in tables 61 | res = Results.findByUserAndTable(@user._id, table._id) 62 | solved += res.solved 63 | ok += res.ok 64 | total += res.total 65 | solved + (if ok > 0 then " + " + ok else "") + " / " + total 66 | 67 | Template.attempts.helpers 68 | result: -> 69 | attempts = 0 70 | for table in @tables 71 | res = Results.findByUserAndTable(@user._id, table._id) 72 | attempts += res.attempts 73 | return attempts 74 | 75 | Template.tableRow.events 76 | # 'click .userName': (e,t) -> 77 | # Session.set("activeUser", t.data.result.user) 78 | 79 | 'dblclick .userName': (e,t) -> 80 | url = "http://informatics.mccme.ru/moodle/user/view.php?id=" + t.data.result.user 81 | window.open(url, '_blank') 82 | 83 | Template.result.events 84 | 'dblclick .res': (e,t) -> 85 | problem = @table._id.substr(1) 86 | result = Results.findByUserAndTable(@user._id, @table._id) 87 | runId = result.lastSubmitId 88 | runSuff = '' 89 | if runId 90 | runSuff = '&run_id=' + runId 91 | if e.ctrlKey 92 | url = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem + '&submit&user_id=' + @user._id 93 | else 94 | url = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem + runSuff 95 | window.open(url, '_blank') 96 | -------------------------------------------------------------------------------- /server/calculations/userCalcs/submitsByWeekCalc.coffee: -------------------------------------------------------------------------------- 1 | @WEEK_ACTIVITY_EXP = 0.55 2 | @LEVEL_RATING_EXP = 2.5 3 | @ACTIVITY_THRESHOLD = 0.1 4 | 5 | levelVersion = (level) -> 6 | if (level.slice(0,3) == "reg") 7 | major = 3 8 | minor = 'А' 9 | else 10 | major = parseInt(level.slice(0, -1)) 11 | minor = level[level.length - 1] 12 | return { 13 | major: major, 14 | minor: minor 15 | } 16 | 17 | levelScore = (level) -> 18 | v = levelVersion(level) 19 | res = Math.pow(LEVEL_RATING_EXP, v.major) 20 | minorExp = Math.pow(LEVEL_RATING_EXP, 0.25) 21 | if v.minor >= 'Б' 22 | res *= minorExp 23 | if v.minor >= 'В' 24 | res *= minorExp 25 | if v.minor >= 'Г' 26 | res *= minorExp 27 | return res 28 | 29 | findProblemLevel = (problemId) -> 30 | problem = Problems.findById(problemId) 31 | problem?.level 32 | 33 | 34 | timeScore = (date) -> 35 | weeks = (new Date() - date)/MSEC_IN_WEEK 36 | #console.log weeks 37 | return Math.pow(WEEK_ACTIVITY_EXP, weeks) 38 | 39 | activityScore = (level, date) -> 40 | v = levelVersion(level) 41 | return Math.sqrt(v.major) * timeScore(date) 42 | 43 | @calculateRatingEtc = (user) -> 44 | thisStart = new Date(startDayForWeeks[user.userList]) 45 | submits = Submits.findByUser(user._id).fetch() 46 | probSolved = {} 47 | weekSolved = {} 48 | weekOk = {} 49 | wasSubmits = {} 50 | rating = 0 51 | activity = 0 52 | for s in submits 53 | if probSolved[s.problem] 54 | continue 55 | level = findProblemLevel(s.problem) 56 | if not level 57 | continue 58 | submitDate = new Date(s.time) 59 | week = Math.floor((submitDate - thisStart) / MSEC_IN_WEEK) 60 | wasSubmits[week] = true 61 | if s.outcome == "AC" 62 | probSolved[s.problem] = true 63 | if !weekSolved[week] 64 | weekSolved[week] = 0 65 | weekSolved[week]++ 66 | rating += levelScore(level) 67 | activity += activityScore(level, submitDate) 68 | else if s.outcome == "OK" 69 | if !weekOk[week] 70 | weekOk[week] = 0 71 | weekOk[week]++ 72 | for level in ["1А", "1Б"] 73 | #console.log "checking add probs ", level, user.baseLevel 74 | if (!user.baseLevel) or (level >= user.baseLevel) 75 | break 76 | for prob in @Problems.findByLevel(level).fetch() 77 | if probSolved[prob._id] 78 | continue 79 | #console.log "correcting user ", user.name, " problem ", prob 80 | rating += levelScore(level) 81 | for week of wasSubmits 82 | if !weekSolved[week] 83 | weekSolved[week] = 0.5 84 | for w of weekSolved 85 | if w<0 86 | delete weekSolved[w] 87 | for w of weekOk 88 | if w<0 89 | delete weekOk[w] 90 | activity *= (1 - WEEK_ACTIVITY_EXP) # make this averaged 91 | return { 92 | solvedByWeek: weekSolved 93 | okByWeek: weekOk 94 | rating: Math.floor(rating) 95 | activity: activity 96 | ratingSort: if activity > ACTIVITY_THRESHOLD then rating else -1/(rating+1) 97 | active: if activity > ACTIVITY_THRESHOLD then 1 else 0 98 | } 99 | 100 | Meteor.startup -> 101 | # Users.findById("238375").updateRatingEtc( ) 102 | # for u in Users.findAll().fetch() 103 | # u.updateRatingEtc() 104 | # console.log u.name, u.rating, u.activity 105 | -------------------------------------------------------------------------------- /server/downloads/downloadContests.coffee: -------------------------------------------------------------------------------- 1 | class ContestDownloader 2 | url: 'http://informatics.mccme.ru/course/view.php?id=1135' 3 | baseUrl: 'http://informatics.mccme.ru/mod/statements/' 4 | 5 | constructor: -> 6 | @order = 0 7 | 8 | makeProblem: (fullText, href, pid, letter, name) -> 9 | { 10 | _id: "p"+pid 11 | letter: letter 12 | name: name 13 | } 14 | 15 | addContest: (cid, name, level, problems) -> 16 | problemIds = [] 17 | for prob in problems 18 | Problems.addProblem(prob._id, prob.letter, prob.name) 19 | problemIds.push(prob._id) 20 | @order++ 21 | Tables.addTable(cid, name, [], problemIds, level, @order*100) 22 | 23 | processContest: (fullText, href, cid, name, level) -> 24 | text = syncDownload(href).content 25 | console.log href, name, level, @url 26 | re = new RegExp 'Задача ([^.]+)\\. ([^<]+)' 27 | secondProbRes = re.exec text 28 | secondProbHref = secondProbRes[1].replace('&','&') 29 | secondProb = @makeProblem(secondProbRes[0], secondProbRes[1], secondProbRes[2], secondProbRes[3], secondProbRes[4]) 30 | 31 | text = syncDownload(@baseUrl + secondProbHref).content 32 | re = new RegExp 'Задача ([^.]+)\\. ([^<]+)', 'gm' 33 | problems = [] 34 | text.replace re, (res, a, b, c, d) => 35 | console.log "res: ", res 36 | problems.push(@makeProblem(res, a, b, c, d)) 37 | problems.splice(1, 0, secondProb); 38 | @addContest(cid, name, level, problems) 39 | 40 | run: -> 41 | text = syncDownload(@url).content 42 | re = new RegExp '(([^:]*): [^<]*)', 'gm' 43 | text.replace re, (a,b,c,d,e) => @processContest(a,b,c,d,e) 44 | 45 | class RegionContestDownloader extends ContestDownloader 46 | contests: 47 | '2009': ['894', '895'] 48 | '2010': ['1540', '1541'] 49 | '2011': ['2748', '2780'] 50 | '2012': ['4345', '4361'] 51 | '2013': ['6667', '6670'] 52 | '2014': ['10372', '10376'] 53 | '2015': ['14482', '14483'] 54 | '2016': ['18805', '18806'] 55 | '2017': ['24702', '24703'] 56 | 57 | contestBaseUrl: 'http://informatics.mccme.ru/mod/statements/view.php?id=' 58 | 59 | run: -> 60 | levels = [] 61 | for year, cont of @contests 62 | console.log "Downloading contests of ", year, cont 63 | fullText = ' тур региональной олимпиады ' + year + ' года' 64 | @processContest('', @contestBaseUrl + cont[0], cont[0], 'Первый' + fullText, 'reg' + year) 65 | @processContest('', @contestBaseUrl + cont[1], cont[1], 'Второй' + fullText, 'reg' + year) 66 | levels.push('reg' + year) 67 | #id, name, tables, problems, parent, order 68 | Tables.addTable("reg", "reg", levels, [], "main", 10000) 69 | table = Tables.findById("reg") 70 | #users = Users.findAll().fetch() 71 | #for user in users 72 | # Results.updateResults(user, table) 73 | 74 | #SyncedCron.add 75 | # name: 'downloadContests', 76 | # schedule: (parser) -> 77 | # return parser.text('every 10 seconds'); 78 | ## return parser.text('every 5 minutes'); 79 | # job: -> 80 | # (new ContestDownloader()).run() 81 | 82 | 83 | #Meteor.startup -> 84 | # (new ContestDownloader()).run() 85 | # (new RegionContestDownloader()).run() 86 | # Tables.removeDuplicateChildren() 87 | # for u in Users.findAll().fetch() 88 | # tables = Tables.findAll().fetch() 89 | # updateResults(u._id) 90 | # u.updateChocos() 91 | # u.updateRatingEtc() 92 | # u.updateLevel( ) 93 | -------------------------------------------------------------------------------- /server/downloads/downloadSubmits.coffee: -------------------------------------------------------------------------------- 1 | class AllSubmitDownloader 2 | 3 | constructor: (@baseUrl, @userList, @submitsPerPage, @minPages, @limitPages) -> 4 | @addedUsers = {} 5 | @dirtyResults = {} 6 | 7 | AC: 'Зачтено/Принято' 8 | IG: 'Проигнорировано' 9 | DQ: 'Дисквалифицировано' 10 | CE: 'Ошибка компиляции' 11 | 12 | addedUsers: {} 13 | 14 | needContinueFromSubmit: (runid) -> 15 | true 16 | 17 | setDirty: (userId, probid) -> 18 | @dirtyResults[userId + "::" + probid] = 1 19 | problem = Problems.findById(probid) 20 | if not problem 21 | console.log "unknown problem ", probid 22 | return 23 | for table in problem.tables 24 | t = table 25 | while true 26 | t = Tables.findById(t) 27 | if t._id == Tables.main 28 | break 29 | @dirtyResults[userId + "::" + t._id] = 1 30 | t = t.parent 31 | @dirtyResults[userId + "::" + Tables.main] = 1 32 | 33 | processSubmit: (uid, name, pid, runid, prob, date, outcome) -> 34 | #if uid == "230963" 35 | # console.log uid, name, pid, runid, prob, date, outcome 36 | res = @needContinueFromSubmit(runid) 37 | if (outcome == @CE) # completely ignore CEs 38 | outcome = "CE" 39 | if (outcome == @AC) 40 | outcome = "AC" 41 | if (outcome == @IG) 42 | outcome = "IG" 43 | if (outcome == @DQ) 44 | outcome = "DQ" 45 | Submits.addSubmit(runid, date, uid, "p"+pid, outcome) 46 | Users.addUser(uid, name, @userList) 47 | @addedUsers[uid] = uid 48 | @setDirty(uid, "p"+pid) 49 | res 50 | 51 | parseSubmits: (submitsTable, canBreak) -> 52 | submitsRows = submitsTable.split("") 53 | result = true 54 | wasSubmit = false 55 | for row in submitsRows 56 | re = new RegExp '[^<]*\\s*([^<]*)\\s*([^<]*)\\s*([^<]*)\\s*[^<]*\\s*([^<]*)', 'gm' 57 | data = re.exec row 58 | if not data 59 | continue 60 | uid = data[1] 61 | name = data[2] 62 | pid = data[3] 63 | runid = data[4] + "p" + pid 64 | prob = data[5] 65 | date = data[6] 66 | outcome = data[7].trim() 67 | resultSubmit = @processSubmit(uid, name, pid, runid, prob, date, outcome) 68 | result = result and resultSubmit 69 | wasSubmit = true 70 | if (not result) and canBreak 71 | break 72 | return result and wasSubmit 73 | 74 | run: -> 75 | console.log "AllSubmitDownloader::run ", @userList, @submitsPerPage, @minPages, '-', @limitPages 76 | page = 0 77 | while true 78 | submitsUrl = @baseUrl(page, @submitsPerPage) 79 | submits = syncDownload(submitsUrl) 80 | submits = submits["data"]["result"]["text"] 81 | result = @parseSubmits(submits, page >= @minPages) 82 | if (page < @minPages) # always load at least minPages pages 83 | result = true 84 | if not result 85 | break 86 | page = page + 1 87 | if page > @limitPages 88 | break 89 | tables = Tables.findAll().fetch() 90 | for uid,tmp of @addedUsers 91 | updateResults(uid, @dirtyResults) 92 | u = Users.findById(uid) 93 | u.updateChocos() 94 | u.updateRatingEtc() 95 | u.updateLevel() 96 | console.log "Finish AllSubmitDownloader::run ", @userList, @limitPages 97 | 98 | class LastSubmitDownloader extends AllSubmitDownloader 99 | needContinueFromSubmit: (runid) -> 100 | !Submits.findById(runid) 101 | 102 | class UntilIgnoredSubmitDownloader extends AllSubmitDownloader 103 | needContinueFromSubmit: (runid) -> 104 | res = Submits.findById(runid)?.outcome 105 | r = !((res == "AC") || (res == "IG")) 106 | return r 107 | 108 | # Лицей 40 109 | lic40url = (page, submitsPerPage) -> 110 | 'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=5401&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=' + submitsPerPage + '&with_comment=&page=' + page + '&action=getHTMLTable' 111 | # Заоч 112 | zaochUrl = (page, submitsPerPage) -> 113 | 'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=5402&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=' + submitsPerPage + '&with_comment=&page=' + page + '&action=getHTMLTable' 114 | 115 | studUrl = (page, submitsPerPage) -> 116 | 'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=7170&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=' + submitsPerPage + '&with_comment=&page=' + page + '&action=getHTMLTable' 117 | 118 | MSEC_IN_DAY = 24*60*60*1000 119 | 120 | runDownload = -> 121 | now = new Date() 122 | console.log "Starting download" 123 | if Downloads.lastDownloadTime("any") > now - 1000*60*10 124 | console.log "Another download in progress, exiting" 125 | return 126 | Downloads.setLastDownloadTime("any", now) 127 | try 128 | if (Downloads.lastDownloadTime("All") < now - MSEC_IN_DAY) and (now.getUTCHours() <= 3) 129 | console.log "running All" 130 | (new AllSubmitDownloader(lic40url, 'lic40', 1000, 1, 1e9)).run() 131 | (new AllSubmitDownloader(zaochUrl, 'zaoch', 1000, 1, 1e9)).run() 132 | (new AllSubmitDownloader(studUrl, 'stud', 1000, 1, 1e9)).run() 133 | Downloads.setLastDownloadTime("All", now) 134 | else if Downloads.lastDownloadTime("UntilIgnored") < now - 2.9*60*1000 135 | console.log "running UntilIgnored" 136 | (new UntilIgnoredSubmitDownloader(lic40url, 'lic40', 100, 2, 4)).run() 137 | (new UntilIgnoredSubmitDownloader(zaochUrl, 'zaoch', 100, 2, 4)).run() 138 | (new UntilIgnoredSubmitDownloader(studUrl, 'stud', 100, 2, 4)).run() 139 | Downloads.setLastDownloadTime("UntilIgnored", now) 140 | else if Downloads.lastDownloadTime("Last") < now - 0.9*60*1000 141 | console.log "running Last" 142 | (new LastSubmitDownloader(lic40url, 'lic40', 20, 1, 1)).run() 143 | (new LastSubmitDownloader(zaochUrl, 'zaoch', 20, 1, 1)).run() 144 | (new LastSubmitDownloader(studUrl, 'stud', 20, 1, 1)).run() 145 | Downloads.setLastDownloadTime("Last", now) 146 | else 147 | console.log "not running anything" 148 | finally 149 | Downloads.setLastDownloadTime("any", new Date(0)) 150 | 151 | SyncedCron.add 152 | name: 'downloadSubmits', 153 | schedule: (parser) -> 154 | return parser.text('every 30 seconds'); 155 | # return parser.text('every 5 minutes'); 156 | job: -> 157 | console.log("1") 158 | runDownload() 159 | 160 | 161 | Meteor.startup -> 162 | SyncedCron.start() 163 | # (new AllSubmitDownloader(lic40url, 'lic40', 1000, 1, 1e9)).run() 164 | #(new AllSubmitDownloader(zaochUrl, 'zaoch', 40, 1, 1e9)).run() 165 | # for u in Users.findAll().fetch() 166 | # tables = Tables.findAll().fetch() 167 | # for t in tables 168 | # Results.updateResults(u, t) 169 | # u.updateChocos() 170 | # u.updateRatingEtc() 171 | # u.updateLevel( ) 172 | # console.log Submits.problemResult("208403", {_id: "1430"}) 173 | -------------------------------------------------------------------------------- /client/lib/bootstrap/custom.bootstrap.import.less: -------------------------------------------------------------------------------- 1 | // This File is for you to modify! 2 | // It won't be overwritten as long as it exists. 3 | // You may include this file into your less files to benefit from 4 | // mixins and variables that bootstrap provides. 5 | 6 | @import "custom.bootstrap.mixins.import.less"; 7 | 8 | 9 | // @import "bootstrap/less/variables.less" 10 | // 11 | // Variables 12 | // -------------------------------------------------- 13 | 14 | 15 | //== Colors 16 | // 17 | //## Gray and brand colors for use across Bootstrap. 18 | 19 | @gray-base: #000; 20 | @gray-darker: lighten(@gray-base, 13.5%); // #222 21 | @gray-dark: lighten(@gray-base, 20%); // #333 22 | @gray: lighten(@gray-base, 33.5%); // #555 23 | @gray-light: lighten(@gray-base, 46.7%); // #777 24 | @gray-lighter: lighten(@gray-base, 93.5%); // #eee 25 | 26 | @brand-primary: darken(#428bca, 6.5%); // #337ab7 27 | @brand-success: #5cb85c; 28 | @brand-info: #5bc0de; 29 | @brand-warning: #f0ad4e; 30 | @brand-danger: #d9534f; 31 | 32 | 33 | //== Scaffolding 34 | // 35 | //## Settings for some of the most global styles. 36 | 37 | //** Background color for ``. 38 | @body-bg: #fff; 39 | //** Global text color on ``. 40 | @text-color: @gray-dark; 41 | 42 | //** Global textual link color. 43 | @link-color: @brand-primary; 44 | //** Link hover color set via `darken()` function. 45 | @link-hover-color: darken(@link-color, 15%); 46 | //** Link hover decoration. 47 | @link-hover-decoration: underline; 48 | 49 | 50 | //== Typography 51 | // 52 | //## Font, line-height, and color for body text, headings, and more. 53 | 54 | @font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; 55 | @font-family-serif: Georgia, "Times New Roman", Times, serif; 56 | //** Default monospace fonts for ``, ``, and `
`.
 57 | @font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
 58 | @font-family-base:        @font-family-sans-serif;
 59 | 
 60 | @font-size-base:          14px;
 61 | @font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
 62 | @font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
 63 | 
 64 | @font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
 65 | @font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
 66 | @font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
 67 | @font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
 68 | @font-size-h5:            @font-size-base;
 69 | @font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
 70 | 
 71 | //** Unit-less `line-height` for use in components like buttons.
 72 | @line-height-base:        1.428571429; // 20/14
 73 | //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
 74 | @line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
 75 | 
 76 | //** By default, this inherits from the ``.
 77 | @headings-font-family:    inherit;
 78 | @headings-font-weight:    500;
 79 | @headings-line-height:    1.1;
 80 | @headings-color:          inherit;
 81 | 
 82 | 
 83 | //== Iconography
 84 | //
 85 | //## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
 86 | 
 87 | //** Load fonts from this directory.
 88 | @icon-font-path:          "../fonts/";
 89 | //** File name for all font files.
 90 | @icon-font-name:          "glyphicons-halflings-regular";
 91 | //** Element ID within SVG icon file.
 92 | @icon-font-svg-id:        "glyphicons_halflingsregular";
 93 | 
 94 | 
 95 | //== Components
 96 | //
 97 | //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
 98 | 
 99 | @padding-base-vertical:     6px;
100 | @padding-base-horizontal:   12px;
101 | 
102 | @padding-large-vertical:    10px;
103 | @padding-large-horizontal:  16px;
104 | 
105 | @padding-small-vertical:    5px;
106 | @padding-small-horizontal:  10px;
107 | 
108 | @padding-xs-vertical:       1px;
109 | @padding-xs-horizontal:     5px;
110 | 
111 | @line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
112 | @line-height-small:         1.5;
113 | 
114 | @border-radius-base:        4px;
115 | @border-radius-large:       6px;
116 | @border-radius-small:       3px;
117 | 
118 | //** Global color for active items (e.g., navs or dropdowns).
119 | @component-active-color:    #fff;
120 | //** Global background color for active items (e.g., navs or dropdowns).
121 | @component-active-bg:       @brand-primary;
122 | 
123 | //** Width of the `border` for generating carets that indicator dropdowns.
124 | @caret-width-base:          4px;
125 | //** Carets increase slightly in size for larger components.
126 | @caret-width-large:         5px;
127 | 
128 | 
129 | //== Tables
130 | //
131 | //## Customizes the `.table` component with basic values, each used across all table variations.
132 | 
133 | //** Padding for ``s and ``s.
134 | @table-cell-padding:            8px;
135 | //** Padding for cells in `.table-condensed`.
136 | @table-condensed-cell-padding:  5px;
137 | 
138 | //** Default background color used for all tables.
139 | @table-bg:                      transparent;
140 | //** Background color used for `.table-striped`.
141 | @table-bg-accent:               #f9f9f9;
142 | //** Background color used for `.table-hover`.
143 | @table-bg-hover:                #f5f5f5;
144 | @table-bg-active:               @table-bg-hover;
145 | 
146 | //** Border color for table and cell borders.
147 | @table-border-color:            #ddd;
148 | 
149 | 
150 | //== Buttons
151 | //
152 | //## For each of Bootstrap's buttons, define text, background and border color.
153 | 
154 | @btn-font-weight:                normal;
155 | 
156 | @btn-default-color:              #333;
157 | @btn-default-bg:                 #fff;
158 | @btn-default-border:             #ccc;
159 | 
160 | @btn-primary-color:              #fff;
161 | @btn-primary-bg:                 @brand-primary;
162 | @btn-primary-border:             darken(@btn-primary-bg, 5%);
163 | 
164 | @btn-success-color:              #fff;
165 | @btn-success-bg:                 @brand-success;
166 | @btn-success-border:             darken(@btn-success-bg, 5%);
167 | 
168 | @btn-info-color:                 #fff;
169 | @btn-info-bg:                    @brand-info;
170 | @btn-info-border:                darken(@btn-info-bg, 5%);
171 | 
172 | @btn-warning-color:              #fff;
173 | @btn-warning-bg:                 @brand-warning;
174 | @btn-warning-border:             darken(@btn-warning-bg, 5%);
175 | 
176 | @btn-danger-color:               #fff;
177 | @btn-danger-bg:                  @brand-danger;
178 | @btn-danger-border:              darken(@btn-danger-bg, 5%);
179 | 
180 | @btn-link-disabled-color:        @gray-light;
181 | 
182 | // Allows for customizing button radius independently from global border radius
183 | @btn-border-radius-base:         @border-radius-base;
184 | @btn-border-radius-large:        @border-radius-large;
185 | @btn-border-radius-small:        @border-radius-small;
186 | 
187 | 
188 | //== Forms
189 | //
190 | //##
191 | 
192 | //** `` background color
193 | @input-bg:                       #fff;
194 | //** `` background color
195 | @input-bg-disabled:              @gray-lighter;
196 | 
197 | //** Text color for ``s
198 | @input-color:                    @gray;
199 | //** `` border color
200 | @input-border:                   #ccc;
201 | 
202 | // TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
203 | //** Default `.form-control` border radius
204 | // This has no effect on ``s in CSS.
205 | @input-border-radius:            @border-radius-base;
206 | //** Large `.form-control` border radius
207 | @input-border-radius-large:      @border-radius-large;
208 | //** Small `.form-control` border radius
209 | @input-border-radius-small:      @border-radius-small;
210 | 
211 | //** Border color for inputs on focus
212 | @input-border-focus:             #66afe9;
213 | 
214 | //** Placeholder text color
215 | @input-color-placeholder:        #999;
216 | 
217 | //** Default `.form-control` height
218 | @input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
219 | //** Large `.form-control` height
220 | @input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
221 | //** Small `.form-control` height
222 | @input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
223 | 
224 | //** `.form-group` margin
225 | @form-group-margin-bottom:       15px;
226 | 
227 | @legend-color:                   @gray-dark;
228 | @legend-border-color:            #e5e5e5;
229 | 
230 | //** Background color for textual input addons
231 | @input-group-addon-bg:           @gray-lighter;
232 | //** Border color for textual input addons
233 | @input-group-addon-border-color: @input-border;
234 | 
235 | //** Disabled cursor for form controls and buttons.
236 | @cursor-disabled:                not-allowed;
237 | 
238 | 
239 | //== Dropdowns
240 | //
241 | //## Dropdown menu container and contents.
242 | 
243 | //** Background for the dropdown menu.
244 | @dropdown-bg:                    #fff;
245 | //** Dropdown menu `border-color`.
246 | @dropdown-border:                rgba(0,0,0,.15);
247 | //** Dropdown menu `border-color` **for IE8**.
248 | @dropdown-fallback-border:       #ccc;
249 | //** Divider color for between dropdown items.
250 | @dropdown-divider-bg:            #e5e5e5;
251 | 
252 | //** Dropdown link text color.
253 | @dropdown-link-color:            @gray-dark;
254 | //** Hover color for dropdown links.
255 | @dropdown-link-hover-color:      darken(@gray-dark, 5%);
256 | //** Hover background for dropdown links.
257 | @dropdown-link-hover-bg:         #f5f5f5;
258 | 
259 | //** Active dropdown menu item text color.
260 | @dropdown-link-active-color:     @component-active-color;
261 | //** Active dropdown menu item background color.
262 | @dropdown-link-active-bg:        @component-active-bg;
263 | 
264 | //** Disabled dropdown menu item background color.
265 | @dropdown-link-disabled-color:   @gray-light;
266 | 
267 | //** Text color for headers within dropdown menus.
268 | @dropdown-header-color:          @gray-light;
269 | 
270 | //** Deprecated `@dropdown-caret-color` as of v3.1.0
271 | @dropdown-caret-color:           #000;
272 | 
273 | 
274 | //-- Z-index master list
275 | //
276 | // Warning: Avoid customizing these values. They're used for a bird's eye view
277 | // of components dependent on the z-axis and are designed to all work together.
278 | //
279 | // Note: These variables are not generated into the Customizer.
280 | 
281 | @zindex-navbar:            1000;
282 | @zindex-dropdown:          1000;
283 | @zindex-popover:           1060;
284 | @zindex-tooltip:           1070;
285 | @zindex-navbar-fixed:      1030;
286 | @zindex-modal-background:  1040;
287 | @zindex-modal:             1050;
288 | 
289 | 
290 | //== Media queries breakpoints
291 | //
292 | //## Define the breakpoints at which your layout will change, adapting to different screen sizes.
293 | 
294 | // Extra small screen / phone
295 | //** Deprecated `@screen-xs` as of v3.0.1
296 | @screen-xs:                  480px;
297 | //** Deprecated `@screen-xs-min` as of v3.2.0
298 | @screen-xs-min:              @screen-xs;
299 | //** Deprecated `@screen-phone` as of v3.0.1
300 | @screen-phone:               @screen-xs-min;
301 | 
302 | // Small screen / tablet
303 | //** Deprecated `@screen-sm` as of v3.0.1
304 | @screen-sm:                  768px;
305 | @screen-sm-min:              @screen-sm;
306 | //** Deprecated `@screen-tablet` as of v3.0.1
307 | @screen-tablet:              @screen-sm-min;
308 | 
309 | // Medium screen / desktop
310 | //** Deprecated `@screen-md` as of v3.0.1
311 | @screen-md:                  992px;
312 | @screen-md-min:              @screen-md;
313 | //** Deprecated `@screen-desktop` as of v3.0.1
314 | @screen-desktop:             @screen-md-min;
315 | 
316 | // Large screen / wide desktop
317 | //** Deprecated `@screen-lg` as of v3.0.1
318 | @screen-lg:                  1200px;
319 | @screen-lg-min:              @screen-lg;
320 | //** Deprecated `@screen-lg-desktop` as of v3.0.1
321 | @screen-lg-desktop:          @screen-lg-min;
322 | 
323 | // So media queries don't overlap when required, provide a maximum
324 | @screen-xs-max:              (@screen-sm-min - 1);
325 | @screen-sm-max:              (@screen-md-min - 1);
326 | @screen-md-max:              (@screen-lg-min - 1);
327 | 
328 | 
329 | //== Grid system
330 | //
331 | //## Define your custom responsive grid.
332 | 
333 | //** Number of columns in the grid.
334 | @grid-columns:              12;
335 | //** Padding between columns. Gets divided in half for the left and right.
336 | @grid-gutter-width:         30px;
337 | // Navbar collapse
338 | //** Point at which the navbar becomes uncollapsed.
339 | @grid-float-breakpoint:     @screen-sm-min;
340 | //** Point at which the navbar begins collapsing.
341 | @grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
342 | 
343 | 
344 | //== Container sizes
345 | //
346 | //## Define the maximum width of `.container` for different screen sizes.
347 | 
348 | // Small screen / tablet
349 | @container-tablet:             (720px + @grid-gutter-width);
350 | //** For `@screen-sm-min` and up.
351 | @container-sm:                 @container-tablet;
352 | 
353 | // Medium screen / desktop
354 | @container-desktop:            (940px + @grid-gutter-width);
355 | //** For `@screen-md-min` and up.
356 | @container-md:                 @container-desktop;
357 | 
358 | // Large screen / wide desktop
359 | @container-large-desktop:      (1140px + @grid-gutter-width);
360 | //** For `@screen-lg-min` and up.
361 | @container-lg:                 @container-large-desktop;
362 | 
363 | 
364 | //== Navbar
365 | //
366 | //##
367 | 
368 | // Basics of a navbar
369 | @navbar-height:                    50px;
370 | @navbar-margin-bottom:             @line-height-computed;
371 | @navbar-border-radius:             @border-radius-base;
372 | @navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
373 | @navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
374 | @navbar-collapse-max-height:       340px;
375 | 
376 | @navbar-default-color:             #777;
377 | @navbar-default-bg:                #f8f8f8;
378 | @navbar-default-border:            darken(@navbar-default-bg, 6.5%);
379 | 
380 | // Navbar links
381 | @navbar-default-link-color:                #777;
382 | @navbar-default-link-hover-color:          #333;
383 | @navbar-default-link-hover-bg:             transparent;
384 | @navbar-default-link-active-color:         #555;
385 | @navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
386 | @navbar-default-link-disabled-color:       #ccc;
387 | @navbar-default-link-disabled-bg:          transparent;
388 | 
389 | // Navbar brand label
390 | @navbar-default-brand-color:               @navbar-default-link-color;
391 | @navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
392 | @navbar-default-brand-hover-bg:            transparent;
393 | 
394 | // Navbar toggle
395 | @navbar-default-toggle-hover-bg:           #ddd;
396 | @navbar-default-toggle-icon-bar-bg:        #888;
397 | @navbar-default-toggle-border-color:       #ddd;
398 | 
399 | 
400 | //=== Inverted navbar
401 | // Reset inverted navbar basics
402 | @navbar-inverse-color:                      lighten(@gray-light, 15%);
403 | @navbar-inverse-bg:                         #222;
404 | @navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
405 | 
406 | // Inverted navbar links
407 | @navbar-inverse-link-color:                 lighten(@gray-light, 15%);
408 | @navbar-inverse-link-hover-color:           #fff;
409 | @navbar-inverse-link-hover-bg:              transparent;
410 | @navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
411 | @navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
412 | @navbar-inverse-link-disabled-color:        #444;
413 | @navbar-inverse-link-disabled-bg:           transparent;
414 | 
415 | // Inverted navbar brand label
416 | @navbar-inverse-brand-color:                @navbar-inverse-link-color;
417 | @navbar-inverse-brand-hover-color:          #fff;
418 | @navbar-inverse-brand-hover-bg:             transparent;
419 | 
420 | // Inverted navbar toggle
421 | @navbar-inverse-toggle-hover-bg:            #333;
422 | @navbar-inverse-toggle-icon-bar-bg:         #fff;
423 | @navbar-inverse-toggle-border-color:        #333;
424 | 
425 | 
426 | //== Navs
427 | //
428 | //##
429 | 
430 | //=== Shared nav styles
431 | @nav-link-padding:                          10px 15px;
432 | @nav-link-hover-bg:                         @gray-lighter;
433 | 
434 | @nav-disabled-link-color:                   @gray-light;
435 | @nav-disabled-link-hover-color:             @gray-light;
436 | 
437 | //== Tabs
438 | @nav-tabs-border-color:                     #ddd;
439 | 
440 | @nav-tabs-link-hover-border-color:          @gray-lighter;
441 | 
442 | @nav-tabs-active-link-hover-bg:             @body-bg;
443 | @nav-tabs-active-link-hover-color:          @gray;
444 | @nav-tabs-active-link-hover-border-color:   #ddd;
445 | 
446 | @nav-tabs-justified-link-border-color:            #ddd;
447 | @nav-tabs-justified-active-link-border-color:     @body-bg;
448 | 
449 | //== Pills
450 | @nav-pills-border-radius:                   @border-radius-base;
451 | @nav-pills-active-link-hover-bg:            @component-active-bg;
452 | @nav-pills-active-link-hover-color:         @component-active-color;
453 | 
454 | 
455 | //== Pagination
456 | //
457 | //##
458 | 
459 | @pagination-color:                     @link-color;
460 | @pagination-bg:                        #fff;
461 | @pagination-border:                    #ddd;
462 | 
463 | @pagination-hover-color:               @link-hover-color;
464 | @pagination-hover-bg:                  @gray-lighter;
465 | @pagination-hover-border:              #ddd;
466 | 
467 | @pagination-active-color:              #fff;
468 | @pagination-active-bg:                 @brand-primary;
469 | @pagination-active-border:             @brand-primary;
470 | 
471 | @pagination-disabled-color:            @gray-light;
472 | @pagination-disabled-bg:               #fff;
473 | @pagination-disabled-border:           #ddd;
474 | 
475 | 
476 | //== Pager
477 | //
478 | //##
479 | 
480 | @pager-bg:                             @pagination-bg;
481 | @pager-border:                         @pagination-border;
482 | @pager-border-radius:                  15px;
483 | 
484 | @pager-hover-bg:                       @pagination-hover-bg;
485 | 
486 | @pager-active-bg:                      @pagination-active-bg;
487 | @pager-active-color:                   @pagination-active-color;
488 | 
489 | @pager-disabled-color:                 @pagination-disabled-color;
490 | 
491 | 
492 | //== Jumbotron
493 | //
494 | //##
495 | 
496 | @jumbotron-padding:              30px;
497 | @jumbotron-color:                inherit;
498 | @jumbotron-bg:                   @gray-lighter;
499 | @jumbotron-heading-color:        inherit;
500 | @jumbotron-font-size:            ceil((@font-size-base * 1.5));
501 | @jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
502 | 
503 | 
504 | //== Form states and alerts
505 | //
506 | //## Define colors for form feedback states and, by default, alerts.
507 | 
508 | @state-success-text:             #3c763d;
509 | @state-success-bg:               #dff0d8;
510 | @state-success-border:           darken(spin(@state-success-bg, -10), 5%);
511 | 
512 | @state-info-text:                #31708f;
513 | @state-info-bg:                  #d9edf7;
514 | @state-info-border:              darken(spin(@state-info-bg, -10), 7%);
515 | 
516 | @state-warning-text:             #8a6d3b;
517 | @state-warning-bg:               #fcf8e3;
518 | @state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
519 | 
520 | @state-danger-text:              #a94442;
521 | @state-danger-bg:                #f2dede;
522 | @state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
523 | 
524 | 
525 | //== Tooltips
526 | //
527 | //##
528 | 
529 | //** Tooltip max width
530 | @tooltip-max-width:           200px;
531 | //** Tooltip text color
532 | @tooltip-color:               #fff;
533 | //** Tooltip background color
534 | @tooltip-bg:                  #000;
535 | @tooltip-opacity:             .9;
536 | 
537 | //** Tooltip arrow width
538 | @tooltip-arrow-width:         5px;
539 | //** Tooltip arrow color
540 | @tooltip-arrow-color:         @tooltip-bg;
541 | 
542 | 
543 | //== Popovers
544 | //
545 | //##
546 | 
547 | //** Popover body background color
548 | @popover-bg:                          #fff;
549 | //** Popover maximum width
550 | @popover-max-width:                   276px;
551 | //** Popover border color
552 | @popover-border-color:                rgba(0,0,0,.2);
553 | //** Popover fallback border color
554 | @popover-fallback-border-color:       #ccc;
555 | 
556 | //** Popover title background color
557 | @popover-title-bg:                    darken(@popover-bg, 3%);
558 | 
559 | //** Popover arrow width
560 | @popover-arrow-width:                 10px;
561 | //** Popover arrow color
562 | @popover-arrow-color:                 @popover-bg;
563 | 
564 | //** Popover outer arrow width
565 | @popover-arrow-outer-width:           (@popover-arrow-width + 1);
566 | //** Popover outer arrow color
567 | @popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
568 | //** Popover outer arrow fallback color
569 | @popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
570 | 
571 | 
572 | //== Labels
573 | //
574 | //##
575 | 
576 | //** Default label background color
577 | @label-default-bg:            @gray-light;
578 | //** Primary label background color
579 | @label-primary-bg:            @brand-primary;
580 | //** Success label background color
581 | @label-success-bg:            @brand-success;
582 | //** Info label background color
583 | @label-info-bg:               @brand-info;
584 | //** Warning label background color
585 | @label-warning-bg:            @brand-warning;
586 | //** Danger label background color
587 | @label-danger-bg:             @brand-danger;
588 | 
589 | //** Default label text color
590 | @label-color:                 #fff;
591 | //** Default text color of a linked label
592 | @label-link-hover-color:      #fff;
593 | 
594 | 
595 | //== Modals
596 | //
597 | //##
598 | 
599 | //** Padding applied to the modal body
600 | @modal-inner-padding:         15px;
601 | 
602 | //** Padding applied to the modal title
603 | @modal-title-padding:         15px;
604 | //** Modal title line-height
605 | @modal-title-line-height:     @line-height-base;
606 | 
607 | //** Background color of modal content area
608 | @modal-content-bg:                             #fff;
609 | //** Modal content border color
610 | @modal-content-border-color:                   rgba(0,0,0,.2);
611 | //** Modal content border color **for IE8**
612 | @modal-content-fallback-border-color:          #999;
613 | 
614 | //** Modal backdrop background color
615 | @modal-backdrop-bg:           #000;
616 | //** Modal backdrop opacity
617 | @modal-backdrop-opacity:      .5;
618 | //** Modal header border color
619 | @modal-header-border-color:   #e5e5e5;
620 | //** Modal footer border color
621 | @modal-footer-border-color:   @modal-header-border-color;
622 | 
623 | @modal-lg:                    900px;
624 | @modal-md:                    600px;
625 | @modal-sm:                    300px;
626 | 
627 | 
628 | //== Alerts
629 | //
630 | //## Define alert colors, border radius, and padding.
631 | 
632 | @alert-padding:               15px;
633 | @alert-border-radius:         @border-radius-base;
634 | @alert-link-font-weight:      bold;
635 | 
636 | @alert-success-bg:            @state-success-bg;
637 | @alert-success-text:          @state-success-text;
638 | @alert-success-border:        @state-success-border;
639 | 
640 | @alert-info-bg:               @state-info-bg;
641 | @alert-info-text:             @state-info-text;
642 | @alert-info-border:           @state-info-border;
643 | 
644 | @alert-warning-bg:            @state-warning-bg;
645 | @alert-warning-text:          @state-warning-text;
646 | @alert-warning-border:        @state-warning-border;
647 | 
648 | @alert-danger-bg:             @state-danger-bg;
649 | @alert-danger-text:           @state-danger-text;
650 | @alert-danger-border:         @state-danger-border;
651 | 
652 | 
653 | //== Progress bars
654 | //
655 | //##
656 | 
657 | //** Background color of the whole progress component
658 | @progress-bg:                 #f5f5f5;
659 | //** Progress bar text color
660 | @progress-bar-color:          #fff;
661 | //** Variable for setting rounded corners on progress bar.
662 | @progress-border-radius:      @border-radius-base;
663 | 
664 | //** Default progress bar color
665 | @progress-bar-bg:             @brand-primary;
666 | //** Success progress bar color
667 | @progress-bar-success-bg:     @brand-success;
668 | //** Warning progress bar color
669 | @progress-bar-warning-bg:     @brand-warning;
670 | //** Danger progress bar color
671 | @progress-bar-danger-bg:      @brand-danger;
672 | //** Info progress bar color
673 | @progress-bar-info-bg:        @brand-info;
674 | 
675 | 
676 | //== List group
677 | //
678 | //##
679 | 
680 | //** Background color on `.list-group-item`
681 | @list-group-bg:                 #fff;
682 | //** `.list-group-item` border color
683 | @list-group-border:             #ddd;
684 | //** List group border radius
685 | @list-group-border-radius:      @border-radius-base;
686 | 
687 | //** Background color of single list items on hover
688 | @list-group-hover-bg:           #f5f5f5;
689 | //** Text color of active list items
690 | @list-group-active-color:       @component-active-color;
691 | //** Background color of active list items
692 | @list-group-active-bg:          @component-active-bg;
693 | //** Border color of active list elements
694 | @list-group-active-border:      @list-group-active-bg;
695 | //** Text color for content within active list items
696 | @list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
697 | 
698 | //** Text color of disabled list items
699 | @list-group-disabled-color:      @gray-light;
700 | //** Background color of disabled list items
701 | @list-group-disabled-bg:         @gray-lighter;
702 | //** Text color for content within disabled list items
703 | @list-group-disabled-text-color: @list-group-disabled-color;
704 | 
705 | @list-group-link-color:         #555;
706 | @list-group-link-hover-color:   @list-group-link-color;
707 | @list-group-link-heading-color: #333;
708 | 
709 | 
710 | //== Panels
711 | //
712 | //##
713 | 
714 | @panel-bg:                    #fff;
715 | @panel-body-padding:          15px;
716 | @panel-heading-padding:       10px 15px;
717 | @panel-footer-padding:        @panel-heading-padding;
718 | @panel-border-radius:         @border-radius-base;
719 | 
720 | //** Border color for elements within panels
721 | @panel-inner-border:          #ddd;
722 | @panel-footer-bg:             #f5f5f5;
723 | 
724 | @panel-default-text:          @gray-dark;
725 | @panel-default-border:        #ddd;
726 | @panel-default-heading-bg:    #f5f5f5;
727 | 
728 | @panel-primary-text:          #fff;
729 | @panel-primary-border:        @brand-primary;
730 | @panel-primary-heading-bg:    @brand-primary;
731 | 
732 | @panel-success-text:          @state-success-text;
733 | @panel-success-border:        @state-success-border;
734 | @panel-success-heading-bg:    @state-success-bg;
735 | 
736 | @panel-info-text:             @state-info-text;
737 | @panel-info-border:           @state-info-border;
738 | @panel-info-heading-bg:       @state-info-bg;
739 | 
740 | @panel-warning-text:          @state-warning-text;
741 | @panel-warning-border:        @state-warning-border;
742 | @panel-warning-heading-bg:    @state-warning-bg;
743 | 
744 | @panel-danger-text:           @state-danger-text;
745 | @panel-danger-border:         @state-danger-border;
746 | @panel-danger-heading-bg:     @state-danger-bg;
747 | 
748 | 
749 | //== Thumbnails
750 | //
751 | //##
752 | 
753 | //** Padding around the thumbnail image
754 | @thumbnail-padding:           4px;
755 | //** Thumbnail background color
756 | @thumbnail-bg:                @body-bg;
757 | //** Thumbnail border color
758 | @thumbnail-border:            #ddd;
759 | //** Thumbnail border radius
760 | @thumbnail-border-radius:     @border-radius-base;
761 | 
762 | //** Custom text color for thumbnail captions
763 | @thumbnail-caption-color:     @text-color;
764 | //** Padding around the thumbnail caption
765 | @thumbnail-caption-padding:   9px;
766 | 
767 | 
768 | //== Wells
769 | //
770 | //##
771 | 
772 | @well-bg:                     #f5f5f5;
773 | @well-border:                 darken(@well-bg, 7%);
774 | 
775 | 
776 | //== Badges
777 | //
778 | //##
779 | 
780 | @badge-color:                 #fff;
781 | //** Linked badge text color on hover
782 | @badge-link-hover-color:      #fff;
783 | @badge-bg:                    @gray-light;
784 | 
785 | //** Badge text color in active nav link
786 | @badge-active-color:          @link-color;
787 | //** Badge background color in active nav link
788 | @badge-active-bg:             #fff;
789 | 
790 | @badge-font-weight:           bold;
791 | @badge-line-height:           1;
792 | @badge-border-radius:         10px;
793 | 
794 | 
795 | //== Breadcrumbs
796 | //
797 | //##
798 | 
799 | @breadcrumb-padding-vertical:   8px;
800 | @breadcrumb-padding-horizontal: 15px;
801 | //** Breadcrumb background color
802 | @breadcrumb-bg:                 #f5f5f5;
803 | //** Breadcrumb text color
804 | @breadcrumb-color:              #ccc;
805 | //** Text color of current page in the breadcrumb
806 | @breadcrumb-active-color:       @gray-light;
807 | //** Textual separator for between breadcrumb elements
808 | @breadcrumb-separator:          "/";
809 | 
810 | 
811 | //== Carousel
812 | //
813 | //##
814 | 
815 | @carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
816 | 
817 | @carousel-control-color:                      #fff;
818 | @carousel-control-width:                      15%;
819 | @carousel-control-opacity:                    .5;
820 | @carousel-control-font-size:                  20px;
821 | 
822 | @carousel-indicator-active-bg:                #fff;
823 | @carousel-indicator-border-color:             #fff;
824 | 
825 | @carousel-caption-color:                      #fff;
826 | 
827 | 
828 | //== Close
829 | //
830 | //##
831 | 
832 | @close-font-weight:           bold;
833 | @close-color:                 #000;
834 | @close-text-shadow:           0 1px 0 #fff;
835 | 
836 | 
837 | //== Code
838 | //
839 | //##
840 | 
841 | @code-color:                  #c7254e;
842 | @code-bg:                     #f9f2f4;
843 | 
844 | @kbd-color:                   #fff;
845 | @kbd-bg:                      #333;
846 | 
847 | @pre-bg:                      #f5f5f5;
848 | @pre-color:                   @gray-dark;
849 | @pre-border-color:            #ccc;
850 | @pre-scrollable-max-height:   340px;
851 | 
852 | 
853 | //== Type
854 | //
855 | //##
856 | 
857 | //** Horizontal offset for forms and lists.
858 | @component-offset-horizontal: 180px;
859 | //** Text muted color
860 | @text-muted:                  @gray-light;
861 | //** Abbreviations and acronyms border color
862 | @abbr-border-color:           @gray-light;
863 | //** Headings small color
864 | @headings-small-color:        @gray-light;
865 | //** Blockquote small color
866 | @blockquote-small-color:      @gray-light;
867 | //** Blockquote font size
868 | @blockquote-font-size:        (@font-size-base * 1.25);
869 | //** Blockquote border color
870 | @blockquote-border-color:     @gray-lighter;
871 | //** Page header border color
872 | @page-header-border-color:    @gray-lighter;
873 | //** Width of horizontal description list titles
874 | @dl-horizontal-offset:        @component-offset-horizontal;
875 | //** Horizontal line color.
876 | @hr-border:                   @gray-lighter;
877 | 


--------------------------------------------------------------------------------
/client/lib/bootstrap/custom.bootstrap.mixins.import.less:
--------------------------------------------------------------------------------
   1 | // THIS FILE IS GENERATED, DO NOT MODIFY IT!
   2 | // These are the mixins bootstrap provides
   3 | // They are included here so you can use them in your less files too,
   4 | // However: you should @import "custom.bootstrap.import.less" instead of this
   5 | 
   6 | 
   7 | // @import "bootstrap/less/mixins.less"
   8 | // Mixins
   9 | // --------------------------------------------------
  10 | 
  11 | // Utilities
  12 | 
  13 | 
  14 | // @import "bootstrap/less/mixins/hide-text.less"
  15 | // CSS image replacement
  16 | //
  17 | // Heads up! v3 launched with only `.hide-text()`, but per our pattern for
  18 | // mixins being reused as classes with the same name, this doesn't hold up. As
  19 | // of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
  20 | //
  21 | // Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
  22 | 
  23 | // Deprecated as of v3.0.1 (will be removed in v4)
  24 | .hide-text() {
  25 |   font: ~"0/0" a;
  26 |   color: transparent;
  27 |   text-shadow: none;
  28 |   background-color: transparent;
  29 |   border: 0;
  30 | }
  31 | 
  32 | // New mixin to use as of v3.0.1
  33 | .text-hide() {
  34 |   .hide-text();
  35 | }
  36 | 
  37 | 
  38 | 
  39 | // @import "bootstrap/less/mixins/opacity.less"
  40 | // Opacity
  41 | 
  42 | .opacity(@opacity) {
  43 |   opacity: @opacity;
  44 |   // IE8 filter
  45 |   @opacity-ie: (@opacity * 100);
  46 |   filter: ~"alpha(opacity=@{opacity-ie})";
  47 | }
  48 | 
  49 | 
  50 | 
  51 | // @import "bootstrap/less/mixins/image.less"
  52 | // Image Mixins
  53 | // - Responsive image
  54 | // - Retina image
  55 | 
  56 | 
  57 | // Responsive image
  58 | //
  59 | // Keep images from scaling beyond the width of their parents.
  60 | .img-responsive(@display: block) {
  61 |   display: @display;
  62 |   max-width: 100%; // Part 1: Set a maximum relative to the parent
  63 |   height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
  64 | }
  65 | 
  66 | 
  67 | // Retina image
  68 | //
  69 | // Short retina mixin for setting background-image and -size. Note that the
  70 | // spelling of `min--moz-device-pixel-ratio` is intentional.
  71 | .img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
  72 |   background-image: url("@{file-1x}");
  73 | 
  74 |   @media
  75 |   only screen and (-webkit-min-device-pixel-ratio: 2),
  76 |   only screen and (   min--moz-device-pixel-ratio: 2),
  77 |   only screen and (     -o-min-device-pixel-ratio: 2/1),
  78 |   only screen and (        min-device-pixel-ratio: 2),
  79 |   only screen and (                min-resolution: 192dpi),
  80 |   only screen and (                min-resolution: 2dppx) {
  81 |     background-image: url("@{file-2x}");
  82 |     background-size: @width-1x @height-1x;
  83 |   }
  84 | }
  85 | 
  86 | 
  87 | 
  88 | // @import "bootstrap/less/mixins/labels.less"
  89 | // Labels
  90 | 
  91 | .label-variant(@color) {
  92 |   background-color: @color;
  93 | 
  94 |   &[href] {
  95 |     &:hover,
  96 |     &:focus {
  97 |       background-color: darken(@color, 10%);
  98 |     }
  99 |   }
 100 | }
 101 | 
 102 | 
 103 | 
 104 | // @import "bootstrap/less/mixins/reset-filter.less"
 105 | // Reset filters for IE
 106 | //
 107 | // When you need to remove a gradient background, do not forget to use this to reset
 108 | // the IE filter for IE9 and below.
 109 | 
 110 | .reset-filter() {
 111 |   filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
 112 | }
 113 | 
 114 | 
 115 | 
 116 | // @import "bootstrap/less/mixins/resize.less"
 117 | // Resize anything
 118 | 
 119 | .resizable(@direction) {
 120 |   resize: @direction; // Options: horizontal, vertical, both
 121 |   overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
 122 | }
 123 | 
 124 | 
 125 | 
 126 | // @import "bootstrap/less/mixins/responsive-visibility.less"
 127 | // Responsive utilities
 128 | 
 129 | //
 130 | // More easily include all the states for responsive-utilities.less.
 131 | .responsive-visibility() {
 132 |   display: block !important;
 133 |   table&  { display: table !important; }
 134 |   tr&     { display: table-row !important; }
 135 |   th&,
 136 |   td&     { display: table-cell !important; }
 137 | }
 138 | 
 139 | .responsive-invisibility() {
 140 |   display: none !important;
 141 | }
 142 | 
 143 | 
 144 | 
 145 | // @import "bootstrap/less/mixins/size.less"
 146 | // Sizing shortcuts
 147 | 
 148 | .size(@width; @height) {
 149 |   width: @width;
 150 |   height: @height;
 151 | }
 152 | 
 153 | .square(@size) {
 154 |   .size(@size; @size);
 155 | }
 156 | 
 157 | 
 158 | 
 159 | // @import "bootstrap/less/mixins/tab-focus.less"
 160 | // WebKit-style focus
 161 | 
 162 | .tab-focus() {
 163 |   // Default
 164 |   outline: thin dotted;
 165 |   // WebKit
 166 |   outline: 5px auto -webkit-focus-ring-color;
 167 |   outline-offset: -2px;
 168 | }
 169 | 
 170 | 
 171 | 
 172 | // @import "bootstrap/less/mixins/reset-text.less"
 173 | .reset-text() {
 174 |   font-family: @font-family-base;
 175 |   // We deliberately do NOT reset font-size.
 176 |   font-style: normal;
 177 |   font-weight: normal;
 178 |   letter-spacing: normal;
 179 |   line-break: auto;
 180 |   line-height: @line-height-base;
 181 |   text-align: left; // Fallback for where `start` is not supported
 182 |   text-align: start;
 183 |   text-decoration: none;
 184 |   text-shadow: none;
 185 |   text-transform: none;
 186 |   white-space: normal;
 187 |   word-break: normal;
 188 |   word-spacing: normal;
 189 |   word-wrap: normal;
 190 | }
 191 | 
 192 | 
 193 | 
 194 | // @import "bootstrap/less/mixins/text-emphasis.less"
 195 | // Typography
 196 | 
 197 | .text-emphasis-variant(@color) {
 198 |   color: @color;
 199 |   a&:hover,
 200 |   a&:focus {
 201 |     color: darken(@color, 10%);
 202 |   }
 203 | }
 204 | 
 205 | 
 206 | 
 207 | // @import "bootstrap/less/mixins/text-overflow.less"
 208 | // Text overflow
 209 | // Requires inline-block or block for proper styling
 210 | 
 211 | .text-overflow() {
 212 |   overflow: hidden;
 213 |   text-overflow: ellipsis;
 214 |   white-space: nowrap;
 215 | }
 216 | 
 217 | 
 218 | 
 219 | // @import "bootstrap/less/mixins/vendor-prefixes.less"
 220 | // Vendor Prefixes
 221 | //
 222 | // All vendor mixins are deprecated as of v3.2.0 due to the introduction of
 223 | // Autoprefixer in our Gruntfile. They will be removed in v4.
 224 | 
 225 | // - Animations
 226 | // - Backface visibility
 227 | // - Box shadow
 228 | // - Box sizing
 229 | // - Content columns
 230 | // - Hyphens
 231 | // - Placeholder text
 232 | // - Transformations
 233 | // - Transitions
 234 | // - User Select
 235 | 
 236 | 
 237 | // Animations
 238 | .animation(@animation) {
 239 |   -webkit-animation: @animation;
 240 |        -o-animation: @animation;
 241 |           animation: @animation;
 242 | }
 243 | .animation-name(@name) {
 244 |   -webkit-animation-name: @name;
 245 |           animation-name: @name;
 246 | }
 247 | .animation-duration(@duration) {
 248 |   -webkit-animation-duration: @duration;
 249 |           animation-duration: @duration;
 250 | }
 251 | .animation-timing-function(@timing-function) {
 252 |   -webkit-animation-timing-function: @timing-function;
 253 |           animation-timing-function: @timing-function;
 254 | }
 255 | .animation-delay(@delay) {
 256 |   -webkit-animation-delay: @delay;
 257 |           animation-delay: @delay;
 258 | }
 259 | .animation-iteration-count(@iteration-count) {
 260 |   -webkit-animation-iteration-count: @iteration-count;
 261 |           animation-iteration-count: @iteration-count;
 262 | }
 263 | .animation-direction(@direction) {
 264 |   -webkit-animation-direction: @direction;
 265 |           animation-direction: @direction;
 266 | }
 267 | .animation-fill-mode(@fill-mode) {
 268 |   -webkit-animation-fill-mode: @fill-mode;
 269 |           animation-fill-mode: @fill-mode;
 270 | }
 271 | 
 272 | // Backface visibility
 273 | // Prevent browsers from flickering when using CSS 3D transforms.
 274 | // Default value is `visible`, but can be changed to `hidden`
 275 | 
 276 | .backface-visibility(@visibility){
 277 |   -webkit-backface-visibility: @visibility;
 278 |      -moz-backface-visibility: @visibility;
 279 |           backface-visibility: @visibility;
 280 | }
 281 | 
 282 | // Drop shadows
 283 | //
 284 | // Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
 285 | // supported browsers that have box shadow capabilities now support it.
 286 | 
 287 | .box-shadow(@shadow) {
 288 |   -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
 289 |           box-shadow: @shadow;
 290 | }
 291 | 
 292 | // Box sizing
 293 | .box-sizing(@boxmodel) {
 294 |   -webkit-box-sizing: @boxmodel;
 295 |      -moz-box-sizing: @boxmodel;
 296 |           box-sizing: @boxmodel;
 297 | }
 298 | 
 299 | // CSS3 Content Columns
 300 | .content-columns(@column-count; @column-gap: @grid-gutter-width) {
 301 |   -webkit-column-count: @column-count;
 302 |      -moz-column-count: @column-count;
 303 |           column-count: @column-count;
 304 |   -webkit-column-gap: @column-gap;
 305 |      -moz-column-gap: @column-gap;
 306 |           column-gap: @column-gap;
 307 | }
 308 | 
 309 | // Optional hyphenation
 310 | .hyphens(@mode: auto) {
 311 |   word-wrap: break-word;
 312 |   -webkit-hyphens: @mode;
 313 |      -moz-hyphens: @mode;
 314 |       -ms-hyphens: @mode; // IE10+
 315 |        -o-hyphens: @mode;
 316 |           hyphens: @mode;
 317 | }
 318 | 
 319 | // Placeholder text
 320 | .placeholder(@color: @input-color-placeholder) {
 321 |   // Firefox
 322 |   &::-moz-placeholder {
 323 |     color: @color;
 324 |     opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
 325 |   }
 326 |   &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+
 327 |   &::-webkit-input-placeholder  { color: @color; } // Safari and Chrome
 328 | }
 329 | 
 330 | // Transformations
 331 | .scale(@ratio) {
 332 |   -webkit-transform: scale(@ratio);
 333 |       -ms-transform: scale(@ratio); // IE9 only
 334 |        -o-transform: scale(@ratio);
 335 |           transform: scale(@ratio);
 336 | }
 337 | .scale(@ratioX; @ratioY) {
 338 |   -webkit-transform: scale(@ratioX, @ratioY);
 339 |       -ms-transform: scale(@ratioX, @ratioY); // IE9 only
 340 |        -o-transform: scale(@ratioX, @ratioY);
 341 |           transform: scale(@ratioX, @ratioY);
 342 | }
 343 | .scaleX(@ratio) {
 344 |   -webkit-transform: scaleX(@ratio);
 345 |       -ms-transform: scaleX(@ratio); // IE9 only
 346 |        -o-transform: scaleX(@ratio);
 347 |           transform: scaleX(@ratio);
 348 | }
 349 | .scaleY(@ratio) {
 350 |   -webkit-transform: scaleY(@ratio);
 351 |       -ms-transform: scaleY(@ratio); // IE9 only
 352 |        -o-transform: scaleY(@ratio);
 353 |           transform: scaleY(@ratio);
 354 | }
 355 | .skew(@x; @y) {
 356 |   -webkit-transform: skewX(@x) skewY(@y);
 357 |       -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
 358 |        -o-transform: skewX(@x) skewY(@y);
 359 |           transform: skewX(@x) skewY(@y);
 360 | }
 361 | .translate(@x; @y) {
 362 |   -webkit-transform: translate(@x, @y);
 363 |       -ms-transform: translate(@x, @y); // IE9 only
 364 |        -o-transform: translate(@x, @y);
 365 |           transform: translate(@x, @y);
 366 | }
 367 | .translate3d(@x; @y; @z) {
 368 |   -webkit-transform: translate3d(@x, @y, @z);
 369 |           transform: translate3d(@x, @y, @z);
 370 | }
 371 | .rotate(@degrees) {
 372 |   -webkit-transform: rotate(@degrees);
 373 |       -ms-transform: rotate(@degrees); // IE9 only
 374 |        -o-transform: rotate(@degrees);
 375 |           transform: rotate(@degrees);
 376 | }
 377 | .rotateX(@degrees) {
 378 |   -webkit-transform: rotateX(@degrees);
 379 |       -ms-transform: rotateX(@degrees); // IE9 only
 380 |        -o-transform: rotateX(@degrees);
 381 |           transform: rotateX(@degrees);
 382 | }
 383 | .rotateY(@degrees) {
 384 |   -webkit-transform: rotateY(@degrees);
 385 |       -ms-transform: rotateY(@degrees); // IE9 only
 386 |        -o-transform: rotateY(@degrees);
 387 |           transform: rotateY(@degrees);
 388 | }
 389 | .perspective(@perspective) {
 390 |   -webkit-perspective: @perspective;
 391 |      -moz-perspective: @perspective;
 392 |           perspective: @perspective;
 393 | }
 394 | .perspective-origin(@perspective) {
 395 |   -webkit-perspective-origin: @perspective;
 396 |      -moz-perspective-origin: @perspective;
 397 |           perspective-origin: @perspective;
 398 | }
 399 | .transform-origin(@origin) {
 400 |   -webkit-transform-origin: @origin;
 401 |      -moz-transform-origin: @origin;
 402 |       -ms-transform-origin: @origin; // IE9 only
 403 |           transform-origin: @origin;
 404 | }
 405 | 
 406 | 
 407 | // Transitions
 408 | 
 409 | .transition(@transition) {
 410 |   -webkit-transition: @transition;
 411 |        -o-transition: @transition;
 412 |           transition: @transition;
 413 | }
 414 | .transition-property(@transition-property) {
 415 |   -webkit-transition-property: @transition-property;
 416 |           transition-property: @transition-property;
 417 | }
 418 | .transition-delay(@transition-delay) {
 419 |   -webkit-transition-delay: @transition-delay;
 420 |           transition-delay: @transition-delay;
 421 | }
 422 | .transition-duration(@transition-duration) {
 423 |   -webkit-transition-duration: @transition-duration;
 424 |           transition-duration: @transition-duration;
 425 | }
 426 | .transition-timing-function(@timing-function) {
 427 |   -webkit-transition-timing-function: @timing-function;
 428 |           transition-timing-function: @timing-function;
 429 | }
 430 | .transition-transform(@transition) {
 431 |   -webkit-transition: -webkit-transform @transition;
 432 |      -moz-transition: -moz-transform @transition;
 433 |        -o-transition: -o-transform @transition;
 434 |           transition: transform @transition;
 435 | }
 436 | 
 437 | 
 438 | // User select
 439 | // For selecting text on the page
 440 | 
 441 | .user-select(@select) {
 442 |   -webkit-user-select: @select;
 443 |      -moz-user-select: @select;
 444 |       -ms-user-select: @select; // IE10+
 445 |           user-select: @select;
 446 | }
 447 | 
 448 | 
 449 | // Components
 450 | 
 451 | 
 452 | // @import "bootstrap/less/mixins/alerts.less"
 453 | // Alerts
 454 | 
 455 | .alert-variant(@background; @border; @text-color) {
 456 |   background-color: @background;
 457 |   border-color: @border;
 458 |   color: @text-color;
 459 | 
 460 |   hr {
 461 |     border-top-color: darken(@border, 5%);
 462 |   }
 463 |   .alert-link {
 464 |     color: darken(@text-color, 10%);
 465 |   }
 466 | }
 467 | 
 468 | 
 469 | 
 470 | // @import "bootstrap/less/mixins/buttons.less"
 471 | // Button variants
 472 | //
 473 | // Easily pump out default styles, as well as :hover, :focus, :active,
 474 | // and disabled options for all buttons
 475 | 
 476 | .button-variant(@color; @background; @border) {
 477 |   color: @color;
 478 |   background-color: @background;
 479 |   border-color: @border;
 480 | 
 481 |   &:focus,
 482 |   &.focus {
 483 |     color: @color;
 484 |     background-color: darken(@background, 10%);
 485 |         border-color: darken(@border, 25%);
 486 |   }
 487 |   &:hover {
 488 |     color: @color;
 489 |     background-color: darken(@background, 10%);
 490 |         border-color: darken(@border, 12%);
 491 |   }
 492 |   &:active,
 493 |   &.active,
 494 |   .open > .dropdown-toggle& {
 495 |     color: @color;
 496 |     background-color: darken(@background, 10%);
 497 |         border-color: darken(@border, 12%);
 498 | 
 499 |     &:hover,
 500 |     &:focus,
 501 |     &.focus {
 502 |       color: @color;
 503 |       background-color: darken(@background, 17%);
 504 |           border-color: darken(@border, 25%);
 505 |     }
 506 |   }
 507 |   &:active,
 508 |   &.active,
 509 |   .open > .dropdown-toggle& {
 510 |     background-image: none;
 511 |   }
 512 |   &.disabled,
 513 |   &[disabled],
 514 |   fieldset[disabled] & {
 515 |     &,
 516 |     &:hover,
 517 |     &:focus,
 518 |     &.focus,
 519 |     &:active,
 520 |     &.active {
 521 |       background-color: @background;
 522 |           border-color: @border;
 523 |     }
 524 |   }
 525 | 
 526 |   .badge {
 527 |     color: @background;
 528 |     background-color: @color;
 529 |   }
 530 | }
 531 | 
 532 | // Button sizes
 533 | .button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
 534 |   padding: @padding-vertical @padding-horizontal;
 535 |   font-size: @font-size;
 536 |   line-height: @line-height;
 537 |   border-radius: @border-radius;
 538 | }
 539 | 
 540 | 
 541 | 
 542 | // @import "bootstrap/less/mixins/panels.less"
 543 | // Panels
 544 | 
 545 | .panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {
 546 |   border-color: @border;
 547 | 
 548 |   & > .panel-heading {
 549 |     color: @heading-text-color;
 550 |     background-color: @heading-bg-color;
 551 |     border-color: @heading-border;
 552 | 
 553 |     + .panel-collapse > .panel-body {
 554 |       border-top-color: @border;
 555 |     }
 556 |     .badge {
 557 |       color: @heading-bg-color;
 558 |       background-color: @heading-text-color;
 559 |     }
 560 |   }
 561 |   & > .panel-footer {
 562 |     + .panel-collapse > .panel-body {
 563 |       border-bottom-color: @border;
 564 |     }
 565 |   }
 566 | }
 567 | 
 568 | 
 569 | 
 570 | // @import "bootstrap/less/mixins/pagination.less"
 571 | // Pagination
 572 | 
 573 | .pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
 574 |   > li {
 575 |     > a,
 576 |     > span {
 577 |       padding: @padding-vertical @padding-horizontal;
 578 |       font-size: @font-size;
 579 |       line-height: @line-height;
 580 |     }
 581 |     &:first-child {
 582 |       > a,
 583 |       > span {
 584 |         .border-left-radius(@border-radius);
 585 |       }
 586 |     }
 587 |     &:last-child {
 588 |       > a,
 589 |       > span {
 590 |         .border-right-radius(@border-radius);
 591 |       }
 592 |     }
 593 |   }
 594 | }
 595 | 
 596 | 
 597 | 
 598 | // @import "bootstrap/less/mixins/list-group.less"
 599 | // List Groups
 600 | 
 601 | .list-group-item-variant(@state; @background; @color) {
 602 |   .list-group-item-@{state} {
 603 |     color: @color;
 604 |     background-color: @background;
 605 | 
 606 |     a&,
 607 |     button& {
 608 |       color: @color;
 609 | 
 610 |       .list-group-item-heading {
 611 |         color: inherit;
 612 |       }
 613 | 
 614 |       &:hover,
 615 |       &:focus {
 616 |         color: @color;
 617 |         background-color: darken(@background, 5%);
 618 |       }
 619 |       &.active,
 620 |       &.active:hover,
 621 |       &.active:focus {
 622 |         color: #fff;
 623 |         background-color: @color;
 624 |         border-color: @color;
 625 |       }
 626 |     }
 627 |   }
 628 | }
 629 | 
 630 | 
 631 | 
 632 | // @import "bootstrap/less/mixins/nav-divider.less"
 633 | // Horizontal dividers
 634 | //
 635 | // Dividers (basically an hr) within dropdowns and nav lists
 636 | 
 637 | .nav-divider(@color: #e5e5e5) {
 638 |   height: 1px;
 639 |   margin: ((@line-height-computed / 2) - 1) 0;
 640 |   overflow: hidden;
 641 |   background-color: @color;
 642 | }
 643 | 
 644 | 
 645 | 
 646 | // @import "bootstrap/less/mixins/forms.less"
 647 | // Form validation states
 648 | //
 649 | // Used in forms.less to generate the form validation CSS for warnings, errors,
 650 | // and successes.
 651 | 
 652 | .form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
 653 |   // Color the label and help text
 654 |   .help-block,
 655 |   .control-label,
 656 |   .radio,
 657 |   .checkbox,
 658 |   .radio-inline,
 659 |   .checkbox-inline,
 660 |   &.radio label,
 661 |   &.checkbox label,
 662 |   &.radio-inline label,
 663 |   &.checkbox-inline label  {
 664 |     color: @text-color;
 665 |   }
 666 |   // Set the border and box shadow on specific inputs to match
 667 |   .form-control {
 668 |     border-color: @border-color;
 669 |     .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
 670 |     &:focus {
 671 |       border-color: darken(@border-color, 10%);
 672 |       @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);
 673 |       .box-shadow(@shadow);
 674 |     }
 675 |   }
 676 |   // Set validation states also for addons
 677 |   .input-group-addon {
 678 |     color: @text-color;
 679 |     border-color: @border-color;
 680 |     background-color: @background-color;
 681 |   }
 682 |   // Optional feedback icon
 683 |   .form-control-feedback {
 684 |     color: @text-color;
 685 |   }
 686 | }
 687 | 
 688 | 
 689 | // Form control focus state
 690 | //
 691 | // Generate a customized focus state and for any input with the specified color,
 692 | // which defaults to the `@input-border-focus` variable.
 693 | //
 694 | // We highly encourage you to not customize the default value, but instead use
 695 | // this to tweak colors on an as-needed basis. This aesthetic change is based on
 696 | // WebKit's default styles, but applicable to a wider range of browsers. Its
 697 | // usability and accessibility should be taken into account with any change.
 698 | //
 699 | // Example usage: change the default blue border and shadow to white for better
 700 | // contrast against a dark gray background.
 701 | .form-control-focus(@color: @input-border-focus) {
 702 |   @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
 703 |   &:focus {
 704 |     border-color: @color;
 705 |     outline: 0;
 706 |     .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
 707 |   }
 708 | }
 709 | 
 710 | // Form control sizing
 711 | //
 712 | // Relative text size, padding, and border-radii changes for form controls. For
 713 | // horizontal sizing, wrap controls in the predefined grid classes. `