Compare commits

..

No commits in common. "eb835bcebeb8bc7843114205f31055262b2e2a57" and "03b3e18126a066220527a5e49037ebf0c7aab266" have entirely different histories.

13 changed files with 26781 additions and 4862 deletions

View file

@ -10,5 +10,6 @@ gem 'base64', '0.1.1'
gem 'forwardable', '1.3.2' gem 'forwardable', '1.3.2'
gem 'bcrypt' gem 'bcrypt'
gem 'gettext' gem 'gettext'
gem 'chartkick'
gem 'mail' gem 'mail'
gem 'redcarpet' gem 'redcarpet'

View file

@ -23,6 +23,7 @@ GEM
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.4.0) benchmark (0.4.0)
bigdecimal (3.1.9) bigdecimal (3.1.9)
chartkick (5.1.4)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.0) connection_pool (2.5.0)
date (3.4.1) date (3.4.1)
@ -107,6 +108,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
base64 (= 0.1.1) base64 (= 0.1.1)
bcrypt bcrypt
chartkick
forwardable (= 1.3.2) forwardable (= 1.3.2)
gettext gettext
mail mail

20
mj.rb
View file

@ -13,9 +13,6 @@ class MajorityJudgment
if arg.is_a?(Array) if arg.is_a?(Array)
@ratings = arg @ratings = arg
@count = { } @count = { }
self.class.values.each do |v|
@count[v[:id]] = 0
end
@n = 0 @n = 0
ratings.each do |r| ratings.each do |r|
@count.has_key?(r) ? @count[r] = @count[r] + 1 : @count[r] = 1 @count.has_key?(r) ? @count[r] = @count[r] + 1 : @count[r] = 1
@ -65,23 +62,6 @@ class MajorityJudgment
return MajorityJudgment.new(counta) <=> MajorityJudgment.new(countb) return MajorityJudgment.new(counta) <=> MajorityJudgment.new(countb)
end end
def break_tie(other)
rounds = [ ]
s = MajorityJudgment.new(self.count.dup)
o = MajorityJudgment.new(other.count.dup)
rounds << [s, o]
while s.mj == o.mj and not s.mj == 0
new_s_count = s.count.dup
new_o_count = o.count.dup
new_s_count[s.mj] = new_s_count[s.mj] - 1
new_o_count[o.mj] = new_o_count[o.mj] - 1
s = MajorityJudgment.new(new_s_count)
o = MajorityJudgment.new(new_o_count)
rounds << [s, o]
end
return rounds
end
def to_s def to_s
puts "#{@count}: {M=>#{self.mj}, P=>#{self.proponents}, O=>#{self.opponents}}" puts "#{@count}: {M=>#{self.mj}, P=>#{self.proponents}, O=>#{self.opponents}}"
end end

