liquid_feedback_frontend

changeset 879:ea3d3757ddc3

Added support for voting comments
author bsw
date Mon Aug 20 01:00:09 2012 +0200 (2012-08-20)
parents a75d64b432ec
children fe39c1fb541b
files app/main/_filter/21_auth.lua app/main/initiative/_show.lua app/main/issue/_show.lua app/main/member/_show_thumb.lua app/main/vote/_action/update.lua app/main/vote/list.lua env/model/has_rendered_content.lua model/direct_voter.lua model/rendered_voter_comment.lua static/icons/16/comment.png static/js/voting.js static/style.css
line diff
     1.1 --- a/app/main/_filter/21_auth.lua	Sat Aug 18 23:22:20 2012 +0200
     1.2 +++ b/app/main/_filter/21_auth.lua	Mon Aug 20 01:00:09 2012 +0200
     1.3 @@ -51,7 +51,8 @@
     1.4  end
     1.5  
     1.6  if app.session:has_access("everything") then
     1.7 -  if module == "member" and (view == "show" or view == "history") then
     1.8 +  if module == "member" and (view == "show" or view == "history") 
     1.9 +    or module == "vote" and view == "list" then
    1.10      auth_needed = false
    1.11    end
    1.12  end
     2.1 --- a/app/main/initiative/_show.lua	Sat Aug 18 23:22:20 2012 +0200
     2.2 +++ b/app/main/initiative/_show.lua	Mon Aug 20 01:00:09 2012 +0200
     2.3 @@ -427,6 +427,7 @@
     2.4              :left_join("vote", nil, { "vote.initiative_id = ? AND vote.member_id = member.id", initiative.id })
     2.5              :add_field("direct_voter.weight as voter_weight")
     2.6              :add_field("coalesce(vote.grade, 0) as grade")
     2.7 +            :add_field("direct_voter.comment as voter_comment")
     2.8              :left_join("initiative", nil, "initiative.id = vote.initiative_id")
     2.9              :left_join("issue", nil, "issue.id = initiative.issue_id")
    2.10        
     3.1 --- a/app/main/issue/_show.lua	Sat Aug 18 23:22:20 2012 +0200
     3.2 +++ b/app/main/issue/_show.lua	Mon Aug 20 01:00:09 2012 +0200
     3.3 @@ -13,7 +13,14 @@
     3.4  local voteable = app.session.member_id and issue.state == 'voting' and
     3.5         app.session.member:has_voting_right_for_unit_id(issue.area.unit_id)
     3.6  
     3.7 -local vote_link_text = direct_voter and _"Change vote" or _"Vote now"
     3.8 +local vote_comment_able = app.session.member_id and issue.closed and direct_voter
     3.9 +
    3.10 +local vote_link_text
    3.11 +if voteable then 
    3.12 +  vote_link_text = direct_voter and _"Change vote" or _"Vote now"
    3.13 +elseif vote_comment_able then
    3.14 +  vote_link_text = direct_voter and _"Change voting comment"
    3.15 +end  
    3.16  
    3.17  
    3.18  local class = "issue"
    3.19 @@ -87,7 +94,7 @@
    3.20  
    3.21    local links = {}
    3.22    
    3.23 -  if voteable then
    3.24 +  if vote_link_text then
    3.25      links[#links+1] ={
    3.26        content = vote_link_text,
    3.27        module = "vote",
     4.1 --- a/app/main/member/_show_thumb.lua	Sat Aug 18 23:22:20 2012 +0200
     4.2 +++ b/app/main/member/_show_thumb.lua	Mon Aug 20 01:00:09 2012 +0200
     4.3 @@ -59,6 +59,16 @@
     4.4                member_id = member.id,
     4.5              },
     4.6              content = function()
     4.7 +              if (member.voter_comment) then
     4.8 +                ui.image{
     4.9 +                  attr = { 
    4.10 +                    alt   = _"Voting comment available",
    4.11 +                    title = _"Voting comment available"
    4.12 +                  },
    4.13 +                  static = "icons/16/comment.png"
    4.14 +                }
    4.15 +              end
    4.16 +
    4.17                if member.grade > 0 then
    4.18                  ui.image{
    4.19                    attr = { 
    4.20 @@ -121,7 +131,7 @@
    4.21              }
    4.22            }
    4.23          end
    4.24 -
    4.25 +        
    4.26          if initiator and initiator.accepted then
    4.27            if member.accepted == nil then
    4.28              slot.put(_"Invited")
     5.1 --- a/app/main/vote/_action/update.lua	Sat Aug 18 23:22:20 2012 +0200
     5.2 +++ b/app/main/vote/_action/update.lua	Mon Aug 20 01:00:09 2012 +0200
     5.3 @@ -1,15 +1,22 @@
     5.4 +local cancel = param.get("cancel") and true or false
     5.5 +if cancel then return end
     5.6 +
     5.7  local issue = Issue:new_selector():add_where{ "id = ?", param.get("issue_id", atom.integer) }:for_share():single_object_mode():exec()
     5.8  
     5.9 +local preview = param.get("preview") or param.get("preview2") == "1" and true or false
    5.10 +
    5.11  if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then
    5.12    error("access denied")
    5.13  end
    5.14  
    5.15 -if issue.closed then
    5.16 +local update_comment = param.get("update_comment") == "1" and true or false
    5.17 +
    5.18 +if issue.closed and not update_comment then
    5.19    slot.put_into("error", _"This issue is already closed.")
    5.20    return false
    5.21  end
    5.22  
    5.23 -if issue.state ~= "voting" then
    5.24 +if issue.state ~= "voting" and not issue.closed then
    5.25    slot.put_into("error", _"Voting has not started yet.")
    5.26    return false
    5.27  end
    5.28 @@ -30,44 +37,84 @@
    5.29  local tempvoting_string = param.get("scoring")
    5.30  
    5.31  local tempvotings = {}
    5.32 -for match in tempvoting_string:gmatch("([^;]+)") do
    5.33 -  for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
    5.34 -    tempvotings[tonumber(initiative_id)] = tonumber(grade)
    5.35 -    if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then
    5.36 -      move_up = tonumber(initiative_id)
    5.37 -    elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then
    5.38 -      move_down = tonumber(initiative_id)
    5.39 +if not update_comment then
    5.40 +  for match in tempvoting_string:gmatch("([^;]+)") do
    5.41 +    for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
    5.42 +      tempvotings[tonumber(initiative_id)] = tonumber(grade)
    5.43 +      if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then
    5.44 +        move_up = tonumber(initiative_id)
    5.45 +      elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then
    5.46 +        move_down = tonumber(initiative_id)
    5.47 +      end
    5.48      end
    5.49    end
    5.50  end
    5.51  
    5.52  if not move_down and not move_up then
    5.53 -  if not direct_voter then
    5.54 -    direct_voter = DirectVoter:new()
    5.55 -    direct_voter.issue_id = issue.id
    5.56 -    direct_voter.member_id = app.session.member_id
    5.57 +  if not preview then
    5.58 +    if not direct_voter then
    5.59 +      if issue.closed then
    5.60 +        slot.put_into("error", _"This issue is already closed.")
    5.61 +        return false
    5.62 +      else 
    5.63 +        direct_voter = DirectVoter:new()
    5.64 +        direct_voter.issue_id = issue.id
    5.65 +        direct_voter.member_id = app.session.member_id
    5.66 +        direct_voter:save()
    5.67 +
    5.68 +        direct_voter = DirectVoter:by_pk(issue.id, app.session.member_id)
    5.69 +      end
    5.70 +    end
    5.71 +    
    5.72 +    local formatting_engine = param.get("formatting_engine")
    5.73 +    local comment = util.trim(param.get("comment"))
    5.74 +
    5.75 +    if comment ~= direct_voter.comment then
    5.76 +      if #comment > 0 then
    5.77 +        direct_voter.formatting_engine = formatting_engine
    5.78 +        direct_voter.comment = comment
    5.79 +        direct_voter.comment_changed = 'now'
    5.80 +        direct_voter:render_content(true)
    5.81 +      else
    5.82 +        direct_voter.formatting_engine = null
    5.83 +        direct_voter.comment = null
    5.84 +        direct_voter.comment_changed = 'now'
    5.85 +      end
    5.86 +    end
    5.87 +    direct_voter:save()
    5.88 +    
    5.89    end
    5.90  
    5.91 -  direct_voter:save()
    5.92 -
    5.93 -  local scoring = param.get("scoring")
    5.94 +  if not update_comment then
    5.95 +    local scoring = param.get("scoring")
    5.96  
    5.97 -  for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do
    5.98 -    local initiative_id = tonumber(initiative_id)
    5.99 -    local grade = tonumber(grade)
   5.100 -    local initiative = Initiative:by_id(initiative_id)
   5.101 -    if initiative.issue.id ~= issue.id then
   5.102 -      error("initiative from wrong issue")
   5.103 +    for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do
   5.104 +      local initiative_id = tonumber(initiative_id)
   5.105 +      local grade = tonumber(grade)
   5.106 +      local initiative = Initiative:by_id(initiative_id)
   5.107 +      if initiative.issue.id ~= issue.id then
   5.108 +        error("initiative from wrong issue")
   5.109 +      end
   5.110 +      if not preview and not issue.closed then
   5.111 +        local vote = Vote:by_pk(initiative_id, app.session.member.id)
   5.112 +        if not vote then
   5.113 +          vote = Vote:new()
   5.114 +          vote.issue_id = issue.id
   5.115 +          vote.initiative_id = initiative.id
   5.116 +          vote.member_id = app.session.member.id
   5.117 +        end
   5.118 +        vote.grade = grade
   5.119 +        vote:save()
   5.120 +      end
   5.121      end
   5.122 -    local vote = Vote:by_pk(initiative_id, app.session.member.id)
   5.123 -    if not vote then
   5.124 -      vote = Vote:new()
   5.125 -      vote.issue_id = issue.id
   5.126 -      vote.initiative_id = initiative.id
   5.127 -      vote.member_id = app.session.member.id
   5.128 -    end
   5.129 -    vote.grade = grade
   5.130 -    vote:save()
   5.131 +  end
   5.132 +
   5.133 +  if not preview and not cancel then
   5.134 +    request.redirect{
   5.135 +      module = "issue",
   5.136 +      view = "show",
   5.137 +      id = issue.id
   5.138 +    }
   5.139    end
   5.140  
   5.141  else
     6.1 --- a/app/main/vote/list.lua	Sat Aug 18 23:22:20 2012 +0200
     6.2 +++ b/app/main/vote/list.lua	Mon Aug 20 01:00:09 2012 +0200
     6.3 @@ -3,7 +3,7 @@
     6.4  local member_id = param.get("member_id", atom.integer)
     6.5  local member
     6.6  
     6.7 -local readonly = false
     6.8 +local preview = param.get("preview") or param.get("preview2") == "1" and true or false
     6.9  
    6.10  if member_id then
    6.11    if not issue.closed then
    6.12 @@ -15,15 +15,21 @@
    6.13  
    6.14  if issue.closed then
    6.15    if not member then
    6.16 -    slot.put_into("error", _"This issue is already closed.")
    6.17 -  end
    6.18 -  if not member then
    6.19      member = app.session.member
    6.20    end
    6.21    readonly = true
    6.22  end
    6.23  
    6.24 +local submit_button_text = _"Finish voting"
    6.25 +
    6.26 +if issue.closed then
    6.27 +  submit_button_text = _"Update voting comment"
    6.28 +end
    6.29 +
    6.30 +local direct_voter
    6.31 +
    6.32  if member then
    6.33 +  direct_voter = DirectVoter:by_pk(issue.id, member.id)
    6.34    local str = _("Ballot of '#{member_name}' for issue ##{issue_id}",
    6.35                    {member_name = string.format('<a href="%s">%s</a>',
    6.36                                            encode.url{
    6.37 @@ -44,6 +50,9 @@
    6.38    ui.title(str)
    6.39  else
    6.40    member = app.session.member
    6.41 +
    6.42 +  direct_voter = DirectVoter:by_pk(issue.id, member.id)
    6.43 +
    6.44    ui.title(_"Voting")
    6.45  
    6.46    ui.actions(function()
    6.47 @@ -53,28 +62,31 @@
    6.48        view = "show",
    6.49        id = issue.id
    6.50      }
    6.51 -    slot.put(" &middot; ")
    6.52 -    ui.link{
    6.53 -      text = _"Discard voting",
    6.54 -      module = "vote",
    6.55 -      action = "update",
    6.56 -      params = {
    6.57 -        issue_id = issue.id,
    6.58 -        discard = true
    6.59 -      },
    6.60 -      routing = {
    6.61 -        default = {
    6.62 -          mode = "redirect",
    6.63 -          module = "issue",
    6.64 -          view = "show",
    6.65 -          id = issue.id
    6.66 +    if direct_voter then
    6.67 +      slot.put(" &middot; ")
    6.68 +      ui.link{
    6.69 +        text = _"Discard voting",
    6.70 +        module = "vote",
    6.71 +        action = "update",
    6.72 +        params = {
    6.73 +          issue_id = issue.id,
    6.74 +          discard = true
    6.75 +        },
    6.76 +        routing = {
    6.77 +          default = {
    6.78 +            mode = "redirect",
    6.79 +            module = "issue",
    6.80 +            view = "show",
    6.81 +            id = issue.id
    6.82 +          }
    6.83          }
    6.84        }
    6.85 -    }
    6.86 +    end
    6.87    end)
    6.88  end
    6.89  
    6.90  
    6.91 +
    6.92  local tempvoting_string = param.get("scoring")
    6.93  
    6.94  local tempvotings = {}
    6.95 @@ -167,6 +179,7 @@
    6.96  }
    6.97  
    6.98  ui.form{
    6.99 +  record = direct_voter,
   6.100    attr = {
   6.101      id = "voting_form",
   6.102      class = readonly and "voting_form_readonly" or "voting_form_active"
   6.103 @@ -174,16 +187,8 @@
   6.104    module = "vote",
   6.105    action = "update",
   6.106    params = { issue_id = issue.id },
   6.107 -  routing = {
   6.108 -    default = {
   6.109 -      mode = "redirect",
   6.110 -      module = "issue",
   6.111 -      view = "show",
   6.112 -      id = issue.id
   6.113 -    }
   6.114 -  },
   6.115    content = function()
   6.116 -    if not readonly then
   6.117 +    if not readonly or preview then
   6.118        local scoring = param.get("scoring")
   6.119        if not scoring then
   6.120          for i, initiative in ipairs(initiatives) do
   6.121 @@ -210,8 +215,8 @@
   6.122          tag = "input",
   6.123          attr = {
   6.124            type = "submit",
   6.125 -          class = "voting_done",
   6.126 -          value = _"Finish voting"
   6.127 +          class = "voting_done1",
   6.128 +          value = submit_button_text
   6.129          }
   6.130        }
   6.131      end
   6.132 @@ -423,15 +428,62 @@
   6.133          end
   6.134        end
   6.135      }
   6.136 -    if not readonly then
   6.137 -      ui.tag{
   6.138 -        tag = "input",
   6.139 -        attr = {
   6.140 -          type = "submit",
   6.141 -          class = "voting_done",
   6.142 -          value = _"Finish voting"
   6.143 +    if app.session.member_id and preview then
   6.144 +      local formatting_engine = param.get("formatting_engine")
   6.145 +      local comment = param.get("comment")
   6.146 +      local rendered_comment = format.wiki_text(comment, formatting_engine)
   6.147 +      slot.put(rendered_comment)
   6.148 +    end
   6.149 +    if (readonly or direct_voter.comment) and not preview then
   6.150 +      ui.heading{ level = "2", content = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) }
   6.151 +      if direct_voter.comment then
   6.152 +        local rendered_comment = direct_voter:get_content('html')
   6.153 +        ui.container{ attr = { class = "member_statement" }, content = function()
   6.154 +          slot.put(rendered_comment)
   6.155 +        end }
   6.156 +        slot.put("<br />")
   6.157 +      end
   6.158 +    end
   6.159 +    if app.session.member_id and app.session.member_id == member.id then
   6.160 +      if not readonly or direct_voter then
   6.161 +        ui.field.hidden{ name = "update_comment", value = param.get("update_comment") or issue.closed and "1" }
   6.162 +        ui.field.select{
   6.163 +          label = _"Wiki engine for statement",
   6.164 +          name = "formatting_engine",
   6.165 +          foreign_records = {
   6.166 +            { id = "rocketwiki", name = "RocketWiki" },
   6.167 +            { id = "compat", name = _"Traditional wiki syntax" }
   6.168 +          },
   6.169 +          attr = {id = "formatting_engine"},
   6.170 +          foreign_id = "id",
   6.171 +          foreign_name = "name",
   6.172 +          value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine
   6.173          }
   6.174 -      }
   6.175 +        ui.field.text{
   6.176 +          label = _"Voting comment (optional)",
   6.177 +          name = "comment",
   6.178 +          multiline = true,
   6.179 +          value = param.get("comment") or direct_voter and direct_voter.comment,
   6.180 +          attr = { style = "height: 20ex;" },
   6.181 +        }
   6.182 +        ui.field.hidden{ name = "preview2", attr = { id = "preview2" }, value = "0" }
   6.183 +        ui.submit{
   6.184 +          name = "preview",
   6.185 +          value = _"Preview voting comment",
   6.186 +          attr = { class = "preview" }
   6.187 +        }
   6.188 +      end
   6.189 +      if not readonly or preview or direct_voter then
   6.190 +        slot.put(" ")
   6.191 +        ui.tag{
   6.192 +          tag = "input",
   6.193 +          attr = {
   6.194 +            type = "submit",
   6.195 +            class = "voting_done2",
   6.196 +            value = submit_button_text
   6.197 +          }
   6.198 +        }
   6.199 +      end
   6.200      end
   6.201    end
   6.202  }
     7.1 --- a/env/model/has_rendered_content.lua	Sat Aug 18 23:22:20 2012 +0200
     7.2 +++ b/env/model/has_rendered_content.lua	Mon Aug 20 01:00:09 2012 +0200
     7.3 @@ -5,15 +5,30 @@
     7.4    -- render content to html, save it as rendered_class and return it
     7.5    function class.object:render_content(force_rendering)
     7.6      -- local draft for update
     7.7 -    local lock = class:new_selector()
     7.8 -      :add_where{ "id = ?", self.id }
     7.9 -      :single_object_mode()
    7.10 -      :for_update()
    7.11 -      :exec()
    7.12 +
    7.13 +    local selector = class:new_selector()
    7.14 +
    7.15 +    if class.primary_key then
    7.16 +      for i, key in ipairs(class.primary_key) do
    7.17 +        selector:add_where{ "$ = ?", { key }, self[key] }
    7.18 +        trace.debug(key, self[key], self.id)
    7.19 +      end
    7.20 +    else
    7.21 +      selector:add_where{ "id = ?", self.id }
    7.22 +    end
    7.23 +    
    7.24 +    local lock = selector:single_object_mode():for_update():exec()
    7.25 +      
    7.26      -- check if there is already a rendered content
    7.27 -    local rendered = rendered_class:new_selector()
    7.28 -      :add_where{ class.table .. "_id = ?", self.id }
    7.29 -      :add_where{ "format = 'html'" }
    7.30 +    local selector = rendered_class:new_selector()
    7.31 +      if type(class.primary_key) == "table" then
    7.32 +        for i, key in ipairs(class.primary_key) do
    7.33 +          selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self[key] }
    7.34 +        end
    7.35 +      else
    7.36 +        selector:add_where{ "$.id = ?", { rendered_class.table }, self.id }
    7.37 +      end
    7.38 +      local rendered = selector:add_where{ "format = 'html'" }
    7.39        :optional_object_mode()
    7.40        :exec()
    7.41      if rendered then
    7.42 @@ -25,7 +40,13 @@
    7.43      end
    7.44      -- create rendered_class record
    7.45      local rendered = rendered_class:new()
    7.46 -    rendered[class.table .. "_id"] = self.id
    7.47 +    if type(class.primary_key) == "table" then
    7.48 +      for i, key in ipairs(class.primary_key) do
    7.49 +        rendered[key] = self[key]
    7.50 +      end
    7.51 +    else
    7.52 +      rendered[class.table .. "_id"] = self.id
    7.53 +    end
    7.54      rendered.format = "html"
    7.55      rendered.content = format.wiki_text(self[content_field_name], self.formatting_engine)
    7.56      rendered:save()
    7.57 @@ -36,9 +57,15 @@
    7.58    -- returns rendered version for specific format
    7.59    function class.object:get_content(format)
    7.60      -- Fetch rendered_class record for specified format
    7.61 -    local rendered = rendered_class:new_selector()
    7.62 -      :add_where{ class.table .. "_id = ?", self.id }
    7.63 -      :add_where{ "format = ?", format }
    7.64 +    local selector = rendered_class:new_selector()
    7.65 +    if type(class.primary_key) == "table" then
    7.66 +      for i, key in ipairs(class.primary_key) do
    7.67 +        selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self.id }
    7.68 +      end
    7.69 +    else
    7.70 +      selector:add_where{ class.table .. "_id = ?", self.id }
    7.71 +    end
    7.72 +    local rendered = selector:add_where{ "format = ?", format }
    7.73        :optional_object_mode()
    7.74        :exec()
    7.75      -- If this format isn't rendered yet, render it
     8.1 --- a/model/direct_voter.lua	Sat Aug 18 23:22:20 2012 +0200
     8.2 +++ b/model/direct_voter.lua	Mon Aug 20 01:00:09 2012 +0200
     8.3 @@ -18,6 +18,8 @@
     8.4    ref           = 'member',
     8.5  }
     8.6  
     8.7 +model.has_rendered_content(DirectVoter, RenderedVoterComment, "comment")
     8.8 +
     8.9  function DirectVoter:by_pk(issue_id, member_id)
    8.10    return self:new_selector()
    8.11      :add_where{ "issue_id = ? AND member_id = ?", issue_id, member_id }
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/model/rendered_voter_comment.lua	Mon Aug 20 01:00:09 2012 +0200
     9.3 @@ -0,0 +1,3 @@
     9.4 +RenderedVoterComment = mondelefant.new_class()
     9.5 +RenderedVoterComment.table = 'rendered_voter_comment'
     9.6 +RenderedVoterComment.primary_key = { "issue_id", "member_id", "format" }
    10.1 Binary file static/icons/16/comment.png has changed
    11.1 --- a/static/js/voting.js	Sat Aug 18 23:22:20 2012 +0200
    11.2 +++ b/static/js/voting.js	Mon Aug 20 01:00:09 2012 +0200
    11.3 @@ -287,8 +287,13 @@
    11.4      var elements = document.getElementsByTagName("input");
    11.5      for (var i=0; i<elements.length; i++) {
    11.6        var element = elements[i];
    11.7 -      if (element.className == "voting_done") {
    11.8 +      if (element.className == "voting_done1" ||
    11.9 +          element.className == "voting_done2" ||
   11.10 +          element.name == "preview") {
   11.11          element.addEventListener("click", function(event) {
   11.12 +          if (event.srcElement.name == "preview") {
   11.13 +            document.getElementById("preview2").value = "1";
   11.14 +          }
   11.15            var scoringString = "";
   11.16            var approvalCount = 0;
   11.17            var disapprovalCount = 0;
    12.1 --- a/static/style.css	Sat Aug 18 23:22:20 2012 +0200
    12.2 +++ b/static/style.css	Mon Aug 20 01:00:09 2012 +0200
    12.3 @@ -727,6 +727,7 @@
    12.4  .login input[type=password],
    12.5  .vertical input[type=password],
    12.6  .vertical textarea,
    12.7 +#voting_form textarea,
    12.8  .vertical select {
    12.9    font-family: sans-serif;
   12.10    font-size: 100%;
   12.11 @@ -745,6 +746,7 @@
   12.12    padding-right: 0;
   12.13  }
   12.14  
   12.15 +#voting_form .ui_field_label,
   12.16  .login .ui_field_label,
   12.17  .vertical .ui_field_label {
   12.18    line-height: 180%;
   12.19 @@ -788,6 +790,7 @@
   12.20    margin-bottom: 0;
   12.21  }
   12.22  
   12.23 +#voting_form input[type=submit],
   12.24  .login input[type=submit],
   12.25  .vertical input[type=submit] {
   12.26    font-size: 100%;
   12.27 @@ -798,6 +801,11 @@
   12.28    padding: 0.75ex;
   12.29  }
   12.30  
   12.31 +#voting_form input[type=submit] {
   12.32 +  margin-left: 0;
   12.33 +}
   12.34 +
   12.35 +
   12.36  .login input[type=submit]:hover,
   12.37  .vertical input[type=submit]:hover,
   12.38  .login input[type=submit]:focus,

Impressum / About Us