Commits

Anonymous committed 6a12fb7

Suggestion API, draft preview, word based diff, multiple fixes
- Added suggestion API
- Initiative API: Drafts optionally delivered as rendered html fragment
- Initiative API: Fixed wrong output of revoked timestamp when using JSON
- Preview added for initiative drafts
- Improved (word based) diff added
- Improved suggestion list
- Added missing sorting of initiative in vote list
- Filter state for member page initiative lists
- Fixed wrong status output in member history
- Fixed wrongly closed div in layout

  • Participants
  • Parent commits 9167176

Comments (0)

Files changed (24)

 icon set 1.3 by Mark James. [ http://www.famfamfam.com/lab/icons/silk/ ]
 His work is licensed under a Creative Commons Attribution 2.5 License.
 [ http://creativecommons.org/licenses/by/2.5/ ]
+
+The emoticons are taken from the following web pages:
+http://de.wikipedia.org/w/index.php?title=Datei:Face-smile.svg and
+http://de.wikipedia.org/w/index.php?title=Datei:Face-sad.svg
+The author granted usage under the following terms:
+"This file has been (or is hereby) released into the public domain by 
+its author, The Tango! Desktop Project. This applies worldwide. In case
+this is not legally possible: The Tango! Desktop Project grants anyone
+the right to use this work for any purpose, without any conditions,
+unless such conditions are required by law."
+The orange and red smiley are modified versions of Face-sad.
+
+

app/main/_layout/default.html

         <div class="support vote_info" id="support">
           <!-- WEBMCP SLOT support -->
         </div>
-        <div style="clear: left;" /></div>
+        <div style="clear: left;"></div>
       </div>
     </div>
     <div class="main" id="default">

app/main/api/initiative.lua

 local state          = param.get("state")
 local agreed         = param.get("agreed")
 local rank           = param.get("rank")
-local search         = param.get("search")
-local search_context = param.get("search_context") or "full"
+--local search         = param.get("search")
+--local search_context = param.get("search_context") or "full"
 local limit          = param.get("limit", atom.integer)
 local order          = param.get("order")
+local render_draft   = param.get("render_draft")
+
+if render_draft and render_draft ~= "html" then
+  error("unsupported render target, only 'html' is supported right now")
+end
 
 local initiatives_selector = Initiative:new_selector()
   :join("issue", nil, "issue.id = initiative.issue_id")
   initiatives_selector:add_where{ "initiative.rank = ?", rank }
 end
 
+--[[
 if search then
   if search_context == "full" then
   elseif search_context == "title" then
   end
 end
+--]]
 
 if order == "supporter_count" then
   initiatives_selector:add_order_by("initiative.supporter_count")
       return format.timestamp(record.created)
     end
   },
-  { name = "revoked",                   field = "initiative.revoked" },
+  {
+    name = "revoked",
+    field = "initiative.revoked",
+    func = function(record)
+      return format.timestamp(record.revoked)
+    end
+  },
   { name = "suggested_initiative_id",   field = "initiative.suggested_initiative_id" },
   { name = "admitted",                  field = "initiative.admitted" },
   { name = "issue_population",          field = "issue.population" },
   {
     name = "current_draft_content",
     func = function(record)
-      return record.current_draft.content
+      if render_draft then
+        return record.current_draft:get_content(render_draft)
+      else
+        return record.current_draft.content
+      end
     end
   }
 }
 
 util.autoapi{
   relation_name = "initiative",
-  selector = initiatives_selector,
-  fields = fields,
-  api_engine = api_engine
+  selector      = initiatives_selector,
+  fields        = fields,
+  api_engine    = api_engine
 }

app/main/api/suggestion.lua

+local id     = param.get("id")
+local min_id = param.get("min_id")
+local max_id = param.get("max_id")
+local initiative_id = param.get("initiative_id")
+local order  = param.get("order")
+local limit  = param.get("limit", atom.integer)
+
+local suggestions_selector = Suggestion:new_selector()
+
+if id then
+  suggestions_selector:add_where{"suggestion.id = ?", id}
+end
+
+if min_id then
+  suggestions_selector:add_where{"suggestion.id >= ?", min_id}
+end
+
+if max_id then
+  suggestions_selector:add_where{"suggestion.id <= ?", max_id}
+end
+
+if order == "id_desc" then
+  suggestions_selector:add_order_by("suggestion.id DESC")
+else
+  suggestions_selector:add_order_by("suggestion.id")
+end
+
+if limit then
+  suggestions_selector:limit(limit)
+end
+
+local api_engine = param.get("api_engine") or "xml"
+
+local fields = {
+
+  { name = "id",                       field = "suggestion.id" },
+  { name = "initiative_id",            field = "suggestion.initiative_id" },
+  { name = "name",                     field = "suggestion.name" },
+  { name = "description",              field = "suggestion.description" },
+  { name = "minus2_unfulfilled_count", field = "suggestion.minus2_unfulfilled_count" },
+  { name = "minus2_fulfilled_count",   field = "suggestion.minus2_fulfilled_count" },
+  { name = "minus1_unfulfilled_count", field = "suggestion.minus1_unfulfilled_count" },
+  { name = "minus1_fulfilled_count",   field = "suggestion.minus1_fulfilled_count" },
+  { name = "plus1_unfulfilled_count",  field = "suggestion.plus1_unfulfilled_count" },
+  { name = "plus1_fulfilled_count",    field = "suggestion.plus1_fulfilled_count" },
+  { name = "plus2_unfulfilled_count",  field = "suggestion.plus2_unfulfilled_count" },
+  { name = "plus2_fulfilled_count",    field = "suggestion.plus2_fulfilled_count" },
+
+}
+
+util.autoapi{
+  relation_name = "suggestion",
+  selector      = suggestions_selector,
+  fields        = fields,
+  api_engine    = api_engine
+}

app/main/draft/_action/add.lua

   error("invalid formatting engine!")
 end
 
+if param.get("preview") then
+  return false
+end
 
 local draft = Draft:new()
 draft.author_id = app.session.member.id

app/main/draft/diff.lua

 local old_draft = Draft:by_id(old_draft_id)
 local new_draft = Draft:by_id(new_draft_id)
 
+local old_draft_content = string.gsub(string.gsub(old_draft.content, "\n", " ###ENTER###\n"), " ", "\n")
+local new_draft_content = string.gsub(string.gsub(new_draft.content, "\n", " ###ENTER###\n"), " ", "\n")
+
 local key = multirand.string(26, "123456789bcdfghjklmnpqrstvwxyz");
 
 local old_draft_filename = encode.file_path(request.get_app_basepath(), 'tmp', "diff-" .. key .. "-old.tmp")
 local new_draft_filename = encode.file_path(request.get_app_basepath(), 'tmp', "diff-" .. key .. "-new.tmp")
 
 local old_draft_file = assert(io.open(old_draft_filename, "w"))
-old_draft_file:write(old_draft.content)
+old_draft_file:write(old_draft_content)
 old_draft_file:write("\n")
 old_draft_file:close()
 
 local new_draft_file = assert(io.open(new_draft_filename, "w"))
-new_draft_file:write(new_draft.content)
+new_draft_file:write(new_draft_content)
 new_draft_file:write("\n")
 new_draft_file:close()
 
-local output, err, status = os.pfilter(nil, "sh", "-c", "diff -U 100000 '" .. old_draft_filename .. "' '" .. new_draft_filename .. "' | grep -v ^--- | grep -v ^+++ | grep -v ^@")
+local output, err, status = os.pfilter(nil, "sh", "-c", "diff -U 1000000000 '" .. old_draft_filename .. "' '" .. new_draft_filename .. "' | grep -v ^--- | grep -v ^+++ | grep -v ^@")
 
 os.remove(old_draft_filename)
 os.remove(new_draft_filename)
 
