// ==UserScript==
// @name cf-append-form
// @name:ja cf-append-form
// @namespace https://twitter.com/lumc_
// @version 1.0
// @description append the form to submit to codeforces contest problem page.
// @description:ja codeforcesのコンテストの問題ページに提出フォームを置くツール.
// @author Luma
// @match http*://codeforces.com/contest/*/problem/*
// @grant none
// ==/UserScript==
/* global $ ace alwaysDisable Codeforces */
;(function () {
'use strict'
const origin = location.origin
const pathname = location.pathname
const modelist = ace.require('ace/ext/modelist')
let $form
let $programType
let $toggleEditor
let $tabSize
let $selectProblem
let contestId
let participantId
let editor
// got from submit page
/* eslint : */
const extensionMap = {
1: 'program.cpp',
2: 'program.cpp',
3: 'program.dpr',
4: 'program.pas',
6: 'program.php',
7: 'program.py',
8: 'program.rb',
9: 'program.cs',
10: 'program.c',
12: 'program.hs',
13: 'program.pl',
19: 'program.ml',
20: '[^{}]*objects+(w+).*|$1.scala',
28: 'program.d',
31: 'a.py',
32: 'program.go',
34: 'program.js',
36: '[^{}]*publics+(final)?s*classs+(w+).*|$2.java',
40: 'a.py',
41: 'a.py',
42: 'program.cpp',
43: 'program.c',
48: 'program.kt',
49: 'program.rs',
50: 'program.cpp',
51: 'program.pas',
52: 'program.cpp',
53: 'program.cpp',
54: 'program.cpp',
55: 'program.js'
}
initAppendForm()
async function initAppendForm () {
// only problem page
const pattern = /(.*)\/problem\/([^/])*\/?$/
if (!pattern.test(pathname)) return
const submitURL = origin + pathname.match(pattern)[1] + '/submit'
const probremID = pathname.match(pattern)[2]
const raw = await $.get(submitURL)
$form = $(raw).find('form.submit-form')
$('.problem-statement').append($form)
editor = ace.edit('editor')
$form.attr('action', submitURL + $form.attr('action'))
$programType = $form.find('select[name=programTypeId]')
$toggleEditor = $form.find('#toggleEditorCheckbox')
$tabSize = $form.find('#tabSizeInput')
$selectProblem = $form.find('[name=submittedProblemIndex]')
// codeforces default settings
editor.setTheme('ace/theme/chrome')
editor.setShowPrintMargin(false)
editor.setOptions({
enableBasicAutocompletion: true
})
$selectProblem.val(probremID)
// そのままdisabledにするとformに含まれなくなるので
const $cloneSelectProblem = $($selectProblem.prop('outerHTML'))
$cloneSelectProblem.prop('disabled', true)
$cloneSelectProblem.removeAttr('name')
$cloneSelectProblem.attr('id', 'submitted_problem_index_fake_display')
$selectProblem.after($cloneSelectProblem)
$selectProblem.prop('hidden', true)
contestId = +raw.match(/contestId\s*=\s*(\d+)/)[1]
participantId = +raw.match(/participantId\s*:\s*(\d+)/)[1]
applyEditorVisibility()
setAceMode()
updateFilesAndLimits()
updateProblemLockInfo()
$toggleEditor.on('change', () => {
applyEditorVisibility()
const editorEnabled = !$toggleEditor.is(':checked')
$.post(
'/data/customtest',
{
communityCode: '',
action: 'setEditorEnabled',
editorEnabled: editorEnabled
},
function (response) {}
)
return false
})
$tabSize.on('change', () => {
const tabSize = $tabSize.val()
editor.setOptions({ tabSize })
$.post(
'/data/customtest',
{ communityCode: '', action: 'setTabSize', tabSize: tabSize },
function (response) {}
)
})
$programType.on('change', () => {
setAceMode()
})
editor.getSession().on('change', function () {
$('#sourceCodeTextarea').val(editor.getValue())
})
$('#sourceCodeTextarea').on('change', function () {
editor.setValue($(this).val(), 1)
})
$form.on('submit', preSubmit)
}
function setAceMode () {
var filePath = extensionMap[$programType.val()]
const mode = modelist.getModeForPath(filePath).mode
if (editor) editor.session.setMode(mode)
}
function applyEditorVisibility () {
if ($('#toggleEditorCheckbox').is(':checked')) {
$('#editor').hide()
$('#sourceCodeTextarea').show()
$('.tabSizeDiv').hide()
} else {
$('#editor').show()
editor.setValue(editor.getValue())
$('#sourceCodeTextarea').hide()
$('.tabSizeDiv').show()
}
}
function updateFilesAndLimits () {
var problemFiles = $('#submittedProblemFiles')
var problemLimits = $('#submittedProblemLimits')
var problemIndex = $('select[name=submittedProblemIndex]').val()
var option = $('select[name=submittedProblemIndex] option:selected')
var timeLimit = option.attr('data-time-limit')
var memoryLimit = option.attr('data-memory-limit')
var inputFile = option.attr('data-input-file')
var outputFile = option.attr('data-output-file')
if (problemIndex === '') {
problemFiles.text('')
problemLimits.text('')
} else {
var filesStyle = 'float: left; font-weight: bold'
if (inputFile === '') {
if (outputFile === '') {
filesStyle = 'float: left;'
problemFiles.text('standard input/output')
} else {
problemFiles.text('standard input / ' + outputFile)
}
} else {
if (outputFile === '') {
problemFiles.text(inputFile + ' / standard output')
} else {
problemFiles.text(inputFile + ' / ' + outputFile)
}
}
problemFiles.attr('style', filesStyle)
problemLimits.text(timeLimit + ' s, ' + memoryLimit + ' MB')
}
}
function preSubmit () {
const button = $form.find('input.submit')
const img = $form.find('img.ajax-loading-gif')
if ($(this).hasAttr('data-submitting')) {
return true
}
if (button.prop('disabled')) {
return false
}
var result = callback.call(this)
if (result || alwaysDisable) {
img.show()
button.prop('disabled', true)
setTimeout(function () {
img.hide()
button.prop('disabled', false)
}, alwaysDisable ? 1000 : 10000)
}
return result
}
function callback () {
var form = $(this)
var $ftaa = form.find("input[name='ftaa']")
var $bfaa = form.find("input[name='bfaa']")
if (window._ftaa && window._bfaa) {
$ftaa.val(window._ftaa)
$bfaa.val(window._bfaa)
}
if (form.attr('enctype') === 'multipart/form-data') {
var sourceFiles = form.find('.table-form input[name=sourceFile]')
if (
sourceFiles.length === 1 &&
sourceFiles[0].files &&
sourceFiles[0].files.length === 0
) {
form.removeAttr('enctype')
}
}
return true
}
function updateProblemLockInfo () {
var problemIndex = $('select[name=submittedProblemIndex]').val()
updateFilesAndLimits()
if (problemIndex !== '') {
$.post(
'/data/problemLock',
{
action: 'checkProblemLock',
contestId,
participantId,
problemIndex
},
function (response) {
if (response['problemLocked'] === 'true') {
Codeforces.setAjaxFormErrors('form table', {
error__submittedProblemIndex:
'Problem was locked for submission, it is impossible to resubmit it'
})
$('.submit-form :submit').attr('disabled', 'disabled')
$('#submittedProblemFiles').text('')
$('#submittedProblemLimits').text('')
} else {
Codeforces.clearAjaxFormErrors('form table')
$('.submit-form :submit').removeAttr('disabled')
}
},
'json'
)
} else {
Codeforces.clearAjaxFormErrors('form table')
$('.submit-form :submit').attr('disabled', 'disabled')
}
}
})()