View file

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-13 20:00-0600\n" "POT-Creation-Date: 2025-12-04 17:25-0600\n"
"PO-Revision-Date: 2025-03-29 20:41-0600\n" "PO-Revision-Date: 2025-03-29 20:41-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -41,20 +41,16 @@ msgstr "Bé"
msgid "Very good" msgid "Very good"
msgstr "Molt bé" msgstr "Molt bé"
#: ../vedia.rb:143 #: ../vedia.rb:125
msgid "Incorrect email or password." msgid "Incorrect email or password."
msgstr "Correu o contrasenya incorrecte." msgstr "Correu o contrasenya incorrecte."
#: ../vedia.rb:170 #: ../vedia.rb:147
msgid "Reset your password" msgid "Reset your password"
msgstr "Reiniciar contrasenya" msgstr "Reiniciar contrasenya"
#: ../vedia.rb:582
msgid "Results of the vote: %{t}"
msgstr "Resultats de la votació: %{t}"
#: ../views/admin.erb:1 ../views/admin_users.erb:1 ../views/admin_votes.erb:1 #: ../views/admin.erb:1 ../views/admin_users.erb:1 ../views/admin_votes.erb:1
#: ../views/layout.erb:30 #: ../views/layout.erb:35
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
@ -62,7 +58,7 @@ msgstr "Admin"
msgid "Users" msgid "Users"
msgstr "Usuàries" msgstr "Usuàries"
#: ../views/admin.erb:13 ../views/layout.erb:26 ../views/votes.erb:1 #: ../views/admin.erb:13 ../views/layout.erb:31 ../views/votes.erb:1
msgid "Votes" msgid "Votes"
msgstr "Votacions" msgstr "Votacions"
@ -114,7 +110,7 @@ msgid "No vote organized."
msgstr "Cap votació organitzada." msgstr "Cap votació organitzada."
#: ../views/admin_users.erb:30 ../views/admin_votes.erb:38 #: ../views/admin_users.erb:30 ../views/admin_votes.erb:38
#: ../views/votes_show_closed.erb:144 #: ../views/votes_show_closed.erb:89
msgid "Ratings" msgid "Ratings"
msgstr "Valoracions" msgstr "Valoracions"
@ -151,8 +147,8 @@ msgid "State: %{state}"
msgstr "Estat: %{state}" msgstr "Estat: %{state}"
#: ../views/admin_votes.erb:19 ../views/votes_edit.erb:90 #: ../views/admin_votes.erb:19 ../views/votes_edit.erb:90
#: ../views/votes_show_closed.erb:178 ../views/votes_show_draft.erb:29 #: ../views/votes_show_closed.erb:123 ../views/votes_show_draft.erb:29
#: ../views/votes_show_open.erb:88 #: ../views/votes_show_open.erb:79
msgid "Organizers" msgid "Organizers"
msgstr "Organitzadores" msgstr "Organitzadores"
@ -166,7 +162,7 @@ msgstr "Suprimir la votació"
#: ../views/candidates_edit.erb:1 #: ../views/candidates_edit.erb:1
msgid "Edit candidate" msgid "Edit candidate"
msgstr "Edita l'opció" msgstr "Edita la opció"
#: ../views/candidates_edit.erb:6 ../views/votes_edit.erb:48 #: ../views/candidates_edit.erb:6 ../views/votes_edit.erb:48
msgid "Enter a name." msgid "Enter a name."
@ -187,48 +183,40 @@ msgid ""
"You can use <a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown</a>" "You can use <a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown</a>"
"." "."
msgstr "" msgstr ""
"Pots fer servir <a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown" "Pots fer servir <a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown</a>."
"</a>."
#: ../views/candidates_edit.erb:21 ../views/votes_edit_description.erb:21 #: ../views/candidates_edit.erb:21 ../views/votes_edit_description.erb:21
msgid "Save" msgid "Save"
msgstr "Guardar" msgstr "Guardar"
#: ../views/layout.erb:35 #: ../views/layout.erb:40
msgid "Logout" msgid "Logout"
msgstr "Desconnexió" msgstr "Desconnexió"
#: ../views/login.erb:1 ../views/login.erb:21 #: ../views/login.erb:1 ../views/login.erb:16
#: ../views/votes_show_unauthenticated.erb:16
msgid "Login" msgid "Login"
msgstr "Connexió" msgstr "Connexió"
#: ../views/login.erb:6 ../views/reset.erb:1 ../views/reset.erb:16 #: ../views/login.erb:9 ../views/reset.erb:5 ../views/reset_change.erb:13
#: ../views/signup.erb:24 ../views/votes_edit.erb:102
#: ../views/votes_show_closed.erb:137 ../views/votes_show_open.erb:95
msgid "Email"
msgstr "Correu"
#: ../views/login.erb:13 ../views/reset_change.erb:17 ../views/signup.erb:28
msgid "Password"
msgstr "Contrasenya"
#: ../views/login.erb:18 ../views/signup.erb:1 ../views/signup.erb:31
msgid "Create account"
msgstr "Crear un compte"
#: ../views/login.erb:19 ../views/reset.erb:1 ../views/reset.erb:8
#: ../views/reset_change.erb:1 ../views/reset_change.erb:20 #: ../views/reset_change.erb:1 ../views/reset_change.erb:20
#: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1 #: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1
msgid "Reset password" msgid "Reset password"
msgstr "Reiniciar contrasenya" msgstr "Reiniciar contrasenya"
#: ../views/login.erb:12 ../views/reset.erb:13 ../views/reset_change.erb:13
#: ../views/signup.erb:24 ../views/votes_edit.erb:102
#: ../views/votes_show_closed.erb:192 ../views/votes_show_open.erb:104
#: ../views/votes_show_unauthenticated.erb:9
msgid "Email"
msgstr "Correu"
#: ../views/login.erb:16 ../views/reset_change.erb:17 ../views/signup.erb:28
#: ../views/votes_show_unauthenticated.erb:13
msgid "Password"
msgstr "Contrasenya"
#: ../views/login.erb:23 ../views/signup.erb:1 ../views/signup.erb:32
msgid "Create account"
msgstr "Crear un compte"
#: ../views/reset.erb:6
msgid "Enter an email address."
msgstr "Entra una direcció de correu."
#: ../views/reset_change.erb:6 ../views/signup.erb:17 #: ../views/reset_change.erb:6 ../views/signup.erb:17
msgid "Enter a password." msgid "Enter a password."
msgstr "Entra una contrasenya." msgstr "Entra una contrasenya."
@ -265,20 +253,12 @@ msgstr "El correu no és una direcció de correu vàlida."
msgid "An account already exists for %{email}." msgid "An account already exists for %{email}."
msgstr "Un compte ja existeix pel correu %{email}." msgstr "Un compte ja existeix pel correu %{email}."
#: ../views/votes.erb:18 ../views/votes_show_open.erb:7 #: ../views/votes.erb:18 ../views/votes_edit.erb:6
msgid "Voted"
msgstr "Votat"
#: ../views/votes.erb:20 ../views/votes_show_open.erb:9
msgid "Not voted"
msgstr "Sense votar"
#: ../views/votes.erb:23 ../views/votes_edit.erb:6
#: ../views/votes_show_closed.erb:7 #: ../views/votes_show_closed.erb:7
msgid "Organizer" msgid "Organizer"
msgstr "Organitzadora" msgstr "Organitzadora"
#: ../views/votes.erb:33 ../views/votes_new.erb:21 #: ../views/votes.erb:28 ../views/votes_new.erb:21
msgid "Create new vote" msgid "Create new vote"
msgstr "Crear una nova votació" msgstr "Crear una nova votació"
@ -295,7 +275,8 @@ msgid "Edit"
msgstr "Editar" msgstr "Editar"
#: ../views/votes_edit.erb:20 ../views/votes_open.erb:36 #: ../views/votes_edit.erb:20 ../views/votes_open.erb:36
#: ../views/votes_show_draft.erb:16 ../views/votes_show_open.erb:24 #: ../views/votes_show_closed.erb:22 ../views/votes_show_draft.erb:16
#: ../views/votes_show_open.erb:19
msgid "Candidates" msgid "Candidates"
msgstr "Opcions" msgstr "Opcions"
@ -320,8 +301,8 @@ msgid "Open vote to participants"
msgstr "Obrir la votació als votants" msgstr "Obrir la votació als votants"
#: ../views/votes_edit.erb:98 ../views/votes_edit.erb:105 #: ../views/votes_edit.erb:98 ../views/votes_edit.erb:105
#: ../views/votes_show_closed.erb:188 ../views/votes_show_closed.erb:195 #: ../views/votes_show_closed.erb:133 ../views/votes_show_closed.erb:140
#: ../views/votes_show_open.erb:100 ../views/votes_show_open.erb:107 #: ../views/votes_show_open.erb:91 ../views/votes_show_open.erb:98
msgid "Add organizer" msgid "Add organizer"
msgstr "Afegeix organitzadora" msgstr "Afegeix organitzadora"
@ -363,108 +344,62 @@ msgstr "Cancel·lar"
msgid "Closed on %{date}" msgid "Closed on %{date}"
msgstr "Tancada el %{date}" msgstr "Tancada el %{date}"
#: ../views/votes_show_closed.erb:24 #: ../views/votes_show_closed.erb:35
msgid "Results" msgid "Results"
msgstr "Resultats" msgstr "Resultats"
#: ../views/votes_show_closed.erb:28 #: ../views/votes_show_closed.erb:39
msgid "No results are available because nobody voted on this vote." msgid "No results are available because nobody voted on this vote."
msgstr "No hi ha cap resultat perquè ningú va votar en aquesta votació." msgstr "No hi ha cap resultat perquè ningú va votar en aquesta votació."
#: ../views/votes_show_closed.erb:41 #: ../views/votes_show_closed.erb:46
msgid "Winning candidate" msgid "Rank"
msgstr "Opció guanyadora" msgstr "Rang"
#: ../views/votes_show_closed.erb:43 #: ../views/votes_show_closed.erb:47
msgid "" msgid "Candidate"
"The winning candidate is the candidate with the highest median vote (↓), calle" msgstr "Opció"
"d <a href='https://en.wikipedia.org/wiki/Majority_judgment'><i>majority judgme"
"nt</i></a>."
msgstr ""
"L'opció guanyadora és l'opció amb la mediana de vots més alta (↓), anomenada <"
"a href='https://en.wikipedia.org/wiki/Majority_judgment'><i>judici majoritari<"
"/i></a>."
#: ../views/votes_show_closed.erb:67 #: ../views/votes_show_closed.erb:48
msgid "See description" msgid "Majority Judgment"
msgstr "Veure descripció" msgstr "Judici majoritari"
#: ../views/votes_show_closed.erb:71 #: ../views/votes_show_closed.erb:49
msgid "Analyze tiebreak" msgid "Proponents"
msgstr "Analitzar desempat" msgstr "Defensores"
#: ../views/votes_show_closed.erb:81 #: ../views/votes_show_closed.erb:50
msgid "This candidate and the winning candidate have the same majority judgment." msgid "Opponents"
msgstr "Aquesta opció i l'opció guanyadora tenen el mateix judici majoritari." msgstr "Detractores"
#: ../views/votes_show_closed.erb:83 #: ../views/votes_show_closed.erb:94
msgid ""
"To break the tie, we remove the vote that corresponds to the majority judgment"
" one-by-one until one candidate has a better majority judgment than the other."
msgstr ""
"Pel desempat, treiem el vot que correspon al judici majoritari un per un fins "
"que alguna opció tingui un millor judici majoritari que l'altre."
#: ../views/votes_show_closed.erb:85
msgid "Votes for this candidate"
msgstr "Vots per aquesta opció"
#: ../views/votes_show_closed.erb:86
msgid "Votes for the winning candidate"
msgstr "Vots per l'opció guanyadora"
#: ../views/votes_show_closed.erb:93
msgid "Without removing any vote."
msgstr "Sense treure cap vot."
#: ../views/votes_show_closed.erb:98
msgid "Removing 1 vote."
msgstr "Treient 1 vot."
#: ../views/votes_show_closed.erb:103
msgid "Removing %{n} votes."
msgstr "Treient %{n} vots."
#: ../views/votes_show_closed.erb:149
msgid "Participant" msgid "Participant"
msgstr "Votant" msgstr "Votant"
#: ../views/votes_show_closed.erb:173 #: ../views/votes_show_closed.erb:118
msgid "Reopen voting period" msgid "Reopen voting period"
msgstr "Reobrir el període de votació" msgstr "Reobrir el període de votació"
#: ../views/votes_show_open.erb:13 #: ../views/votes_show_open.erb:8
msgid "Closes on %{date}" msgid "Closes on %{date}"
msgstr "Tanca el %{date}" msgstr "Tanca el %{date}"
#: ../views/votes_show_open.erb:29 #: ../views/votes_show_open.erb:24
msgid "Missing rating for candidate <i>%{name}</i>." msgid "Missing rating for candidate <i>%{name}</i>."
msgstr "Falta una valoració per l'opció <i>%{name}</i>." msgstr "Falta una valoració per l'opció <i>%{name}</i>."
#: ../views/votes_show_open.erb:35 #: ../views/votes_show_open.erb:54
msgid "Thank you for voting!"
msgstr "Gràcies per votar!"
#: ../views/votes_show_open.erb:63
msgid "Vote" msgid "Vote"
msgstr "Votar" msgstr "Votar"
#: ../views/votes_show_open.erb:66 #: ../views/votes_show_open.erb:57
msgid "Participants" msgid "Participants"
msgstr "Votants" msgstr "Votants"
#: ../views/votes_show_open.erb:77 #: ../views/votes_show_open.erb:68
msgid "Change back to draft vote" msgid "Change back to draft vote"
msgstr "Tornar a l'esborrany de votació" msgstr "Tornar a l'esborrany de votació"
#: ../views/votes_show_open.erb:81 #: ../views/votes_show_open.erb:72
msgid "Close votes and show results" msgid "Close votes and show results"
msgstr "Tancar la votació i veure els resultats" msgstr "Tancar la votació i veure els resultats"
#: ../views/votes_show_unauthenticated.erb:5
msgid "You need to log in to see the details of this vote."
msgstr "T'has de connectar per veure el detall d'aquesta votació."
#: ../views/votes_show_unknown.erb:2
msgid "Vote not found..."
msgstr "Votació no encontrada..."

