bsw/jbe@19: local issue = Issue:by_id(param.get("issue_id"), atom.integer) bsw/jbe@19: bsw/jbe@19: local member_id = param.get("member_id", atom.integer) bsw/jbe@19: local member jbe@945: local readonly = false bsw/jbe@19: jbe@950: local preview = param.get("preview") and true or false poelzi@156: bsw/jbe@19: if member_id then bsw/jbe@19: if not issue.closed then bsw/jbe@19: error("access denied") bsw/jbe@19: end bsw/jbe@19: member = Member:by_id(member_id) bsw/jbe@19: readonly = true bsw/jbe@19: end bsw/jbe@19: poelzi@138: if issue.closed then poelzi@156: if not member then poelzi@158: member = app.session.member poelzi@158: end poelzi@156: readonly = true poelzi@138: end poelzi@138: bsw@879: local submit_button_text = _"Finish voting" bsw@879: bsw@879: if issue.closed then bsw@879: submit_button_text = _"Update voting comment" bsw@879: end bsw@879: bsw@879: local direct_voter bsw@879: bsw/jbe@19: if member then bsw@879: direct_voter = DirectVoter:by_pk(issue.id, member.id) poelzi@156: local str = _("Ballot of '#{member_name}' for issue ##{issue_id}", poelzi@156: {member_name = string.format('%s', poelzi@156: encode.url{ poelzi@156: module = "member", poelzi@156: view = "show", poelzi@156: id = member.id, poelzi@156: }, poelzi@156: encode.html(member.name)), poelzi@156: issue_id = string.format('%s', poelzi@156: encode.url{ poelzi@156: module = "issue", poelzi@156: view = "show", poelzi@156: id = issue.id, poelzi@156: }, poelzi@156: encode.html(tostring(issue.id))) poelzi@156: } poelzi@156: ) bsw@880: ui.raw_title(str) bsw/jbe@19: else bsw/jbe@19: member = app.session.member bsw@879: bsw@879: direct_voter = DirectVoter:by_pk(issue.id, member.id) bsw@879: bsw@604: ui.title(_"Voting") bsw/jbe@19: bsw@604: ui.actions(function() bsw/jbe@19: ui.link{ bsw@604: text = _"Cancel", bsw/jbe@19: module = "issue", bsw/jbe@19: view = "show", bsw/jbe@19: id = issue.id bsw/jbe@19: } bsw@879: if direct_voter then bsw@879: slot.put(" · ") bsw@879: ui.link{ bsw@879: text = _"Discard voting", bsw@879: module = "vote", bsw@879: action = "update", bsw@879: params = { bsw@879: issue_id = issue.id, bsw@879: discard = true bsw@879: }, bsw@879: routing = { bsw@879: default = { bsw@879: mode = "redirect", bsw@879: module = "issue", bsw@879: view = "show", bsw@879: id = issue.id bsw@879: } bsw@26: } bsw@26: } bsw@879: end bsw/jbe@19: end) bsw/jbe@19: end bsw/jbe@19: bsw/jbe@19: bsw@879: bsw/jbe@19: local tempvoting_string = param.get("scoring") bsw/jbe@19: bsw/jbe@19: local tempvotings = {} bsw/jbe@19: if tempvoting_string then bsw/jbe@19: for match in tempvoting_string:gmatch("([^;]+)") do bsw/jbe@19: for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do bsw/jbe@19: tempvotings[tonumber(initiative_id)] = tonumber(grade) bsw/jbe@19: end bsw/jbe@5: end bsw/jbe@19: end bsw/jbe@5: bsw@95: local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec() bsw/jbe@5: bsw/jbe@5: local min_grade = -1; bsw/jbe@5: local max_grade = 1; bsw/jbe@5: bsw/jbe@5: for i, initiative in ipairs(initiatives) do bsw/jbe@5: -- TODO performance bsw/jbe@19: initiative.vote = Vote:by_pk(initiative.id, member.id) bsw/jbe@19: if tempvotings[initiative.id] then bsw/jbe@19: initiative.vote = {} bsw/jbe@19: initiative.vote.grade = tempvotings[initiative.id] bsw/jbe@19: end bsw/jbe@5: if initiative.vote then bsw/jbe@5: if initiative.vote.grade > max_grade then bsw/jbe@5: max_grade = initiative.vote.grade bsw/jbe@5: end bsw/jbe@5: if initiative.vote.grade < min_grade then bsw/jbe@5: min_grade = initiative.vote.grade bsw/jbe@5: end bsw/jbe@5: end bsw/jbe@5: end bsw/jbe@5: bsw/jbe@5: local sections = {} bsw/jbe@5: for i = min_grade, max_grade do bsw/jbe@5: sections[i] = {} bsw/jbe@5: for j, initiative in ipairs(initiatives) do bsw/jbe@5: if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then bsw/jbe@5: sections[i][#(sections[i])+1] = initiative bsw/jbe@5: end bsw/jbe@5: end bsw/jbe@5: end bsw/jbe@5: bsw/jbe@19: local approval_count, disapproval_count = 0, 0 bsw/jbe@19: for i = min_grade, -1 do bsw/jbe@19: if #sections[i] > 0 then bsw/jbe@19: disapproval_count = disapproval_count + 1 bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: local approval_count = 0 bsw/jbe@19: for i = 1, max_grade do bsw/jbe@19: if #sections[i] > 0 then bsw/jbe@19: approval_count = approval_count + 1 bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@5: bsw/jbe@5: bsw/jbe@5: bsw/jbe@19: if not readonly then bsw/jbe@19: util.help("vote.list", _"Voting") bsw/jbe@19: slot.put('') bsw/jbe@19: slot.put('') bsw/jbe@19: end bsw/jbe@19: bsw/jbe@19: ui.script{ bsw/jbe@19: script = function() bsw/jbe@19: slot.put( bsw/jbe@19: "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n", bsw/jbe@19: "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n", bsw/jbe@19: "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n", bsw/jbe@19: "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n", bsw/jbe@19: "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n", bsw/jbe@19: "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n", bsw/jbe@19: "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n", bsw/jbe@19: "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n", bsw/jbe@19: "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n", bsw/jbe@19: "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n", bsw/jbe@19: "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n", bsw/jbe@19: "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n", bsw/jbe@19: "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n", bsw/jbe@19: "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n", bsw/jbe@19: "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n" bsw/jbe@19: ) bsw/jbe@19: end bsw/jbe@19: } bsw/jbe@5: bsw/jbe@5: ui.form{ bsw@879: record = direct_voter, bsw/jbe@19: attr = { bsw/jbe@19: id = "voting_form", bsw/jbe@19: class = readonly and "voting_form_readonly" or "voting_form_active" bsw/jbe@19: }, bsw/jbe@5: module = "vote", bsw/jbe@5: action = "update", bsw/jbe@5: params = { issue_id = issue.id }, bsw/jbe@5: content = function() bsw@879: if not readonly or preview then bsw/jbe@19: local scoring = param.get("scoring") bsw/jbe@19: if not scoring then bsw/jbe@19: for i, initiative in ipairs(initiatives) do bsw/jbe@19: local vote = initiative.vote bsw/jbe@19: if vote then bsw/jbe@19: tempvotings[initiative.id] = vote.grade bsw@522: else bsw@522: tempvotings[initiative.id] = 0 bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: local tempvotings_list = {} bsw/jbe@19: for key, val in pairs(tempvotings) do bsw/jbe@19: tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val) bsw/jbe@19: end bsw/jbe@19: if #tempvotings_list > 0 then bsw/jbe@19: scoring = table.concat(tempvotings_list, ";") bsw/jbe@19: else bsw/jbe@19: scoring = "" bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: slot.put('') bsw/jbe@19: -- TODO abstrahieren bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw@86: type = "submit", bsw@879: class = "voting_done1", bsw@879: value = submit_button_text bsw/jbe@19: } bsw/jbe@5: } bsw/jbe@19: end bsw/jbe@5: ui.container{ bsw/jbe@5: attr = { id = "voting" }, bsw/jbe@5: content = function() bsw/jbe@19: local approval_index, disapproval_index = 0, 0 bsw/jbe@5: for grade = max_grade, min_grade, -1 do bsw/jbe@19: local entries = sections[grade] bsw/jbe@5: local class bsw/jbe@5: if grade > 0 then bsw/jbe@5: class = "approval" bsw/jbe@5: elseif grade < 0 then bsw/jbe@5: class = "disapproval" bsw/jbe@5: else bsw/jbe@5: class = "abstention" bsw/jbe@5: end bsw/jbe@19: if bsw/jbe@19: #entries > 0 or bsw/jbe@19: (grade == 1 and not approval_used) or bsw/jbe@19: (grade == -1 and not disapproval_used) or bsw/jbe@19: grade == 0 bsw/jbe@19: then bsw/jbe@19: ui.container{ bsw/jbe@19: attr = { class = class }, bsw/jbe@19: content = function() bsw/jbe@19: local heading bsw/jbe@19: if class == "approval" then bsw/jbe@19: approval_used = true bsw/jbe@19: approval_index = approval_index + 1 bsw/jbe@19: if approval_count > 1 then bsw/jbe@19: if approval_index == 1 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Approval (first preference) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Approval (first preference) [many entries]" bsw/jbe@19: end bsw/jbe@19: elseif approval_index == 2 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Approval (second preference) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Approval (second preference) [many entries]" bsw/jbe@19: end bsw/jbe@19: elseif approval_index == 3 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Approval (third preference) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Approval (third preference) [many entries]" bsw/jbe@19: end bsw/jbe@19: else bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Approval (#th preference) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Approval (#th preference) [many entries]" bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: else bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Approval [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Approval [many entries]" bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: elseif class == "abstention" then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Abstention [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Abstention [many entries]" bsw/jbe@19: end bsw/jbe@19: elseif class == "disapproval" then bsw/jbe@19: disapproval_used = true bsw/jbe@19: disapproval_index = disapproval_index + 1 bsw/jbe@19: if disapproval_count > disapproval_index + 1 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Disapproval (prefer to lower blocks) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Disapproval (prefer to lower blocks) [many entries]" bsw/jbe@19: end bsw/jbe@19: elseif disapproval_count == 2 and disapproval_index == 1 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Disapproval (prefer to lower block) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Disapproval (prefer to lower block) [many entries]" bsw/jbe@19: end bsw/jbe@19: elseif disapproval_index == disapproval_count - 1 then bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Disapproval (prefer to last block) [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Disapproval (prefer to last block) [many entries]" bsw/jbe@19: end bsw/jbe@19: else bsw/jbe@19: if #entries == 1 then bsw/jbe@19: heading = _"Disapproval [single entry]" bsw/jbe@19: else bsw/jbe@19: heading = _"Disapproval [many entries]" bsw/jbe@6: end bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: ui.tag { bsw/jbe@19: tag = "div", bsw/jbe@19: attr = { class = "cathead" }, bsw/jbe@19: content = heading bsw/jbe@19: } bsw/jbe@19: for i, initiative in ipairs(entries) do bsw/jbe@19: ui.container{ bsw/jbe@19: attr = { bsw/jbe@19: class = "movable", bsw/jbe@19: id = "entry_" .. tostring(initiative.id) bsw/jbe@19: }, bsw/jbe@19: content = function() bsw/jbe@19: local initiators_selector = initiative:get_reference_selector("initiating_members") bsw/jbe@19: :add_where("accepted") bsw/jbe@19: local initiators = initiators_selector:exec() bsw/jbe@19: local initiator_names = {} bsw/jbe@19: for i, initiator in ipairs(initiators) do bsw/jbe@19: initiator_names[#initiator_names+1] = initiator.name bsw/jbe@19: end bsw/jbe@19: local initiator_names_string = table.concat(initiator_names, ", ") bsw/jbe@19: ui.container{ bsw@519: attr = { style = "float: right; position: relative;" }, bsw/jbe@19: content = function() bsw/jbe@19: ui.link{ bsw/jbe@19: attr = { class = "clickable" }, bsw/jbe@19: content = _"Show", bsw/jbe@19: module = "initiative", bsw/jbe@19: view = "show", bsw/jbe@19: id = initiative.id bsw/jbe@19: } bsw/jbe@19: slot.put(" ") bsw/jbe@19: ui.link{ bsw/jbe@19: attr = { class = "clickable", target = "_blank" }, bsw/jbe@19: content = _"(new window)", bsw/jbe@19: module = "initiative", bsw/jbe@19: view = "show", bsw/jbe@19: id = initiative.id bsw/jbe@19: } bsw/jbe@19: if not readonly then bsw/jbe@19: slot.put(" ") bsw/jbe@19: ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" } bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@19: } bsw/jbe@19: if not readonly then bsw/jbe@19: ui.container{ bsw@519: attr = { style = "float: left; position: relative;" }, bsw/jbe@19: content = function() bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw@519: onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);", bsw@519: name = "move_up_" .. tostring(initiative.id), bsw/jbe@19: class = not disabled and "clickable" or nil, bsw/jbe@19: type = "image", bsw/jbe@19: src = encode.url{ static = "icons/move_up.png" }, bsw/jbe@19: alt = _"Move up" bsw/jbe@19: } bsw/jbe@19: } bsw/jbe@19: slot.put(" ") bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw@519: onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);", bsw@519: name = "move_down_" .. tostring(initiative.id), bsw/jbe@19: class = not disabled and "clickable" or nil, bsw/jbe@19: type = "image", bsw/jbe@19: src = encode.url{ static = "icons/move_down.png" }, bsw/jbe@19: alt = _"Move down" bsw/jbe@19: } bsw/jbe@19: } bsw/jbe@19: slot.put(" ") bsw/jbe@19: end bsw/jbe@6: } bsw/jbe@6: end bsw/jbe@6: ui.container{ bsw/jbe@19: content = function() bsw@285: ui.tag{ content = "i" .. initiative.id .. ": " } bsw@285: ui.tag{ content = initiative.shortened_name } bsw@286: slot.put("
") bsw@286: for i, initiator in ipairs(initiators) do bsw@286: ui.link{ bsw@286: attr = { class = "clickable" }, bsw@286: content = function () bsw@286: execute.view{ bsw@286: module = "member_image", bsw@286: view = "_show", bsw@286: params = { bsw@286: member = initiator, bsw@286: image_type = "avatar", bsw@286: show_dummy = true, bsw@286: class = "micro_avatar", bsw@286: popup_text = text bsw@286: } bsw@286: } bsw@286: end, bsw@286: module = "member", view = "show", id = initiator.id bsw/jbe@19: } bsw@286: slot.put(" ") bsw@290: ui.tag{ content = initiator.name } bsw@286: slot.put(" ") bsw/jbe@19: end bsw/jbe@19: end bsw/jbe@6: } bsw/jbe@6: end bsw/jbe@19: } bsw/jbe@19: end bsw/jbe@5: end bsw/jbe@19: } bsw/jbe@19: end bsw/jbe@5: end bsw/jbe@5: end bsw/jbe@5: } bsw@879: if app.session.member_id and preview then bsw@879: local formatting_engine = param.get("formatting_engine") bsw@879: local comment = param.get("comment") bsw@879: local rendered_comment = format.wiki_text(comment, formatting_engine) bsw@879: slot.put(rendered_comment) bsw@879: end bsw@885: if (readonly or direct_voter and direct_voter.comment) and not preview then bsw@880: local text bsw@881: if direct_voter and direct_voter.comment_changed then bsw@880: text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) bsw@881: elseif direct_voter and direct_voter.comment then bsw@880: text = _"Voting comment" bsw@880: end bsw@881: if text then bsw@880: ui.heading{ level = "2", content = text } bsw@881: end bsw@881: if direct_voter and direct_voter.comment then bsw@879: local rendered_comment = direct_voter:get_content('html') bsw@879: ui.container{ attr = { class = "member_statement" }, content = function() bsw@879: slot.put(rendered_comment) bsw@879: end } bsw@879: slot.put("
") bsw@879: end bsw@879: end bsw@879: if app.session.member_id and app.session.member_id == member.id then bsw@879: if not readonly or direct_voter then bsw@879: ui.field.hidden{ name = "update_comment", value = param.get("update_comment") or issue.closed and "1" } bsw@879: ui.field.select{ bsw@879: label = _"Wiki engine for statement", bsw@879: name = "formatting_engine", bsw@879: foreign_records = { bsw@879: { id = "rocketwiki", name = "RocketWiki" }, bsw@879: { id = "compat", name = _"Traditional wiki syntax" } bsw@879: }, bsw@879: attr = {id = "formatting_engine"}, bsw@879: foreign_id = "id", bsw@879: foreign_name = "name", bsw@879: value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine bsw/jbe@19: } bsw@879: ui.field.text{ bsw@879: label = _"Voting comment (optional)", bsw@879: name = "comment", bsw@879: multiline = true, bsw@879: value = param.get("comment") or direct_voter and direct_voter.comment, bsw@879: attr = { style = "height: 20ex;" }, bsw@879: } bsw@879: ui.submit{ bsw@879: name = "preview", bsw@879: value = _"Preview voting comment", bsw@879: attr = { class = "preview" } bsw@879: } bsw@879: end bsw@879: if not readonly or preview or direct_voter then bsw@879: slot.put(" ") bsw@879: ui.tag{ bsw@879: tag = "input", bsw@879: attr = { bsw@879: type = "submit", bsw@879: class = "voting_done2", bsw@879: value = submit_button_text bsw@879: } bsw@879: } bsw@879: end bsw/jbe@19: end bsw/jbe@5: end bsw/jbe@5: } bsw/jbe@5: bsw/jbe@5: