# HG changeset patch # User bsw # Date 1581365449 -3600 # Node ID 17e7082c377a6db8798087b037eec588c1cd42c0 # Parent 3e9b0f1adec3ea3e542535a1be88e841e4ee55b2 Added image attachments for initiatives diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/_filter/21_auth.lua --- a/app/main/_filter/21_auth.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/app/main/_filter/21_auth.lua Mon Feb 10 21:10:49 2020 +0100 @@ -71,6 +71,7 @@ or module == "suggestion" and view == "show" or module == "draft" and view == "diff" or module == "draft" and view == "show" + or module == "file" and view == "show.jpg" or module == "index" and view == "search" or module == "index" and view == "usage_terms" or module == "help" and view == "introduction" diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/draft/_action/add.lua --- a/app/main/draft/_action/add.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/app/main/draft/_action/add.lua Mon Feb 10 21:10:49 2020 +0100 @@ -21,7 +21,68 @@ draft_text = abstract .. "" .. draft_text end -return Draft:update_content( +if config.attachments then + local file_upload_session = param.get("file_upload_session") + file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "") + local file_uploads = json.array() + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json") + local fh = io.open(filename, "r") + if fh then + file_uploads = json.import(fh:read("*a")) + end + for i, file_upload in ipairs(file_uploads) do + if param.get("file_upload_delete_" .. file_upload.id, atom.boolean) then + for j = i, #file_uploads - 1 do + file_uploads[j] = file_uploads[j+1] + end + file_uploads[#file_uploads] = nil + end + end + local convert_func = config.attachments.convert_func + local last_id = param.get("file_upload_last_id", atom.number) + if last_id and last_id > 0 then + if last_id > 1024 then + last_id = 1024 + end + for i = 1, last_id do + local file = param.get("file_" .. i) + if file and #file > 0 then + local id = multirand.string( + 32, + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + ) + local data, err, status = convert_func(file) + if status ~= 0 or data == nil then + slot.put_into("error", _"Error while converting image. Please note, that only JPG files are supported!") + return false + end + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. id .. ".jpg") + local fh = assert(io.open(filename, "w")) + fh:write(file) + fh:write("\n") + fh:close() + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. id .. ".preview.jpg") + local fh = assert(io.open(filename, "w")) + fh:write(data) + fh:write("\n") + fh:close() + table.insert(file_uploads, json.object{ + id = id, + filename = filename, + title = param.get("title_" .. i), + description = param.get("description_" .. i) + }) + end + end + end + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json") + local fh = assert(io.open(filename, "w")) + fh:write(json.export(file_uploads)) + fh:write("\n") + fh:close() +end + +local draft_id = Draft:update_content( app.session.member.id, param.get("initiative_id", atom.integer), param.get("formatting_engine"), @@ -29,3 +90,66 @@ nil, param.get("preview") or param.get("edit") ) + +if draft_id and config.attachments then + local file_upload_session = param.get("file_upload_session") + file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "") + + local draft_attachments = DraftAttachment:new_selector() + :add_where{ "draft_attachment.draft_id = ?", draft_id } + :exec() + + for i, draft_attachment in ipairs(draft_attachments) do + if param.get("file_delete_" .. draft_attachment.file_id, atom.boolean) then + draft_attachment:destroy() + end + end + + local file_uploads = json.array() + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json") + local fh = io.open(filename, "r") + if fh then + file_uploads = json.import(fh:read("*a")) + end + for i, file_upload in ipairs(file_uploads) do + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".jpg") + local data + local fh = io.open(filename, "r") + if fh then + data = fh:read("*a") + end + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".preview.jpg") + local data_preview + local fh = io.open(filename, "r") + if fh then + data_preview = fh:read("*a") + end + + local hash = moonhash.sha3_512(data) + + local file = File:new_selector() + :add_where{ "hash = ?", hash } + :add_where{ "content_type = ?", "image/jpeg" } + :optional_object_mode() + :exec() + + if not file then + file = File:new() + file.content_type = "image/jpeg" + file.hash = hash + file.data = data + file.preview_content_type = "image/jpeg" + file.preview_data = data_preview + file:save() + end + + local draft_attachment = DraftAttachment:new() + draft_attachment.draft_id = draft_id + draft_attachment.file_id = file.id + draft_attachment.title = file_upload.title + draft_attachment.description = file_upload.description + draft_attachment:save() + end +end + +return draft_id and true or false diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/draft/new.lua --- a/app/main/draft/new.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/app/main/draft/new.lua Mon Feb 10 21:10:49 2020 +0100 @@ -23,7 +23,7 @@ ui.form{ record = draft, - attr = { class = "vertical section" }, + attr = { class = "vertical section", enctype = 'multipart/form-data' }, module = "draft", action = "add", params = { initiative_id = initiative.id }, @@ -65,6 +65,54 @@ } slot.put("
") + if config.attachments then + local file_upload_session = param.get("file_upload_session") or multirand.string( + 32, + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + ) + file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "") + ui.field.hidden{ name = "file_upload_session", value = file_upload_session } + local files = File:new_selector() + :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id") + :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id } + :reset_fields() + :add_field("file.id") + :add_field("draft_attachment.title") + :add_field("draft_attachment.description") + :add_order_by("draft_attachment.id") + :exec() + + if #files > 0 then + ui.container { + content = function() + for i, file in ipairs(files) do + if param.get("file_delete_" .. file.id, atom.boolean) then + ui.field.hidden{ name = "file_delete_" .. file.id, value = "1" } + else + ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } } + ui.container{ content = file.title or "" } + ui.container{ content = file.description or "" } + slot.put("