+local last_state = "first_run"
+
+local function process_line(line)
+  local state_char = string.sub(line, 1, 1)
+  local state
+  if state_char == "+" then
+    state = "added"
+  elseif state_char == "-" then
+    state = "removed"
+  elseif state_char == " " then
+    state = "unchanged"
+  end
+  local state_changed = false
+  if state ~= last_state then
+    if last_state ~= "first_run" then
+      slot.put("</span> ")
+    end
+    last_state = state
+    state_changed = true
+    slot.put("<span class=\"diff_" .. tostring(state) .. "\">")
+  end
+
+  line = string.sub(line, 2, #line)
+  if line ~= "###ENTER###" then
+    if not state_changed then
+      slot.put(" ")
+    end
+    slot.put(line)
+  else
+    slot.put("<br />")
+  end
+end
+
 if not status then
   ui.field.text{ value = _"The drafts do not differ" }
 else
-  slot.put('<table class="diff">')
-  slot.put('<tr><th width="50%">' .. _"Old draft revision" .. '</th><th width="50%">' .. _"New draft revision" .. '</th></tr>')
-
-  local last_state = "unchanged"
-  local lines = {}
-  local removed_lines = nil
-
-  local function process_line(line)
-    local state = "unchanged"
-    local char = line:sub(1,1)
-    line = line:sub(2)
-    state = "unchanged"
-    if char == "-" then
-      state = "-"
-    elseif char == "+" then
-      state = "+"
-    elseif char == "!" then
-      state = "eof"
+  ui.container{
+    tag = "div",
+    attr = { class = "diff" },
+    content = function()
+      output = output:gsub("[^\n\r]+", function(line)
+        process_line(line)
+      end)
     end
-    if last_state == "unchanged" then
-      if state == "unchanged" then
-        lines[#lines+1] = line
-      elseif (state == "-") or (state == "+") or (state == "eof") then
-        local text = table.concat(lines, "\n")
-        slot.put("<tr><td>", encode.html_newlines(encode.html(text)), "</td><td>", encode.html_newlines(encode.html(text)), "</td></tr>")
-        lines = { line }
-      end
-    elseif last_state == "-" then
-      if state == "-" then
-        lines[#lines+1] = line
-      elseif state == "+" then
-        removed_lines = lines
-        lines = { line }
-      elseif (state == "unchanged") or (state == "eof") then
-        local text = table.concat(lines,"\n")
-        slot.put('<tr><td class="removed">', encode.html_newlines(encode.html(text)), "</td><td></td></tr>")
-        lines = { line }
-      end
-    elseif last_state == "+" then
-      if state == "+" then
-        lines[#lines+1] = line
-      elseif (state == "-") or (state == "unchanged") or (state == "eof") then
-        if removed_lines then
-          local text = table.concat(lines, "\n")
-          local removed_text = table.concat(removed_lines, "\n")
-          slot.put('<tr><td class="removed">', encode.html_newlines(encode.html(removed_text)), '</td><td class="added">', encode.html_newlines(encode.html(text)), "</td></tr>")
-        else
-          local text = table.concat(lines, "\n")
-          slot.put('<tr><td></td><td class="added">', encode.html_newlines(encode.html(text)), "</td></tr>")
-        end
-        removed_lines = nil
-        lines = { line }
-      end
-    end
-    last_state = state
-  end
-
-  output = output .. " "
-  output = output:gsub("[^\n\r]+", function(line)
-    process_line(line)
-  end)
-  process_line("!")
-
-  slot.put("</table>")
+  }
 end 
 

app/main/draft/new.lua

   }
 end)
 
+
+
 ui.form{
   record = initiative.current_draft,
   attr = { class = "vertical" },
   action = "add",
   params = { initiative_id = initiative.id },
   routing = {
-    default = {
+    ok = {
       mode = "redirect",
       module = "initiative",
       view = "show",
   content = function()
 
     ui.field.text{ label = _"Author", value = app.session.member.name, readonly = true }
+
+    if param.get("preview") then
+      ui.container{
+        attr = { class = "draft_content wiki" },
+        content = function()
+          slot.put(format.wiki_text(param.get("content"), param.get("formatting_engine")))
+        end
+      }
+      slot.put("<br />")
+      ui.submit{ text = _"Save" }
+      slot.put("<br />")
+      slot.put("<br />")
+    end
+    slot.put("<br />")
+
+
     ui.field.select{
       label = _"Wiki engine",
       name = "formatting_engine",
       label = _"Content",
       name = "content",
       multiline = true,
-      attr = { style = "height: 50ex;" }
+      attr = { style = "height: 50ex;" },
+      value = param.get("content")
    }
 
+    ui.submit{ name = "preview", text = _"Preview" }
     ui.submit{ text = _"Save" }
   end
 }

app/main/initiative/_action/create.lua

   error("invalid formatting engine!")
 end
 
+if param.get("preview") then
+  return
+end
 
 
 local initiative = Initiative:new()

app/main/initiative/new.lua

         foreign_records = tmp,
         foreign_id = "id",
         foreign_name = "name",
-        value = (area.default_policy or {}).id
+        value = area.default_policy and area.default_policy.id or param.get("policy_id", atom.integer)
       }
       ui.tag{
         tag = "div",
         end
       }
     end
+    
+    if param.get("preview") then
+      ui.heading{ level = 1, content = encode.html(param.get("name")) }
+      local discussion_url = param.get("discussion_url")
+      ui.container{
+        attr = { class = "ui_field_label" },
+        content = _"Discussion with initiators"
+      }
+      ui.tag{
+        tag = "span",
+        content = function()
+          if discussion_url:find("^https?://") then
+            if discussion_url and #discussion_url > 0 then
+              ui.link{
+                attr = {
+                  class = "actions",
+                  target = "_blank",
+                  title = discussion_url
+                },
+                content = discussion_url,
+                external = discussion_url
+              }
+            end
+          else
+            slot.put(encode.html(discussion_url))
+          end
+        end
+      }
+      ui.container{
+        attr = { class = "draft_content wiki" },
+        content = function()
+          slot.put(format.wiki_text(param.get("draft"), param.get("formatting_engine")))
+        end
+      }
+      slot.put("<br />")
+      ui.submit{ text = _"Save" }
+      slot.put("<br />")
+      slot.put("<br />")
+    end
     slot.put("<br />")
-    ui.field.text{ label = _"Title of initiative", name = "name" }
-    ui.field.text{ label = _"Discussion URL", name = "discussion_url" }
+
+    ui.field.text{
+      label = _"Title of initiative",
+      name  = "name",
+      value = param.get("name")
+    }
+    ui.field.text{
+      label = _"Discussion URL",
+      name = "discussion_url",
+      value = param.get("discussion_url")
+    }
     ui.field.select{
       label = _"Wiki engine",
       name = "formatting_engine",
         { id = "compat", name = _"Traditional wiki syntax" }
       },
       foreign_id = "id",
-      foreign_name = "name"
+      foreign_name = "name",
+      value = param.get("formatting_engine")
     }
-    ui.field.text{ label = _"Draft", name = "draft", multiline = true, attr = { style = "height: 50ex;" } }
+    ui.field.text{
+      label = _"Draft",
+      name = "draft",
+      multiline = true, 
+      attr = { style = "height: 50ex;" },
+      value = param.get("draft")
+    }
+    ui.submit{ name = "preview", text = _"Preview" }
     ui.submit{ text = _"Save" }
   end
 }

app/main/member/_list_supported_initiatives.lua

+local initiatives_selector = param.get("initiatives_selector", "table")
+
+ui.filters{
+  label = _"Filter",
+  name = "filter_voting",
+  selector = initiatives_selector,
+  {
+    label = _"Filter",
+    {
+      name = "open",
+      label = _"Open",
+      selector_modifier = function(selector)
+          selector:add_where("issue.closed ISNULL")
+      end
+    },
+    {
+      name = "new",
+      label = _"New",
+      selector_modifier = function(selector)
+        selector:add_where("issue.accepted ISNULL AND issue.closed ISNULL")
+      end
+    },
+    {
+      name = "accepted",
+      label = _"In discussion",
+      selector_modifier = function(selector)
+        selector:add_where("issue.accepted NOTNULL AND issue.half_frozen ISNULL AND issue.closed ISNULL")
+      end
+    },
+    {
+      name = "half_frozen",
+      label = _"Frozen",
+      selector_modifier = function(selector)
+        selector:add_where("issue.half_frozen NOTNULL AND issue.fully_frozen ISNULL")
+      end
+    },
+    {
+      name = "frozen",
+      label = _"Voting",
+      selector_modifier = function(selector)
+        selector:add_where("issue.fully_frozen NOTNULL AND issue.closed ISNULL")
+        filter_voting = true
+      end
+    },
+    {
+      name = "finished",
+      label = _"Finished",
+      selector_modifier = function(selector)
+        selector:add_where("issue.closed NOTNULL AND issue.fully_frozen NOTNULL")
+      end
+    },
+    {
+      name = "cancelled",
+      label = _"Cancelled",
+      selector_modifier = function(selector)
+        selector:add_where("issue.closed NOTNULL AND issue.fully_frozen ISNULL")
+      end
+    },
+    {
+      name = "any",
+      label = _"Any",
+      selector_modifier = function(selector) end
+    },
+  },
+  content = function()
+    execute.view{
+      module = "initiative",
+      view = "_list",
+      params = { initiatives_selector = initiatives_selector }
+    }
+  end
+}

app/main/member/history.lua

           }
           ui.tag{
             tag = "td",
-            content =  member.active and _'activated' or _'deactivated',
+            content = entry.active and _'activated' or _'deactivated',
           }
           ui.tag{
             tag = "td",

app/main/member/show_tab.lua

 }
 
 local supported_initiatives_selector = member:get_reference_selector("supported_initiatives")
+
 tabs[#tabs+1] = {
   name = "supported_initiatives",
   label = _"Supported initiatives" .. " (" .. tostring(supported_initiatives_selector:count()) .. ")",
   icon = { static = "icons/16/thumb_up_green.png" },
-  module = "initiative",
-  view = "_list",
+  module = "member",
+  view = "_list_supported_initiatives",
   params = { initiatives_selector = supported_initiatives_selector },
 }
 
   name = "initiatied_initiatives",
   label = _"Initiated initiatives" .. " (" .. tostring(initiated_initiatives_selector:count()) .. ")",
   icon = { static = "icons/16/user_edit.png" },
-  module = "initiative",
-  view = "_list",
+  module = "member",
+  view = "_list_supported_initiatives",
   params = { initiatives_selector = initiated_initiatives_selector },
 }
 