View file

@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-13 20:00-0600\n" "POT-Creation-Date: 2025-12-04 17:25-0600\n"
"PO-Revision-Date: 2026-01-13 20:00-0600\n" "PO-Revision-Date: 2025-12-04 17:25-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
@ -42,20 +42,16 @@ msgstr ""
msgid "Very good" msgid "Very good"
msgstr "" msgstr ""
#: ../vedia.rb:143 #: ../vedia.rb:125
msgid "Incorrect email or password." msgid "Incorrect email or password."
msgstr "" msgstr ""
#: ../vedia.rb:170 #: ../vedia.rb:147
msgid "Reset your password" msgid "Reset your password"
msgstr "" msgstr ""
#: ../vedia.rb:582
msgid "Results of the vote: %{t}"
msgstr ""
#: ../views/admin.erb:1 ../views/admin_users.erb:1 ../views/admin_votes.erb:1 #: ../views/admin.erb:1 ../views/admin_users.erb:1 ../views/admin_votes.erb:1
#: ../views/layout.erb:30 #: ../views/layout.erb:35
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
@ -63,7 +59,7 @@ msgstr ""
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: ../views/admin.erb:13 ../views/layout.erb:26 ../views/votes.erb:1 #: ../views/admin.erb:13 ../views/layout.erb:31 ../views/votes.erb:1
msgid "Votes" msgid "Votes"
msgstr "" msgstr ""
@ -115,7 +111,7 @@ msgid "No vote organized."
msgstr "" msgstr ""
#: ../views/admin_users.erb:30 ../views/admin_votes.erb:38 #: ../views/admin_users.erb:30 ../views/admin_votes.erb:38
#: ../views/votes_show_closed.erb:144 #: ../views/votes_show_closed.erb:89
msgid "Ratings" msgid "Ratings"
msgstr "" msgstr ""
@ -152,8 +148,8 @@ msgid "State: %{state}"
msgstr "" msgstr ""
#: ../views/admin_votes.erb:19 ../views/votes_edit.erb:90 #: ../views/admin_votes.erb:19 ../views/votes_edit.erb:90
#: ../views/votes_show_closed.erb:178 ../views/votes_show_draft.erb:29 #: ../views/votes_show_closed.erb:123 ../views/votes_show_draft.erb:29
#: ../views/votes_show_open.erb:88 #: ../views/votes_show_open.erb:79
msgid "Organizers" msgid "Organizers"
msgstr "" msgstr ""
@ -193,39 +189,32 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: ../views/layout.erb:35 #: ../views/layout.erb:40
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: ../views/login.erb:1 ../views/login.erb:21 #: ../views/login.erb:1 ../views/login.erb:16
#: ../views/votes_show_unauthenticated.erb:16
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: ../views/login.erb:6 ../views/reset.erb:1 ../views/reset.erb:16 #: ../views/login.erb:9 ../views/reset.erb:5 ../views/reset_change.erb:13
#: ../views/reset_change.erb:1 ../views/reset_change.erb:20
#: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1
msgid "Reset password"
msgstr ""
#: ../views/login.erb:12 ../views/reset.erb:13 ../views/reset_change.erb:13
#: ../views/signup.erb:24 ../views/votes_edit.erb:102 #: ../views/signup.erb:24 ../views/votes_edit.erb:102
#: ../views/votes_show_closed.erb:192 ../views/votes_show_open.erb:104 #: ../views/votes_show_closed.erb:137 ../views/votes_show_open.erb:95
#: ../views/votes_show_unauthenticated.erb:9
msgid "Email" msgid "Email"
msgstr "" msgstr ""
#: ../views/login.erb:16 ../views/reset_change.erb:17 ../views/signup.erb:28 #: ../views/login.erb:13 ../views/reset_change.erb:17 ../views/signup.erb:28
#: ../views/votes_show_unauthenticated.erb:13
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: ../views/login.erb:23 ../views/signup.erb:1 ../views/signup.erb:32 #: ../views/login.erb:18 ../views/signup.erb:1 ../views/signup.erb:31
msgid "Create account" msgid "Create account"
msgstr "" msgstr ""
#: ../views/reset.erb:6 #: ../views/login.erb:19 ../views/reset.erb:1 ../views/reset.erb:8
msgid "Enter an email address." #: ../views/reset_change.erb:1 ../views/reset_change.erb:20
#: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1
msgid "Reset password"
msgstr "" msgstr ""
#: ../views/reset_change.erb:6 ../views/signup.erb:17 #: ../views/reset_change.erb:6 ../views/signup.erb:17
@ -262,20 +251,12 @@ msgstr ""
msgid "An account already exists for %{email}." msgid "An account already exists for %{email}."
msgstr "" msgstr ""
#: ../views/votes.erb:18 ../views/votes_show_open.erb:7 #: ../views/votes.erb:18 ../views/votes_edit.erb:6
msgid "Voted"
msgstr ""
#: ../views/votes.erb:20 ../views/votes_show_open.erb:9
msgid "Not voted"
msgstr ""
#: ../views/votes.erb:23 ../views/votes_edit.erb:6
#: ../views/votes_show_closed.erb:7 #: ../views/votes_show_closed.erb:7
msgid "Organizer" msgid "Organizer"
msgstr "" msgstr ""
#: ../views/votes.erb:33 ../views/votes_new.erb:21 #: ../views/votes.erb:28 ../views/votes_new.erb:21
msgid "Create new vote" msgid "Create new vote"
msgstr "" msgstr ""
@ -292,7 +273,8 @@ msgid "Edit"
msgstr "" msgstr ""
#: ../views/votes_edit.erb:20 ../views/votes_open.erb:36 #: ../views/votes_edit.erb:20 ../views/votes_open.erb:36
#: ../views/votes_show_draft.erb:16 ../views/votes_show_open.erb:24 #: ../views/votes_show_closed.erb:22 ../views/votes_show_draft.erb:16
#: ../views/votes_show_open.erb:19
msgid "Candidates" msgid "Candidates"
msgstr "" msgstr ""
@ -317,8 +299,8 @@ msgid "Open vote to participants"
msgstr "" msgstr ""
#: ../views/votes_edit.erb:98 ../views/votes_edit.erb:105 #: ../views/votes_edit.erb:98 ../views/votes_edit.erb:105
#: ../views/votes_show_closed.erb:188 ../views/votes_show_closed.erb:195 #: ../views/votes_show_closed.erb:133 ../views/votes_show_closed.erb:140
#: ../views/votes_show_open.erb:100 ../views/votes_show_open.erb:107 #: ../views/votes_show_open.erb:91 ../views/votes_show_open.erb:98
msgid "Add organizer" msgid "Add organizer"
msgstr "" msgstr ""
@ -358,103 +340,62 @@ msgstr ""
msgid "Closed on %{date}" msgid "Closed on %{date}"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:24 #: ../views/votes_show_closed.erb:35
msgid "Results" msgid "Results"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:28 #: ../views/votes_show_closed.erb:39
msgid "No results are available because nobody voted on this vote." msgid "No results are available because nobody voted on this vote."
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:41 #: ../views/votes_show_closed.erb:46
msgid "Winning candidate" msgid "Rank"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:43 #: ../views/votes_show_closed.erb:47
msgid "" msgid "Candidate"
"The winning candidate is the candidate with the highest median vote (↓), calle"
"d <a href='https://en.wikipedia.org/wiki/Majority_judgment'><i>majority judgme"
"nt</i></a>."
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:67 #: ../views/votes_show_closed.erb:48
msgid "See description" msgid "Majority Judgment"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:71 #: ../views/votes_show_closed.erb:49
msgid "Analyze tiebreak" msgid "Proponents"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:81 #: ../views/votes_show_closed.erb:50
msgid "This candidate and the winning candidate have the same majority judgment." msgid "Opponents"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:83 #: ../views/votes_show_closed.erb:94
msgid ""
"To break the tie, we remove the vote that corresponds to the majority judgment"
" one-by-one until one candidate has a better majority judgment than the other."
msgstr ""
#: ../views/votes_show_closed.erb:85
msgid "Votes for this candidate"
msgstr ""
#: ../views/votes_show_closed.erb:86
msgid "Votes for the winning candidate"
msgstr ""
#: ../views/votes_show_closed.erb:93
msgid "Without removing any vote."
msgstr ""
#: ../views/votes_show_closed.erb:98
msgid "Removing 1 vote."
msgstr ""
#: ../views/votes_show_closed.erb:103
msgid "Removing %{n} votes."
msgstr ""
#: ../views/votes_show_closed.erb:149
msgid "Participant" msgid "Participant"
msgstr "" msgstr ""
#: ../views/votes_show_closed.erb:173 #: ../views/votes_show_closed.erb:118
msgid "Reopen voting period" msgid "Reopen voting period"
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:13 #: ../views/votes_show_open.erb:8
msgid "Closes on %{date}" msgid "Closes on %{date}"
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:29 #: ../views/votes_show_open.erb:24
msgid "Missing rating for candidate <i>%{name}</i>." msgid "Missing rating for candidate <i>%{name}</i>."
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:35 #: ../views/votes_show_open.erb:54
msgid "Thank you for voting!"
msgstr ""
#: ../views/votes_show_open.erb:63
msgid "Vote" msgid "Vote"
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:66 #: ../views/votes_show_open.erb:57
msgid "Participants" msgid "Participants"
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:77 #: ../views/votes_show_open.erb:68
msgid "Change back to draft vote" msgid "Change back to draft vote"
msgstr "" msgstr ""
#: ../views/votes_show_open.erb:81 #: ../views/votes_show_open.erb:72
msgid "Close votes and show results" msgid "Close votes and show results"
msgstr "" msgstr ""
#: ../views/votes_show_unauthenticated.erb:5
msgid "You need to log in to see the details of this vote."
msgstr ""
#: ../views/votes_show_unknown.erb:2
msgid "Vote not found..."
msgstr ""