") + end + end + end + } + end + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json") + local fh = io.open(filename, "r") + if fh then + local file_uploads = json.import(fh:read("*a")) + for i, file_upload in ipairs(file_uploads) do + ui.image{ module = "draft", view = "show_file_upload", params = { + file_upload_session = file_upload_session, file_id = file_upload.id, preview = true + } } + ui.container{ content = file_upload.title or "" } + ui.container{ content = file_upload.description or "" } + slot.put("
") + end + end + end + ui.tag{ tag = "input", attr = { @@ -123,7 +171,71 @@ else ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" } end + slot.put("
") + if config.attachments then + local file_upload_session = param.get("file_upload_session") or multirand.string( + 32, + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + ) + file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "") + ui.field.hidden{ name = "file_upload_session", value = file_upload_session } + local files = File:new_selector() + :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id") + :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id } + :reset_fields() + :add_field("file.id") + :add_field("draft_attachment.title") + :add_field("draft_attachment.description") + :add_order_by("draft_attachment.id") + :exec() + + if #files > 0 then + ui.container { + content = function() + for i, file in ipairs(files) do + ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } } + ui.container{ content = file.title or "" } + ui.container{ content = file.description or "" } + ui.field.boolean{ label = _"delete", name = "file_delete_" .. file.id, value = param.get("file_delete_" .. file.id) and true or false } + slot.put("