app/main/opinion/_action/update.lua

   if opinion then
     opinion:destroy()
   end
-  slot.put_into("notice", _"Your opinion has been deleted")
+  slot.put_into("notice", _"Your rating has been deleted")
   return
 end
 
 
 opinion:save()
 
-slot.put_into("notice", _"Your opinion has been updated")
+slot.put_into("notice", _"Your rating has been updated")

app/main/suggestion/_list.lua

               end
             },
             {
-              label = _"Collective opinion",
+              label = _"Collective opinion of supporters",
               label_attr = { style = "width: 101px;" },
               content = function(record)
                 if record.minus2_unfulfilled_count then
-                  local max_value = record.initiative.issue.population
+                  local max_value = record.initiative.supporter_count
                   ui.bargraph{
                     max_value = max_value,
-                    width = 50,
+                    width = 100,
                     bars = {
-                      { color = "#ddd", value = max_value - record.minus2_unfulfilled_count - record.minus1_unfulfilled_count - record.minus2_fulfilled_count - record.minus1_fulfilled_count },
+                      { color = "#0a0", value = record.plus2_unfulfilled_count + record.plus2_fulfilled_count },
+                      { color = "#8f8", value = record.plus1_unfulfilled_count + record.plus1_fulfilled_count },
+                      { color = "#eee", value = max_value - record.minus2_unfulfilled_count - record.minus1_unfulfilled_count - record.minus2_fulfilled_count - record.minus1_fulfilled_count - record.plus1_unfulfilled_count - record.plus2_unfulfilled_count - record.plus1_fulfilled_count - record.plus2_fulfilled_count},
                       { color = "#f88", value = record.minus1_unfulfilled_count + record.minus1_fulfilled_count },
                       { color = "#a00", value = record.minus2_unfulfilled_count + record.minus2_fulfilled_count },
-                      { color = "#0a0", value = record.plus2_unfulfilled_count + record.plus2_fulfilled_count },
-                      { color = "#8f8", value = record.plus1_unfulfilled_count + record.plus1_fulfilled_count },
-                      { color = "#ddd", value = max_value - record.plus1_unfulfilled_count - record.plus2_unfulfilled_count - record.plus1_fulfilled_count - record.plus2_fulfilled_count },
                     }
                   }
                 end
             },
             {
               label = _"My opinion",
+              label_attr = { style = "width: 130px; font-style: italic;" },
               content = function(record)
                 local degree
                 local opinion
                   content = function()
                     if app.session.member_id then
                       if initiative.issue.state == "voting" or initiative.issue.state == "closed" then
-                        ui.tag{
-                          tag = "span",
-                          attr = { class = "action" .. (degree == -2 and " active_red2" or "") },
-                          content = _"must not"
-                        }
-                        ui.tag{
-                          tag = "span",
-                          attr = { class = "action" .. (degree == -1 and " active_red1" or "") },
-                          content = _"should not"
-                        }
-                        ui.tag{
-                          tag = "span",
-                          attr = { class = "action" .. (degree == nil and " active" or "") },
-                          content = _"neutral"
-                        }
-                        ui.tag{
-                          tag = "span",
-                          attr = { class = "action" .. (degree == 1 and " active_green1" or "") },
-                          content = _"should"
-                        }
-                        ui.tag{
-                          tag = "span",
-                          attr = { class = "action" .. (degree == 2 and " active_green2" or "") },
-                          content = _"must"
-                        }
+                        if degree == -2 then
+                          ui.tag{
+                            tag = "span",
+                            attr = {
+                              class = "action" .. (degree == -2 and " active_red2" or "")
+                            },
+                            content = _"must not"
+                          }
+                        end
+                        if degree == -1 then
+                          ui.tag{
+                            tag = "span",
+                            attr = { class = "action" .. (degree == -1 and " active_red1" or "") },
+                            content = _"should not"
+                          }
+                        end
+                        if degree == nil then
+                          ui.tag{
+                            tag = "span",
+                            attr = { class = "action" .. (degree == nil and " active" or "") },
+                            content = _"neutral"
+                          }
+                        end
+                        if degree == 1 then
+                          ui.tag{
+                            tag = "span",
+                            attr = { class = "action" .. (degree == 1 and " active_green1" or "") },
+                            content = _"should"
+                          }
+                        end
+                        if degree == 2 then
+                          ui.tag{
+                            tag = "span",
+                            attr = { class = "action" .. (degree == 2 and " active_green2" or "") },
+                            content = _"must"
+                          }
+                        end
                       else
                         ui.link{
-                          attr = { class = "action" .. (degree == -2 and " active_red2" or "") },
-                          text = _"must not",
+                          attr = { class = "action" .. (degree == 2 and " active_green2" or "") },
+                          text = _"must",
                           module = "opinion",
                           action = "update",
                           routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
                           params = {
                             suggestion_id = record.id,
-                            degree = -2
+                            degree = 2
+                          },
+                          partial = partial
+                        }
+                        slot.put(" ")
+                        ui.link{
+                          attr = { class = "action" .. (degree == 1 and " active_green1" or "") },
+                          text = _"should",
+                          module = "opinion",
+                          action = "update",
+                          routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
+                          params = {
+                            suggestion_id = record.id,
+                            degree = 1
+                          },
+                          partial = partial
+                        }
+                        slot.put(" ")
+                        ui.link{
+                          attr = { class = "action" .. (degree == nil and " active" or "") },
+                          text = _"neutral",
+                          module = "opinion",
+                          action = "update",
+                          routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
+                          params = {
+                            suggestion_id = record.id,
+                            delete = true
                           },
                           partial = partial
                         }
                         }
                         slot.put(" ")
                         ui.link{
-                          attr = { class = "action" .. (degree == nil and " active" or "") },
-                          text = _"neutral",
+                          attr = { class = "action" .. (degree == -2 and " active_red2" or "") },
+                          text = _"must not",
                           module = "opinion",
                           action = "update",
                           routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
                           params = {
                             suggestion_id = record.id,
-                            delete = true
-                          },
-                          partial = partial
-                        }
-                        slot.put(" ")
-                        ui.link{
-                          attr = { class = "action" .. (degree == 1 and " active_green1" or "") },
-                          text = _"should",
-                          module = "opinion",
-                          action = "update",
-                          routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
-                          params = {
-                            suggestion_id = record.id,
-                            degree = 1
-                          },
-                          partial = partial
-                        }
-                        slot.put(" ")
-                        ui.link{
-                          attr = { class = "action" .. (degree == 2 and " active_green2" or "") },
-                          text = _"must",
-                          module = "opinion",
-                          action = "update",
-                          routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
-                          params = {
-                            suggestion_id = record.id,
-                            degree = 2
+                            degree = -2
                           },
                           partial = partial
                         }
               end
             },
             {
-              content = function(record)
-                local opinion
-                if app.session.member_id then
-                  opinion = Opinion:by_pk(app.session.member.id, record.id)
-                end
-                if opinion and not opinion.fulfilled then
-                  ui.image{ static = "icons/16/cross.png" }
-                end
-              end
-            },
-            {
               label = _"Suggestion currently not implemented",
               label_attr = { style = "width: 101px;" },
               content = function(record)
                 if record.minus2_unfulfilled_count then
-                  local max_value = record.initiative.issue.population
+                  local max_value = record.initiative.supporter_count
                   ui.bargraph{
                     max_value = max_value,
-                    width = 50,
+                    width = 100,
                     bars = {
-                      { color = "#ddd", value = max_value - record.minus2_unfulfilled_count - record.minus1_unfulfilled_count },
+                      { color = "#0a0", value = record.plus2_unfulfilled_count },
+                      { color = "#8f8", value = record.plus1_unfulfilled_count },
+                      { color = "#eee", value = max_value - record.minus2_unfulfilled_count - record.minus1_unfulfilled_count - record.plus1_unfulfilled_count - record.plus2_unfulfilled_count },
                       { color = "#f88", value = record.minus1_unfulfilled_count },
                       { color = "#a00", value = record.minus2_unfulfilled_count },
-                      { color = "#0a0", value = record.plus2_unfulfilled_count },
-                      { color = "#8f8", value = record.plus1_unfulfilled_count },
-                      { color = "#ddd", value = max_value - record.plus1_unfulfilled_count - record.plus2_unfulfilled_count },
                     }
                   }
                 end
               end
             },
             {
-              content = function(record)
-                local opinion
-                if app.session.member_id then
-                  opinion = Opinion:by_pk(app.session.member.id, record.id)
-                end
-                if opinion and opinion.fulfilled then
-                    ui.image{ static = "icons/16/tick.png" }
-                end
-              end
-            },
-            {
               label = _"Suggestion currently implemented",
               label_attr = { style = "width: 101px;" },
               content = function(record)
                 if record.minus2_fulfilled_count then
-                  local max_value = record.initiative.issue.population
+                  local max_value = record.initiative.supporter_count
                   ui.bargraph{
                     max_value = max_value,
-                    width = 50,
+                    width = 100,
                     bars = {
-                      { color = "#ddd", value = max_value - record.minus2_fulfilled_count - record.minus1_fulfilled_count },
+                      { color = "#0a0", value = record.plus2_fulfilled_count },
+                      { color = "#8f8", value = record.plus1_fulfilled_count },
+                      { color = "#eee", value = max_value - record.minus2_fulfilled_count - record.minus1_fulfilled_count - record.plus1_fulfilled_count - record.plus2_fulfilled_count},
                       { color = "#f88", value = record.minus1_fulfilled_count },
                       { color = "#a00", value = record.minus2_fulfilled_count },
-                      { color = "#0a0", value = record.plus2_fulfilled_count },
-                      { color = "#8f8", value = record.plus1_fulfilled_count },
-                      { color = "#ddd", value = max_value - record.plus1_fulfilled_count - record.plus2_fulfilled_count },
                     }
                   }
                 end
               end
             },
             {
-              label_attr = { style = "width: 200px;" },
+              label = app.session.member_id and _"I consider suggestion as" or nil,
+              label_attr = { style = "width: 100px; font-style: italic;" },
               content = function(record)
                 local degree
                 local opinion
                   degree = opinion.degree
                 end
                 if opinion then
-                  if not opinion.fulfilled then
-                    local text = ""
-                    if opinion.degree > 0 then
-                      text = _"Mark suggestion as implemented and express satisfaction"
-                    else
-                      text = _"Mark suggestion as implemented and express dissatisfaction"
-                    end
-                    ui.link{
-                      attr = { class = "action" },
-                      text = text,
-                      module = "opinion",
-                      action = "update",
-                      routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
-                      params = {
-                        suggestion_id = record.id,
-                        fulfilled = true
-                      },
-                      partial = partial
-                    }
-                  else
-                    if opinion.degree > 0 then
-                      text = _"Mark suggestion as not implemented and express dissatisfaction"
-                    else
-                      text = _"Mark suggestion as not implemented and express satisfaction"
-                    end
-                    ui.link{
-                      attr = { class = "action" },
-                      text = text,
-                      module = "opinion",
-                      action = "update",
-                      routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
-                      params = {
-                        suggestion_id = record.id,
-                        fulfilled = false
-                      },
-                      partial = partial
-                    }
-                  end
+
+                  ui.link{
+                    attr = { class = opinion.fulfilled and "action active" or "action" },
+                    text = _"implemented",
+                    module = "opinion",
+                    action = "update",
+                    routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
+                    params = {
+                      suggestion_id = record.id,
+                      fulfilled = true
+                    },
+                    partial = partial
+                  }
+                  slot.put("<br />")
+                  ui.link{
+                    attr = { class = not opinion.fulfilled and "action active" or "action" },
+                    text = _"not implemented",
+                    module = "opinion",
+                    action = "update",
+                    routing = { default = { mode = "redirect", module = request.get_module(), view = request.get_view(), id = param.get_id_cgi(), params = param.get_all_cgi() } },
+                    params = {
+                      suggestion_id = record.id,
+                      fulfilled = false
+                    },
+                    partial = partial
+                  }
+
                 end
               end
             },
             {
+              label = app.session.member_id and _"So I'm" or nil,
               content = function(record)
                 local opinion
                 if app.session.member_id then
                 end
                 if opinion then
                   if (opinion.fulfilled and opinion.degree > 0) or (not opinion.fulfilled and opinion.degree < 0) then
-                    ui.image{ static = "icons/16/thumb_up_green.png" }
+                    local title = _"satisfied"
+                    ui.image{ attr = { alt = title, title = title }, static = "icons/emoticon_happy.png" }
+                  elseif opinion.degree == 1 or opinion.degree == -1 then
+                    local title = _"a bit unsatisfied"
+                    ui.image{ attr = { alt = title, title = title }, static = "icons/emoticon_unhappy.png" }
                   else
-                    ui.image{ static = "icons/16/thumb_down_red.png" }
+                    local title = _"more unsatisfied"
+                    ui.image{ attr = { alt = title, title = title }, static = "icons/emoticon_unhappy_red.png" }
                   end
                 end
               end

app/main/vote/list.lua

   end
 end
 
-local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):exec()
+local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec()
 
 local min_grade = -1;
 local max_grade = 1;