24043
public/Chart.bundle.js Normal file

File diff suppressed because it is too large Load diff

4494
public/bootstrap.js vendored

File diff suppressed because it is too large Load diff

2570
public/chartkick.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ require 'sinatra/activerecord'
require 'bcrypt' require 'bcrypt'
require 'gettext' require 'gettext'
require 'securerandom' require 'securerandom'
require 'chartkick'
require 'mail' require 'mail'
require 'ostruct' require 'ostruct'
require 'tzinfo' require 'tzinfo'
@ -579,7 +580,7 @@ def close_expired_votes
mail = Mail.new mail = Mail.new
mail.from = settings.admin_email mail.from = settings.admin_email
mail.to = user.email mail.to = user.email
mail.subject = _("Results of the vote: %{t}") % { t: vote.title } mail.subject = _("Results of the vote: #{vote.title}")
template = ERB.new(File.read("views/votes_close_email.erb")) template = ERB.new(File.read("views/votes_close_email.erb"))
mail.body = template.result(binding) mail.body = template.result(binding)
mail.deliver mail.deliver
@ -592,8 +593,6 @@ helpers do
if session[:user_id] if session[:user_id]
User.find(session[:user_id]) User.find(session[:user_id])
elsif settings.spoof_admin elsif settings.spoof_admin
session.clear
session[:timezone] = 'UTC'
User.find(1) User.find(1)
else else
nil nil