") + end + end + } + end + + local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json") + local fh = io.open(filename, "r") + if fh then + local file_uploads = json.import(fh:read("*a")) + for i, file_upload in ipairs(file_uploads) do + ui.image{ module = "draft", view = "show_file_upload", params = { + file_upload_session = file_upload_session, file_id = file_upload.id, preview = true + } } + ui.container{ content = file_upload.title or "" } + ui.container{ content = file_upload.description or "" } + ui.field.boolean{ label = _"delete", name = "file_upload_delete_" .. file_upload.id } + slot.put("
") + end + end + ui.container{ attr = { id = "file_upload_template", style = "display: none;" }, content = function() + ui.field.text{ label = _"Title", name = "__ID_title__" } + ui.field.text{ label = _"Description", name = "__ID_description__" } + ui.field.image{ field_name = "__ID_file__" } + end } + ui.container{ attr = { id = "file_upload" }, content = function() + end } + ui.field.hidden{ attr = { id = "file_upload_last_id" }, name = "file_upload_last_id" } + ui.script{ script = [[ var file_upload_id = 1; ]] } + ui.tag{ tag = "a", content = _"Attach image", attr = { + href = "#", + 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;" + } } + slot.put("
") + + slot.put("
") + + end ui.tag{ tag = "input", diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/draft/show.lua --- a/app/main/draft/show.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/app/main/draft/show.lua Mon Feb 10 21:10:49 2020 +0100 @@ -6,61 +6,122 @@ return end +local member = app.session.member + +if member then + draft.initiative:load_everything_for_member_id(member.id) + draft.initiative.issue:load_everything_for_member_id(member.id) +end local source = param.get("source", atom.boolean) -execute.view{ - module = "issue", - view = "_head", - params = { issue = draft.initiative.issue } -} +execute.view{ module = "issue", view = "_head", params = { issue = draft.initiative.issue, link_issue = true } } + +ui.grid{ content = function() -ui.section( function() - - ui.sectionHead( function() - ui.link{ - module = "initiative", view = "show", id = draft.initiative.id, - content = function () + ui.cell_main{ content = function() + + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() + + ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border" }, content = function () + ui.heading { - level = 1, - content = draft.initiative.display_name + attr = { class = "mdl-card__title-text" }, + level = 2, + content = function() + ui.link{ + module = "initiative", view = "show", id = draft.initiative.id, + content = draft.initiative.display_name + } + ui.container{ content = _("Draft revision #{id}", { id = draft.id } ) } + end } - end - } - ui.container { attr = { class = "right" }, content = function() - if source then - ui.link{ - content = _"Rendered", + end } + + ui.container{ attr = { class = "draft mdl-card__title mdl-card--border" }, content = function() + if config.render_external_reference and config.render_external_reference.draft then + config.render_external_reference.draft(draft, function (callback) + ui.sectionRow(callback) + end) + end + + execute.view{ module = "draft", - view = "show", - id = param.get_id(), - params = { source = false } + view = "_show", + params = { draft = draft, source = source } } - else - ui.link{ - content = _"Source", - module = "draft", - view = "show", - id = param.get_id(), - params = { source = true } - } + + + + end } + + if config.attachments then + + local files = File:new_selector() + :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id") + :add_where{ "draft_attachment.draft_id = ?", draft.id } + :reset_fields() + :add_field("file.id") + :add_field("draft_attachment.title") + :add_field("draft_attachment.description") + :add_order_by("draft_attachment.id") + :exec() + + if #files > 0 then + ui.container { + attr = { class = "mdl-card__content mdl-card--border" }, + content = function() + for i, file in ipairs(files) do + ui.link{ module = "file", view = "show.jpg", id = file.id, content = function() + ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } } + end } + ui.container{ content = file.title or "" } + ui.container{ content = file.description or "" } + slot.put("

") + end + end + } + end end + + ui.container{ attr = { class = "mdl-card__actions" }, content = function() + if source then + ui.link{ + attr = { class = "mdl-button mdl-js-button" }, + content = _"Rendered", + module = "draft", + view = "show", + id = param.get_id(), + params = { source = false } + } + else + ui.link{ + attr = { class = "mdl-button mdl-js-button" }, + content = _"Source", + module = "draft", + view = "show", + id = param.get_id(), + params = { source = true } + } + end + + end } end } - ui.heading { level = 2, content = _("Draft revision #{id}", { id = draft.id } ) } - end) - - if config.render_external_reference and config.render_external_reference.draft then - config.render_external_reference.draft(draft, function (callback) - ui.sectionRow(callback) - end) - end - - ui.sectionRow( function() - - execute.view{ - module = "draft", - view = "_show", - params = { draft = draft, source = source } + + end } + + ui.cell_sidebar{ content = function() + if config.logo then + config.logo() + end + execute.view { + module = "issue", view = "_sidebar", + params = { + issue = draft.initiative.issue, + initiative = draft.initiative, + member = app.session.member + } } - end) -end) + end } + +end } diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/draft/show_file_upload.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/main/draft/show_file_upload.lua Mon Feb 10 21:10:49 2020 +0100 @@ -0,0 +1,22 @@ +local file_upload_session = param.get("file_upload_session") +file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "") + +local file_id = param.get("file_id") +file_id = string.gsub(file_id, "[^A-Za-z0-9]", "") + +local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_id .. ".jpg") + +if param.get("preview", atom.boolean) then + filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_id .. ".preview.jpg") +end + +local data + +local fh = io.open(filename, "r") +if fh then + data = fh:read("*a") +end + + +slot.set_layout(nil, content_type) +slot.put_into("data", data) diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/file/show.jpg.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/main/file/show.jpg.lua Mon Feb 10 21:10:49 2020 +0100 @@ -0,0 +1,12 @@ +local id = param.get_id() + +local file = File:by_id(id) + +local output = file.data + +if param.get("preview", atom.boolean) then + output = file.preview_data +end + +slot.set_layout(nil, file.content_type) +slot.put_into("data", output) diff -r 3e9b0f1adec3 -r 17e7082c377a app/main/initiative/show.lua --- a/app/main/initiative/show.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/app/main/initiative/show.lua Mon Feb 10 21:10:49 2020 +0100 @@ -135,6 +135,35 @@ end } + if config.attachments then + + local files = File:new_selector() + :left_join("draft_attachment", nil, "draft_attachment.file_id = file.id") + :add_where{ "draft_attachment.draft_id = ?", initiative.current_draft.id } + :reset_fields() + :add_field("file.id") + :add_field("draft_attachment.title") + :add_field("draft_attachment.description") + :add_order_by("draft_attachment.id") + :exec() + + if #files > 0 then + ui.container { + attr = { class = "mdl-card__content mdl-card--border" }, + content = function() + for i, file in ipairs(files) do + ui.link{ module = "file", view = "show.jpg", id = file.id, content = function() + ui.image{ module = "file", view = "show.jpg", id = file.id, params = { preview = true } } + end } + ui.container{ content = file.title or "" } + ui.container{ content = file.description or "" } + slot.put("

") + end + end + } + end + end + local drafts_count = initiative:get_reference_selector("drafts"):count() if not config.voting_only then diff -r 3e9b0f1adec3 -r 17e7082c377a model/draft.lua --- a/model/draft.lua Mon Dec 09 15:54:57 2019 +0100 +++ b/model/draft.lua Mon Feb 10 21:10:49 2020 +0100 @@ -97,5 +97,19 @@ draft:render_content() + local draft_attachments = DraftAttachment:new_selector() + :add_where{ "draft_id = ?", old_draft.id } + :exec() + + for i, draft_attachment in ipairs(draft_attachments) do + local new_draft_attachment = DraftAttachment:new() + new_draft_attachment.draft_id = draft.id + new_draft_attachment.file_id = draft_attachment.file_id + new_draft_attachment.title = draft_attachment.title + new_draft_attachment.description = draft_attachment.description + new_draft_attachment:save() + end + slot.put_into("notice", _"The initiative text has been updated") + return draft.id end diff -r 3e9b0f1adec3 -r 17e7082c377a model/draft_attachment.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/model/draft_attachment.lua Mon Feb 10 21:10:49 2020 +0100 @@ -0,0 +1,5 @@ +DraftAttachment = mondelefant.new_class() +DraftAttachment.table = "draft_attachment" +DraftAttachment.primary_key = "id" + + diff -r 3e9b0f1adec3 -r 17e7082c377a model/file.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/model/file.lua Mon Feb 10 21:10:49 2020 +0100 @@ -0,0 +1,8 @@ +File = mondelefant.new_class() +File.table = "file" +File.primary_key = "id" + +File.binary_columns = { + data = true, + preview_data = true +}