config/default.lua

 config.app_name = "LiquidFeedback"
-config.app_version = "beta26"
+config.app_version = "beta27"
 
 config.app_title = config.app_name .. " (" .. request.get_config_name() .. " environment)"
 

locale/translations.de.lua

 ["Choose member"] = "Mitglied auswählen";
 ["Click for details"] = "Klicke für Details";
 ["Closed"] = "geschlossen";
-["Collective opinion"] = "Meinungsbild";
+["Collective opinion of supporters"] = "Meinungsbild der Unterstützer";
 ["Commit suggestion"] = "Anregung speichern";
 ["Compare"] = "Vergleichen";
 ["Confirm"] = "Bestätigen";
 ["Email address confirmation"] = "Bestätigung der E-Mail-Adresse";
 ["Email address is confirmed now"] = "E-Mail-Adresse ist jetzt bestätigt";
 ["Email address too short!"] = "E-Mail-Adresse ist zu kurz!";
-["Email confirmation request"] = "Bestätigung Deiner E-Mail-Adresse";
 ["Email unconfirmed"] = "Unbestätigte E-Mail-Adresse";
-["Empty help text: #{id}.#{lang}.txt"] = "Leerer Hilfe-Text: #{id}.#{lang}.txt";
 ["Error while converting image. Please note, that only JPG files are supported!"] = "Fehler beim Konvertieren des Bilds. Bitte beachte, dass nur JPG-Dateien unterstützt werden.";
 ["Error while resolving openid. Internal message: '#{errmsg}'"] = "Fehler beim Auflösen der OpenID. Interne Fehlermeldung: '#{errmsg}'";
 ["Error while updating member, database reported:<br /><br /> (#{errormessage})"] = "Fehler beim aktualisieren des Mitglieds, die Datenbank berichtet folgenden Fehler:<br /><br /> (#{errormessage})";
 ["Help for: #{text}"] = "Hilfe zu: #{text}";
 ["Hide"] = "Verstecken";
 ["Hide filter details"] = "Filter-Details verstecken";
-["Hide this help message"] = "Diesen Hilfetext ausblenden";
 ["Home"] = "Startseite";