View file

@ -9,7 +9,8 @@
<% else %> <% else %>
<title>Vedia</title> <title>Vedia</title>
<% end %> <% end %>
<script src="/bootstrap.js"></script> <script src="/chartkick.js"></script>
<script src="/Chart.bundle.js"></script>
<link rel="stylesheet" href="/bootstrap.css"> <link rel="stylesheet" href="/bootstrap.css">
<link rel="stylesheet" href="/bootstrap-icons.css"> <link rel="stylesheet" href="/bootstrap-icons.css">
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href="/style.css">

View file

@ -17,8 +17,4 @@
background-color: <%= v[:color] %>; background-color: <%= v[:color] %>;
} }
.mj-cell-<%= v[:id] %> {
background-color: <%= v[:color] %> !important;
}
<% end %> <% end %>

View file

@ -19,6 +19,17 @@
</div> </div>
</div> </div>
<h2 class="mb-4"><%= _("Candidates") %></h2>
<% @vote.candidates.each do |candidate| %>
<div class="card mb-4">
<div class="card-body">
<h3 class="mb-3"><%= candidate.name %></h3>
<%= markdown(candidate.description) %>
</div>
</div>
<% end %>
<div class="mb-5"></div> <div class="mb-5"></div>
<h2 class="mb-4"><%= _("Results") %></h2> <h2 class="mb-4"><%= _("Results") %></h2>
@ -29,121 +40,55 @@
<% else %> <% else %>
<% r = 0 %> <table class="table mb-4">
<% @results = @vote.candidates.sort { |a, b| a.mj <=> b.mj }.reverse %>
<% @results.each do |c| %>
<% r = r + 1 %>
<div class="card mb-4">
<div class="card-body">
<% if r == 1 %>
<p class="fs-5">
<i class="bi bi-trophy-fill"></i>
<%= _("Winning candidate") %>
</p>
<p><%= _("The winning candidate is the candidate with the highest median vote (↓), called <a href='https://en.wikipedia.org/wiki/Majority_judgment'><i>majority judgment</i></a>.") %></p>
<% end %>
<p class="fs-5">
<%= "##{r}" %>
<% value = settings.values.select { |e| e[:id] == c.mj.mj }.first %>
<span class="badge bg-<%= value[:id] %>"><%= _(value[:label]) %></span>
</p>
<h3><%= c.name %></h3>
<table class="table table-borderless">
<tr> <tr>
<% (1..c.mj.n).each do |i| %> <th></th>
<td class="text-center <% unless i == c.mj.majority %>opacity-0<% end %>">↓</td> <th><%= _("Rank") %></th>
<% end %> <th><%= _("Candidate") %></th>
<th><%= _("Majority Judgment") %></th>
<th><%= _("Proponents") %></th>
<th><%= _("Opponents") %></th>
</tr> </tr>
<% i = 0 %>
<% @vote.candidates.sort { |a, b| a.mj <=> b.mj }.reverse.each do |candidate| %>
<% i = i + 1 %>
<% if i == 1 %>
<tr class="table-success">
<td><i class="bi bi-trophy-fill"></i></td>
<td><%= i %></i></td>
<% else %>
<tr> <tr>
<% settings.values.reverse.each do |v| %> <td></td>
<% (1..c.mj.count[v[:id]]).each do |r| %> <td><%= i %></td>
<td class="mj-cell-<%= v[:id] %>">&nbsp;</td>
<% end %>
<% end %> <% end %>
<td><%= candidate.name %></td>
<% value = settings.values.select { |e| e[:id] == candidate.mj.mj }.first %>
<td class="h5"><span class="badge bg-<%= value[:id] %>"><%= _(value[:label]) %></span></td>
<td><%= candidate.mj.proponents %></td>
<td><%= candidate.mj.opponents %></td>
</tr> </tr>
</table>
<p class="d-inline-flex gap-3">
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#description-<%= r %>" role="button" aria-expanded="false" aria-controls="description-<%= r %>">
<%= _("See description") %>
</a>
<% if r > 1 and c.mj.mj == @results.first.mj.mj %>
<a class="btn btn-primary" data-bs-toggle="collapse" href="#tiebreak-<%= r %>" role="button" aria-expanded="false" aria-controls="tiebreak-<%= r %>">
<%= _("Analyze tiebreak") %>
</a>
<% end %> <% end %>
</p> </table>
<div class="collapse" id="description-<%= r %>">
<%= markdown(c.description) %> <%
</div> data = []
<% if r > 1 and c.mj.mj == @results.first.mj.mj %> colors = []
<div class="collapse" id="tiebreak-<%= r %>"> settings.values.reverse.each do |v|
<p> d = { name: _(v[:label]), data: [] }
<%= _("This candidate and the winning candidate have the same majority judgment.") %> @vote.candidates.sort { |a, b| a.mj <=> b.mj }.reverse.each do |c|
</p> d[:data] << [c.name, c.mj.count[v[:id]]]
<p><%= _("To break the tie, we remove the vote that corresponds to the majority judgment one-by-one until one candidate has a better majority judgment than the other.") %></p> end
<div class="row fw-bold"> data << d
<div class="col"><%= _("Votes for this candidate") %></div> colors << v[:color]
<div class="col text-end"><%= _("Votes for the winning candidate") %></div> end
</div> %>
<% n = 0 %> <%= bar_chart data, colors: colors, stacked: true, legend: 'bottom' %>
<% c.mj.break_tie(@results.first.mj).each do |a, b| %>
<hr>
<% if n == 0 %>
<div class="row">
<div class="col"><%= _("Without removing any vote.") %></div>
</div>
<% end %>
<% if n == 1 %>
<div class="row">
<div class="col"><%= _("Removing 1 vote.") %></div>
</div>
<% end %>
<% if n > 1 %>
<div class="row">
<div class="col"><%= _("Removing %{n} votes.") % { n: n } %></div>
</div>
<% end %>
<table class="table table-borderless">
<tr>
<% (1..a.n).each do |i| %>
<td class="text-center <% unless i == a.majority %>opacity-0<% end %>">↓</td>
<% end %>
<th class="w-100"></th>
<% (1..b.n).each do |i| %>
<td class="text-center <% unless b.n - i + 1 == b.majority %>opacity-0<% end %>">↓</td>
<% end %>
</tr>
<tr>
<% settings.values.reverse.each do |v| %>
<% (1..a.count[v[:id]]).each do |r| %>
<td class="mj-cell-<%= v[:id] %>">&nbsp;</td>
<% end %>
<% end %>
<td class="text-center fw-bold">
<% if a.mj < b.mj %><<% end %>
<% if a.mj > b.mj %>><% end %>
<% if a.mj == b.mj %>=<% end %>
</td>
<% settings.values.each do |v| %>
<% (1..b.count[v[:id]]).each do |r| %>
<td class="mj-cell-<%= v[:id] %>">&nbsp;</td>
<% end %>
<% end %>
</tr>
</table>
<% n = n + 1 %>
<% end %>
</div>
<% end %>
</div>
</div>
<% end %>
<div class="mb-5"></div> <div class="mb-5"></div>
<h2 class="mb-4"><%= _("Ratings") + " (#{@vote.ratings.collect { |rating| rating.user }.uniq.count})" %></h2> <h2 class="mb-4"><%= _("Ratings") %></h2>
<table class="table table-striped w-auto mb-5"> <table class="table table-striped mb-5">
<thead> <thead>
<tr> <tr>
<th><%= _("Participant") %></th> <th><%= _("Participant") %></th>

View file

@ -32,7 +32,7 @@
<% end %> <% end %>
<% if @voted %> <% if @voted %>
<p class="alert alert-success mb-4"><%= _("Thank you for voting!") %></p> <p class="alert alert-success mb-4"><%= _("Gràcies per votar!") %></p>
<% end %> <% end %>
<form action="/votes/<%= @vote.secure_id %>/ratings" method="post" class="mb-5"> <form action="/votes/<%= @vote.secure_id %>/ratings" method="post" class="mb-5">