liquid_feedback_frontend

changeset 1649:4188405c2425

Rework of suggestion views
author bsw
date Thu Feb 11 15:48:02 2021 +0100 (2021-02-11)
parents 1335379a85d2
children 12a73990db76
files app/main/initiative/_suggestions.lua app/main/interest/_action/xhr_update.lua app/main/opinion/_action/update.lua app/main/opinion/_action/xhr_update.lua app/main/suggestion/_collective_rating.lua app/main/suggestion/show.lua env/ui/icon.lua locale/translations.de.lua model/opinion.lua static/js/xhr.js static/lf4.css
line diff
     1.1 --- a/app/main/initiative/_suggestions.lua	Thu Feb 11 15:45:56 2021 +0100
     1.2 +++ b/app/main/initiative/_suggestions.lua	Thu Feb 11 15:48:02 2021 +0100
     1.3 @@ -5,9 +5,130 @@
     1.4  end
     1.5  
     1.6  
     1.7 +if direct_supporter then
     1.8 +  ui.tag{ tag = "dialog", attr = { id = "rating_dialog" }, content = function ()
     1.9 +
    1.10 +    local opinion = {}
    1.11 +    ui.form { 
    1.12 +      attr = { onsubmit = "updateOpinion(); return false;" },
    1.13 +      module = "opinion", action = "update",
    1.14 +      routing = { default = {
    1.15 +        mode = "redirect", 
    1.16 +        module = "initiative", view = "show", id = initiative.id
    1.17 +      } },
    1.18 +      content = function ()
    1.19 +        ui.field.hidden{ attr = { id = "rating_suggestion_id" }, name = "suggestion_id" }        
    1.20 +        ui.container{ attr = { class = "opinon-question" }, content = _"Should the initiator implement this suggestion?" }
    1.21 +        ui.container { content = function ()
    1.22 +        
    1.23 +          local options = {
    1.24 +            { degree =  2, label = _"must" },
    1.25 +            { degree =  1, label = _"should" },
    1.26 +            { degree =  0, label = _"neutral" },
    1.27 +            { degree = -1, label = _"should not" },
    1.28 +            { degree = -2, label = _"must not" },
    1.29 +          }
    1.30 +          
    1.31 +          for i, option in ipairs(options) do
    1.32 +            ui.tag{
    1.33 +              tag = "label", 
    1.34 +              attr = { 
    1.35 +                id = "rating_degree" .. option.degree,
    1.36 +                class = "mdl-radio mdl-js-radio mdl-js-ripple-effect"
    1.37 +              }, 
    1.38 +              ["for"] = "rating_degree" .. option.degree, 
    1.39 +              content = function()
    1.40 +                ui.tag{
    1.41 +                  tag = "input",
    1.42 +                  attr = {
    1.43 +                    class = "mdl-radio__button",
    1.44 +                    type = "radio",
    1.45 +                    name = "degree",
    1.46 +                    value = option.degree
    1.47 +                  }
    1.48 +                }
    1.49 +                ui.tag{
    1.50 +                  attr = { class = "mdl-radio__label" },
    1.51 +                  content = option.label
    1.52 +                }
    1.53 +              end
    1.54 +            }
    1.55 +            slot.put("     ")
    1.56 +          end
    1.57 +        end }
    1.58 +        
    1.59 +        slot.put("<br />")
    1.60 +
    1.61 +        ui.container{ attr = { class = "opinon-question" }, content = _"Did the initiator implement this suggestion?" }
    1.62 +        ui.container { content = function ()
    1.63 +
    1.64 +          local options = {
    1.65 +            { degree = "false", id = "notfulfilled", label = _"No (not yet)" },
    1.66 +            { degree = "true", id = "fulfilled", label = _"Yes, it's implemented" },
    1.67 +          }
    1.68 +          
    1.69 +          for i, option in ipairs(options) do
    1.70 +            ui.tag{
    1.71 +              tag = "label", 
    1.72 +              attr = {
    1.73 +                id = "rating_" .. option.id,
    1.74 +                class = "mdl-radio mdl-js-radio mdl-js-ripple-effect"
    1.75 +              }, 
    1.76 +              ["for"] = "rating_" .. option.id, 
    1.77 +              content = function()
    1.78 +                ui.tag{
    1.79 +                  tag = "input",
    1.80 +                  attr = {
    1.81 +                    class = "mdl-radio__button",
    1.82 +                    type = "radio",
    1.83 +                    name = "fulfilled",
    1.84 +                    value = option.degree,
    1.85 +                  }
    1.86 +                }
    1.87 +                ui.tag{
    1.88 +                  attr = { class = "mdl-radio__label" },
    1.89 +                  content = option.label
    1.90 +                }
    1.91 +              end
    1.92 +            }
    1.93 +            slot.put(" &nbsp;&nbsp;&nbsp; ")
    1.94 +          end
    1.95 +        end }
    1.96 +    
    1.97 +        slot.put("<br />")
    1.98 +        
    1.99 +        ui.tag{
   1.100 +          tag = "input",
   1.101 +          attr = {
   1.102 +            type = "submit",
   1.103 +            class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
   1.104 +            value = _"publish my rating"
   1.105 +          },
   1.106 +          content = ""
   1.107 +        }
   1.108 +        
   1.109 +        slot.put(" &nbsp; ")
   1.110 +        
   1.111 +        ui.tag{
   1.112 +          tag = "input",
   1.113 +          attr = {
   1.114 +            onclick = "document.getElementById('rating_dialog').close(); return false;",
   1.115 +            type = "submit",
   1.116 +            class = "mdl-button mdl-js-button",
   1.117 +            value = _"cancel"
   1.118 +          },
   1.119 +          content = ""
   1.120 +        }
   1.121 +        
   1.122 +      end 
   1.123 +    }
   1.124 +
   1.125 +  end }
   1.126 +end
   1.127 +
   1.128 +
   1.129  ui.link { attr = { name = "suggestions" }, text = "" }
   1.130  
   1.131 -
   1.132  ui.container {
   1.133    attr = { class = "section suggestions" },
   1.134    content = function ()
   1.135 @@ -38,108 +159,18 @@
   1.136            ui.tag{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
   1.137              ui.heading { level = 2, 
   1.138                attr = { class = "mdl-card__title-text" },
   1.139 -              content = format.string(suggestion.name, {
   1.140 -              truncate_at = 160, truncate_suffix = true
   1.141 -            }) }
   1.142 -
   1.143 -            if opinion then
   1.144 -              
   1.145 -              ui.container { attr = { class = "mdl-card__content"}, content = function()
   1.146 -                local class = ""
   1.147 -                local text = ""
   1.148 -                
   1.149 -                if opinion.degree == 2 then
   1.150 -                  class = "must"
   1.151 -                  text = _"must"
   1.152 -                elseif opinion.degree == 1 then
   1.153 -                  class = "should"
   1.154 -                  text = _"should"
   1.155 -                elseif opinion.degree == 0 then
   1.156 -                  class = "neutral"
   1.157 -                  text = _"neutral"
   1.158 -                elseif opinion.degree == -1 then
   1.159 -                  class = "shouldnot"
   1.160 -                  text = _"should not"
   1.161 -                elseif opinion.degree == -2 then
   1.162 -                  class = "mustnot"
   1.163 -                  text = _"must not"
   1.164 -                end
   1.165 -                
   1.166 -                ui.tag { 
   1.167 -                  attr = { class = class }, 
   1.168 -                  content = text 
   1.169 +              content = function()
   1.170 +                ui.tag{ content = format.string(suggestion.name, {
   1.171 +                  truncate_at = 160, truncate_suffix = true })
   1.172                  }
   1.173 -                
   1.174 -                slot.put ( " " )
   1.175 -                
   1.176 -                if 
   1.177 -                  (opinion.degree > 0 and not opinion.fulfilled)
   1.178 -                  or (opinion.degree < 0 and opinion.fulfilled)
   1.179 -                then
   1.180 -                  ui.tag{ content = _"but" }
   1.181 -                else
   1.182 -                  ui.tag{ content = _"and" }
   1.183 -                end
   1.184 -                  
   1.185 -                slot.put ( " " )
   1.186 -                
   1.187 -                local class = ""
   1.188 -                local text = ""
   1.189 -                
   1.190 -                if opinion.fulfilled then
   1.191 -                  class = "implemented"
   1.192 -                  text = _"is implemented"
   1.193 -                else
   1.194 -                  class = "notimplemented"
   1.195 -                  text = _"is not implemented"
   1.196 -                end
   1.197 +              end
   1.198 +            }
   1.199 +          end }
   1.200  
   1.201 -                ui.tag { 
   1.202 -                  attr = { class = class }, 
   1.203 -                  content = text
   1.204 -                }
   1.205 -
   1.206 -                if 
   1.207 -                  (opinion.degree > 0 and not opinion.fulfilled)
   1.208 -                  or (opinion.degree < 0 and opinion.fulfilled)
   1.209 -                then
   1.210 -                  if math.abs(opinion.degree) > 1 then
   1.211 -                    slot.put(" !!")
   1.212 -                  else
   1.213 -                    slot.put(" !")
   1.214 -                  end
   1.215 -                else
   1.216 -                  slot.put(" ✓")
   1.217 -                end
   1.218 -
   1.219 -              end }
   1.220 -
   1.221 -            end
   1.222 -              
   1.223 -          end }
   1.224 +            
   1.225        
   1.226            ui.container{ attr = { class = "suggestion-content" }, content = function()
   1.227          
   1.228 -            local plus2  = (suggestion.plus2_unfulfilled_count or 0)
   1.229 -                + (suggestion.plus2_fulfilled_count or 0)
   1.230 -            local plus1  = (suggestion.plus1_unfulfilled_count  or 0)
   1.231 -                + (suggestion.plus1_fulfilled_count or 0)
   1.232 -            local minus1 = (suggestion.minus1_unfulfilled_count  or 0)
   1.233 -                + (suggestion.minus1_fulfilled_count or 0)
   1.234 -            local minus2 = (suggestion.minus2_unfulfilled_count  or 0)
   1.235 -                + (suggestion.minus2_fulfilled_count or 0)
   1.236 -            
   1.237 -            local with_opinion = plus2 + plus1 + minus1 + minus2
   1.238 -
   1.239 -            local neutral = (suggestion.initiative.supporter_count or 0)
   1.240 -                - with_opinion
   1.241 -
   1.242 -            local neutral2 = with_opinion 
   1.243 -                  - (suggestion.plus2_fulfilled_count or 0)
   1.244 -                  - (suggestion.plus1_fulfilled_count or 0)
   1.245 -                  - (suggestion.minus1_fulfilled_count or 0)
   1.246 -                  - (suggestion.minus2_fulfilled_count or 0)
   1.247 -            
   1.248              ui.container { 
   1.249                attr = { class = "mdl-card__content mdl-card--border suggestionInfo" },
   1.250                content = function ()
   1.251 @@ -148,203 +179,153 @@
   1.252                    util.micro_avatar ( suggestion.author )
   1.253                  end
   1.254                  
   1.255 -                if with_opinion > 0 then
   1.256 -                  ui.container { attr = { class = "suggestion-rating" }, content = function ()
   1.257 -                    ui.tag { content = _"collective rating:" }
   1.258 -                    slot.put("&nbsp;")
   1.259 -                    ui.bargraph{
   1.260 -                      max_value = suggestion.initiative.supporter_count,
   1.261 -                      width = 100,
   1.262 -                      bars = {
   1.263 -                        { color = "#0a0", value = plus2 },
   1.264 -                        { color = "#8a8", value = plus1 },
   1.265 -                        { color = "#eee", value = neutral },
   1.266 -                        { color = "#a88", value = minus1 },
   1.267 -                        { color = "#a00", value = minus2 },
   1.268 -                      }
   1.269 -                    }
   1.270 -                    slot.put(" | ")
   1.271 -                    ui.tag { content = _"implemented:" }
   1.272 -                    slot.put ( "&nbsp;" )
   1.273 -                    ui.bargraph{
   1.274 -                      max_value = with_opinion,
   1.275 -                      width = 100,
   1.276 -                      bars = {
   1.277 -                        { color = "#0a0", value = suggestion.plus2_fulfilled_count },
   1.278 -                        { color = "#8a8", value = suggestion.plus1_fulfilled_count },
   1.279 -                        { color = "#eee", value = neutral2 },
   1.280 -                        { color = "#a88", value = suggestion.minus1_fulfilled_count },
   1.281 -                        { color = "#a00", value = suggestion.minus2_fulfilled_count },
   1.282 -                      }
   1.283 -                    }
   1.284 -                  end }
   1.285 -                end
   1.286 +                execute.view{
   1.287 +                  module = "suggestion", view = "_collective_rating", params = {
   1.288 +                    suggestion = suggestion
   1.289 +                  }
   1.290 +                }
   1.291  
   1.292                end 
   1.293              }
   1.294                  
   1.295              ui.container {
   1.296 -              attr = { class = "mdl-card__content mdl-card--border suggestion-text draft" },
   1.297 +              attr = { class = "mdl-card__content suggestion-text draft" },
   1.298                content = function ()
   1.299                  slot.put ( suggestion:get_content( "html" ) )
   1.300 +
   1.301 +                ui.container { attr = { class = "floatx-right" }, content = function()
   1.302 +                
   1.303 +                  ui.link { 
   1.304 +                    attr = { 
   1.305 +                      class = "mdl-button mdl-js-button mdl-button--icon suggestion-more",
   1.306 +                      onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.remove('folded');document.querySelector('#s" .. suggestion.id .. "').classList.add('unfolded'); return false;"
   1.307 +                    },
   1.308 +                    content = function()
   1.309 +                      ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "expand_more" }
   1.310 +                    end
   1.311 +                  }
   1.312 +                  
   1.313 +                  ui.link { 
   1.314 +                    attr = { 
   1.315 +                      class = "mdl-button mdl-js-button mdl-button--icon suggestion-less",
   1.316 +                      onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.add('folded');document.querySelector('#s" .. suggestion.id .. "').classList.remove('unfolded'); return false;"
   1.317 +                    },
   1.318 +                    content = function()
   1.319 +                      ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "expand_less" }
   1.320 +                    end
   1.321 +                  }
   1.322 +                  --[[
   1.323 +                  ui.link{
   1.324 +                    attr = { class = "mdl-button" },
   1.325 +                    content = _"Details",
   1.326 +                    module = "suggestion", view = "show", id = suggestion.id
   1.327 +                  }
   1.328 +                  --]]
   1.329 +                end }
   1.330                 
   1.331                end
   1.332              }
   1.333   
   1.334 -            if direct_supporter then
   1.335 -              ui.container{ attr = { class = "mdl-card__content rating" }, content = function ()
   1.336 -
   1.337 -                if not opinion then
   1.338 -                  opinion = {}
   1.339 -                end
   1.340 -                ui.form { 
   1.341 -                  module = "opinion", action = "update", params = {
   1.342 -                    suggestion_id = suggestion.id
   1.343 -                  },
   1.344 -                  routing = { default = {
   1.345 -                    mode = "redirect", 
   1.346 -                    module = "initiative", view = "show", id = suggestion.initiative_id,
   1.347 -                    params = { suggestion_id = suggestion.id },
   1.348 -                    anchor = "s" .. suggestion.id -- TODO webmcp
   1.349 -                  } },
   1.350 -                  content = function ()
   1.351 -                    
   1.352 -                    ui.container{ attr = { class = "opinon-question" }, content = _"Should the initiator implement this suggestion?" }
   1.353 -                    ui.container { content = function ()
   1.354 -                    
   1.355 -                      local options = {
   1.356 -                        { degree =  2, label = _"must" },
   1.357 -                        { degree =  1, label = _"should" },
   1.358 -                        { degree =  0, label = _"neutral" },
   1.359 -                        { degree = -1, label = _"should not" },
   1.360 -                        { degree = -2, label = _"must not" },
   1.361 -                      }
   1.362 -                      
   1.363 -                      for i, option in ipairs(options) do
   1.364 -                        local active = opinion.degree == option.degree or opinion.degree == nil and option.degree == 0
   1.365 -                        ui.tag{
   1.366 -                          tag = "label", 
   1.367 -                          attr = { class = "mdl-radio mdl-js-radio mdl-js-ripple-effect" }, 
   1.368 -                          ["for"] = "s" .. suggestion.id .. "_degree" .. option.degree, 
   1.369 -                          content = function()
   1.370 -                            ui.tag{
   1.371 -                              tag = "input",
   1.372 -                              attr = {
   1.373 -                                class = "mdl-radio__button",
   1.374 -                                type = "radio",
   1.375 -                                name = "degree",
   1.376 -                                value = option.degree,
   1.377 -                                id = "s" .. suggestion.id .. "_degree" .. option.degree,
   1.378 -                                checked = active and "checked" or nil
   1.379 -                              }
   1.380 -                            }
   1.381 -                            ui.tag{
   1.382 -                              attr = { class = "mdl-radio__label" },
   1.383 -                              content = option.label
   1.384 -                            }
   1.385 -                          end
   1.386 -                        }
   1.387 -                        slot.put(" &nbsp;&nbsp;&nbsp; ")
   1.388 -                      end
   1.389 -                    end }
   1.390 -                    
   1.391 -                    slot.put("<br />")
   1.392 -
   1.393 -                    ui.container{ attr = { class = "opinon-question" }, content = _"Did the initiator implement this suggestion?" }
   1.394 -                    ui.container { content = function ()
   1.395 -
   1.396 -                      local options = {
   1.397 -                        { degree = "false", id = "notfulfilled", label = _"No (not yet)" },
   1.398 -                        { degree = "true", id = "fulfilled", label = _"Yes, it's implemented" },
   1.399 -                      }
   1.400 -                      
   1.401 -                      for i, option in ipairs(options) do
   1.402 -                        local active = opinion.fulfilled == (option.degree == "true" and true or false)
   1.403 -                        ui.tag{
   1.404 -                          tag = "label", 
   1.405 -                          attr = { class = "mdl-radio mdl-js-radio mdl-js-ripple-effect" }, 
   1.406 -                          ["for"] = "s" .. suggestion.id .. "_" .. option.id, 
   1.407 -                          content = function()
   1.408 -                            ui.tag{
   1.409 -                              tag = "input",
   1.410 -                              attr = {
   1.411 -                                class = "mdl-radio__button",
   1.412 -                                type = "radio",
   1.413 -                                name = "fulfilled",
   1.414 -                                value = option.degree,
   1.415 -                                id = "s" .. suggestion.id .. "_" .. option.id,
   1.416 -                                checked = active and "checked" or nil
   1.417 -                              }
   1.418 -                            }
   1.419 -                            ui.tag{
   1.420 -                              attr = { class = "mdl-radio__label" },
   1.421 -                              content = option.label
   1.422 -                            }
   1.423 -                          end
   1.424 -                        }
   1.425 -                        slot.put(" &nbsp;&nbsp;&nbsp; ")
   1.426 -                      end
   1.427 -                    end }
   1.428 -                
   1.429 -                    slot.put("<br />")
   1.430 -                    
   1.431 -                    ui.tag{
   1.432 -                      tag = "input",
   1.433 -                      attr = {
   1.434 -                        type = "submit",
   1.435 -                        class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
   1.436 -                        value = _"publish my rating"
   1.437 -                      },
   1.438 -                      content = ""
   1.439 -                    }
   1.440 -                    
   1.441 -                  end 
   1.442 -                }
   1.443 -
   1.444 -              end }
   1.445 -            end
   1.446 -            
   1.447            end }
   1.448  
   1.449 -          ui.container { attr = { class = "rating-button" }, content = function()
   1.450 -          
   1.451 -            local text = _"Read more"
   1.452 +          ui.container { attr = { class = "mdl-card__actions mdl-card--border" }, content = function()
   1.453 +
   1.454 +            ui.container{ attr = { class = "float-right" }, content = function()
   1.455 +              ui.tag{ attr = { id = "s" .. suggestion.id .. "_rating_text" }, content = function()
   1.456 +                local text = ""
   1.457 +                if opinion then
   1.458 +                  if opinion.degree == 2 then
   1.459 +                    text = _"must"
   1.460 +                  elseif opinion.degree == 1 then
   1.461 +                    text = _"should"
   1.462 +                  elseif opinion.degree == 0 then
   1.463 +                    text = _"neutral"
   1.464 +                  elseif opinion.degree == -1 then
   1.465 +                    text = _"should not"
   1.466 +                  elseif opinion.degree == -2 then
   1.467 +                    text = _"must not"
   1.468 +                  end
   1.469 +                  ui.tag { content = text }
   1.470 +                  slot.put ( " " )
   1.471 +                  if 
   1.472 +                    (opinion.degree > 0 and not opinion.fulfilled)
   1.473 +                    or (opinion.degree < 0 and opinion.fulfilled)
   1.474 +                  then
   1.475 +                    ui.tag{ content = _"but" }
   1.476 +                  else
   1.477 +                    ui.tag{ content = _"and" }
   1.478 +                  end
   1.479 +                  slot.put ( " " )
   1.480 +                  local text = ""
   1.481 +                  if opinion.fulfilled then
   1.482 +                    text = _"is implemented"
   1.483 +                  else
   1.484 +                    text = _"is not implemented"
   1.485 +                  end
   1.486 +                  ui.tag { content = text }
   1.487 +                end
   1.488 +              end }
   1.489 +              local id = "s" .. suggestion.id .. "_rating_icon"
   1.490 +              if opinion and (
   1.491 +                  (opinion.degree > 0 and not opinion.fulfilled)
   1.492 +                  or (opinion.degree < 0 and opinion.fulfilled)
   1.493 +                )
   1.494 +              then
   1.495 +                slot.put(" ")
   1.496 +                if math.abs(opinion.degree) > 1 then
   1.497 +                  ui.icon("warning", "red", id)
   1.498 +                else
   1.499 +                  ui.icon("warning", nil, id)
   1.500 +                end
   1.501 +              elseif opinion then
   1.502 +                slot.put(" ")
   1.503 +                ui.icon("done", nil, id)
   1.504 +              else
   1.505 +                slot.put(" ")
   1.506 +                ui.icon("blank", nil, id)
   1.507 +              end
   1.508 +            end }
   1.509              
   1.510 -            if direct_supporter then
   1.511 -              text = text .. " / " .. _"Rate suggestion"
   1.512 -            end
   1.513 -              
   1.514 -            ui.link { 
   1.515 -              attr = { 
   1.516 -                class = "mdl-button mdl-js-button suggestion-more",
   1.517 -                onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.remove('folded');document.querySelector('#s" .. suggestion.id .. "').classList.add('unfolded'); return false;"
   1.518 +            ui.link{
   1.519 +              attr = {
   1.520 +                id = "s" .. suggestion.id .. "_rate_button",
   1.521 +                class = "mdl-button",
   1.522 +                onclick = "rateSuggestion(" .. suggestion.id .. ", " .. (opinion and opinion.degree or 0) .. ", " .. (opinion and (opinion.fulfilled and "true" or "false") or "null") .. ");return false;"
   1.523                },
   1.524 -              text = text
   1.525 +              content = function()
   1.526 +                if opinion then
   1.527 +                  ui.tag { content = _"update rating" }
   1.528 +                else
   1.529 +                  ui.tag { content = _"rate suggestion" }
   1.530 +                end
   1.531 +              end
   1.532              }
   1.533 -            
   1.534 -            ui.link { 
   1.535 -              attr = { 
   1.536 -                class = "mdl-button suggestion-less",
   1.537 -                onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.add('folded');document.querySelector('#s" .. suggestion.id .. "').classList.remove('unfolded'); return false;"
   1.538 -              },
   1.539 -              text = _"Show less"
   1.540 -            }
   1.541 -            --[[
   1.542 +          
   1.543              ui.link{
   1.544                attr = { class = "mdl-button" },
   1.545                content = _"Details",
   1.546                module = "suggestion", view = "show", id = suggestion.id
   1.547              }
   1.548 -            --]]
   1.549 +
   1.550            end }
   1.551            ui.script{ script = [[
   1.552 +            var rateSuggestionRateText = "]] .. _"rate suggestion" .. [[";
   1.553 +            var rateSuggestionUpdateRatingText = "]] .. _"update rating" .. [[";
   1.554 +            var rateSuggestionDegreeTexts = {
   1.555 +              "-2": "]] .. _"must not" .. [[",
   1.556 +              "-1": "]] .. _"should not" .. [[",
   1.557 +              "1": "]] .. _"should" .. [[",
   1.558 +              "2": "]] .. _"must" .. [["
   1.559 +            }
   1.560 +            var rateSuggestionAndText = "]] .. _"and" .. [[";
   1.561 +            var rateSuggestionButText = "]] .. _"but" .. [[";
   1.562 +            var rateSuggestionFulfilledText = "]] .. _"is implemented" .. [[";
   1.563 +            var rateSuggestionNotFulfilledText = "]] .. _"is not implemented" .. [[";
   1.564              window.addEventListener("load", function() {
   1.565                var textEl = document.querySelector('#s]] .. suggestion.id .. [[ .suggestion-content');
   1.566                var height = textEl.clientHeight;
   1.567 -              if (height > 180) {
   1.568 +              if (height > 250) {
   1.569                  document.querySelector('#s]] .. suggestion.id .. [[').classList.add('folded');
   1.570 -                document.querySelector('#s]] .. suggestion.id .. [[ .rating-button').classList.add('mdl-card__actions');
   1.571 -                document.querySelector('#s]] .. suggestion.id .. [[ .rating-button').classList.add('mdl-card--border');
   1.572                }
   1.573              });
   1.574            ]] }
     2.1 --- a/app/main/interest/_action/xhr_update.lua	Thu Feb 11 15:45:56 2021 +0100
     2.2 +++ b/app/main/interest/_action/xhr_update.lua	Thu Feb 11 15:48:02 2021 +0100
     2.3 @@ -3,7 +3,9 @@
     2.4  
     2.5  slot.set_layout()
     2.6  
     2.7 -if not Interest:update(issue_id, app.session.member, interested) then
     2.8 +if Interest:update(issue_id, app.session.member, interested) then
     2.9 +  slot.put_into("data", "ok")
    2.10 +else
    2.11    request.set_status("500 Internal Server Error")
    2.12  end
    2.13  
     3.1 --- a/app/main/opinion/_action/update.lua	Thu Feb 11 15:45:56 2021 +0100
     3.2 +++ b/app/main/opinion/_action/update.lua	Thu Feb 11 15:48:02 2021 +0100
     3.3 @@ -1,65 +1,6 @@
     3.4 -local member_id = app.session.member.id
     3.5 -
     3.6  local suggestion_id = param.get("suggestion_id", atom.integer)
     3.7 -
     3.8 -local opinion = Opinion:by_pk(member_id, suggestion_id)
     3.9 -
    3.10 -local suggestion = Suggestion:by_id(suggestion_id)
    3.11 -
    3.12  local degree = param.get("degree", atom.number)
    3.13  local fulfilled = param.get("fulfilled", atom.boolean)
    3.14  
    3.15 -
    3.16 -if not suggestion then
    3.17 -  slot.put_into("error", _"This suggestion has been meanwhile deleted")
    3.18 -  return false
    3.19 -end
    3.20 -
    3.21 --- TODO important m1 selectors returning result _SET_!
    3.22 -local issue = suggestion.initiative:get_reference_selector("issue"):for_share():single_object_mode():exec()
    3.23 -
    3.24 -if issue.closed then
    3.25 -  slot.put_into("error", _"This issue is already closed.")
    3.26 -  return false
    3.27 -elseif issue.fully_frozen then 
    3.28 -  slot.put_into("error", _"Voting for this issue has already begun.")
    3.29 -  return false
    3.30 -elseif 
    3.31 -  (issue.half_frozen and issue.phase_finished) or
    3.32 -  (not issue.accepted and issue.phase_finished) 
    3.33 -then
    3.34 -  slot.put_into("error", _"Current phase is already closed.")
    3.35 -  return false
    3.36 -end
    3.37 +return Opinion:update(suggestion_id, app.session.member_id, degree, fulfilled)
    3.38  
    3.39 -if degree == 0 then
    3.40 -  if opinion then
    3.41 -    opinion:destroy()
    3.42 -  end
    3.43 -  --slot.put_into("notice", _"Your rating has been deleted")
    3.44 -  return
    3.45 -end
    3.46 -
    3.47 -if degree ~= 0 and not app.session.member:has_voting_right_for_unit_id(suggestion.initiative.issue.area.unit_id) then
    3.48 -  return execute.view { module = "index", view = "403" }
    3.49 -end
    3.50 -
    3.51 -if not opinion then
    3.52 -  opinion = Opinion:new()
    3.53 -  opinion.member_id     = member_id
    3.54 -  opinion.suggestion_id = suggestion_id
    3.55 -  opinion.fulfilled     = false
    3.56 -end
    3.57 -
    3.58 -
    3.59 -if degree ~= nil then
    3.60 -  opinion.degree = degree
    3.61 -end
    3.62 -
    3.63 -if fulfilled ~= nil then
    3.64 -  opinion.fulfilled = fulfilled
    3.65 -end
    3.66 -
    3.67 -opinion:save()
    3.68 -
    3.69 ---slot.put_into("notice", _"Your rating has been updated")
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/app/main/opinion/_action/xhr_update.lua	Thu Feb 11 15:48:02 2021 +0100
     4.3 @@ -0,0 +1,14 @@
     4.4 +local suggestion_id = param.get("suggestion_id", atom.integer)
     4.5 +local degree = param.get("degree", atom.number)
     4.6 +local fulfilled = param.get("fulfilled", atom.boolean)
     4.7 +
     4.8 +print(degree, fulfilled)
     4.9 +
    4.10 +slot.set_layout()
    4.11 +
    4.12 +if Opinion:update(suggestion_id, app.session.member_id, degree, fulfilled) then
    4.13 +  slot.put_into("data", "ok")
    4.14 +else
    4.15 +  request.set_status("500 Internal Server Error")
    4.16 +end
    4.17 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/app/main/suggestion/_collective_rating.lua	Thu Feb 11 15:48:02 2021 +0100
     5.3 @@ -0,0 +1,54 @@
     5.4 +local suggestion = param.get("suggestion", "table")
     5.5 +
     5.6 +local plus2  = (suggestion.plus2_unfulfilled_count or 0)
     5.7 +    + (suggestion.plus2_fulfilled_count or 0)
     5.8 +local plus1  = (suggestion.plus1_unfulfilled_count  or 0)
     5.9 +local minus1 = (suggestion.minus1_unfulfilled_count  or 0)
    5.10 +    + (suggestion.minus1_fulfilled_count or 0)
    5.11 +local minus2 = (suggestion.minus2_unfulfilled_count  or 0)
    5.12 +    + (suggestion.minus2_fulfilled_count or 0)
    5.13 +
    5.14 +local with_opinion = plus2 + plus1 + minus1 + minus2
    5.15 +
    5.16 +local neutral = (suggestion.initiative.supporter_count or 0)
    5.17 +    - with_opinion
    5.18 +
    5.19 +local neutral2 = with_opinion 
    5.20 +      - (suggestion.plus2_fulfilled_count or 0)
    5.21 +      - (suggestion.plus1_fulfilled_count or 0)
    5.22 +      - (suggestion.minus1_fulfilled_count or 0)
    5.23 +      - (suggestion.minus2_fulfilled_count or 0)
    5.24 +
    5.25 +
    5.26 +if with_opinion > 0 then
    5.27 +  ui.container { attr = { class = "suggestion-rating" }, content = function ()
    5.28 +    ui.tag { content = _"collective rating:" }
    5.29 +    slot.put("&nbsp;")
    5.30 +    ui.bargraph{
    5.31 +      max_value = suggestion.initiative.supporter_count,
    5.32 +      width = 100,
    5.33 +      bars = {
    5.34 +        { color = "#0a0", value = plus2 },
    5.35 +        { color = "#8a8", value = plus1 },
    5.36 +        { color = "#eee", value = neutral },
    5.37 +        { color = "#a88", value = minus1 },
    5.38 +        { color = "#a00", value = minus2 },
    5.39 +      }
    5.40 +    }
    5.41 +    slot.put(" | ")
    5.42 +    ui.tag { content = _"implemented:" }
    5.43 +    slot.put ( "&nbsp;" )
    5.44 +    ui.bargraph{
    5.45 +      max_value = with_opinion,
    5.46 +      width = 100,
    5.47 +      bars = {
    5.48 +        { color = "#0a0", value = suggestion.plus2_fulfilled_count },
    5.49 +        { color = "#8a8", value = suggestion.plus1_fulfilled_count },
    5.50 +        { color = "#eee", value = neutral2 },
    5.51 +        { color = "#a88", value = suggestion.minus1_fulfilled_count },
    5.52 +        { color = "#a00", value = suggestion.minus2_fulfilled_count },
    5.53 +      }
    5.54 +    }
    5.55 +  end }
    5.56 +end
    5.57 +
     6.1 --- a/app/main/suggestion/show.lua	Thu Feb 11 15:45:56 2021 +0100
     6.2 +++ b/app/main/suggestion/show.lua	Thu Feb 11 15:48:02 2021 +0100
     6.3 @@ -23,106 +23,48 @@
     6.4  initiative.issue:load_everything_for_member_id(app.session.member_id)
     6.5  
     6.6  
     6.7 -execute.view{ module = "issue", view = "_sidebar_state", params = {
     6.8 -  initiative = initiative
     6.9 -} }
    6.10 +
    6.11 +execute.view{ module = "issue", view = "_head", params = { issue = initiative.issue, link_issue = true } }
    6.12  
    6.13 -execute.view { 
    6.14 -  module = "issue", view = "_sidebar_issue", 
    6.15 -  params = {
    6.16 -    issue = initiative.issue,
    6.17 -    highlight_initiative_id = initiative.id
    6.18 -  }
    6.19 -}
    6.20 +ui.grid{ content = function()
    6.21 +  ui.cell_main{ content = function()
    6.22  
    6.23 -execute.view {
    6.24 -  module = "issue", view = "_sidebar_whatcanido",
    6.25 -  params = { initiative = initiative }
    6.26 -}
    6.27 -
    6.28 -execute.view { 
    6.29 -  module = "issue", view = "_sidebar_members", params = {
    6.30 -    issue = initiative.issue, initiative = initiative
    6.31 -  }
    6.32 -}
    6.33 +    ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function()
    6.34 +      ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
    6.35 +        ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = suggestion.name }
    6.36 +      end }
    6.37  
    6.38 -
    6.39 -
    6.40 -execute.view {
    6.41 -  module = "issue", view = "_head", params = {
    6.42 -    issue = initiative.issue
    6.43 -  }
    6.44 -}
    6.45 -
    6.46 -
    6.47 -ui.section( function()
    6.48 -  ui.sectionHead( function()
    6.49 -    ui.link{
    6.50 -      module = "initiative", view = "show", id = initiative.id,
    6.51 -      content = function ()
    6.52 -        ui.heading { 
    6.53 -          level = 1,
    6.54 -          content = initiative.display_name
    6.55 +      ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function()
    6.56 +        if app.session:has_access("authors_pseudonymous") and suggestion.author then 
    6.57 +          util.micro_avatar(suggestion.author)
    6.58 +        end
    6.59 +        execute.view{
    6.60 +          module = "suggestion", view = "_collective_rating", params = {
    6.61 +            suggestion = suggestion
    6.62 +          }
    6.63          }
    6.64 -      end
    6.65 -    }
    6.66 -    ui.heading { level = 2, content = _("Suggestion for improvement #{id}", { id = suggestion.id }) }
    6.67 -  end )
    6.68 -  ui.sectionRow( function()
    6.69 +      end }
    6.70  
    6.71 -    ui.heading{ level = 2, content = suggestion.name }
    6.72 -    if app.session:has_access("authors_pseudonymous") and suggestion.author then 
    6.73 -      util.micro_avatar(suggestion.author)
    6.74 -    end
    6.75 -  end )
    6.76 -  ui.sectionRow( function()
    6.77 -    ui.container{
    6.78 -      attr = { class = "suggestion_content wiki" },
    6.79 -      content = function()
    6.80 +      ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function()
    6.81          slot.put(suggestion:get_content("html"))
    6.82 -      end
    6.83 -    }
    6.84 -    
    6.85 -  end )
    6.86 -end )
    6.87 -
    6.88 -ui.section( function()
    6.89 -  ui.sectionHead( function()
    6.90 -    ui.heading { level = 2, content = _"Collective rating" }
    6.91 -  end )
    6.92 -  ui.sectionRow( function()
    6.93 +      end }
    6.94  
    6.95 -    execute.view{
    6.96 -      module = "suggestion",
    6.97 -      view = "_list_element",
    6.98 -      params = {
    6.99 -        suggestions_selector = Suggestion:new_selector():add_where{ "id = ?", suggestion.id },
   6.100 -        initiative = suggestion.initiative,
   6.101 -        show_name = false,
   6.102 -        show_filter = false
   6.103 -      }
   6.104 -    }
   6.105 -  end)
   6.106 -end)
   6.107 -  
   6.108 -if app.session:has_access("all_pseudonymous") then
   6.109 -  ui.section( function()
   6.110 -    ui.sectionHead( function()
   6.111 -      ui.heading { level = 2, content = _"Individual ratings" }
   6.112 -    end )
   6.113 -    ui.sectionRow( function()
   6.114 +      if app.session:has_access("all_pseudonymous") then
   6.115 +        ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function()
   6.116 +          execute.view{
   6.117 +            module = "opinion",
   6.118 +            view = "_list",
   6.119 +            params = { 
   6.120 +              opinions_selector = Opinion:new_selector()
   6.121 +                :add_where{ "suggestion_id = ?", suggestion.id }
   6.122 +                :join("member", nil, "member.id = opinion.member_id")
   6.123 +                :add_order_by("member.id DESC")
   6.124 +            }
   6.125 +          }
   6.126 +        end }
   6.127 +      end
   6.128  
   6.129 -      execute.view{
   6.130 -        module = "opinion",
   6.131 -        view = "_list",
   6.132 -        params = { 
   6.133 -          opinions_selector = Opinion:new_selector()
   6.134 -            :add_where{ "suggestion_id = ?", suggestion.id }
   6.135 -            :join("member", nil, "member.id = opinion.member_id")
   6.136 -            :add_order_by("member.id DESC")
   6.137 -        }
   6.138 -      }
   6.139 +    end }
   6.140 +  end }
   6.141 +end }
   6.142  
   6.143 -    end)
   6.144 -  end)
   6.145 -end
   6.146 \ No newline at end of file
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/env/ui/icon.lua	Thu Feb 11 15:48:02 2021 +0100
     7.3 @@ -0,0 +1,5 @@
     7.4 +function ui.icon(icon, class, id)
     7.5 +  class = class and "icon-" .. class .. " " or ""
     7.6 +  class = class .. "material-icons"
     7.7 +  ui.tag{ tag = "i", attr = { id = id, class = class }, content = icon }
     7.8 +end
     8.1 --- a/locale/translations.de.lua	Thu Feb 11 15:45:56 2021 +0100
     8.2 +++ b/locale/translations.de.lua	Thu Feb 11 15:48:02 2021 +0100
     8.3 @@ -15,9 +15,7 @@
     8.4  ["#{count} in voting"] = "#{count} in Abstimmung";
     8.5  ["#{count} matching issues found"] = "#{count} passende Themen gefunden";
     8.6  ["#{count} matching members found"] = "#{count} passende Mitglieder gefunden";
     8.7 -["#{count} more areas in this unit"] = "#{count} weitere Themenbereiche in dieser Gliederung";
     8.8  ["#{count} new"] = "#{count} neue";
     8.9 -["#{count} of them have an area delegation set"] = "bei #{count} davon ist eine Delegation für den Themenbereichs gesetzt";
    8.10  ["#{count} suggestions added"] = "#{count} neue Anregungen";
    8.11  ["#{count} suggestions added for #{initiative}"] = "#{count} neue Anregungen für #{initiative}";
    8.12  ["#{count} supporter"] = "#{count} Unterstützer";
    8.13 @@ -32,7 +30,6 @@
    8.14  ["#{number} Image(s) has been deleted"] = "Es wurde(n) #{number} Bild(er) gelöscht";
    8.15  ["#{number} Image(s) has been updated"] = "Es wurde(n) #{number} Bild(er) aktualisiert";
    8.16  ["#{percentage}%"] = false;
    8.17 -["#{policy} ##{id}"] = false;
    8.18  ["#{result}: #{yes_count} Yes (#{yes_percent}), #{no_count} No (#{no_percent}), #{neutral_count} Abstention (#{neutral_percent})"] = "#{result}: #{yes_count} Ja (#{yes_percent}), #{no_count} Nein (#{no_percent}), #{neutral_count} Enthaltung (#{neutral_percent})";
    8.19  ["(+ #{count} potential)"] = "(+ #{count} potentielle)";
    8.20  ["(detached)"] = "(abgelöst)";
    8.21 @@ -85,11 +82,8 @@
    8.22  ["Admission time"] = "Dauer der Zulassungsphase";
    8.23  ["Admitted"] = "zugelassen";
    8.24  ["Agents"] = "Zugriffsberechtigte";
    8.25 -["All areas in my units"] = "Alle Themenbereiche in meinen Gliederungen";
    8.26  ["All fields are optional. Please enter only data which should be published."] = "Alle Felder sind optional. Bitte nur Daten eingeben, die veröffentlicht werden sollen.";
    8.27  ["All initiatives failed 2nd quorum"] = "Alle Initiativen sind am 2. Quorum gescheitert";
    8.28 -["All issues"] = "Alle Themen";
    8.29 -["All issues in your units. Use filters above to limit results."] = "Alle Themen in deinen Gliederungen werden angezeigt. Verwende die Filter oben um die Ergebnisse zu begrenzen.";
    8.30  ["All members"] = "Alle Teilnehmer";
    8.31  ["All units"] = "Alle Gliederungen";
    8.32  ["Allowed policies"] = "Erlaubte Regelwerke";
    8.33 @@ -110,7 +104,6 @@
    8.34  ["Approval [single entry]"] = "Zustimmung";
    8.35  ["Approved"] = "Angenommen";
    8.36  ["Are you aware that revoking an initiative is irrevocable?"] = "Bist du dir bewusst, dass das Zurückziehen unwiderrufbar ist?";
    8.37 -["Area"] = "Themenbereich";
    8.38  ["Area delegation"] = "Delegation für Themenbereich";
    8.39  ["As long as you are interested in this issue yourself, the delegation is suspended for this issue, but it will be applied again in the voting phase unless you vote yourself."] = "Solange du selber am Thema interessiert bist, wird die Delegation für dieses Thema ausgesetzt, jedoch während der Abstimmung erneut aktiv, es sei denn du stimmst selber ab.";
    8.40  ["As soon as one initiative of this issue reaches the 1st quorum of #{quorum} support, the issue will proceed to discussion phase."] = "Sobald eine Initiative des Themas das 1. Quorum von #{quorum} Unterstützern erreicht, geht das Thema in die Diskussionsphase über.";
    8.41 @@ -120,7 +113,6 @@
    8.42  ["Available policies"] = "Verfügbare Regelwerke";
    8.43  ["Avatar"] = "Avatar";
    8.44  ["Ballot of '#{member_name}'"] = "Stimmzettel von '#{member_name}'";
    8.45 -["Become a member"] = "Mitglied werden";
    8.46  ["Best not admitted initiative"] = "Beste nicht zugelassene Initiative";
    8.47  ["Broken delegations"] = "Kaputte Delegationen";
    8.48  ["Calculation"] = "Auszählung";
    8.49 @@ -149,7 +141,6 @@
    8.50  ["Closed issues"] = "Geschlossene Themen";
    8.51  ["Closed user group"] = "Geschlossene Benutzergruppe";
    8.52  ["Collective opinion of supporters"] = "Meinungsbild der Unterstützer";
    8.53 -["Collective rating"] = "Kollektive Bewertung";
    8.54  ["Comment"] = "Kommentar";
    8.55  ["Compare"] = "Vergleichen";
    8.56  ["Comparision of revisions #{id1} and #{id2}"] = "Vergleich der Revisionen #{id1} und #{id2}";
    8.57 @@ -193,7 +184,6 @@
    8.58  ["Delegation abandoned"] = "Delegation ausgesetzt";
    8.59  ["Delegation turned off for area"] = "Delegation für Themenbereich ausgesetzt";
    8.60  ["Delegation turned off for issue"] = "Delegation für Thema ausgesetzt";
    8.61 -["Delegations"] = "Delegationen";
    8.62  ["Delete account"] = "Konto löschen";
    8.63  ["Denied at"] = "Abgelehnt am/um";
    8.64  ["Deny request"] = "Anfrage ablehnen";
    8.65 @@ -240,7 +230,6 @@
    8.66  ["Edit your profile data"] = "Profildaten bearbeiten";
    8.67  ["Eligible as winner"] = "Als Gewinner qualifiziert";
    8.68  ["Eligible members (#{count})"] = "Stimmberechtigte Teilnehmer (#{count})";
    8.69 -["Eligible voters"] = "Stimmberechtigte";
    8.70  ["Email address"] = "E-Mail-Adresse";
    8.71  ["Email address confirmation"] = "Bestätigung der E-Mail-Adresse";
    8.72  ["Email address for notifications"] = "E-Mail-Adresse für Benachrichtungen";
    8.73 @@ -323,8 +312,7 @@
    8.74  ["I want to switch to another account"] = "Ich möchte zu einem anderen Konto wechseln";
    8.75  ["I want to take a look at other organizational units"] = "Ich möchte einen Blick auf andere Gliederungen werfen";
    8.76  ["I want to vote"] = "Ich möchte abstimmen";
    8.77 -["I'm interested in this issue"] = "Ich bin an diesem Thema interessiert";
    8.78 -["I'm supporting this initiative"] = "Ich unterstütze diese Initiative";
    8.79 +["ID"] = false;
    8.80  ["IP address"] = "IP-Adresse";
    8.81  ["Id"] = "Id";
    8.82  ["Identification"] = "Identifikation";
    8.83 @@ -335,7 +323,6 @@
    8.84  ["If you have not received a PIN code, our team will need to check your registration manually. We will be in touch within two working days. Please accept our apologies for the inconvenience."] = "Wenn du keine PIN erhalten hast, muss unser Team die Registrierung manuell überprüfen. Wir melden uns innerhalb von 2 Arbeitstagen. Wir bitten um Entschuldigung.";
    8.85  ["Implicitly admitted"] = "Implizit zugelassen";
    8.86  ["Incoming delegations"] = "Eingehende Delegationen";
    8.87 -["Incoming delegations for '#{member_name}'"] = "Eingehende Delegationen für '#{member_name}'";
    8.88  ["Incoming delegations for '#{member}'"] = "Eingehende Delegationen für '#{member}'";
    8.89  ["Indent sub items with spaces"] = "Unterpunkte mit Leerschritt einrücken";
    8.90  ["Indirect majority"] = "Indirekte Mehrheit";
    8.91 @@ -343,22 +330,16 @@
    8.92  ["Indirect majority non negative"] = "Indirekte Mehrheit Nicht-Negativ";
    8.93  ["Indirect majority numerator"] = "Indirekte Mehrheit Zähler";
    8.94  ["Indirect majority positive"] = "Indirekte Mehrheit Positiv";
    8.95 -["Individual ratings"] = "Individuelle Bewertungen";
    8.96  ["Initiative is revoked now"] = "Initiative ist jetzt zurückgezogen";
    8.97  ["Initiative not admitted"] = "Initiative nicht zugelassen";
    8.98  ["Initiative quorum"] = "Quorum Initiative";
    8.99  ["Initiative revoked"] = "Initiative zurückgezogen";
   8.100  ["Initiative revoked: #{initiative_name}"] = "Initiative zurückgezogen: #{initiative_name}";
   8.101  ["Initiative: "] = "Initiative: ";
   8.102 -["Initiatives"] = "Initiativen";
   8.103  ["Initiatives and issues"] = "Initiativen und Themen";
   8.104  ["Initiatives created by this member"] = "Initiativen von diesem Mitglied";
   8.105  ["Initiatives that invited you to become initiator:"] = "Initiative, die Dich eingeladen haben, Initiator zu werden:";
   8.106 -["Initiator invites"] = "Einladungen";
   8.107  ["Initiators"] = "Initiatoren";
   8.108 -["Interest already removed"] = "Interesse wurde bereits abgemeldet";
   8.109 -["Interest removed"] = "Interesse entfernt";
   8.110 -["Interest updated"] = "Interesse aktualisiert";
   8.111  ["Interested members"] = "Interessierte Teilnehmer";
   8.112  ["Interval format:"] = "Intervall-Format";
   8.113  ["Introduction"] = "Einführung";
   8.114 @@ -421,8 +402,6 @@
   8.115  ["Member has not approved latest draft"] = "Mitglied hat den letzten Entwurf noch nicht angenommen";
   8.116  ["Member inactive?"] = "Mitglied inaktiv?";
   8.117  ["Member is already saved in your contacts!"] = "Mitglied ist schon in Deinen Kontakten!";
   8.118 -["Member is not participating in any of the #{count} areas in this unit"] = "Mitglied nimmt an keinem der #{count} Themenbereiche dieser Gliederung teil";
   8.119 -["Member is not participating in the only area of the unit"] = "Mitglied nimmt am einzigen Themenbereich der Gliederung nicht teil";
   8.120  ["Member is now invited to be initiator"] = "Mitglied ist jetzt als Initiator eingeladen";
   8.121  ["Member list"] = "Mitgliederliste";
   8.122  ["Member name"] = "Mitglied Name";
   8.123 @@ -442,7 +421,6 @@
   8.124  ["Monday"] = "Montag";
   8.125  ["Move down"] = "Runter schieben";
   8.126  ["Move up"] = "Hoch schieben";
   8.127 -["My areas"] = "Meine Themenbereiche";
   8.128  ["Name"] = "Name";
   8.129  ["New"] = "Neu";
   8.130  ["New area"] = "Neues Themenbereich";
   8.131 @@ -451,7 +429,6 @@
   8.132  ["New initiative"] = "Neue Initiative";
   8.133  ["New initiative draft"] = "Neuer Entwurfstext der Initiative";
   8.134  ["New issue"] = "Neues Thema";
   8.135 -["New member"] = "Neues Mitglied";
   8.136  ["New newsletter"] = "Neues Rundschreiben";
   8.137  ["New organizational unit"] = "Neue Gliederung";
   8.138  ["New password"] = "Neues Kennwort";
   8.139 @@ -467,6 +444,7 @@
   8.140  ["No PIN code received?"] = "Keine PIN erhalten?";
   8.141  ["No account yet?"] = "Noch kein Konto?";
   8.142  ["No admission needed"] = "Keine Zulassung nötig";
   8.143 +["No applications connected"] = "Keinen verundenen Anwendungen";
   8.144  ["No changes to your images were made"] = "An Deinen Bildern wurde nichts geändert";
   8.145  ["No default"] = "Kein Standard";
   8.146  ["No delegation"] = "Keine Delegation";
   8.147 @@ -485,7 +463,6 @@
   8.148  ["None"] = "Keine";
   8.149  ["None yet"] = "Bisher keine";
   8.150  ["Not a member"] = "Kein Mitglied";
   8.151 -["Not voted issues"] = "Nicht abgestimmt";
   8.152  ["Notification address unconfirmed"] = "E-Mail-Adresse für Benachrichtigungen unbestätigt";
   8.153  ["Notification email"] = "E-Mail für Benachrichtigungen";
   8.154  ["Notification email (confirmed)"] = "E-Mail für Benachrichtigungen (bestätigt)";
   8.155 @@ -498,8 +475,6 @@
   8.156  ["On that page please enter the reset code:\n\n"] = "Auf dieser Seite gib bitte den folgenden Rücksetzcode ein:\n\n";
   8.157  ["One issue"] = "Ein Thema";
   8.158  ["One issue you are interested in"] = "Ein Thema, das Dich interessiert";
   8.159 -["One more area in this unit"] = "Ein weiterer Themenbereich in dieser Gliederung";
   8.160 -["One of them have an area delegation set"] = "Bei einem davon ist eine Delegation für den Themenbereich gesetzt";
   8.161  ["Open in FirstLife"] = false;
   8.162  ["Open initiatives you are supporting which has been updated their draft:"] = "Offene, von dir unterstützte Initiativen, deren Antragstext aktualisiert wurde:";
   8.163  ["Open request"] = "Anfrage öffnen";
   8.164 @@ -576,9 +551,7 @@
   8.165  ["Put a hypen (-) or asterisk (*) followed by a space in front of each item"] = "Einen Strich (-) oder Sternchen (*) gefolgt von einem Leerschritt vor jeden Listenpunkt setzen";
   8.166  ["Quick guide"] = "Kurzanleitung";
   8.167  ["Quorums"] = "Quoren";
   8.168 -["Rate suggestion"] = "Anregungen bewerten";
   8.169  ["Reached #{sign}#{num}/#{den}"] = "#{sign}#{num}/#{den} erreicht";
   8.170 -["Read more"] = "Mehr lesen";
   8.171  ["Recover login name"] = "Login-Namen wiederherstellen";
   8.172  ["Redirect URI forbidden"] = false;
   8.173  ["Redirect URI invalid"] = false;
   8.174 @@ -661,8 +634,6 @@
   8.175  ["Show full history"] = "Komlette Historie anzeigen";
   8.176  ["Show full member list"] = "Komplette Mitgliederliste anzeigen";
   8.177  ["Show inactive"] = "Inaktive anzeigen";
   8.178 -["Show less"] = "Weniger anzeigen";
   8.179 -["Show my voting ballot"] = "Meinen Stimmzettel anzeigen";
   8.180  ["Show older events"] = "Zeige ältere Ereignisse";
   8.181  ["Show policies in use"] = "Zeige Regelwerke in Verwendung";
   8.182  ["Show policies not in use"] = "Zeige deaktivierte Regelwerke";
   8.183 @@ -681,22 +652,17 @@
   8.184  ["Status quo: #{rank}"] = "Status quo: #{rank}";
   8.185  ["Strict indirect majority"] = "Strenge indirekte Mehrheit";
   8.186  ["Subject"] = "Betreff";
   8.187 -["Subject area subscribed"] = "für Themenbereich angemeldet";
   8.188  ["Subject areas"] = "Themenbereiche";
   8.189  ["Subscribed members (#{count})"] = "Angemeldete Teilnehmer (#{count})";
   8.190 -["Subscription already removed"] = "bereits abgemeldet";
   8.191 -["Subscription removed"] = "vom Themenbereich erfolgreich abgemeldet";
   8.192  ["Suggest no initiative"] = "Keine Initiative empfehlen";
   8.193  ["Suggestion"] = "Verbesserungsvorschlag";
   8.194  ["Suggestion currently implemented"] = "Verbesserungsvorschlag zur Zeit umgesetzt";
   8.195  ["Suggestion currently not implemented"] = "Verbesserungsvorschlag zur Zeit nicht umgesetzt";
   8.196  ["Suggestion does not exist anymore"] = "Verbesserungsvorschlag existiert nicht mehr";
   8.197 -["Suggestion for improvement #{id}"] = "Verbesserungsvorschlag #{id}";
   8.198  ["Suggestions"] = "Verbesserungsvorschläge";
   8.199  ["Suggestions for improvement (#{count})"] = "Verbesserungsvorschläge (#{count})";
   8.200  ["Sunday"] = "Sonntag";
   8.201  ["Syntax help"] = "Syntax-Hilfe";
   8.202 -["System administration"] = "Systemadministration";
   8.203  ["System settings"] = "Systemeinstellungen";
   8.204  ["Take a look through the existing issues. Maybe someone else started a debate on your topic (and you can join it) or the topic has been decided already in the past."] = "Schau dir die bestehenden Themen an. Vielleicht hat schon jemand eine Debatte über dein Thema gestartet (und du kannst daran teilnehmen) oder über das Thema wurde bereits in der Vergangenheit entschieden.";
   8.205  ["Tell others about this initiative:"] = "Anderen von dieser Initiative berichten:";
   8.206 @@ -751,7 +717,6 @@
   8.207  ["Unit delegation"] = "Gliederungsdelegation";
   8.208  ["Unit list"] = "Liste der Gliederungen";
   8.209  ["Unknown author"] = "Unbekannter Autor";
   8.210 -["Updated drafts"] = "Neue Entwürfe";
   8.211  ["Upload avatar/photo"] = "Avatar/Foto hochladen";
   8.212  ["Use [Text](http://example.com/) for links"] = "[Text](http://example.com/) verwenden für Links";
   8.213  ["Use terms"] = "Nutzungsbedingungen";
   8.214 @@ -794,7 +759,6 @@
   8.215  ["What this member is currently supporting"] = "Was dieses Mitglied zur Zeit unterstützt";
   8.216  ["Wiki engine"] = "Wiki engine";
   8.217  ["Wiki engine for statement"] = "Wiki engine für das Statement";
   8.218 -["Withdraw membership"] = "Mitgliedschaft aufgeben";
   8.219  ["Yes"] = "Ja";
   8.220  ["Yes, it's implemented"] = "Ja, ist umgesetzt";
   8.221  ["You already voted this issue"] = "Du hast dieses Thema bereits abgestimmt";
   8.222 @@ -807,11 +771,7 @@
   8.223  ["You are invited to LiquidFeedback. To register please click the following link:\n\n"] = "Du bist zu LiquidFeedback eingeladen. Bitte klicke auf den folgenden Link, um dich zu registrieren:\n\n";
   8.224  ["You are invited to become initiator of '#{initiative_name}'"] = "Einladung Initiator der Initiative '#{initiative_name}' zu werden";
   8.225  ["You are invited to become initiator of this initiative"] = "Du bist eingeladen Initiator dieser Initiative zu werden";
   8.226 -["You are member"] = "Du bist Mitglied";
   8.227 -["You are not eligible to participate"] = "Du bist nicht teilnahmeberechtigt";
   8.228  ["You are not entitled to vote in this unit"] = "Du bist in dieser Gliederung nicht stimmberechtigt";
   8.229 -["You are not participating in any of the #{count} areas in this unit"] = "Du nimmst an keinem der #{count} Themenbereiche dieser Gliederung teil";
   8.230 -["You are not participating in the only area of the unit"] = "Du nimmst am einzigen Themenbereich der Gliederung nicht teil";
   8.231  ["You are now initiator of this initiative"] = "Du bist jetzt Initiator dieser Initiative";
   8.232  ["You are receiving updates by email for this subject area"] = "Es werden Aktualisierungen per E-Mail für diesen Themenbereich versendet";
   8.233  ["You are supporter"] = "Du bist Unterstützer";
   8.234 @@ -836,7 +796,6 @@
   8.235  ["You have been granted access privileges for the following account:"] = "Du hast eine Zugriffsberechtigung für folgendes Konto erteilt:";
   8.236  ["You have been granted access to the following account(s):"] = "Du hast Zugriff auf folgende Konten:";
   8.237  ["You have been subscribed for update emails about this subject area"] = "Es werden zukünftig Aktualisierungen per E-Mail für diesen Themenbereich versendet";
   8.238 -["You have been voted"] = "Du hast abgestimmt";
   8.239  ["You have rejected access privileges for the following account:"] = "Du hast die Zugriffsberechtigung auf folgendes Konto abgelehnt:";
   8.240  ["You have to mark 'Are you sure' to revoke!"] = "Zum Zurückziehen musst Du 'Sicher?' auswählen";
   8.241  ["You have voted"] = "Du hast abgestimmt";
   8.242 @@ -965,7 +924,6 @@
   8.243  ["delegated to"] = "delegiert an";
   8.244  ["delegates to"] = "delegiert an";
   8.245  ["delegation"] = "Delegation";
   8.246 -["delegation suspended during discussion"] = "Delegation während der Diskussion ausgesetzt";
   8.247  ["delete"] = "löschen";
   8.248  ["delete<br /><br />"] = "löschen<br /><br />";
   8.249  ["denied"] = "abgelehnt";
   8.250 @@ -999,7 +957,6 @@
   8.251  ["implemented:"] = "umgesetzt:";
   8.252  ["in all phases"] = "in allen Phasen";
   8.253  ["in my units"] = "in meinen Gliederungen";
   8.254 -["inactive"] = "inaktiv";
   8.255  ["initiated by me"] = "von mir initiiert";
   8.256  ["interest"] = "Interesse";
   8.257  ["interest via delegation"] = "Interesse per Delegation";
   8.258 @@ -1064,6 +1021,7 @@
   8.259  ["publish my rating"] = "Wertung veröffentlichen";
   8.260  ["publish profile data"] = "Profildaten veröffentlichen";
   8.261  ["publish suggestion"] = "Verbesserungsvorschlag veröffentlichen";
   8.262 +["rate suggestion"] = "bewerten";
   8.263  ["reached #{quorum}"] = "#{quorum} erreicht";
   8.264  ["refresh my support"] = "meine Unterstützung erneuern";
   8.265  ["refuse invitation"] = "Einladung ablehnen";
   8.266 @@ -1096,7 +1054,6 @@
   8.267  ["start a new competing initiative"] = "eine neue konkurrierende Initiative starten";
   8.268  ["start an initiative in a new issue"] = "eine Initiative in einem neuen Thema starten";
   8.269  ["structured discussion"] = "strukturierte Diskussion";
   8.270 -["subject area: #{name}"] = "Themengebiet: #{name}";
   8.271  ["subscribe for update emails about this area"] = "Aktualisierungen per E-Mail für diesen Themenbereich abbonieren";
   8.272  ["subscribe subject areas or add your interested to issues and you will be notified about changes (follow the instruction on the area or issue page)"] = "Melde dich für Themenbereiche an oder interessiere dich für Themen, damit du benachrichtigt wirst (folge den Anweisungen auf der Themenbereichs- bzw. Themenseite)";
   8.273  ["supporter"] = "Unterstützer";
   8.274 @@ -1116,13 +1073,12 @@
   8.275  ["unblock member"] = "Blockierung aufheben";
   8.276  ["unconfirmed address"] = "unbestätigte Adresse";
   8.277  ["unit"] = "Gliederung";
   8.278 -["unit / area"] = "Gliederung / Themenbereich";
   8.279 -["unit: #{name}"] = "Gliederung: #{name}";
   8.280  ["unsubscribe from update emails about this area"] = "Aktualisierungen per E-Mail für diesen Themenbereich abschalten";
   8.281  ["until"] = "bis";
   8.282  ["update area"] = "Themenbereich aktualisiern";
   8.283  ["update member"] = "Mitglied aktualisieren";
   8.284  ["update policy"] = "Regelwerk aktualisieren";
   8.285 +["update rating"] = "neu bewerten";
   8.286  ["update unit"] = "Gliederung aktualisieren";
   8.287  ["variable"] = "variabel";
   8.288  ["verified"] = "überprüft";
   8.289 @@ -1150,8 +1106,6 @@
   8.290  ["years [interval time left]"] = "Jahre";
   8.291  ["years [interval]"] = "Jahre";
   8.292  ["yesterday at #{time}"] = "gestern um #{time}";
   8.293 -["you are interested"] = "du bist interessiert";
   8.294 -["you are subscribed"] = "du bist am Themembreich angemeldet";
   8.295  ["you have #{count} incoming delegations"] = "#{count} eingehende Delegationen";
   8.296  ["you restricted your support by rating suggestions as must or must not"] = "deine Untersützung ist aufgrund von 'muss'- oder 'darf nicht'-Verbesserungsvorschlägen beschränkt";
   8.297  ["you voted"] = "du hast abgestimmt";
     9.1 --- a/model/opinion.lua	Thu Feb 11 15:45:56 2021 +0100
     9.2 +++ b/model/opinion.lua	Thu Feb 11 15:48:02 2021 +0100
     9.3 @@ -33,3 +33,63 @@
     9.4      :optional_object_mode()
     9.5      :exec()
     9.6  end
     9.7 +
     9.8 +
     9.9 +function Opinion:update(suggestion_id, member_id, degree, fulfilled)
    9.10 +
    9.11 +  local opinion = Opinion:by_pk(member_id, suggestion_id)
    9.12 +  local suggestion = Suggestion:by_id(suggestion_id)
    9.13 +
    9.14 +  if not suggestion then
    9.15 +    slot.put_into("error", _"This suggestion has been meanwhile deleted")
    9.16 +    return false
    9.17 +  end
    9.18 +
    9.19 +  -- TODO important m1 selectors returning result _SET_!
    9.20 +  local issue = suggestion.initiative:get_reference_selector("issue"):for_share():single_object_mode():exec()
    9.21 +
    9.22 +  if issue.closed then
    9.23 +    slot.put_into("error", _"This issue is already closed.")
    9.24 +    return false
    9.25 +  elseif issue.fully_frozen then 
    9.26 +    slot.put_into("error", _"Voting for this issue has already begun.")
    9.27 +    return false
    9.28 +  elseif 
    9.29 +    (issue.half_frozen and issue.phase_finished) or
    9.30 +    (not issue.accepted and issue.phase_finished) 
    9.31 +  then
    9.32 +    slot.put_into("error", _"Current phase is already closed.")
    9.33 +    return false
    9.34 +  end
    9.35 +
    9.36 +  if degree == 0 then
    9.37 +    if opinion then
    9.38 +      opinion:destroy()
    9.39 +    end
    9.40 +    return true
    9.41 +  end
    9.42 +
    9.43 +  if degree ~= 0 and not app.session.member:has_voting_right_for_unit_id(suggestion.initiative.issue.area.unit_id) then
    9.44 +    return execute.view { module = "index", view = "403" }
    9.45 +  end
    9.46 +
    9.47 +  if not opinion then
    9.48 +    opinion = Opinion:new()
    9.49 +    opinion.member_id     = member_id
    9.50 +    opinion.suggestion_id = suggestion_id
    9.51 +    opinion.fulfilled     = false
    9.52 +  end
    9.53 +
    9.54 +
    9.55 +  if degree ~= nil then
    9.56 +    opinion.degree = degree
    9.57 +  end
    9.58 +
    9.59 +  if fulfilled ~= nil then
    9.60 +    opinion.fulfilled = fulfilled
    9.61 +  end
    9.62 +
    9.63 +  opinion:save()
    9.64 +  return true
    9.65 +
    9.66 +end
    10.1 --- a/static/js/xhr.js	Thu Feb 11 15:45:56 2021 +0100
    10.2 +++ b/static/js/xhr.js	Thu Feb 11 15:48:02 2021 +0100
    10.3 @@ -19,16 +19,98 @@
    10.4    data.append("issue_id", issueId);
    10.5    data.append("interested", interested);
    10.6  
    10.7 -  fetch(baseURL + "/interest/xhr_update", {
    10.8 -      method : "POST",
    10.9 -      body: data
   10.10 -  }).then(
   10.11 -      response => {
   10.12 -        if (response.status != 200) {
   10.13 -          window.alert("Error during update");
   10.14 -        }
   10.15 -      }
   10.16 -  );
   10.17 +  fetch(baseURL + "interest/xhr_update", {
   10.18 +    method: "POST",
   10.19 +    body: data
   10.20 +  }).then(response => {
   10.21 +    if (response.status != 200) {
   10.22 +      window.alert("Error during update");
   10.23 +    }
   10.24 +  });
   10.25  
   10.26  }
   10.27  
   10.28 +function rateSuggestion(id, degree, fulfilled) {
   10.29 +  document.getElementById('rating_suggestion_id').value = id;
   10.30 +  document.getElementById('rating_degree' + degree).MaterialRadio.check();
   10.31 +  if (fulfilled) {
   10.32 +    document.getElementById('rating_fulfilled').MaterialRadio.check();
   10.33 +  } else if (fulfilled == false) {
   10.34 +    document.getElementById('rating_notfulfilled').MaterialRadio.check();    
   10.35 +  } else {
   10.36 +    document.getElementById('rating_fulfilled').MaterialRadio.uncheck();    
   10.37 +    document.getElementById('rating_notfulfilled').MaterialRadio.uncheck();    
   10.38 +  }
   10.39 +  document.getElementById('rating_dialog').showModal();
   10.40 +}
   10.41 +
   10.42 +function updateOpinion() {
   10.43 +  var suggestionId = document.getElementById("rating_suggestion_id").value;
   10.44 +  
   10.45 +  var degree = 0;
   10.46 +  if (document.getElementById("rating_degree-2").children[0].checked)
   10.47 +    degree = -2;
   10.48 +  else if (document.getElementById("rating_degree-1").children[0].checked)
   10.49 +    degree = -1;
   10.50 +  else if (document.getElementById("rating_degree1").children[0].checked)
   10.51 +    degree = 1;
   10.52 +  else if (document.getElementById("rating_degree2").children[0].checked)
   10.53 +    degree = 2;
   10.54 +  var fulfilled = false;
   10.55 +  if (document.getElementById("rating_fulfilled").children[0].checked)
   10.56 +    fulfilled = true;
   10.57 +  if (degree == 0)
   10.58 +    fulfilled = null;
   10.59 +  var data = new FormData();
   10.60 +  data.append("suggestion_id", suggestionId);
   10.61 +  data.append("degree", degree);
   10.62 +  data.append("fulfilled", fulfilled);
   10.63 +
   10.64 +  var degreeText = rateSuggestionDegreeTexts[degree];
   10.65 +  var fulfilledText = fulfilled ? rateSuggestionFulfilledText : rateSuggestionNotFulfilledText;
   10.66 +  var andButText;
   10.67 +  var icon;
   10.68 +  var iconColor;
   10.69 +  if (
   10.70 +    (degree > 0 && ! fulfilled)
   10.71 +    || (degree < 0 && fulfilled) 
   10.72 +  ) {
   10.73 +    icon = "warning";
   10.74 +    if (degree == 2 || degree == -2) { 
   10.75 +      iconColor = "red";
   10.76 +    }
   10.77 +    andButText = rateSuggestionButText;
   10.78 +  } else {
   10.79 +    andButText = rateSuggestionAndText;
   10.80 +    icon = "done";
   10.81 +  }
   10.82 +  var text = degreeText + " " + andButText + " " + fulfilledText;
   10.83 +  if (degree == 0) {
   10.84 +    text = "";
   10.85 +    icon = "blank";
   10.86 +  }
   10.87 +  document.getElementById("s" + suggestionId + "_rating_text").innerHTML = text;
   10.88 +  document.getElementById("s" + suggestionId + "_rating_icon").innerHTML = icon;
   10.89 +  if (iconColor == "red") {
   10.90 +    document.getElementById("s" + suggestionId + "_rating_icon").classList.add("icon-red");
   10.91 +  } else {
   10.92 +    document.getElementById("s" + suggestionId + "_rating_icon").classList.remove("icon-red");
   10.93 +  }  
   10.94 +  if (degree == 0) {
   10.95 +    document.getElementById("s" + suggestionId + "_rate_button").innerHTML = rateSuggestionRateText;
   10.96 +  } else {
   10.97 +    document.getElementById("s" + suggestionId + "_rate_button").innerHTML = rateSuggestionUpdateRatingText;
   10.98 +  }
   10.99 +  document.getElementById("s" + suggestionId + "_rate_button").setAttribute("onclick", "rateSuggestion(" + suggestionId + ", " + degree + ", " + fulfilled + ");return false;")
  10.100 +  document.getElementById('rating_dialog').close();
  10.101 +
  10.102 +  fetch(baseURL + "opinion/xhr_update", {
  10.103 +    method: "POST",
  10.104 +    body: data
  10.105 +  }).then(response => {
  10.106 +    if (response.status != 200) {
  10.107 +      window.alert("Error during update");
  10.108 +    }
  10.109 +  });
  10.110 +
  10.111 +}
    11.1 --- a/static/lf4.css	Thu Feb 11 15:45:56 2021 +0100
    11.2 +++ b/static/lf4.css	Thu Feb 11 15:48:02 2021 +0100
    11.3 @@ -413,13 +413,17 @@
    11.4    }
    11.5  }
    11.6  
    11.7 +
    11.8 +.suggestion-content {
    11.9 +  position: relative;
   11.10 +}
   11.11 +
   11.12  .folded {
   11.13    position: relative;
   11.14  }
   11.15  .folded .suggestion-content {
   11.16 -  max-height: 180px;
   11.17 +  max-height: 250px;
   11.18    overflow: hidden;
   11.19 -  position: relative;
   11.20  }
   11.21  .folded .suggestion-content:before {
   11.22    content: "";
   11.23 @@ -433,6 +437,9 @@
   11.24  .suggestion-less,
   11.25  .suggestion-more {
   11.26    display: none;
   11.27 +  position: absolute;
   11.28 +  bottom: 5px;
   11.29 +  right: 5px;
   11.30  }
   11.31  .folded .suggestion-more,
   11.32  .unfolded .suggestion-less {

Impressum / About Us