-["I accept the terms of use by checking the following checkbox:"] = "Ich akzeptiere die Nutzungsbedingungen durch Auswahl der folgenden Ankreuzbox:";
+["I consider suggestion as"] = "Ich halte die Anregung für";
 ["Id"] = "Id";
 ["Ident number"] = "Ident-Nummer";
 ["If this link is not working, please open following url in your web browser:\n\n"] = "Sollte der Link nicht funktionieren, öffne bitte die folgenden URL in Deinem Web-Browser:\n\n";
 ["JavaScript is disabled or not available."] = "JavaScript ist abgeschaltet oder nicht verfügbar.";
 ["Last author"] = "Letzter Autor";
 ["Last snapshot:"] = "Letzte Auszählung:";
-["Legend:"] = "Legende:";
 ["License"] = "Lizenz";
 ["Locked?"] = "Gesperrt?";
 ["Login"] = "Anmeldung";
 ["Majority"] = "Mehrheit";
 ["Manage filter"] = "Filter verwalten";
 ["Manage timeline filters"] = "Zeitachsen-Filter verwalten";
-["Mark suggestion as implemented and express dissatisfaction"] = "Anregung als umgesetzt markieren und Unzufriedenheit ausdrücken";
-["Mark suggestion as implemented and express satisfaction"] = "Anregung als umgesetzt markieren und Zufriedenheit ausdrücken";
-["Mark suggestion as not implemented and express dissatisfaction"] = "Anregung als nicht umgesetzt markieren und Unzufriedenheit ausdrücken";
-["Mark suggestion as not implemented and express satisfaction"] = "Anregung als nicht umgesetzt markieren und Zufriedenheit ausdrücken";
 ["Max potential support"] = "Max. potentielle Unterstützer";
 ["Max support"] = "Max. Unterstützer";
 ["Member"] = "Mitglied";
 ["Membership updated"] = "Mitgliedschaft aktualisiert";
 ["Memberships"] = "Mitgliedschaften";
 ["Message of the day"] = "Hinweise";
-["Missing help text: #{id}.#{lang}.txt"] = "Fehlender Hilfe-Text: #{id}.#{lang}.txt";
 ["Mobile phone"] = "Mobiltelefon";
 ["Monday"] = "Montag";
 ["Move down"] = "Runter schieben";
 ["New address"] = "Neue E-Mail-Adresse";
 ["New draft"] = "Neuer Entwurf";
 ["New draft has been added to initiative"] = "Neuer Entwurf wurde der Initiative hinzugefügt";
-["New draft revision"] = "Neue Revision des Entwurfs";
 ["New initiative"] = "Neue Initiative";
 ["New issue"] = "Neues Thema";
 ["New password"] = "Neues Kennwort";
 ["Number of incoming delegations, follow link to see more details"] = "Anzahl eingehender Delegationen, Link folgen für mehr Details";
 ["Number of initiatives to preview"] = "Anzahl der Initiativen in der Vorschau";
 ["OK"] = "OK";
-["Old draft revision"] = "Alte Revision des Entwurfs";
 ["Old password"] = "Altes Kennwort";
 ["Old password is wrong"] = "Das alte Kennwort ist falsch";
 ["Oldest"] = "Älteste";
-["On that page please enter the confirmation code:\n\n"] = "Auf dieser Seite gib bitte folgenden Bestätigungscode ein:\n\n";
 ["On that page please enter the reset code:\n\n"] = "Auf dieser Seite gib bitte den folgenden Rücksetzcode ein:\n\n";
 ["One issue"] = "Ein Thema";
 ["One issue you are interested in"] = "Ein Thema, das Dich interessiert";
 ["Please choose a policy"] = "Bitte wähle ein Regelwerk";
 ["Please choose two different versions of the draft to compare"] = "Bitte wähle zwei verschiedene Versionen des Drafts, um sie zu vergleichen.";
 ["Please choose two versions of the draft to compare"] = "Bitte wähle zwei Versionen des Drafts, um sie zu vergleichen.";
-["Please confirm your email address by clicking the following link:\n\n"] = "Bitte bestätige Deine E-Mail-Adresse, indem Du den folgenden Link anklickst:\n\n";
 ["Please enter the email reset code you have received:"] = "Bitte gib den Rücksetzcode ein, den Du erhalten hast:";
 ["Please enter the invite code you've received."] = "Bitte gib den Invite-Code ein, den Du erhalten hast.";
 ["Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link."] = "Bitte gib Deine E-Mail-Adresse ein. Diese Adresse wird für automatische Benachrichtigungen (wenn Du diese anforderst) sowie zum Zurücksetzen des Kennworts verwendet. Diese Adresse wird nicht veröffentlicht. Nach Abschluß der Registration wirst Du eine E-Mail mit einem Link zum Bestätigen der Adresse erhalten.";
 ["Potential support"] = "Potentielle Unterstützung";
 ["Potential supported"] = "Potentiell unterstützt";
 ["Potential supporter"] = "Potentielle Unterstützer";
+["Preview"] = "Vorschau";
 ["Previous initiative"] = "Vorherige Initiative";
 ["Previous issue"] = "Vorheriges Thema";
 ["Profession"] = "Beruf";
 ["Show member"] = "Mitglied anzeigen";
 ["Show name history"] = "Namenshistorie zeigen";
 ["Show only events which match... (or associtated)"] = "Zeige nur Ereignisse welche folgendes erfüllen... (oder-verknüpft)";
+["So I'm"] = "Also bin ich";
 ["Software"] = "Software";
 ["Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."] = "Einige auf JavaScript basierende Funktionen (insbesondere der Abstimmung) sind nicht benutzbar.\nFür diese Beta verwende bitte eine aktuelle Version von Firefox, Safari, Opera(?), Konqueror oder einen anderen (mehr) den Standards entsprechenden Browser.\nEin alternativer Zugriff ohne JavaScript wird bald zur Verfügung stehen.";
 ["Sorry, but there is not confirmed email address for your account. Please contact the administrator or support."] = "Sorry, aber für diesen Account ist keine bestätigte E-Mail-Adresse hinterlegt. Bitte wende Dich an den Administrator oder den Support.";
 ["Supported initiatives"] = "Unterstützte Initiativen";
 ["Supporter"] = "Unterstützer";
 ["Tabs"] = "Registerkarten";
-["Terms accepted"] = "Bedingungen akzeptiert";
 ["Terms of use"] = "Nutzungsbedingungen";
 ["The API key has been changed too fast."] = "Der API-Schlüssel wurde zu schnell geändert.";
 ["The code you've entered is invalid"] = "Der Code, den Du eingeben hast, ist nicht gültig!";
 ["Tuesday"] = "Dienstag";
 ["Type of tabs"] = "Tabulatortyp";
 ["Unconfirmed address"] = "Unbestätigte E-Mail";
-["Unknown author"] = "Unbekannter Autor";
 ["Updated drafts"] = "Neue Entwürfe";
 ["Upload images"] = "Bilder hochladen";
 ["Verification time"] = "Zeit für die Überprüfung";
 ["You didn't saved any member as contact yet."] = "Du hast noch kein Mitglied als Kontakt gespeichert!";
 ["You have saved this member as contact"] = "Du hast das Mitglied als Kontakt gespeichert";
 ["You have saved this member as contact."] = "Du hast das Mitglied als Kontakt gespeichert.";
-["You have to accept the terms of use to complete registration."] = "Du musst die Nutzungsbedingungen akzeptieren um die Registration abzuschliessen.";
 ["You have to mark 'Are you sure' to revoke!"] = "Zum Zurückziehen musst Du 'Sicher?' auswählen";
 ["You need to be logged in, to use all features of this system."] = "Du musst eingeloggt sein, um alle Funktionen dieses Systems nutzen zu können.";
 ["You want to vote later"] = "Du willst später abstimmen";
 ["Your global delegation has been updated."] = "Deine globale Delegation wurde geändert";
 ["Your login has been changed to '#{login}'"] = "Dein Anmeldename wurde auf '#{login}' geändert";
 ["Your name has been changed"] = "Dein Name wurde geändert";
-["Your opinion has been deleted"] = "Deine Meinung wurde gelöscht";
-["Your opinion has been updated"] = "Deine Meinung wurde aktualisiert";
 ["Your page has been updated"] = "Deine Seite wurde aktualisiert";
 ["Your password has been updated successfully"] = "Das Kennwort wurde erfolgreich geändert";
+["Your rating has been deleted"] = "Deine Bewertung wurde gelöscht";
+["Your rating has been updated"] = "Deine Bewertung wurde aktualisiert";
 ["Your suggestion has been added"] = "Deine Anregung wurde hinzufügt";
 ["Your support has been added to this initiative"] = "Deine Unterstützung wurde der Initiative hinzugefügt";
 ["Your support has been removed from this initiative"] = "Deine Unterstützung wurde der Initiave entzogen";
 ["Z-A"] = "Z-A";
 ["[Registered members only]"] = "[nur für Registrierte]";
 ["[not displayed public]"] = "[nicht öffentlich]";
