liquid_feedback_frontend

changeset 1496:ed3c40911ae1

Completed image attachments feature
author bsw
date Tue Feb 11 15:24:36 2020 +0100 (2020-02-11)
parents 17e7082c377a
children a59dd2ae9cd8
files app/main/draft/_action/add.lua app/main/draft/diff.lua app/main/draft/new.lua app/main/index/_head.lua app/main/initiative/_action/create.lua app/main/initiative/new.lua app/main/initiative/show.lua app/main/issue/_sidebar_issue.lua
line diff
     1.1 --- a/app/main/draft/_action/add.lua	Mon Feb 10 21:10:49 2020 +0100
     1.2 +++ b/app/main/draft/_action/add.lua	Tue Feb 11 15:24:36 2020 +0100
     1.3 @@ -1,24 +1,270 @@
     1.4 -local draft_text = param.get("content")
     1.5 +local initiative
     1.6 +local new_initiative
     1.7 +local draft_id
     1.8 +local status
     1.9 +
    1.10 +if param.get("initiative_id", atom.integer) then
    1.11 +
    1.12 +  local function donew()
    1.13 +    local draft_text = param.get("content")
    1.14 +
    1.15 +    if not draft_text then
    1.16 +      return false
    1.17 +    end
    1.18 +
    1.19 +    local draft_text = util.wysihtml_preproc(draft_text)
    1.20 +
    1.21 +    local valid_html, error_message = util.html_is_safe(draft_text)
    1.22 +    if not valid_html then
    1.23 +      slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
    1.24 +      return false
    1.25 +    end
    1.26 +
    1.27 +    if config.initiative_abstract then
    1.28 +      local abstract = param.get("abstract")
    1.29 +      if not abstract then
    1.30 +        return false
    1.31 +      end
    1.32 +      abstract = encode.html(abstract)
    1.33 +      draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
    1.34 +    end
    1.35 +
    1.36 +    draft_id = Draft:update_content(
    1.37 +      app.session.member.id, 
    1.38 +      param.get("initiative_id", atom.integer),
    1.39 +      param.get("formatting_engine"),
    1.40 +      draft_text,
    1.41 +      nil,
    1.42 +      param.get("preview") or param.get("edit")
    1.43 +    )
    1.44 +    return draft_id and true or false
    1.45 +  end
    1.46 +
    1.47 +  status = donew()
    1.48 +
    1.49 +else
    1.50 +
    1.51 +  local function donew()
    1.52 +    local issue
    1.53 +    local area
    1.54  
    1.55 -if not draft_text then
    1.56 -  return false
    1.57 -end
    1.58 +    local issue_id = param.get("issue_id", atom.integer)
    1.59 +    if issue_id then
    1.60 +      issue = Issue:new_selector():add_where{"id=?",issue_id}:for_share():single_object_mode():exec()
    1.61 +      if issue.closed then
    1.62 +        slot.put_into("error", _"This issue is already closed.")
    1.63 +        return false
    1.64 +      elseif issue.fully_frozen then 
    1.65 +        slot.put_into("error", _"Voting for this issue has already begun.")
    1.66 +        return false
    1.67 +      elseif issue.phase_finished then
    1.68 +        slot.put_into("error", _"Current phase is already closed.")
    1.69 +        return false
    1.70 +      end
    1.71 +      area = issue.area
    1.72 +    else
    1.73 +      local area_id = param.get("area_id", atom.integer)
    1.74 +      area = Area:new_selector():add_where{"id=?",area_id}:single_object_mode():exec()
    1.75 +      if not area.active then
    1.76 +        slot.put_into("error", "Invalid area.")
    1.77 +        return false
    1.78 +      end
    1.79 +    end
    1.80 +
    1.81 +    if not app.session.member:has_voting_right_for_unit_id(area.unit_id) then
    1.82 +      return execute.view { module = "index", view = "403" }
    1.83 +    end
    1.84 +
    1.85 +    local policy_id = param.get("policy_id", atom.integer)
    1.86 +    local policy
    1.87 +    if policy_id then
    1.88 +      policy = Policy:by_id(policy_id)
    1.89 +    end
    1.90  
    1.91 -local draft_text = util.wysihtml_preproc(draft_text)
    1.92 +    if not issue then
    1.93 +      if policy_id == -1 then
    1.94 +        slot.put_into("error", _"Please choose a policy")
    1.95 +        return false
    1.96 +      end
    1.97 +      if not policy.active then
    1.98 +        slot.put_into("error", "Invalid policy.")
    1.99 +        return false
   1.100 +      end
   1.101 +      if policy.polling and not app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   1.102 +        return execute.view { module = "index", view = "403" }
   1.103 +      end
   1.104 +      if not area:get_reference_selector("allowed_policies")
   1.105 +        :add_where{ "policy.id = ?", policy_id }
   1.106 +        :optional_object_mode()
   1.107 +        :exec()
   1.108 +      then
   1.109 +        slot.put_into("error", "policy not allowed")
   1.110 +        return false
   1.111 +      end
   1.112 +    end
   1.113 +
   1.114 +    local is_polling = (issue and param.get("polling", atom.boolean)) or (policy and policy.polling) or false
   1.115 +
   1.116 +    local tmp = db:query({ "SELECT text_entries_left, initiatives_left FROM member_contingent_left WHERE member_id = ? AND polling = ?", app.session.member.id, is_polling }, "opt_object")
   1.117 +    if not tmp or tmp.initiatives_left < 1 then
   1.118 +      slot.put_into("error", _"Sorry, your contingent for creating initiatives has been used up. Please try again later.")
   1.119 +      return false
   1.120 +    end
   1.121 +    if tmp and tmp.text_entries_left < 1 then
   1.122 +      slot.put_into("error", _"Sorry, you have reached your personal flood limit. Please be slower...")
   1.123 +      return false
   1.124 +    end
   1.125 +
   1.126 +    local name = param.get("name")
   1.127 +
   1.128 +    local name = util.trim(name)
   1.129 +
   1.130 +    if #name < 3 then
   1.131 +      slot.put_into("error", _"Please enter a meaningful title for your initiative!")
   1.132 +      return false
   1.133 +    end
   1.134 +
   1.135 +    if #name > 140 then
   1.136 +      slot.put_into("error", _"This title is too long!")
   1.137 +      return false
   1.138 +    end
   1.139  
   1.140 -local valid_html, error_message = util.html_is_safe(draft_text)
   1.141 -if not valid_html then
   1.142 -  slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
   1.143 -  return false
   1.144 -end
   1.145 +    local timing
   1.146 +    if not issue and policy.free_timeable then
   1.147 +      local free_timing_string = util.trim(param.get("free_timing"))
   1.148 +      if not free_timing_string or #free_timing_string < 1 then
   1.149 +        slot.put_into("error", _"Choose timing")
   1.150 +        return false
   1.151 +      end
   1.152 +      local available_timings
   1.153 +      if config.free_timing and config.free_timing.available_func then
   1.154 +        available_timings = config.free_timing.available_func(policy)
   1.155 +        if available_timings == false then
   1.156 +          slot.put_into("error", "error in free timing config")
   1.157 +          return false
   1.158 +        end
   1.159 +      end
   1.160 +      if available_timings then
   1.161 +        local timing_available = false
   1.162 +        for i, available_timing in ipairs(available_timings) do
   1.163 +          if available_timing.id == free_timing_string then
   1.164 +      	    timing_available = true
   1.165 +          end
   1.166 +        end
   1.167 +        if not timing_available then
   1.168 +          slot.put_into("error", _"Invalid timing")
   1.169 +          return false
   1.170 +        end
   1.171 +      end
   1.172 +      timing = config.free_timing.calculate_func(policy, free_timing_string)
   1.173 +      if not timing then
   1.174 +        slot.put_into("error", "error in free timing config")
   1.175 +        return false
   1.176 +      end
   1.177 +    end
   1.178 +
   1.179 +    local draft_text = param.get("content")
   1.180 +
   1.181 +    if not draft_text then
   1.182 +      slot.put_into("error", "no draft text")
   1.183 +      return false
   1.184 +    end
   1.185 +
   1.186 +    local draft_text = util.wysihtml_preproc(draft_text)
   1.187 +
   1.188 +    local valid_html, error_message = util.html_is_safe(draft_text)
   1.189 +    if not valid_html then
   1.190 +      slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
   1.191 +      return false
   1.192 +    end
   1.193 +
   1.194 +    if config.initiative_abstract then
   1.195 +      local abstract = param.get("abstract")
   1.196 +      if not abstract then
   1.197 +        slot.put_into("error", "no abstract")
   1.198 +        return false
   1.199 +      end
   1.200 +      abstract = encode.html(abstract)
   1.201 +      draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
   1.202 +    end
   1.203 +
   1.204 +    local location = param.get("location")
   1.205 +    if location == "" then
   1.206 +      location = nil
   1.207 +    end
   1.208 +
   1.209 +    if param.get("preview") or param.get("edit") then
   1.210 +      return false
   1.211 +    end
   1.212  
   1.213 -if config.initiative_abstract then
   1.214 -  local abstract = param.get("abstract")
   1.215 -  if not abstract then
   1.216 -    return false
   1.217 +    initiative = Initiative:new()
   1.218 +
   1.219 +    if not issue then
   1.220 +      issue = Issue:new()
   1.221 +      issue.area_id = area.id
   1.222 +      issue.policy_id = policy_id
   1.223 +      
   1.224 +      if policy.polling then
   1.225 +        issue.accepted = 'now'
   1.226 +        issue.state = 'discussion'
   1.227 +        initiative.polling = true
   1.228 +        
   1.229 +        if policy.free_timeable then
   1.230 +          issue.discussion_time = timing.discussion
   1.231 +          issue.verification_time = timing.verification
   1.232 +          issue.voting_time = timing.voting
   1.233 +        end
   1.234 +        
   1.235 +      end
   1.236 +      
   1.237 +      issue:save()
   1.238 +
   1.239 +      if config.etherpad then
   1.240 +        local result = net.curl(
   1.241 +          config.etherpad.api_base 
   1.242 +          .. "api/1/createGroupPad?apikey=" .. config.etherpad.api_key
   1.243 +          .. "&groupID=" .. config.etherpad.group_id
   1.244 +          .. "&padName=Issue" .. tostring(issue.id)
   1.245 +          .. "&text=" .. request.get_absolute_baseurl() .. "issue/show/" .. tostring(issue.id) .. ".html"
   1.246 +        )
   1.247 +      end
   1.248 +    end
   1.249 +
   1.250 +    if param.get("polling", atom.boolean) and app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   1.251 +      initiative.polling = true
   1.252 +    end
   1.253 +    initiative.issue_id = issue.id
   1.254 +    initiative.name = name
   1.255 +    initiative:save()
   1.256 +
   1.257 +    new_initiative = initiative
   1.258 +
   1.259 +    local draft = Draft:new()
   1.260 +    draft.initiative_id = initiative.id
   1.261 +    draft.formatting_engine = formatting_engine
   1.262 +    draft.content = draft_text
   1.263 +    draft.location = location
   1.264 +    draft.author_id = app.session.member.id
   1.265 +    draft:save()
   1.266 +
   1.267 +    draft_id = draft.id
   1.268 +
   1.269 +    local initiator = Initiator:new()
   1.270 +    initiator.initiative_id = initiative.id
   1.271 +    initiator.member_id = app.session.member.id
   1.272 +    initiator.accepted = true
   1.273 +    initiator:save()
   1.274 +
   1.275 +    if not is_polling then
   1.276 +      local supporter = Supporter:new()
   1.277 +      supporter.initiative_id = initiative.id
   1.278 +      supporter.member_id = app.session.member.id
   1.279 +      supporter.draft_id = draft.id
   1.280 +      supporter:save()
   1.281 +    end
   1.282 +
   1.283    end
   1.284 -  abstract = encode.html(abstract)
   1.285 -  draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
   1.286 +  status = donew()
   1.287  end
   1.288  
   1.289  if config.attachments then
   1.290 @@ -80,76 +326,78 @@
   1.291    fh:write(json.export(file_uploads))
   1.292    fh:write("\n")
   1.293    fh:close()
   1.294 -end
   1.295 +
   1.296 +  if draft_id then
   1.297 +    local file_upload_session = param.get("file_upload_session")
   1.298 +    file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   1.299  
   1.300 -local draft_id = Draft:update_content(
   1.301 -  app.session.member.id, 
   1.302 -  param.get("initiative_id", atom.integer),
   1.303 -  param.get("formatting_engine"),
   1.304 -  draft_text,
   1.305 -  nil,
   1.306 -  param.get("preview") or param.get("edit")
   1.307 -)
   1.308 +    local draft_attachments = DraftAttachment:new_selector()
   1.309 +      :add_where{ "draft_attachment.draft_id = ?", draft_id }
   1.310 +      :exec()
   1.311 +
   1.312 +    for i, draft_attachment in ipairs(draft_attachments) do
   1.313 +      if param.get("file_delete_" .. draft_attachment.file_id, atom.boolean) then
   1.314 +        draft_attachment:destroy()
   1.315 +      end
   1.316 +    end
   1.317  
   1.318 -if draft_id and config.attachments then
   1.319 -  local file_upload_session = param.get("file_upload_session")
   1.320 -  file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   1.321 +    local file_uploads = json.array()
   1.322 +    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   1.323 +    local fh = io.open(filename, "r")
   1.324 +    if fh then
   1.325 +      file_uploads = json.import(fh:read("*a"))
   1.326 +    end
   1.327 +    for i, file_upload in ipairs(file_uploads) do
   1.328 +      local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".jpg")
   1.329 +      local data
   1.330 +      local fh = io.open(filename, "r")
   1.331 +      if fh then
   1.332 +        data = fh:read("*a")
   1.333 +      end
   1.334 +      local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".preview.jpg")
   1.335 +      local data_preview
   1.336 +      local fh = io.open(filename, "r")
   1.337 +      if fh then
   1.338 +        data_preview = fh:read("*a")
   1.339 +      end
   1.340 +
   1.341 +      local hash = moonhash.sha3_512(data)
   1.342  
   1.343 -  local draft_attachments = DraftAttachment:new_selector()
   1.344 -    :add_where{ "draft_attachment.draft_id = ?", draft_id }
   1.345 -    :exec()
   1.346 +      local file = File:new_selector()
   1.347 +        :add_where{ "hash = ?", hash }
   1.348 +        :add_where{ "content_type = ?", "image/jpeg" }
   1.349 +        :optional_object_mode()
   1.350 +        :exec()
   1.351  
   1.352 -  for i, draft_attachment in ipairs(draft_attachments) do
   1.353 -    if param.get("file_delete_" .. draft_attachment.file_id, atom.boolean) then
   1.354 -      draft_attachment:destroy()
   1.355 +      if not file then
   1.356 +        file = File:new()
   1.357 +        file.content_type = "image/jpeg"
   1.358 +        file.hash = hash
   1.359 +        file.data = data
   1.360 +        file.preview_content_type = "image/jpeg"
   1.361 +        file.preview_data = data_preview
   1.362 +        file:save()
   1.363 +      end
   1.364 +
   1.365 +      local draft_attachment = DraftAttachment:new()
   1.366 +      draft_attachment.draft_id = draft_id
   1.367 +      draft_attachment.file_id = file.id
   1.368 +      draft_attachment.title = file_upload.title
   1.369 +      draft_attachment.description = file_upload.description
   1.370 +      draft_attachment:save()
   1.371      end
   1.372    end
   1.373  
   1.374 -  local file_uploads = json.array()
   1.375 -  local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   1.376 -  local fh = io.open(filename, "r")
   1.377 -  if fh then
   1.378 -    file_uploads = json.import(fh:read("*a"))
   1.379 -  end
   1.380 -  for i, file_upload in ipairs(file_uploads) do
   1.381 -    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".jpg")
   1.382 -    local data
   1.383 -    local fh = io.open(filename, "r")
   1.384 -    if fh then
   1.385 -      data = fh:read("*a")
   1.386 -    end
   1.387 -    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".preview.jpg")
   1.388 -    local data_preview
   1.389 -    local fh = io.open(filename, "r")
   1.390 -    if fh then
   1.391 -      data_preview = fh:read("*a")
   1.392 -    end
   1.393 -
   1.394 -    local hash = moonhash.sha3_512(data)
   1.395 -
   1.396 -    local file = File:new_selector()
   1.397 -      :add_where{ "hash = ?", hash }
   1.398 -      :add_where{ "content_type = ?", "image/jpeg" }
   1.399 -      :optional_object_mode()
   1.400 -      :exec()
   1.401 -
   1.402 -    if not file then
   1.403 -      file = File:new()
   1.404 -      file.content_type = "image/jpeg"
   1.405 -      file.hash = hash
   1.406 -      file.data = data
   1.407 -      file.preview_content_type = "image/jpeg"
   1.408 -      file.preview_data = data_preview
   1.409 -      file:save()
   1.410 -    end
   1.411 -
   1.412 -    local draft_attachment = DraftAttachment:new()
   1.413 -    draft_attachment.draft_id = draft_id
   1.414 -    draft_attachment.file_id = file.id
   1.415 -    draft_attachment.title = file_upload.title
   1.416 -    draft_attachment.description = file_upload.description
   1.417 -    draft_attachment:save()
   1.418 -  end
   1.419  end
   1.420  
   1.421 -return draft_id and true or false
   1.422 +print(new_initiative, status)
   1.423 +if new_initiative and status ~= false then
   1.424 +  request.redirect{
   1.425 +    module = "initiative",
   1.426 +    view = "show",
   1.427 +    id = new_initiative.id
   1.428 +  }
   1.429 +end
   1.430 +
   1.431 +return status
   1.432 +
     2.1 --- a/app/main/draft/diff.lua	Mon Feb 10 21:10:49 2020 +0100
     2.2 +++ b/app/main/draft/diff.lua	Tue Feb 11 15:24:36 2020 +0100
     2.3 @@ -190,6 +190,98 @@
     2.4            end 
     2.5          end
     2.6        }
     2.7 +
     2.8 +      local old_files = File:new_selector()
     2.9 +        :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id")
    2.10 +        :add_where{ "draft_attachment.draft_id = ?", old_draft.id }
    2.11 +        :reset_fields()
    2.12 +        :add_field("file.id")
    2.13 +        :add_field("draft_attachment.title")
    2.14 +        :add_field("draft_attachment.description")
    2.15 +        :add_order_by("draft_attachment.id")
    2.16 +        :exec()
    2.17 +
    2.18 +      local new_files = File:new_selector()
    2.19 +        :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id")
    2.20 +        :add_where{ "draft_attachment.draft_id = ?", new_draft.id }
    2.21 +        :reset_fields()
    2.22 +        :add_field("file.id")
    2.23 +        :add_field("draft_attachment.title")
    2.24 +        :add_field("draft_attachment.description")
    2.25 +        :add_order_by("draft_attachment.id")
    2.26 +        :exec()
    2.27 +
    2.28 +      local added_files = {}
    2.29 +      for i, new_file in ipairs(new_files) do
    2.30 +        local added = true
    2.31 +        for j, old_file in ipairs(old_files) do
    2.32 +          if 
    2.33 +            old_file.file_id == new_file.file_id 
    2.34 +            and old_file.title == new_file.title
    2.35 +            and old_file.description == new_file.description
    2.36 +          then
    2.37 +            added = false
    2.38 +          end
    2.39 +        end
    2.40 +        if added then
    2.41 +          table.insert(added_files, new_file)
    2.42 +        end
    2.43 +      end
    2.44 +
    2.45 +      if #added_files > 0 then
    2.46 +        ui.container {
    2.47 +          attr = { class = "mdl-card__content mdl-card--border" },
    2.48 +          content = function()
    2.49 +            ui.container{ content = _"Added attachments" }
    2.50 +            for i, file in ipairs(added_files) do
    2.51 +              ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
    2.52 +              ui.container{ content = file.title or "" }
    2.53 +              ui.container{ content = file.description or "" }
    2.54 +              slot.put("<br /><br />")
    2.55 +            end
    2.56 +          end
    2.57 +        }
    2.58 +      end
    2.59 +
    2.60 +      local removed_files = {}      
    2.61 +      for i, old_file in ipairs(old_files) do
    2.62 +        local removed = true
    2.63 +        for j, new_file in ipairs(new_files) do
    2.64 +          if 
    2.65 +            old_file.file_id == new_file.file_id 
    2.66 +            and old_file.title == new_file.title
    2.67 +            and old_file.description == new_file.description
    2.68 +          then
    2.69 +            removed = false
    2.70 +          end
    2.71 +        end
    2.72 +        if removed then
    2.73 +          table.insert(removed_files, old_file)
    2.74 +        end
    2.75 +      end
    2.76 +
    2.77 +
    2.78 +      if #removed_files > 0 then
    2.79 +        ui.container {
    2.80 +          attr = { class = "mdl-card__content mdl-card--border" },
    2.81 +          content = function()
    2.82 +            ui.container{ content = _"Removed attachments" }
    2.83 +            for i, file in ipairs(removed_files) do
    2.84 +              ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
    2.85 +              ui.container{ content = file.title or "" }
    2.86 +              ui.container{ content = file.description or "" }
    2.87 +              slot.put("<br /><br />")
    2.88 +            end
    2.89 +          end
    2.90 +        }
    2.91 +      end
    2.92 +
    2.93 +      ui.container {
    2.94 +        attr = { class = "draft mdl-card__content mdl-card--border" },
    2.95 +        content = function ()
    2.96 +        end
    2.97 +      }
    2.98 +
    2.99      end }
   2.100    end }
   2.101    ui.cell_sidebar{ content = function()
     3.1 --- a/app/main/draft/new.lua	Mon Feb 10 21:10:49 2020 +0100
     3.2 +++ b/app/main/draft/new.lua	Tue Feb 11 15:24:36 2020 +0100
     3.3 @@ -1,24 +1,68 @@
     3.4 -local initiative = Initiative:by_id(param.get("initiative_id"))
     3.5 -initiative:load_everything_for_member_id(app.session.member_id)
     3.6 -initiative.issue:load_everything_for_member_id(app.session.member_id)
     3.7 +local issue
     3.8 +local area
     3.9 +local area_id
    3.10  
    3.11 -if initiative.issue.closed then
    3.12 -  slot.put_into("error", _"This issue is already closed.")
    3.13 -  return
    3.14 -elseif initiative.issue.half_frozen then 
    3.15 -  slot.put_into("error", _"This issue is already frozen.")
    3.16 -  return
    3.17 -elseif initiative.issue.phase_finished then
    3.18 -  slot.put_into("error", _"Current phase is already closed.")
    3.19 -  return
    3.20 +local issue_id = param.get("issue_id", atom.integer)
    3.21 +if issue_id then
    3.22 +  issue = Issue:new_selector():add_where{"id=?",issue_id}:single_object_mode():exec()
    3.23 +  issue:load_everything_for_member_id(app.session.member_id)
    3.24 +  area = issue.area
    3.25 +else
    3.26 +  area_id = param.get("area_id", atom.integer)
    3.27 +  if area_id then
    3.28 +    area = Area:new_selector():add_where{"id=?",area_id}:single_object_mode():exec()
    3.29 +    area:load_delegation_info_once_for_member_id(app.session.member_id)
    3.30 +  end
    3.31 +end
    3.32 +
    3.33 +local polling = param.get("polling", atom.boolean)
    3.34 +
    3.35 +local policy_id = param.get("policy_id", atom.integer)
    3.36 +local policy
    3.37 +
    3.38 +local preview = param.get("preview")
    3.39 +
    3.40 +if #(slot.get_content("error")) > 0 then
    3.41 +  preview = false
    3.42 +end
    3.43 +
    3.44 +if policy_id then
    3.45 +  policy = Policy:by_id(policy_id)
    3.46  end
    3.47  
    3.48 -local draft = initiative.current_draft
    3.49 -if config.initiative_abstract then
    3.50 -  draft.abstract = string.match(draft.content, "(.+)<!%--END_OF_ABSTRACT%-->")
    3.51 -  if draft.abstract then
    3.52 -    draft.content = string.match(draft.content, "<!%--END_OF_ABSTRACT%-->(.*)")
    3.53 +
    3.54 +
    3.55 +
    3.56 +local initiative_id = param.get("initiative_id")
    3.57 +local initiative = Initiative:by_id(initiative_id)
    3.58 +local draft
    3.59 +if initiative then
    3.60 +  initiative:load_everything_for_member_id(app.session.member_id)
    3.61 +  initiative.issue:load_everything_for_member_id(app.session.member_id)
    3.62 +
    3.63 +  if initiative.issue.closed then
    3.64 +    slot.put_into("error", _"This issue is already closed.")
    3.65 +    return
    3.66 +  elseif initiative.issue.half_frozen then 
    3.67 +    slot.put_into("error", _"This issue is already frozen.")
    3.68 +    return
    3.69 +  elseif initiative.issue.phase_finished then
    3.70 +    slot.put_into("error", _"Current phase is already closed.")
    3.71 +    return
    3.72    end
    3.73 +
    3.74 +  draft = initiative.current_draft
    3.75 +  if config.initiative_abstract then
    3.76 +    draft.abstract = string.match(draft.content, "(.+)<!%--END_OF_ABSTRACT%-->")
    3.77 +    if draft.abstract then
    3.78 +      draft.content = string.match(draft.content, "<!%--END_OF_ABSTRACT%-->(.*)")
    3.79 +    end
    3.80 +  end
    3.81 +end
    3.82 +
    3.83 +if not initiative and not issue and not area then
    3.84 +  ui.heading{ content = _"Missing parameter" }
    3.85 +  return false
    3.86  end
    3.87  
    3.88  ui.form{
    3.89 @@ -26,13 +70,17 @@
    3.90    attr = { class = "vertical section", enctype = 'multipart/form-data' },
    3.91    module = "draft",
    3.92    action = "add",
    3.93 -  params = { initiative_id = initiative.id },
    3.94 +  params = {
    3.95 +    area_id = area and area.id,
    3.96 +    issue_id = issue and issue.id or nil,
    3.97 +    initiative_id = initiative_id
    3.98 +  },
    3.99    routing = {
   3.100      ok = {
   3.101        mode = "redirect",
   3.102        module = "initiative",
   3.103        view = "show",
   3.104 -      id = initiative.id
   3.105 +      id = initiative_id
   3.106      }
   3.107    },
   3.108    content = function()
   3.109 @@ -41,11 +89,30 @@
   3.110        ui.cell_main{ content = function()
   3.111          ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function()
   3.112            ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
   3.113 -            ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = initiative.display_name }
   3.114 +            if initiative then
   3.115 +              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = initiative.display_name }
   3.116 +            elseif param.get("name") then
   3.117 +              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = param.get("name") }
   3.118 +            elseif issue then
   3.119 +              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _("New competing initiative in issue '#{issue}'", { issue = issue.name }) }
   3.120 +            elseif area then
   3.121 +              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _("New issue in area '#{area}'", { area = area.name }) }
   3.122 +            end
   3.123            end }
   3.124            ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function()
   3.125 +
   3.126 +-- -------- PREVIEW
   3.127              if param.get("preview") then
   3.128                ui.sectionRow( function()
   3.129 +                if not issue and not initiative then
   3.130 +                  ui.container { content = policy.name }
   3.131 +                end
   3.132 +                if param.get("free_timing") then
   3.133 +                  ui.container { content = param.get("free_timing") }
   3.134 +                end
   3.135 +                slot.put("<br />")
   3.136 +                ui.field.hidden{ name = "policy_id", value = param.get("policy_id") }
   3.137 +                ui.field.hidden{ name = "name", value = param.get("name") }
   3.138                  if config.initiative_abstract then
   3.139                    ui.field.hidden{ name = "abstract", value = param.get("abstract") }
   3.140                    ui.container{
   3.141 @@ -72,31 +139,35 @@
   3.142                    )
   3.143                    file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   3.144                    ui.field.hidden{ name = "file_upload_session", value = file_upload_session }
   3.145 -                   local files = File:new_selector()
   3.146 -                    :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id")
   3.147 -                    :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id }
   3.148 -                    :reset_fields()
   3.149 -                    :add_field("file.id")
   3.150 -                    :add_field("draft_attachment.title")
   3.151 -                    :add_field("draft_attachment.description")
   3.152 -                    :add_order_by("draft_attachment.id")
   3.153 -                    :exec()
   3.154 +                  if initiative then
   3.155 +                     local files = File:new_selector()
   3.156 +                      :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id")
   3.157 +                      :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id }
   3.158 +                      :reset_fields()
   3.159 +                      :add_field("file.id")
   3.160 +                      :add_field("draft_attachment.title")
   3.161 +                      :add_field("draft_attachment.description")
   3.162 +                      :add_order_by("draft_attachment.id")
   3.163 +                      :exec()
   3.164  
   3.165 -                  if #files > 0 then
   3.166 -                    ui.container {
   3.167 -                      content = function()
   3.168 -                        for i, file in ipairs(files) do
   3.169 -                          if param.get("file_delete_" .. file.id, atom.boolean) then
   3.170 -                            ui.field.hidden{ name = "file_delete_" .. file.id, value = "1" }
   3.171 -                          else
   3.172 -                            ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
   3.173 -                            ui.container{ content = file.title or "" }
   3.174 -                            ui.container{ content = file.description or "" }
   3.175 -                            slot.put("<br /><br />")
   3.176 +                    if #files > 0 then
   3.177 +                      ui.container {
   3.178 +                        content = function()
   3.179 +                          for i, file in ipairs(files) do
   3.180 +                            if param.get("file_delete_" .. file.id, atom.boolean) then
   3.181 +                              ui.field.hidden{ name = "file_delete_" .. file.id, value = "1" }
   3.182 +                            else
   3.183 +                              ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
   3.184 +                              ui.container{ content = function()
   3.185 +                                ui.tag{ tag = "strong", content = file.title or "" }
   3.186 +                              end }
   3.187 +                              ui.container{ content = file.description or "" }
   3.188 +                              slot.put("<br /><br />")
   3.189 +                            end
   3.190                            end
   3.191                          end
   3.192 -                      end
   3.193 -                    }
   3.194 +                      }
   3.195 +                    end
   3.196                    end
   3.197                    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   3.198                    local fh = io.open(filename, "r")
   3.199 @@ -106,7 +177,9 @@
   3.200                        ui.image{ module = "draft", view = "show_file_upload", params = {
   3.201                          file_upload_session = file_upload_session, file_id = file_upload.id, preview = true
   3.202                        } }
   3.203 -                      ui.container{ content = file_upload.title or "" }
   3.204 +                      ui.container{ content = function()
   3.205 +                        ui.tag{ tag = "strong", content = file_upload.title or "" }
   3.206 +                      end }
   3.207                        ui.container{ content = file_upload.description or "" }
   3.208                        slot.put("<br />")
   3.209                      end
   3.210 @@ -139,47 +212,108 @@
   3.211                  ui.link{
   3.212                    attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" },
   3.213                    content = _"Cancel",
   3.214 -                  module = "initiative",
   3.215 +                  module = initiative and "initiative" or "area",
   3.216                    view = "show",
   3.217 -                  id = initiative.id
   3.218 +                  id = initiative_id or area_id
   3.219                  }
   3.220                end )
   3.221  
   3.222 +-- -------- EDIT
   3.223              else
   3.224 -              ui.sectionRow( function()
   3.225 -                if config.initiative_abstract then
   3.226 -                  ui.container { content = _"Enter abstract:" }
   3.227 -                  ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function()
   3.228 +
   3.229 +              if not issue_id and not initiative_id then
   3.230 +                local tmp = { { id = -1, name = "" } }
   3.231 +                for i, allowed_policy in ipairs(area.allowed_policies) do
   3.232 +                  if not allowed_policy.polling or app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   3.233 +                    tmp[#tmp+1] = allowed_policy
   3.234 +                  end
   3.235 +                end
   3.236 +                ui.container{ content = _"Please choose a policy for the new issue:" }
   3.237 +                ui.field.select{
   3.238 +                  name = "policy_id",
   3.239 +                  foreign_records = tmp,
   3.240 +                  foreign_id = "id",
   3.241 +                  foreign_name = "name",
   3.242 +                  value = param.get("policy_id", atom.integer) or area.default_policy and area.default_policy.id
   3.243 +                }
   3.244 +                if policy and policy.free_timeable then
   3.245 +                  local available_timings
   3.246 +                  if config.free_timing and config.free_timing.available_func then
   3.247 +                    available_timings = config.free_timing.available_func(policy)
   3.248 +                    if available_timings == false then
   3.249 +                      slot.put_into("error", "error in free timing config")
   3.250 +                      return false
   3.251 +                    end
   3.252 +                  end
   3.253 +                  ui.heading{ level = 4, content = _"Free timing:" }
   3.254 +                  if available_timings then
   3.255 +                    ui.field.select{
   3.256 +                      name = "free_timing",
   3.257 +                      foreign_records = available_timings,
   3.258 +                      foreign_id = "id",
   3.259 +                      foreign_name = "name",
   3.260 +                      value = param.get("free_timing")
   3.261 +                    }
   3.262 +                  else
   3.263                      ui.field.text{
   3.264 -                      name = "abstract",
   3.265 -                      multiline = true, 
   3.266 -                      attr = { id = "abstract", style = "height: 20ex; width: 100%;" },
   3.267 -                      value = param.get("abstract")
   3.268 +                      name = "free_timing",
   3.269 +                      value = param.get("free_timing")
   3.270                      }
   3.271 -                  end }
   3.272 +                  end
   3.273                  end
   3.274 -                
   3.275 -                ui.container { content = _"Enter your proposal and/or reasons:" }
   3.276 -                ui.field.wysihtml{
   3.277 -                  name = "content",
   3.278 -                  multiline = true, 
   3.279 -                  attr = { id = "draft", style = "height: 50ex; width: 100%;" },
   3.280 -                  value = param.get("content")
   3.281 -                }
   3.282 -                if not issue or issue.state == "admission" or issue.state == "discussion" then
   3.283 -                  ui.container { content = _"You can change your text again anytime during admission and discussion phase" }
   3.284 -                else
   3.285 -                  ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" }
   3.286 -                end
   3.287 +              end
   3.288 +
   3.289 +              if issue and issue.policy.polling and app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   3.290 +                slot.put("<br />")
   3.291 +                ui.field.boolean{ name = "polling", label = _"No admission needed", value = polling }
   3.292 +              end
   3.293 +
   3.294 +              if not initiative then
   3.295 +                ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label mdl-card__fullwidth" }, content = function ()
   3.296 +                  ui.field.text{
   3.297 +                    attr = { id = "lf-initiative__name", class = "mdl-textfield__input" },
   3.298 +                    label_attr = { class = "mdl-textfield__label", ["for"] = "lf-initiative__name" },
   3.299 +                    label = _"Title",
   3.300 +                    name  = "name",
   3.301 +                    value = param.get("name")
   3.302 +                  }
   3.303 +                end }
   3.304 +              end
   3.305  
   3.306 -                slot.put("<br />")
   3.307 -                if config.attachments then
   3.308 -                  local file_upload_session = param.get("file_upload_session") or multirand.string(
   3.309 -                    32,
   3.310 -                    '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
   3.311 -                  )
   3.312 -                  file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   3.313 -                  ui.field.hidden{ name = "file_upload_session", value = file_upload_session }
   3.314 +              if config.initiative_abstract then
   3.315 +                ui.container { content = _"Enter abstract:" }
   3.316 +                ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function()
   3.317 +                  ui.field.text{
   3.318 +                    name = "abstract",
   3.319 +                    multiline = true, 
   3.320 +                    attr = { id = "abstract", style = "height: 20ex; width: 100%;" },
   3.321 +                    value = param.get("abstract")
   3.322 +                  }
   3.323 +                end }
   3.324 +              end
   3.325 +              
   3.326 +              ui.container { content = _"Enter your proposal and/or reasons:" }
   3.327 +              ui.field.wysihtml{
   3.328 +                name = "content",
   3.329 +                multiline = true, 
   3.330 +                attr = { id = "draft", style = "height: 50ex; width: 100%;" },
   3.331 +                value = param.get("content")
   3.332 +              }
   3.333 +              if not issue or issue.state == "admission" or issue.state == "discussion" then
   3.334 +                ui.container { content = _"You can change your text again anytime during admission and discussion phase" }
   3.335 +              else
   3.336 +                ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" }
   3.337 +              end
   3.338 +
   3.339 +              slot.put("<br />")
   3.340 +              if config.attachments then
   3.341 +                local file_upload_session = param.get("file_upload_session") or multirand.string(
   3.342 +                  32,
   3.343 +                  '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
   3.344 +                )
   3.345 +                file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   3.346 +                ui.field.hidden{ name = "file_upload_session", value = file_upload_session }
   3.347 +                if initiative then
   3.348                    local files = File:new_selector()
   3.349                      :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id")
   3.350                      :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id }
   3.351 @@ -195,7 +329,9 @@
   3.352                        content = function()
   3.353                          for i, file in ipairs(files) do
   3.354                            ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
   3.355 -                          ui.container{ content = file.title or "" }
   3.356 +                          ui.container{ content = function()
   3.357 +                            ui.tag{ tag = "strong", content = file.title or "" }
   3.358 +                          end }
   3.359                            ui.container{ content = file.description or "" }
   3.360                            ui.field.boolean{ label = _"delete", name = "file_delete_" .. file.id, value = param.get("file_delete_" .. file.id) and true or false }
   3.361                            slot.put("<br /><br />")
   3.362 @@ -203,61 +339,63 @@
   3.363                        end
   3.364                      }
   3.365                    end
   3.366 -
   3.367 -                  local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   3.368 -                  local fh = io.open(filename, "r")
   3.369 -                  if fh then
   3.370 -                    local file_uploads = json.import(fh:read("*a"))
   3.371 -                    for i, file_upload in ipairs(file_uploads) do
   3.372 -                      ui.image{ module = "draft", view = "show_file_upload", params = {
   3.373 -                        file_upload_session = file_upload_session, file_id = file_upload.id, preview = true
   3.374 -                      } }
   3.375 -                      ui.container{ content = file_upload.title or "" }
   3.376 -                      ui.container{ content = file_upload.description or "" }
   3.377 -                      ui.field.boolean{ label = _"delete", name = "file_upload_delete_" .. file_upload.id }
   3.378 -                      slot.put("<br />")
   3.379 -                    end
   3.380 +                end
   3.381 +                local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   3.382 +                local fh = io.open(filename, "r")
   3.383 +                if fh then
   3.384 +                  local file_uploads = json.import(fh:read("*a"))
   3.385 +                  for i, file_upload in ipairs(file_uploads) do
   3.386 +                    ui.image{ module = "draft", view = "show_file_upload", params = {
   3.387 +                      file_upload_session = file_upload_session, file_id = file_upload.id, preview = true
   3.388 +                    } }
   3.389 +                    ui.container{ content = function()
   3.390 +                      ui.tag{ tag = "strong", content = file_upload.title or "" }
   3.391 +                    end }
   3.392 +                    ui.container{ content = file_upload.description or "" }
   3.393 +                    ui.field.boolean{ label = _"delete", name = "file_upload_delete_" .. file_upload.id }
   3.394 +                    slot.put("<br />")
   3.395                    end
   3.396 -                  ui.container{ attr = { id = "file_upload_template", style = "display: none;" }, content = function()
   3.397 -                    ui.field.text{ label = _"Title", name = "__ID_title__" }
   3.398 -                    ui.field.text{ label = _"Description", name = "__ID_description__" }
   3.399 -                    ui.field.image{ field_name = "__ID_file__" }
   3.400 -                  end }
   3.401 -                  ui.container{ attr = { id = "file_upload" }, content = function()
   3.402 -                  end }
   3.403 -                  ui.field.hidden{ attr = { id = "file_upload_last_id" }, name = "file_upload_last_id" }
   3.404 -                  ui.script{ script = [[ var file_upload_id = 1; ]] }
   3.405 -                  ui.tag{ tag = "a", content = _"Attach image", attr = { 
   3.406 -                    href = "#",
   3.407 -                    onclick = "var html = document.getElementById('file_upload_template').innerHTML; html = html.replace('__ID_file__', 'file_' + file_upload_id); html = html.replace('__ID_title__', 'title_' + file_upload_id); html = html.replace('__ID_description__', 'description_' + file_upload_id); var el = document.createElement('div'); el.innerHTML = html; document.getElementById('file_upload').appendChild(el); document.getElementById('file_upload_last_id').value = file_upload_id; file_upload_id++; return false;"
   3.408 -                  } }
   3.409 -                  slot.put("<br />")
   3.410 -                  
   3.411 -                  slot.put("<br />")
   3.412 +                end
   3.413 +                ui.container{ attr = { id = "file_upload_template", style = "display: none;" }, content = function()
   3.414 +                  ui.field.text{ label = _"Title", name = "__ID_title__" }
   3.415 +                  ui.field.text{ label = _"Description", name = "__ID_description__" }
   3.416 +                  ui.field.image{ field_name = "__ID_file__" }
   3.417 +                end }
   3.418 +                ui.container{ attr = { id = "file_upload" }, content = function()
   3.419 +                end }
   3.420 +                ui.field.hidden{ attr = { id = "file_upload_last_id" }, name = "file_upload_last_id" }
   3.421 +                ui.script{ script = [[ var file_upload_id = 1; ]] }
   3.422 +                ui.tag{ tag = "a", content = _"Attach image", attr = { 
   3.423 +                  href = "#",
   3.424 +                  onclick = "var html = document.getElementById('file_upload_template').innerHTML; html = html.replace('__ID_file__', 'file_' + file_upload_id); html = html.replace('__ID_title__', 'title_' + file_upload_id); html = html.replace('__ID_description__', 'description_' + file_upload_id); var el = document.createElement('div'); el.innerHTML = html; document.getElementById('file_upload').appendChild(el); document.getElementById('file_upload_last_id').value = file_upload_id; file_upload_id++; return false;"
   3.425 +                } }
   3.426 +                slot.put("<br />")
   3.427 +                
   3.428 +                slot.put("<br />")
   3.429  
   3.430 -                end
   3.431 +              end
   3.432  
   3.433 -                ui.tag{
   3.434 -                  tag = "input",
   3.435 -                  attr = {
   3.436 -                    type = "submit",
   3.437 -                    name = "preview",
   3.438 -                    class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored",
   3.439 -                    value = _'Preview'
   3.440 -                  },
   3.441 -                  content = ""
   3.442 -                }
   3.443 -                slot.put(" &nbsp; ")
   3.444 -                
   3.445 -                ui.link{
   3.446 -                  content = _"Cancel",
   3.447 -                  module = "initiative",
   3.448 -                  view = "show",
   3.449 -                  id = initiative.id,
   3.450 -                  attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }
   3.451 -                }
   3.452 -                
   3.453 -              end )
   3.454 +              ui.tag{
   3.455 +                tag = "input",
   3.456 +                attr = {
   3.457 +                  type = "submit",
   3.458 +                  name = "preview",
   3.459 +                  class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored",
   3.460 +                  value = _'Preview'
   3.461 +                },
   3.462 +                content = ""
   3.463 +              }
   3.464 +              slot.put(" &nbsp; ")
   3.465 +              
   3.466 +              ui.link{
   3.467 +                content = _"Cancel",
   3.468 +                module = initiative and "initiative" or issue and "issue" or "index",
   3.469 +                view = area and "index" or "show",
   3.470 +                id = initiative_id or issue_id,
   3.471 +                params = { area = area_id, unit = area and area.unit_id or nil },
   3.472 +                attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }
   3.473 +              }
   3.474 +              
   3.475              end
   3.476            end }
   3.477          end }
     4.1 --- a/app/main/index/_head.lua	Mon Feb 10 21:10:49 2020 +0100
     4.2 +++ b/app/main/index/_head.lua	Tue Feb 11 15:24:36 2020 +0100
     4.3 @@ -116,7 +116,7 @@
     4.4          if not config.voting_only and app.session.member_id and app.session.member:has_initiative_right_for_unit_id ( area.unit_id ) then
     4.5            ui.link {
     4.6              attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" },
     4.7 -            module = "initiative", view = "new",
     4.8 +            module = "draft", view = "new",
     4.9              params = { area_id = area.id },
    4.10              content = function()
    4.11                ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "add" }
     5.1 --- a/app/main/initiative/_action/create.lua	Mon Feb 10 21:10:49 2020 +0100
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,219 +0,0 @@
     5.4 -local issue
     5.5 -local area
     5.6 -
     5.7 -local issue_id = param.get("issue_id", atom.integer)
     5.8 -if issue_id then
     5.9 -  issue = Issue:new_selector():add_where{"id=?",issue_id}:for_share():single_object_mode():exec()
    5.10 -  if issue.closed then
    5.11 -    slot.put_into("error", _"This issue is already closed.")
    5.12 -    return false
    5.13 -  elseif issue.fully_frozen then 
    5.14 -    slot.put_into("error", _"Voting for this issue has already begun.")
    5.15 -    return false
    5.16 -  elseif issue.phase_finished then
    5.17 -    slot.put_into("error", _"Current phase is already closed.")
    5.18 -    return false
    5.19 -  end
    5.20 -  area = issue.area
    5.21 -else
    5.22 -  local area_id = param.get("area_id", atom.integer)
    5.23 -  area = Area:new_selector():add_where{"id=?",area_id}:single_object_mode():exec()
    5.24 -  if not area.active then
    5.25 -    slot.put_into("error", "Invalid area.")
    5.26 -    return false
    5.27 -  end
    5.28 -end
    5.29 -
    5.30 -if not app.session.member:has_voting_right_for_unit_id(area.unit_id) then
    5.31 -  return execute.view { module = "index", view = "403" }
    5.32 -end
    5.33 -
    5.34 -local policy_id = param.get("policy_id", atom.integer)
    5.35 -local policy
    5.36 -if policy_id then
    5.37 -  policy = Policy:by_id(policy_id)
    5.38 -end
    5.39 -
    5.40 -if not issue then
    5.41 -  if policy_id == -1 then
    5.42 -    slot.put_into("error", _"Please choose a policy")
    5.43 -    return false
    5.44 -  end
    5.45 -  if not policy.active then
    5.46 -    slot.put_into("error", "Invalid policy.")
    5.47 -    return false
    5.48 -  end
    5.49 -  if policy.polling and not app.session.member:has_polling_right_for_unit_id(area.unit_id) then
    5.50 -    return execute.view { module = "index", view = "403" }
    5.51 -  end
    5.52 -  if not area:get_reference_selector("allowed_policies")
    5.53 -    :add_where{ "policy.id = ?", policy_id }
    5.54 -    :optional_object_mode()
    5.55 -    :exec()
    5.56 -  then
    5.57 -    slot.put_into("error", "policy not allowed")
    5.58 -    return false
    5.59 -  end
    5.60 -end
    5.61 -
    5.62 -local is_polling = (issue and param.get("polling", atom.boolean)) or (policy and policy.polling) or false
    5.63 -
    5.64 -local tmp = db:query({ "SELECT text_entries_left, initiatives_left FROM member_contingent_left WHERE member_id = ? AND polling = ?", app.session.member.id, is_polling }, "opt_object")
    5.65 -if not tmp or tmp.initiatives_left < 1 then
    5.66 -  slot.put_into("error", _"Sorry, your contingent for creating initiatives has been used up. Please try again later.")
    5.67 -  return false
    5.68 -end
    5.69 -if tmp and tmp.text_entries_left < 1 then
    5.70 -  slot.put_into("error", _"Sorry, you have reached your personal flood limit. Please be slower...")
    5.71 -  return false
    5.72 -end
    5.73 -
    5.74 -local name = param.get("name")
    5.75 -
    5.76 -local name = util.trim(name)
    5.77 -
    5.78 -if #name < 3 then
    5.79 -  slot.put_into("error", _"Please enter a meaningful title for your initiative!")
    5.80 -  return false
    5.81 -end
    5.82 -
    5.83 -if #name > 140 then
    5.84 -  slot.put_into("error", _"This title is too long!")
    5.85 -  return false
    5.86 -end
    5.87 -
    5.88 -local timing
    5.89 -if not issue and policy.free_timeable then
    5.90 -  local free_timing_string = util.trim(param.get("free_timing"))
    5.91 -  if not free_timing_string or #free_timing_string < 1 then
    5.92 -    slot.put_into("error", _"Choose timing")
    5.93 -    return false
    5.94 -  end
    5.95 -  local available_timings
    5.96 -  if config.free_timing and config.free_timing.available_func then
    5.97 -    available_timings = config.free_timing.available_func(policy)
    5.98 -    if available_timings == false then
    5.99 -      slot.put_into("error", "error in free timing config")
   5.100 -      return false
   5.101 -    end
   5.102 -  end
   5.103 -  if available_timings then
   5.104 -    local timing_available = false
   5.105 -    for i, available_timing in ipairs(available_timings) do
   5.106 -      if available_timing.id == free_timing_string then
   5.107 -	timing_available = true
   5.108 -      end
   5.109 -    end
   5.110 -    if not timing_available then
   5.111 -      slot.put_into("error", _"Invalid timing")
   5.112 -      return false
   5.113 -    end
   5.114 -  end
   5.115 -  timing = config.free_timing.calculate_func(policy, free_timing_string)
   5.116 -  if not timing then
   5.117 -    slot.put_into("error", "error in free timing config")
   5.118 -    return false
   5.119 -  end
   5.120 -end
   5.121 -
   5.122 -local draft_text = param.get("draft")
   5.123 -
   5.124 -if not draft_text then
   5.125 -  return false
   5.126 -end
   5.127 -
   5.128 -local draft_text = util.wysihtml_preproc(draft_text)
   5.129 -
   5.130 -local valid_html, error_message = util.html_is_safe(draft_text)
   5.131 -if not valid_html then
   5.132 -  slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
   5.133 -  return false
   5.134 -end
   5.135 -
   5.136 -if config.initiative_abstract then
   5.137 -  local abstract = param.get("abstract")
   5.138 -  if not abstract then
   5.139 -    return false
   5.140 -  end
   5.141 -  abstract = encode.html(abstract)
   5.142 -  draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
   5.143 -end
   5.144 -
   5.145 -local location = param.get("location")
   5.146 -if location == "" then
   5.147 -  location = nil
   5.148 -end
   5.149 -
   5.150 -if param.get("preview") or param.get("edit") then
   5.151 -  return
   5.152 -end
   5.153 -
   5.154 -local initiative = Initiative:new()
   5.155 -
   5.156 -if not issue then
   5.157 -  issue = Issue:new()
   5.158 -  issue.area_id = area.id
   5.159 -  issue.policy_id = policy_id
   5.160 -  
   5.161 -  if policy.polling then
   5.162 -    issue.accepted = 'now'
   5.163 -    issue.state = 'discussion'
   5.164 -    initiative.polling = true
   5.165 -    
   5.166 -    if policy.free_timeable then
   5.167 -      issue.discussion_time = timing.discussion
   5.168 -      issue.verification_time = timing.verification
   5.169 -      issue.voting_time = timing.voting
   5.170 -    end
   5.171 -    
   5.172 -  end
   5.173 -  
   5.174 -  issue:save()
   5.175 -
   5.176 -  if config.etherpad then
   5.177 -    local result = net.curl(
   5.178 -      config.etherpad.api_base 
   5.179 -      .. "api/1/createGroupPad?apikey=" .. config.etherpad.api_key
   5.180 -      .. "&groupID=" .. config.etherpad.group_id
   5.181 -      .. "&padName=Issue" .. tostring(issue.id)
   5.182 -      .. "&text=" .. request.get_absolute_baseurl() .. "issue/show/" .. tostring(issue.id) .. ".html"
   5.183 -    )
   5.184 -  end
   5.185 -end
   5.186 -
   5.187 -if param.get("polling", atom.boolean) and app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   5.188 -  initiative.polling = true
   5.189 -end
   5.190 -initiative.issue_id = issue.id
   5.191 -initiative.name = name
   5.192 -initiative:save()
   5.193 -
   5.194 -local draft = Draft:new()
   5.195 -draft.initiative_id = initiative.id
   5.196 -draft.formatting_engine = formatting_engine
   5.197 -draft.content = draft_text
   5.198 -draft.location = location
   5.199 -draft.author_id = app.session.member.id
   5.200 -draft:save()
   5.201 -
   5.202 -local initiator = Initiator:new()
   5.203 -initiator.initiative_id = initiative.id
   5.204 -initiator.member_id = app.session.member.id
   5.205 -initiator.accepted = true
   5.206 -initiator:save()
   5.207 -
   5.208 -if not is_polling then
   5.209 -  local supporter = Supporter:new()
   5.210 -  supporter.initiative_id = initiative.id
   5.211 -  supporter.member_id = app.session.member.id
   5.212 -  supporter.draft_id = draft.id
   5.213 -  supporter:save()
   5.214 -end
   5.215 -
   5.216 -slot.put_into("notice", _"Initiative successfully created")
   5.217 -
   5.218 -request.redirect{
   5.219 -  module = "initiative",
   5.220 -  view = "show",
   5.221 -  id = initiative.id
   5.222 -}
     6.1 --- a/app/main/initiative/new.lua	Mon Feb 10 21:10:49 2020 +0100
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,279 +0,0 @@
     6.4 -local issue
     6.5 -local area
     6.6 -
     6.7 -local issue_id = param.get("issue_id", atom.integer)
     6.8 -if issue_id then
     6.9 -  issue = Issue:new_selector():add_where{"id=?",issue_id}:single_object_mode():exec()
    6.10 -  issue:load_everything_for_member_id(app.session.member_id)
    6.11 -  area = issue.area
    6.12 -
    6.13 -else
    6.14 -  local area_id = param.get("area_id", atom.integer)
    6.15 -  area = Area:new_selector():add_where{"id=?",area_id}:single_object_mode():exec()
    6.16 -  area:load_delegation_info_once_for_member_id(app.session.member_id)
    6.17 -end
    6.18 -
    6.19 -local polling = param.get("polling", atom.boolean)
    6.20 -
    6.21 -local policy_id = param.get("policy_id", atom.integer)
    6.22 -local policy
    6.23 -
    6.24 -local preview = param.get("preview")
    6.25 -
    6.26 -if #(slot.get_content("error")) > 0 then
    6.27 -  preview = false
    6.28 -end
    6.29 -
    6.30 -if policy_id then
    6.31 -  policy = Policy:by_id(policy_id)
    6.32 -end
    6.33 -
    6.34 -if issue_id then
    6.35 -  execute.view {
    6.36 -    module = "issue", view = "_head", 
    6.37 -    params = { issue = issue, member = app.session.member }
    6.38 -  }
    6.39 -else
    6.40 -  --[[
    6.41 -  execute.view {
    6.42 -    module = "area", view = "_head", 
    6.43 -    params = { area = area, member = app.session.member }
    6.44 -  }
    6.45 -  --]]
    6.46 -  --[[
    6.47 -  execute.view { 
    6.48 -    module = "initiative", view = "_sidebar_policies", 
    6.49 -    params = {
    6.50 -      area = area,
    6.51 -    }
    6.52 -  }
    6.53 -  --]]
    6.54 -end
    6.55 -
    6.56 -ui.form{
    6.57 -  module = "initiative",
    6.58 -  action = "create",
    6.59 -  params = {
    6.60 -    area_id = area.id,
    6.61 -    issue_id = issue and issue.id or nil
    6.62 -  },
    6.63 -  attr = { class = "vertical" },
    6.64 -  content = function()
    6.65 -    ui.grid{ content = function()
    6.66 -      ui.cell_main{ content = function()
    6.67 -        ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function()
    6.68 -          ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
    6.69 -            if preview then
    6.70 -              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Preview" }
    6.71 -            elseif issue_id then
    6.72 -              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"New competing initiative" }
    6.73 -            else
    6.74 -              ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Create a new issue" }
    6.75 -            end
    6.76 -          end }
    6.77 -            
    6.78 -          ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function()
    6.79 -
    6.80 -          
    6.81 -            if preview then
    6.82 -              
    6.83 -              ui.section( function()
    6.84 -                ui.sectionHead( function()
    6.85 -                  ui.heading{ level = 1, content = encode.html(param.get("name")) }
    6.86 -                  if not issue then
    6.87 -                    ui.container { content = policy.name }
    6.88 -                  end
    6.89 -                  if param.get("free_timing") then
    6.90 -                    ui.container { content = param.get("free_timing") }
    6.91 -                  end
    6.92 -                  slot.put("<br />")
    6.93 -                  
    6.94 -                  local draft_text = param.get("draft")
    6.95 -                  local draft_text = util.wysihtml_preproc(draft_text)
    6.96 -
    6.97 -                  ui.field.hidden{ name = "policy_id", value = param.get("policy_id") }
    6.98 -                  ui.field.hidden{ name = "name", value = param.get("name") }
    6.99 -                  if config.initiative_abstract then
   6.100 -                    ui.field.hidden{ name = "abstract", value = param.get("abstract") }
   6.101 -                    ui.container{
   6.102 -                      attr = { class = "abstract" },
   6.103 -                      content = param.get("abstract")
   6.104 -                    }
   6.105 -                    slot.put("<br />")
   6.106 -                  end
   6.107 -                  ui.field.hidden{ name = "draft", value = draft_text }
   6.108 -                  ui.field.hidden{ name = "free_timing", value = param.get("free_timing") }
   6.109 -                  ui.field.hidden{ name = "polling", value = param.get("polling", atom.boolean) }
   6.110 -                  ui.field.hidden{ name = "location", value = param.get("location") }
   6.111 -                  local formatting_engine
   6.112 -                  if config.enforce_formatting_engine then
   6.113 -                    formatting_engine = config.enforce_formatting_engine
   6.114 -                  else
   6.115 -                    formatting_engine = param.get("formatting_engine")
   6.116 -                  end
   6.117 -                  ui.container{
   6.118 -                    attr = { class = "draft" },
   6.119 -                    content = function()
   6.120 -                      slot.put(draft_text)
   6.121 -                    end
   6.122 -                  }
   6.123 -                  slot.put("<br />")
   6.124 -
   6.125 -                  ui.tag{
   6.126 -                    tag = "input",
   6.127 -                    attr = {
   6.128 -                      type = "submit",
   6.129 -                      class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored",
   6.130 -                      value = _'Publish now'
   6.131 -                    },
   6.132 -                    content = ""
   6.133 -                  }
   6.134 -                  slot.put(" &nbsp; ")
   6.135 -                  ui.tag{
   6.136 -                    tag = "input",
   6.137 -                    attr = {
   6.138 -                      type = "submit",
   6.139 -                      name = "edit",
   6.140 -                      class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect",
   6.141 -                      value = _'Edit again'
   6.142 -                    },
   6.143 -                    content = ""
   6.144 -                  }
   6.145 -                  slot.put(" &nbsp; ")
   6.146 -                  local class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect"
   6.147 -                  if issue then
   6.148 -                    ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id, attr = { class = class } }
   6.149 -                  else
   6.150 -                    ui.link{ content = _"Cancel", module = "index", view = "index", params = { unit = area.unit_id, area = area.id }, attr = { class = class } }
   6.151 -                  end
   6.152 -                end )
   6.153 -              end )
   6.154 -            else
   6.155 -              
   6.156 -              ui.sectionRow( function()
   6.157 -              --[[
   6.158 -                if not preview and not issue_id then
   6.159 -                  ui.container { attr = { class = "section" }, content = _"Before creating a new issue, please check any existant issues before, if the topic is already in discussion." }
   6.160 -                  slot.put("<br />")
   6.161 -                end
   6.162 -              --]]
   6.163 -                if not issue_id then
   6.164 -                  local tmp = { { id = -1, name = "" } }
   6.165 -                  for i, allowed_policy in ipairs(area.allowed_policies) do
   6.166 -                    if not allowed_policy.polling or app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   6.167 -                      tmp[#tmp+1] = allowed_policy
   6.168 -                    end
   6.169 -                  end
   6.170 -                  ui.container{ content = _"Please choose a policy for the new issue:" }
   6.171 -                  ui.field.select{
   6.172 -                    name = "policy_id",
   6.173 -                    foreign_records = tmp,
   6.174 -                    foreign_id = "id",
   6.175 -                    foreign_name = "name",
   6.176 -                    value = param.get("policy_id", atom.integer) or area.default_policy and area.default_policy.id
   6.177 -                  }
   6.178 -                  if policy and policy.free_timeable then
   6.179 -                    local available_timings
   6.180 -                    if config.free_timing and config.free_timing.available_func then
   6.181 -                      available_timings = config.free_timing.available_func(policy)
   6.182 -                      if available_timings == false then
   6.183 -                        slot.put_into("error", "error in free timing config")
   6.184 -                        return false
   6.185 -                      end
   6.186 -                    end
   6.187 -                    ui.heading{ level = 4, content = _"Free timing:" }
   6.188 -                    if available_timings then
   6.189 -                      ui.field.select{
   6.190 -                        name = "free_timing",
   6.191 -                        foreign_records = available_timings,
   6.192 -                        foreign_id = "id",
   6.193 -                        foreign_name = "name",
   6.194 -                        value = param.get("free_timing")
   6.195 -                      }
   6.196 -                    else
   6.197 -                      ui.field.text{
   6.198 -                        name = "free_timing",
   6.199 -                        value = param.get("free_timing")
   6.200 -                      }
   6.201 -                    end
   6.202 -                  end
   6.203 -                end
   6.204 -
   6.205 -                if issue and issue.policy.polling and app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   6.206 -                  slot.put("<br />")
   6.207 -                  ui.field.boolean{ name = "polling", label = _"No admission needed", value = polling }
   6.208 -                end
   6.209 -                
   6.210 -                ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label mdl-card__fullwidth" }, content = function ()
   6.211 -                  ui.field.text{
   6.212 -                    attr = { id = "lf-initiative__name", class = "mdl-textfield__input" },
   6.213 -                    label_attr = { class = "mdl-textfield__label", ["for"] = "lf-initiative__name" },
   6.214 -                    label = _"Title",
   6.215 -                    name  = "name",
   6.216 -                    value = param.get("name")
   6.217 -                  }
   6.218 -                end }
   6.219 -                
   6.220 -                if config.initiative_abstract then
   6.221 -                  ui.container { content = _"Enter abstract:" }
   6.222 -                  ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function()
   6.223 -                    ui.field.text{
   6.224 -                      name = "abstract",
   6.225 -                      multiline = true, 
   6.226 -                      attr = { id = "abstract", style = "height: 20ex; width: 100%;" },
   6.227 -                      value = param.get("abstract")
   6.228 -                    }
   6.229 -                  end }
   6.230 -                end
   6.231 -                
   6.232 -                ui.container { content = _"Enter your proposal and/or reasons:" }
   6.233 -                ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function()
   6.234 -                  ui.field.wysihtml{
   6.235 -                    name = "draft",
   6.236 -                    multiline = true, 
   6.237 -                    attr = { id = "draft", style = "height: 50ex; width: 100%;" },
   6.238 -                    value = param.get("draft") or config.draft_template
   6.239 -                  }
   6.240 -                end }
   6.241 -                if not issue or issue.state == "admission" or issue.state == "discussion" then
   6.242 -                  ui.container { content = _"You can change your text again anytime during admission and discussion phase" }
   6.243 -                else
   6.244 -                  ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" }
   6.245 -                end
   6.246 -                slot.put("<br />")
   6.247 -                                
   6.248 -                slot.put("<br />")
   6.249 -                ui.tag{
   6.250 -                  tag = "input",
   6.251 -                  attr = {
   6.252 -                    type = "submit",
   6.253 -                    name = "preview",
   6.254 -                    class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored",
   6.255 -                    value = _'Preview'
   6.256 -                  },
   6.257 -                  content = ""
   6.258 -                }
   6.259 -                slot.put(" &nbsp; ")
   6.260 -                
   6.261 -                local class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect"
   6.262 -                if issue then
   6.263 -                  
   6.264 -                  ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id, attr = { class = class } }
   6.265 -                else
   6.266 -                  ui.link{ content = _"Cancel", module = "index", view = "index", params = { unit = area.unit_id, area = area.id }, attr = { class = class } }
   6.267 -                end
   6.268 -              end )
   6.269 -            end
   6.270 -          end }
   6.271 -        end }
   6.272 -      end }
   6.273 -      if config.map or config.firstlife then
   6.274 -        ui.cell_sidebar{ content = function()
   6.275 -          ui.container{ attr = { class = "mdl-special-card map mdl-shadow--2dp" }, content = function()
   6.276 -            ui.field.location{ name = "location", value = param.get("location") }
   6.277 -          end }
   6.278 -        end }
   6.279 -      end
   6.280 -    end }
   6.281 -  end
   6.282 -}
     7.1 --- a/app/main/initiative/show.lua	Mon Feb 10 21:10:49 2020 +0100
     7.2 +++ b/app/main/initiative/show.lua	Tue Feb 11 15:24:36 2020 +0100
     7.3 @@ -155,7 +155,9 @@
     7.4                  ui.link{ module = "file", view = "show.jpg", id = file.id, content = function()
     7.5                    ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } }
     7.6                  end }
     7.7 -                ui.container{ content = file.title or "" }
     7.8 +                ui.container{ content = function()
     7.9 +                  ui.tag{ tag = "strong", content = file.title or "" }
    7.10 +                end }
    7.11                  ui.container{ content = file.description or "" }
    7.12                  slot.put("<br /><br />")
    7.13                end
     8.1 --- a/app/main/issue/_sidebar_issue.lua	Mon Feb 10 21:10:49 2020 +0100
     8.2 +++ b/app/main/issue/_sidebar_issue.lua	Tue Feb 11 15:24:36 2020 +0100
     8.3 @@ -36,7 +36,7 @@
     8.4    ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function()
     8.5      ui.link {
     8.6        attr = { class = "mdl-button mdl-js-button" },
     8.7 -      module = "initiative", view = "new", 
     8.8 +      module = "draft", view = "new", 
     8.9        params = { issue_id = issue.id },
    8.10        content = _"start a new competing initiative"
    8.11      }

Impressum / About Us