www/secretary/workbench/views/forms/icla.js.rb (286 lines of code) (raw):
class ICLA < Vue
def initialize
@filed = false
@checked = nil
@submitted = false
@pdfdata = nil # not yet parsed file
@pdfdisabled = false # true if parse fails
@pdfbusy = false # busy parsing
end
def render
_h4 'ICLA'
_div.buttons do
_button 'clear form', disabled: @filed,
onClick: lambda {clear_form()}
_button (@pdfdata.nil? ? 'Parse/use PDF data' : @pdfdisabled ? 'No PDF data found' : 'Use PDF data'),
disabled: (@filed or @pdfdisabled or @pdfbusy),
onClick: lambda {getpdfdata()}
_button 'Use mail data', disabled: @filed,
onClick: lambda {process_response({})}
end
_form method: 'post', action: '../../tasklist/icla', target: 'content' do
_input type: 'hidden', name: 'message'
_input type: 'hidden', name: 'selected'
_input type: 'hidden', name: 'signature', value: @@signature
_table.form do
_tr do
_th 'Submitter'
_td do
_ @@headers.name
_ ' ('
_ @@headers.from
_ ')'
end
end
_tr do
_th 'E-mail'
_td do
_input name: 'email', value: @email, required: true, type: 'email',
disabled: (@filed or @pdfbusy)
end
end
_tr do
_th 'Message'
_td do
_input name: 'errmsg', value: @errmsg, required: false, disabled: true
end
end
_tr do
_th 'Real Name'
_td do
_input name: 'realname', value: @realname, required: true,
disabled: (@filed or @pdfbusy),
onChange: self.changeRealName, onBlur: self.changeRealName
end
end
_tr do
_th 'Public Name'
_td do
_input name: 'pubname', value: @pubname, required: true,
disabled: (@filed or @pdfbusy), onFocus: lambda {@pubname ||= @realname},
onChange: self.changePublicName, onBlur: self.changePublicName
end
end
_tr do
_th 'Family First'
_td do
_input name: 'familyfirst', required: true,
type: 'checkbox', checked: @familyfirst,
disabled: (@filed or @pdfbusy),
onChange: self.changeFamilyFirst, onBlur: self.changeFamilyFirst
end
end
_tr do
_th 'File Name'
_td do
_input name: 'filename', value: @filename, required: true,
pattern: '[a-zA-Z][\-\w]+(\.[a-z]+)?', disabled: (@filed or @pdfbusy) # bare '-' not allowed in v-mode character classes
end
end
end
_table.form do
_tr do
_th 'User ID'
_td do
_input name: 'user', value: @user, onBlur: self.validate_userid,
disabled: (@filed or @pdfbusy), pattern: '^[a-z][a-z0-9]{2,}$'
end
end
# May be useful in future
# _tr do
# _th 'LDAP givenname'
# _td do
# _input name: 'ldapgivenname', value: @ldapgivenname,
# disabled: (@filed or @pdfbusy)
# end
# end
# _tr do
# _th 'LDAP sn'
# _td do
# _input name: 'ldapsn', value: @ldapsn,
# disabled: (@filed or @pdfbusy)
# end
# end
#
_tr do
if @project
_th do
_a 'Project', href: "https://lists.apache.org/list.html?private@#{@project}.apache.org", target: 'content'
end
else
_th 'Project'
end
_td do
_select name: 'project', value: @project, disabled: (@filed or @pdfbusy) do
_option ''
@@projects.each do |project|
_option project
end
end
end
end
unless @pdfproject.nil? or @pdfproject == @project
_tr do
_th 'Project (PDF)'
_td do
_ @pdfproject
end
end
end
_tr do
_th 'Vote Link'
_td do
_input type: 'url', name: 'votelink', value: @votelink,
disabled: @filed
end
end
end
_input.btn.btn_primary value: 'File', type: 'submit', ref: 'file',
formnovalidate: true
end
end
def missing_pdf_value(value, fieldname)
field = document.querySelector("input[name=#{fieldname}]")
field.style.backgroundColor = value ? '' : 'yellow'
field.title = 'Cannot check this field against the PDF'
end
# needs to be called even if post fails
def process_response(parsed)
clear_form()
pdfdata = @pdfdata || {}
name = parsed.FullName || @@headers.name || ''
missing_pdf_value(pdfdata.FullName, 'realname')
# reorder name if there is a single comma present
parts = name.split(',')
if parts.length == 2 and parts[1] !~ /^\s*(jr|ph\.d)\.?$/i
name = "#{parts[1].strip()} #{parts[0]}"
end
@realname = name
@pubname = parsed.PublicName || name
missing_pdf_value(pdfdata.PublicName || pdfdata.RealName, 'pubname')
@familyfirst = parsed.FamilyFirst || false
@filename = self.genfilename(name, @familyfirst)
@email = parsed.EMail || @@headers.from
@errmsg = ''
errmsg = document.querySelector('input[name=errmsg]')
errmsg.style.borderColor = ''
email = document.querySelector('input[name=email]')
email.style.borderColor = email.style.backgroundColor = ''
if pdfdata.EMail
if pdfdata.EMail != @@headers.from
email.style.borderColor = 'red'
email.style.backgroundColor = 'yellow'
@errmsg = "Submitter email does not match PDF email"
errmsg.style.borderColor = 'red'
end
else
email.style.borderColor = 'red'
email.style.backgroundColor = 'yellow'
@errmsg = "Could not check email against PDF"
errmsg.style.borderColor = 'red'
end
@user = parsed.ApacheID || ''
project = parsed.Project
@project = project if @@projects.include? project
@pdfproject = parsed.PDFProject
# Not needed currently
# pubnamearray = @pubname.split(" ")
# @ldapsn = self.genldapsn(pubnamearray, @familyfirst)
# @ldapgivenname = self.genldapgivenname(pubnamearray, @familyfirst)
end
def clear_form()
@pubname = @realname = @email = @filename = @user = ''
@project = @pdfproject = ''
@votelink = ''
@familyfirst = false
end
def getpdfdata()
if @pdfdata # use existing data if present
process_response(@pdfdata)
else
data = {message: window.parent.location.pathname, attachment: @@selected}
@pdfbusy = true
HTTP.post('../../actions/parse-icla', data).then {|result|
@pdfbusy = false
@pdfdata = result.parsed
@pdfdisabled = @pdfdata.keys().length <= 1 # response contains dataSource key
process_response(@pdfdata)
}.catch {|message|
@pdfbusy = false
alert message
}
end
end
# on initial display, default various fields based on headers, and update
# state
def mounted()
@pdfdata = nil # Not yet parsed
clear_form
# watch for status updates
window.addEventListener 'message', self.status_update
getpdfdata() # use PDFData by preference
end
def beforeDestroy()
window.removeEventListener 'message', self.status_update
end
# as fields change, enable/disable the associated buttons and adjust
# input requirements.
def updated()
# ICLA file form
valid = %w(realname pubname email filename).all? do |name|
document.querySelector("input[name=#{name}]").validity.valid
end
# new account request form - perform checks only if user is valid
user = document.querySelector('input[name=user]')
project = document.querySelector('select[name=project]')
votelink = document.querySelector('input[name=votelink]')
valid &&= project.validity.valid
# project votelink are only required with valid users; only validate
# votelink if the user is valid
if user.validity.valid and user.value.length > 0
project.required = votelink.required = true
valid &= votelink.validity.valid
else
votelink.required = false
project.required = (user.value.length > 0)
end
$refs.file.disabled = !valid or @filed or @submitted
# wire up form
jQuery('form')[0].addEventListener('submit', self.file)
jQuery('input[name=message]').val(window.parent.location.pathname)
jQuery('input[name=selected]').val(decodeURIComponent(@@selected))
# Safari autocomplete workaround: trigger change on leaving field
# https://github.com/facebook/react/issues/2125
if navigator.userAgent.include? 'Safari'
Array(document.getElementsByTagName('input')).each do |input|
input.addEventListener('blur', self.onblur)
end
end
end
# when real name changes, update file name
def changeRealName(event)
@realname = event.target.value || ''; # not nil
@filename = self.genfilename(@realname, @familyfirst)
end
# when family first changes, update file name and LDAP default fields
def changeFamilyFirst(event)
@filename = self.genfilename(@realname, @familyfirst)
# not needed currently
# pubnamearray = @pubname.split(' ')
# @ldapsn = self.genldapsn(pubnamearray, @familyfirst)
# @ldapgivenname = self.genldapgivenname(pubnamearray, @familyfirst)
end
# when public name changes, update LDAP default fields
def changePublicName(event)
@pubname = event.target.value;
# not needed currently
# pubnamearray = @pubname.split(' ')
# @ldapsn = self.genldapsn(pubnamearray, @familyfirst)
# @ldapgivenname = self.genldapgivenname(pubnamearray, @familyfirst)
end
# generate file name from the real name
def genfilename(realname, familyfirst)
nominalname = asciize(realname.strip()).downcase()
if !familyfirst
return nominalname
else
# compute file name with family first; move first name to last
namearray = nominalname.split('-')
namearray.push(namearray[0])
namearray.shift()
return namearray.join('-')
end
end
# generate LDAP sn from public name
# simply return either the first or last name
def genldapsn(pnamearray, ffirst)
if ffirst
return pnamearray[0]
else
return pnamearray[-1]
end
end
# generate LDAP givenName from public name
# simply return the remainder after removing either the first or last name
def genldapgivenname(pnamearray, ffirst)
if ffirst
pnamearray.shift()
return pnamearray.join(' ')
else
pnamearray.pop()
return pnamearray.join(' ')
end
end
# when leaving an input field, trigger change event (for Safari)
def onblur(event)
jQuery(event.target).trigger(:change)
end
# handle ICLA form submission
def file(event)
setTimeout 0 do
@submitted = true
@filed = true
end
end
# validate userid is available
def validate_userid(event)
return unless @user and @user != @checked
input = event.target
HTTP.post('../../actions/check-id', id: @user).then {|result|
input.setCustomValidity(result.message)
@checked = @user
}.catch {|message|
alert message
}
end
# when tasks complete (or are aborted) reset form
def status_update(event)
@submitted = false
@filed = false
end
end