+["a bit unsatisfied"] = "etwas unzufrieden";
 ["activated"] = "aktiviert";
 ["and #{count} more initiatives"] = "und #{count} weitere Initiativen";
 ["deactivated"] = "deaktiviert";
-["delete<br /><br />"] = "löschen<br /><br />";
 ["disabled"] = "ausgeschaltet";
 ["email"] = "E-Mail";
+["implemented"] = "umgesetzt";
 ["last 24 hours"] = "letzte 24 Stunden";
 ["login name"] = "Anmeldename";
+["more unsatisfied"] = "sehr unzufrieden";
 ["must"] = "muss";
 ["must not"] = "darf nicht";
 ["must/should"] = "muss/soll";
 ["must/should not"] = "muss/soll nicht";
 ["neutral"] = "neutral";
+["not implemented"] = "nicht umgesetzt";
 ["requested"] = "angefragt";
+["satisfied"] = "zufrieden";
 ["should"] = "soll";
 ["should not"] = "soll nicht";
 ["to reset your password please click on the following link:\n\n"] = "um Dein Kennwort zurückzusetzen klicke bitte den folgenden Link an:\n\n";

locale/translations.en.lua

 ["Choose member"] = false;
 ["Click for details"] = false;
 ["Closed"] = false;
-["Collective opinion"] = false;
+["Collective opinion of supporters"] = false;
 ["Commit suggestion"] = false;
 ["Compare"] = false;
 ["Confirm"] = false;
 ["Email address confirmation"] = false;
 ["Email address is confirmed now"] = false;
 ["Email address too short!"] = false;
-["Email confirmation request"] = false;
 ["Email unconfirmed"] = false;
-["Empty help text: #{id}.#{lang}.txt"] = false;
 ["Error while converting image. Please note, that only JPG files are supported!"] = false;
 ["Error while resolving openid. Internal message: '#{errmsg}'"] = false;
 ["Error while updating member, database reported:<br /><br /> (#{errormessage})"] = false;
 ["Help for: #{text}"] = false;
 ["Hide"] = false;
 ["Hide filter details"] = false;
-["Hide this help message"] = false;
 ["Home"] = false;
-["I accept the terms of use by checking the following checkbox:"] = false;
+["I consider suggestion as"] = false;
 ["Id"] = false;
 ["Ident number"] = false;
 ["If this link is not working, please open following url in your web browser:\n\n"] = false;
 ["JavaScript is disabled or not available."] = false;
 ["Last author"] = false;
 ["Last snapshot:"] = false;
-["Legend:"] = false;
 ["License"] = false;
 ["Locked?"] = false;
 ["Login"] = false;
 ["Majority"] = false;
 ["Manage filter"] = false;
 ["Manage timeline filters"] = false;
-["Mark suggestion as implemented and express dissatisfaction"] = false;
-["Mark suggestion as implemented and express satisfaction"] = false;
-["Mark suggestion as not implemented and express dissatisfaction"] = false;
-["Mark suggestion as not implemented and express satisfaction"] = false;
 ["Max potential support"] = false;
 ["Max support"] = false;
 ["Member"] = false;
 ["Membership updated"] = false;
 ["Memberships"] = false;
 ["Message of the day"] = false;
-["Missing help text: #{id}.#{lang}.txt"] = false;
 ["Mobile phone"] = false;
 ["Monday"] = false;
 ["Move down"] = false;
 ["New address"] = false;
 ["New draft"] = false;
 ["New draft has been added to initiative"] = false;
-["New draft revision"] = false;
 ["New initiative"] = false;
 ["New issue"] = false;
 ["New password"] = false;
 ["Number of incoming delegations, follow link to see more details"] = false;
 ["Number of initiatives to preview"] = false;
 ["OK"] = false;
-["Old draft revision"] = false;
 ["Old password"] = false;
 ["Old password is wrong"] = false;
 ["Oldest"] = false;
-["On that page please enter the confirmation code:\n\n"] = false;
 ["On that page please enter the reset code:\n\n"] = false;
 ["One issue"] = false;
 ["One issue you are interested in"] = false;
 ["Please choose a policy"] = false;
 ["Please choose two different versions of the draft to compare"] = false;
 ["Please choose two versions of the draft to compare"] = false;
-["Please confirm your email address by clicking the following link:\n\n"] = false;
 ["Please enter the email reset code you have received:"] = false;
 ["Please enter the invite code you've received."] = false;
 ["Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link."] = false;
 ["Potential support"] = false;
 ["Potential supported"] = false;
 ["Potential supporter"] = false;
+["Preview"] = false;
 ["Previous initiative"] = false;
 ["Previous issue"] = false;
 ["Profession"] = false;
 ["Show member"] = false;
 ["Show name history"] = false;
 ["Show only events which match... (or associtated)"] = false;
+["So I'm"] = false;
 ["Software"] = false;
 ["Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."] = false;
 ["Sorry, but there is not confirmed email address for your account. Please contact the administrator or support."] = false;
 ["Supported initiatives"] = false;
 ["Supporter"] = false;
 ["Tabs"] = false;
-["Terms accepted"] = false;
 ["Terms of use"] = false;
 ["The API key has been changed too fast."] = false;
 ["The code you've entered is invalid"] = false;
 ["Tuesday"] = false;
 ["Type of tabs"] = false;
 ["Unconfirmed address"] = false;
-["Unknown author"] = false;
 ["Updated drafts"] = false;
 ["Upload images"] = false;
 ["Verification time"] = false;
 ["You didn't saved any member as contact yet."] = false;
 ["You have saved this member as contact"] = false;
 ["You have saved this member as contact."] = false;
-["You have to accept the terms of use to complete registration."] = false;
 ["You have to mark 'Are you sure' to revoke!"] = false;
 ["You need to be logged in, to use all features of this system."] = false;
 ["You want to vote later"] = false;
 ["Your global delegation has been updated."] = false;
 ["Your login has been changed to '#{login}'"] = false;
 ["Your name has been changed"] = false;
-["Your opinion has been deleted"] = false;
-["Your opinion has been updated"] = false;
 ["Your page has been updated"] = false;
 ["Your password has been updated successfully"] = false;
+["Your rating has been deleted"] = false;
+["Your rating has been updated"] = false;
 ["Your suggestion has been added"] = false;
 ["Your support has been added to this initiative"] = false;
 ["Your support has been removed from this initiative"] = false;
 ["Z-A"] = false;
 ["[Registered members only]"] = false;
 ["[not displayed public]"] = false;
+["a bit unsatisfied"] = false;
 ["activated"] = false;
 ["and #{count} more initiatives"] = false;
 ["deactivated"] = false;
-["delete<br /><br />"] = false;
 ["disabled"] = false;
 ["email"] = false;
+["implemented"] = false;
 ["last 24 hours"] = false;
 ["login name"] = false;
+["more unsatisfied"] = false;
 ["must"] = false;
 ["must not"] = false;
 ["must/should"] = false;
 ["must/should not"] = false;
 ["neutral"] = false;
+["not implemented"] = false;
 ["requested"] = false;
+["satisfied"] = false;
 ["should"] = false;
 ["should not"] = false;
 ["to reset your password please click on the following link:\n\n"] = false;

locale/translations.eo.lua

 ["Choose member"] = "Elekti membron";
 ["Click for details"] = "Klaki por detaloj";
 ["Closed"] = "Fermita";
-["Collective opinion"] = "Opinioresumo";
+["Collective opinion of supporters"] = false;
 ["Commit suggestion"] = "Enmeti sugeston";
 ["Compare"] = "Kompari";
 ["Confirm"] = "Konfirmi";
 ["Email address confirmation"] = "Konfirmo de la retadreson";
 ["Email address is confirmed now"] = "Retadreso nun estas konfirmita";
 ["Email address too short!"] = "Retadreso estas tro mallonga!";
-["Email confirmation request"] = "Konfirmopeto de via retadreso";
 ["Email unconfirmed"] = false;
