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(" ") 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(" ") 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(" ") 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(" ") 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(" ") 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 }