-["Empty help text: #{id}.#{lang}.txt"] = "Malplena helpoteksto: #{id}.#{lang}.txt";
 ["Error while converting image. Please note, that only JPG files are supported!"] = false;
 ["Error while resolving openid. Internal message: '#{errmsg}'"] = "Eraro dum trovado de openid. Interna mesaĝo: '#{errmsg}'";
 ["Error while updating member, database reported:<br /><br /> (#{errormessage})"] = "Eraro dum ĝisdatigo de la membro, la datumbazo raportas sekvan eraron:<br /><br /> (#{errormessage})";
 ["Help for: #{text}"] = "Helpo por: #{text}";
 ["Hide"] = "Kaŝi";
 ["Hide filter details"] = "Kaŝi filtrodetalojn";
-["Hide this help message"] = "Kaŝi tiun ĉi helpotekston";
 ["Home"] = "Ĉefpaĝo";
-["I accept the terms of use by checking the following checkbox:"] = "Mi akceptas la uzokondiĉojn per la selekto de la sekva markobutono:";
+["I consider suggestion as"] = false;
 ["Id"] = "Identigilo";
 ["Ident number"] = "Identonumero";
 ["If this link is not working, please open following url in your web browser:\n\n"] = "Se tiu ligilo ne funkcias, bonvolu malfermi la sekvan URLon per via retumilo:\n\n";
 ["JavaScript is disabled or not available."] = "JavaScript ne estas ŝaltita aŭ disponebla.";
 ["Last author"] = "Lasta aŭtoro";
 ["Last snapshot:"] = "Lasta fulmrigardo:";
-["Legend:"] = "Legendo:";
 ["License"] = "Licenco";
 ["Locked?"] = "Ĉu blokita?";
 ["Login"] = "Ensaluti";
 ["Majority"] = "Plimulto";
 ["Manage filter"] = "Administri filtrilojn";
 ["Manage timeline filters"] = "Administri tempolinio-filtrilojn";
-["Mark suggestion as implemented and express dissatisfaction"] = "Marki sugeston kiel realigitan kaj esprimi malkontentecon";
-["Mark suggestion as implemented and express satisfaction"] = "Marki sugeston kiel realigitan kaj esprimi kontentecon";
-["Mark suggestion as not implemented and express dissatisfaction"] = "Marki sugeston kiel ne realigitan kaj esprimi malkontentecon";
-["Mark suggestion as not implemented and express satisfaction"] = "Marki sugeston kiel ne realigitan kaj esprimi kontentecon";
 ["Max potential support"] = "Maksimumo da eblaj subtenantoj";
 ["Max support"] = "Maksimuma subteno";
 ["Member"] = "Membro";
 ["Membership updated"] = "Membreco ĝisdatigita";
 ["Memberships"] = "Membrecoj";
 ["Message of the day"] = "Mesaĝoj";
-["Missing help text: #{id}.#{lang}.txt"] = "Mankas helpoteksto: #{id}.#{lang}.txt";
 ["Mobile phone"] = "Poŝtelefono";
 ["Monday"] = "Lundo";
 ["Move down"] = "Movi malsupren";
 ["New address"] = false;
 ["New draft"] = "Nova skizo";
 ["New draft has been added to initiative"] = "La nova skizo estas aldonita al la iniciato";
-["New draft revision"] = "Nova revizio de la skizo";
 ["New initiative"] = "Nova iniciato";
 ["New issue"] = "Nova temo";
 ["New password"] = "Nova pasvorto";
 ["Number of incoming delegations, follow link to see more details"] = "Nombro de alvenantaj delegacioj, sekvu ligilon por pli da detaloj";
 ["Number of initiatives to preview"] = "Nombro de iniciatoj por antaŭmontri";
 ["OK"] = "Bone";
-["Old draft revision"] = "Malnova revizio de la skizo";
 ["Old password"] = "Malnova pasvorto";
 ["Old password is wrong"] = "La malnova pasvorto estas malĝusta";
 ["Oldest"] = "Plej malnova";
-["On that page please enter the confirmation code:\n\n"] = "Bonvolu enigi sur tiu paĝo la konfirmokodon:\n\n";
 ["On that page please enter the reset code:\n\n"] = "Bonvolu enigu sur tiu paĝo la remetokodon:\n\n";
 ["One issue"] = "Unu temo";
 ["One issue you are interested in"] = "Unu temo, kiu vin interesas";
 ["Please choose a policy"] = "Bonvolu elekti regularon";
 ["Please choose two different versions of the draft to compare"] = "Bonvolu elekti du malsamajn versiojn de la skizo por kompari ilin.";
 ["Please choose two versions of the draft to compare"] = "Bonvolu elekti du versiojn de la skizo por kompari ili.";
-["Please confirm your email address by clicking the following link:\n\n"] = "Bonvolu konfirmi vian retadreson klakante sekvan ligilon:\n\n";
 ["Please enter the email reset code you have received:"] = "Bonvolu enigi retpoŝtoremetokodon, kiun vi ricevis:";
 ["Please enter the invite code you've received."] = "Bonvolu enigi la invitokodon, kiun vi ricevis.";
 ["Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link."] = "Bonvolu enigi vian retadreson. Tiu adreso estas uzita por aŭtomataj sciigoj (se vi petas tiajn) kaj por remeti la pasvorton. Tiu adreso ne estos publikigita. Post la fino de la registrado, vi ricevos retpoŝton kun ligilo al la konfirmo de la adreso.";
 ["Potential support"] = "Eventuala subteno";
 ["Potential supported"] = "Eble subtenota";
 ["Potential supporter"] = "Eventuala subtenonto";
+["Preview"] = false;
 ["Previous initiative"] = "Antaŭa initciato";
 ["Previous issue"] = "Antaŭa temo";
 ["Profession"] = "Profesio";
 ["Show member"] = "Montri membron";
 ["Show name history"] = "Montri nomohistorion";
 ["Show only events which match... (or associtated)"] = "Montri nur eventojn, kiuj kongruas... (aŭ kunligitaj)";
+["So I'm"] = false;
 ["Software"] = "Programaro";
 ["Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."] = "Kelkaj funkcioj bazitaj je JavaScript (precipe la voĉdono) ne funkcios.\nBonvolu, uzi por tiu betaversio aktualan version de Firefox, Safari, Opera(?), Konqueror aŭ alian (pli) normokonforman retumilon.\nAlternativa atingo sen JavaScript estos baldaŭ disponebla.";
 ["Sorry, but there is not confirmed email address for your account. Please contact the administrator or support."] = "Pardonu, por tiu konto ne ekzistas konfirmita retadreson. Bonvolu vin turni al administranto aŭ al la helpantaro.";
 ["Supported initiatives"] = "Iniciatoj subtenitaj";
 ["Supporter"] = "Subtenantoj";
 ["Tabs"] = "Langetoj";
-["Terms accepted"] = "Kondiĉoj akceptitaj";
 ["Terms of use"] = "Uzokondiĉoj";
 ["The API key has been changed too fast."] = "La API-ŝlosilo estas ŝanĝita tro rapide.";
 ["The code you've entered is invalid"] = "La kodo, kiun vi enigis ne estas valida!";
 ["Tuesday"] = "Mardo";
 ["Type of tabs"] = "Tipo de langetoj";
 ["Unconfirmed address"] = false;
-["Unknown author"] = "Aŭtoro nekonata";
 ["Updated drafts"] = "Skizoj ĝisdatigitaj";
 ["Upload images"] = "Alŝuti bildojn";
 ["Verification time"] = "Tempo por la kontrolo";
 ["You didn't saved any member as contact yet."] = "Vi ankoraŭ ne konservis membron kiel kontakton!";
 ["You have saved this member as contact"] = "Vi konservis membron kiel kontakton";
 ["You have saved this member as contact."] = "Vi konservis membron kiel kontakton.";
-["You have to accept the terms of use to complete registration."] = "Vi devas akcepti la uzokondiĉojn por fini la registradon.";
 ["You have to mark 'Are you sure' to revoke!"] = "Por nuligi vi devas elekti 'Certa?'";
 ["You need to be logged in, to use all features of this system."] = "Vi devas esti ensalutinta por uzi ĉiujn funkciojn de tiu sistemo.";
 ["You want to vote later"] = "Vi volas baloti pli malfrue";
 ["Your global delegation has been updated."] = "Via ĝenerala delegacio estas ĝisdatigita";
 ["Your login has been changed to '#{login}'"] = "Via salutnomo estas ĝisdatigita al '#{login}'";
 ["Your name has been changed"] = "Via nomo estas ĝisdatigita";
-["Your opinion has been deleted"] = "Via opinio estas viŝita";
-["Your opinion has been updated"] = "Via opinio estas ĝisdatigita";
 ["Your page has been updated"] = "Via paĝo estas ĝisdatigita";
 ["Your password has been updated successfully"] = "Via pasvorto estas sukcese ĝisdatigita";
+["Your rating has been deleted"] = false;
+["Your rating has been updated"] = false;
 ["Your suggestion has been added"] = "Via sugesto estas aldonita";
 ["Your support has been added to this initiative"] = "Via subteno estas aldonita al la iniciato";
 ["Your support has been removed from this initiative"] = "Via subteno estas forigita de la iniciato";
 ["Z-A"] = "Z-A";
 ["[Registered members only]"] = "[Nur registritaj membroj]";
 ["[not displayed public]"] = "[ne afiŝita publike]";
+["a bit unsatisfied"] = false;
 ["activated"] = false;
 ["and #{count} more initiatives"] = "kaj #{count} pliaj iniciatoj";
 ["deactivated"] = false;
-["delete<br /><br />"] = "forviŝi<br /><br />";
 ["disabled"] = "malaktiva";
 ["email"] = "retpoŝto";
+["implemented"] = false;
 ["last 24 hours"] = "lastaj 24 horoj";
 ["login name"] = "Salutnomo";
+["more unsatisfied"] = false;
 ["must"] = "devas";
 ["must not"] = "ne rajtas";
 ["must/should"] = "devus";
 ["must/should not"] = "ne devus";
 ["neutral"] = "neŭtrala";
+["not implemented"] = false;
 ["requested"] = "petita";
+["satisfied"] = false;
 ["should"] = "devus";
 ["should not"] = "ne devus";
 ["to reset your password please click on the following link:\n\n"] = "por remeti vian pasvorton bonvolu klaki sekvan ligilon:\n\n";

locale/translations.fr.lua

 ["Choose member"] = false;
 ["Click for details"] = false;
 ["Closed"] = false;
-["Collective opinion"] = false;
+["Collective opinion of supporters"] = false;
 ["Commit suggestion"] = false;
 ["Compare"] = false;
 ["Confirm"] = false;
 ["Email address confirmation"] = false;
 ["Email address is confirmed now"] = false;
 ["Email address too short!"] = false;
-["Email confirmation request"] = false;
 ["Email unconfirmed"] = false;
-["Empty help text: #{id}.#{lang}.txt"] = false;
 ["Error while converting image. Please note, that only JPG files are supported!"] = false;
 ["Error while resolving openid. Internal message: '#{errmsg}'"] = false;
 ["Error while updating member, database reported:<br /><br /> (#{errormessage})"] = false;
 ["Help for: #{text}"] = false;
 ["Hide"] = false;
 ["Hide filter details"] = false;
-["Hide this help message"] = false;
 ["Home"] = false;
-["I accept the terms of use by checking the following checkbox:"] = false;
+["I consider suggestion as"] = false;
 ["Id"] = false;
 ["Ident number"] = false;
 ["If this link is not working, please open following url in your web browser:\n\n"] = false;
 ["JavaScript is disabled or not available."] = false;
 ["Last author"] = false;
 ["Last snapshot:"] = false;
-["Legend:"] = false;
 ["License"] = false;
 ["Locked?"] = false;
 ["Login"] = false;
 ["Majority"] = false;
 ["Manage filter"] = false;
 ["Manage timeline filters"] = false;
-["Mark suggestion as implemented and express dissatisfaction"] = false;
-["Mark suggestion as implemented and express satisfaction"] = false;
-["Mark suggestion as not implemented and express dissatisfaction"] = false;
-["Mark suggestion as not implemented and express satisfaction"] = false;
 ["Max potential support"] = false;
 ["Max support"] = false;
 ["Member"] = false;
 ["Membership updated"] = false;
 ["Memberships"] = false;
 ["Message of the day"] = false;
-["Missing help text: #{id}.#{lang}.txt"] = false;
 ["Mobile phone"] = false;
 ["Monday"] = false;
 ["Move down"] = false;
 ["New address"] = false;
 ["New draft"] = false;
 ["New draft has been added to initiative"] = false;
-["New draft revision"] = false;
 ["New initiative"] = false;
 ["New issue"] = false;
 ["New password"] = false;
 ["Number of incoming delegations, follow link to see more details"] = false;
 ["Number of initiatives to preview"] = false;
 ["OK"] = false;
-["Old draft revision"] = false;
 ["Old password"] = false;
 ["Old password is wrong"] = false;
 ["Oldest"] = false;
-["On that page please enter the confirmation code:\n\n"] = false;
 ["On that page please enter the reset code:\n\n"] = false;
 ["One issue"] = false;
 ["One issue you are interested in"] = false;
 ["Please choose a policy"] = false;
 ["Please choose two different versions of the draft to compare"] = false;
 ["Please choose two versions of the draft to compare"] = false;
-["Please confirm your email address by clicking the following link:\n\n"] = false;
 ["Please enter the email reset code you have received:"] = false;
 ["Please enter the invite code you've received."] = false;
 ["Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link."] = false;
 ["Potential support"] = false;
 ["Potential supported"] = false;
 ["Potential supporter"] = false;
+["Preview"] = false;
 ["Previous initiative"] = false;
 ["Previous issue"] = false;
 ["Profession"] = false;
 ["Show member"] = false;
 ["Show name history"] = false;
 ["Show only events which match... (or associtated)"] = false;
+["So I'm"] = false;
 ["Software"] = false;
 ["Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."] = false;
 ["Sorry, but there is not confirmed email address for your account. Please contact the administrator or support."] = false;
 ["Supported initiatives"] = false;
 ["Supporter"] = false;
 ["Tabs"] = false;
-["Terms accepted"] = false;
 ["Terms of use"] = false;
 ["The API key has been changed too fast."] = false;
 ["The code you've entered is invalid"] = false;
 ["Tuesday"] = false;
 ["Type of tabs"] = false;
 ["Unconfirmed address"] = false;
-["Unknown author"] = false;
 ["Updated drafts"] = false;
 ["Upload images"] = false;
 ["Verification time"] = false;
 ["You didn't saved any member as contact yet."] = false;
 ["You have saved this member as contact"] = false;
 ["You have saved this member as contact."] = false;
-["You have to accept the terms of use to complete registration."] = false;
 ["You have to mark 'Are you sure' to revoke!"] = false;
 ["You need to be logged in, to use all features of this system."] = false;
 ["You want to vote later"] = false;
 ["Your global delegation has been updated."] = false;
 ["Your login has been changed to '#{login}'"] = false;
 ["Your name has been changed"] = false;
-["Your opinion has been deleted"] = false;
-["Your opinion has been updated"] = false;
 ["Your page has been updated"] = false;
 ["Your password has been updated successfully"] = false;
+["Your rating has been deleted"] = false;
+["Your rating has been updated"] = false;
 ["Your suggestion has been added"] = false;
 ["Your support has been added to this initiative"] = false;
 ["Your support has been removed from this initiative"] = false;
 ["Z-A"] = false;
 ["[Registered members only]"] = false;
 ["[not displayed public]"] = false;
+["a bit unsatisfied"] = false;
 ["activated"] = false;
 ["and #{count} more initiatives"] = false;
 ["deactivated"] = false;
-["delete<br /><br />"] = false;
 ["disabled"] = false;
 ["email"] = false;
+["implemented"] = false;
 ["last 24 hours"] = false;
 ["login name"] = false;
+["more unsatisfied"] = false;
 ["must"] = false;
 ["must not"] = false;
 ["must/should"] = false;
 ["must/should not"] = false;
 ["neutral"] = false;
+["not implemented"] = false;
 ["requested"] = false;
+["satisfied"] = false;
 ["should"] = false;
 ["should not"] = false;
 ["to reset your password please click on the following link:\n\n"] = false;

static/icons/emoticon_happy.png

+--- emoticon_happy.png
++++ emoticon_happy.png
+GIT binary patch
+literal 1646
+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
+

static/icons/emoticon_unhappy.png

+--- emoticon_unhappy.png
++++ emoticon_unhappy.png
+GIT binary patch
+literal 1735
+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
+

static/icons/emoticon_unhappy_red.png

+--- emoticon_unhappy_red.png
++++ emoticon_unhappy_red.png
+GIT binary patch
+literal 1734
+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
+
   padding: 1ex;
 }
 
-.diff .added {
+.diff_added {
   background-color: #cfc;
+  text-decoration: underline;
 }
 
-.diff .removed {
+.diff_removed {
   background-color: #fcc;
+  text-decoration: line-through;
 }
 
 .slot_issue_info {