From 9e88f68d8394a73aa72af2010b342137b689fef2 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Mon, 16 Sep 2024 15:31:51 +0200 Subject: [PATCH 01/26] Add macro for button Implements #312 --- root/common_templates/macros.tt | 49 +++++++++++++++++++++++++++++++++ root/static/css/print.css | 3 +- root/static/css/style.css | 14 +++++++++- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/root/common_templates/macros.tt b/root/common_templates/macros.tt index a33132ff4..5fc655a4a 100644 --- a/root/common_templates/macros.tt +++ b/root/common_templates/macros.tt @@ -103,4 +103,53 @@ length=items.size ~%] [% END %] +[%~ END; + +# Arguments for button and tooltip_button +# opts: +# - variant: optional, str - one of the bootstrap variants, default: primary +# - disabled: bool, optional - triggering the "disabled" HTML attribute, default: false +# - href: str, optional - if set the button is rendered as Tag with the given href instead of - + [% tooltip_button( { + variant => 'outline-primary', + icon => 'add', + disabled => !create_url, + tooltip => { + content => 'Create ' _ (purchase_lists.size > 0 ? 'another' : 'a') _ ' purchase list', + line_through => 1, + }, + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#addList', + }, + container_classes => 'd-grid', + button_classes => 'btn-no-quad', + } ) %] [% END %] @@ -41,50 +53,62 @@ BLOCK new_list %] [% purchase_list.ingredients_count %]
- - - + [% tooltip_button( { + variant => 'outline-dark', + disabled => !purchase_list.update_url, + icon => 'edit', + tooltip => { content => 'Rename purchase list', line_through => 1 }, + attrs => { + 'data-url' => purchase_list.update_url, + 'data-name' => purchase_list.name, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#updateList', + }, + button_classes => 'btn-sm update-trigger', + } ); - [% IF purchase_lists.size > 1; - IF purchase_list.make_default_url %] + IF purchase_lists.size > 1 %]
- + [% tooltip_button( { + variant => 'outline-dark', + disabled => !purchase_list.make_default_url, + icon => 'shopping_cart_checkout', + tooltip => { + content => purchase_list.is_default ? 'Is already the default purchase list' : 'Make purchase list the default purchase list', + line_through => purchase_list.is_default ? 0 : 1, + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
- [% ELSE %] - - - - [% END; - END; + [% END; - IF purchase_list.delete_url; - IF is_in_use %] - - - - [% ELSE %] -
- -
- [% END; + button_classes = [ 'btn-sm' ]; + button_classes.push('delete-trigger') IF is_in_use; - ELSE %] - - - - [% END %] + tooltip_button( { + button_classes => button_classes, + variant => 'outline-danger', + disabled => !purchase_list.delete_url || (purchase_lists.size > 1 && purchase_list.is_default), + icon => 'delete_forever', + tooltip => { + content => purchase_lists.size > 1 && purchase_list.is_default ? 'Can’t delete default purchase list' : 'Delete purchase list', + line_through => purchase_lists.size > 1 && purchase_list.is_default ? 0 : 1, + }, + attrs => is_in_use ? { + 'data-url' => purchase_list.delete_url, + 'data-name' => purchase_list.name, + 'data-count' => purchase_list.items_count, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#delete-list', + } : { + form => 'delete-list-form-' _ purchase_list.id, + type => 'submit', + }, + } ) %] +
-- GitLab From 5cba14d658ea5848d2d33e5c016ec3e5081380de Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Mon, 16 Sep 2024 20:52:24 +0200 Subject: [PATCH 05/26] Respect project state und capabilities in purchase list editor --- lib/Coocook/Controller/PurchaseList.pm | 8 + root/static/css/style.css | 5 - root/static/js/purchase_list/edit.js | 343 +++++++++++--------- root/static/js/script.js | 30 ++ root/templates/purchase_list/_edit_table.tt | 39 ++- root/templates/purchase_list/edit.tt | 56 +++- 6 files changed, 298 insertions(+), 183 deletions(-) diff --git a/lib/Coocook/Controller/PurchaseList.pm b/lib/Coocook/Controller/PurchaseList.pm index c9e669ee2..2235f76a5 100644 --- a/lib/Coocook/Controller/PurchaseList.pm +++ b/lib/Coocook/Controller/PurchaseList.pm @@ -91,6 +91,14 @@ sub edit : GET HEAD Chained('base') PathPart('') Args(0) RequiresCapability('vie $dish->{url} = $c->project_uri( '/dish/edit', $dish->{id} ); } + $c->stash( can_edit => !!$c->has_capability('edit_project') ); + $c->json_stash( + list_permissions => { + can_edit => !!$c->has_capability('edit_project'), + is_archived => !!$c->project->archived, + }, + ); + $c->has_capability('edit_project') or return; diff --git a/root/static/css/style.css b/root/static/css/style.css index b5b0b7150..48de23d01 100644 --- a/root/static/css/style.css +++ b/root/static/css/style.css @@ -390,11 +390,6 @@ cc-autocomplete { border-width: 0; } -.tt-disabled-btn { - pointer-events: auto !important; - cursor: default; -} - .purchase-list-table .selected { --bs-table-accent-bg: var(--bs-table-striped-bg); color: var(--bs-table-striped-color); diff --git a/root/static/js/purchase_list/edit.js b/root/static/js/purchase_list/edit.js index 749124fdc..7bbdaa9c9 100644 --- a/root/static/js/purchase_list/edit.js +++ b/root/static/js/purchase_list/edit.js @@ -1,14 +1,20 @@ // Purchase list moving constants -const moveBtn = document.getElementById("move-btn"); -const moveBtnDisabled = document.getElementById("move-btn-disabled"); +const moveBtn = getBtn( + "#move-btn", + "Move selected items/ingredients to different purchase list", + "No items/ingredients selected to move" +); const moveItemsForm = document.getElementById("move-items-form"); const moveModal = document.getElementById("move-items"); const selectPl = document.getElementById("move-items-target"); const currentPurchaseListId = parseInt(location.pathname.split("/").pop()); // Shop section moving constants -const assignBtn = document.getElementById("assign-btn"); -const assignBtnDisabled = document.getElementById("assign-btn-disabled"); +const assignBtn = getBtn( + "#assign-btn", + "Assign selected articles to a shop section", + "No articles selected to assign" +); const assignArticlesForm = document.getElementById("assign-articles-form"); const assignModal = document.getElementById("assign-articles"); const selectShopSection = document.getElementById("assign-articles-target"); @@ -21,13 +27,183 @@ const articleSelectList = document.getElementById("article-select-list"); const messages = document.getElementById("messages"); const purchaseLists = getJsonData("purchase_lists"); -const singleList = purchaseLists.length === 1; +const singleList = (purchaseLists || []).length === 1; let shopSections = getJsonData("shop_sections"); let articles = {}; let preselectedPurchaseList = null; +const permissions = getJsonData("list_permissions"); let SKIP = false; +(() => { + if (permissions.is_archived || !permissions.can_edit) { + moveBtn.disable(`Can't move items because ${getDisabledReason()}`); + assignBtn.disable( + `Can't assign articles because ${getDisabledReason()}` + ); + return; + } + + init(); + + // Purchase lists + buildPurchaseListOptions(); + + moveModal.addEventListener("shown.bs.modal", () => selectPl.focus()); + moveModal.addEventListener("hidden.bs.modal", () => { + if (preselectedPurchaseList !== null) { + selectPl.value = preselectedPurchaseList; + selectPl.setCustomValidity(""); + } else { + selectPl.value = ""; + selectPl.setCustomValidity("Please select a target purchase list"); + } + }); + + moveItemsForm.addEventListener("submit", async (e) => { + e.preventDefault(); + const items = document.querySelectorAll( + `input[id^="move-item"]:checked` + ); + const ingredients = document.querySelectorAll( + `input[id^="move-ingredient"]:checked` + ); + const data = `target_purchase_list=${selectPl.value}${Array.from(items) + .map((elem) => `&item=${elem.id.split("-").pop()}`) + .join("")}${Array.from(ingredients) + .map((elem) => `&ingredient=${elem.id.split("-").pop()}`) + .join("")}`; + const url = + location.pathname + + (location.pathname.endsWith("/") ? "" : "/") + + "move_items_ingredients"; + const res = await fetch(url, { + method: "post", + headers: { + Accept: "text/html", + "Content-Type": "application/x-www-form-urlencoded", + }, + body: data, + }); + + if (!res.ok) { + showMessage( + "An error occured during moving the selected items/ingredients", + "danger" + ); + } else { + document.getElementById("list-container").innerHTML = + await res.text(); + showMessage( + "Successfully moved the selected items/ingredients", + "success" + ); + init(); + } + + bootstrap.Modal.getInstance(moveModal).hide(); + }); + + // Shop sections + initAutocomplete(); + + assignModal.addEventListener("shown.bs.modal", () => + selectShopSection.focus() + ); + assignModal.addEventListener("show.bs.modal", () => { + const items = document.querySelectorAll( + `input[id^="move-item"]:checked` + ); + const selectedArticles = mapArticleAmount(items); + const error = []; + + for (const [id, data] of Object.entries(selectedArticles)) { + if (articles[id].num !== data.num) { + error.push({ + name: data.name, + selected: data.num, + total: articles[id].num, + }); + } + } + + if (error.length === 0) { + articleSelectWarning.classList.add("d-none"); + articleSelectWarning.classList.remove("d-flex"); + } else { + articleSelectNum.innerText = `article${ + error.length === 1 ? "" : "s" + }`; + articleSelectList.innerHTML = error + .map( + (elem) => + `
  • ${elem.name} - ${elem.selected} of ${elem.total} items selected
  • ` + ) + .join(""); + articleSelectWarning.classList.remove("d-none"); + articleSelectWarning.classList.add("d-flex"); + } + }); + assignModal.addEventListener("hidden.bs.modal", () => { + selectShopSection.clear(); + }); + + assignArticlesForm.addEventListener("submit", async (e) => { + e.preventDefault(); + + const section = String( + new FormData(e.target).get("target-shop-section") + ).trim(); + const numSection = parseInt(section); + + const shopSection = `${ + !(section === "null" || Number.isInteger(numSection)) ? "new_" : "" + }shop_section=${section}`; + + const items = document.querySelectorAll( + `input[id^="move-item"]:checked` + ); + + const data = `${shopSection}${Array.from(items) + .map((elem) => `&article=${elem.dataset.articleId}`) + .join("")}`; + const url = + location.pathname + + (location.pathname.endsWith("/") ? "" : "/") + + "assign_articles_to_shop_section"; + const res = await fetch(url, { + method: "post", + headers: { + Accept: "text/html", + "Content-Type": "application/x-www-form-urlencoded", + }, + body: data, + }); + + if (!res.ok) { + showMessage( + "An error occured during moving the selected articles", + "danger" + ); + } else { + document.getElementById("list-container").innerHTML = + await res.text(); + showMessage("Successfully moved the selected articles", "success"); + init(); + selectShopSection.options = getShopSectionOptions(); + selectShopSection.clear(); + } + + bootstrap.Modal.getInstance(assignModal).hide(); + }); +})(); + +function getDisabledReason() { + return permissions.is_archived + ? "the project is archived" + : "you don't have enough permissions for this action"; +} + function showMessage(msg, type) { const msgElem = document.createElement("div"); msgElem.className = `alert alert-${type}`; @@ -111,12 +287,6 @@ function mapArticleAmount(items) { } function init() { - if (shopSections === undefined || purchaseLists === undefined) { - if (!singleList) moveBtnDisabled.classList.add("d-none"); - assignBtnDisabled.classList.add("d-none"); - return; - } - const items = document.querySelectorAll(`input[id^="move-item"]`); const ingredients = document.querySelectorAll( `input[id^="move-ingredient"]` @@ -186,26 +356,20 @@ function checkDisabled() { } if (items > 0 && !singleIngredientSelected) { - assignBtn?.classList.remove("d-none"); - assignBtnDisabled?.classList.add("d-none"); + assignBtn?.enable(); } else { - assignBtn?.classList.add("d-none"); - assignBtnDisabled?.classList.remove("d-none"); + assignBtn?.disable(); } if (!singleList) { if (items > 0 || ingredients.length > 0) { - moveBtn?.classList.remove("d-none"); - moveBtnDisabled?.classList.add("d-none"); + moveBtn?.enable(); } else { - moveBtn?.classList.add("d-none"); - moveBtnDisabled?.classList.remove("d-none"); + moveBtn?.disable(); } } } -init(); - // Purchase lists function buildPurchaseListOptions() { for (const pl of purchaseLists) { @@ -244,60 +408,6 @@ function buildPurchaseListOptions() { }); } -buildPurchaseListOptions(); - -moveModal.addEventListener("shown.bs.modal", () => selectPl.focus()); -moveModal.addEventListener("hidden.bs.modal", () => { - if (preselectedPurchaseList !== null) { - selectPl.value = preselectedPurchaseList; - selectPl.setCustomValidity(""); - } else { - selectPl.value = ""; - selectPl.setCustomValidity("Please select a target purchase list"); - } -}); - -moveItemsForm.addEventListener("submit", async (e) => { - e.preventDefault(); - const items = document.querySelectorAll(`input[id^="move-item"]:checked`); - const ingredients = document.querySelectorAll( - `input[id^="move-ingredient"]:checked` - ); - const data = `target_purchase_list=${selectPl.value}${Array.from(items) - .map((elem) => `&item=${elem.id.split("-").pop()}`) - .join("")}${Array.from(ingredients) - .map((elem) => `&ingredient=${elem.id.split("-").pop()}`) - .join("")}`; - const url = - location.pathname + - (location.pathname.endsWith("/") ? "" : "/") + - "move_items_ingredients"; - const res = await fetch(url, { - method: "post", - headers: { - Accept: "text/html", - "Content-Type": "application/x-www-form-urlencoded", - }, - body: data, - }); - - if (!res.ok) { - showMessage( - "An error occured during moving the selected items/ingredients", - "danger" - ); - } else { - document.getElementById("list-container").innerHTML = await res.text(); - showMessage( - "Successfully moved the selected items/ingredients", - "success" - ); - init(); - } - - bootstrap.Modal.getInstance(moveModal).hide(); -}); - // Shop sections function getShopSectionOptions() { return [ @@ -321,86 +431,3 @@ function initAutocomplete() { selectShopSection.clear(); }, 1_000); } - -initAutocomplete(); - -assignModal.addEventListener("shown.bs.modal", () => selectShopSection.focus()); -assignModal.addEventListener("show.bs.modal", () => { - const items = document.querySelectorAll(`input[id^="move-item"]:checked`); - const selectedArticles = mapArticleAmount(items); - const error = []; - - for (const [id, data] of Object.entries(selectedArticles)) { - if (articles[id].num !== data.num) { - error.push({ - name: data.name, - selected: data.num, - total: articles[id].num, - }); - } - } - - if (error.length === 0) { - articleSelectWarning.classList.add("d-none"); - articleSelectWarning.classList.remove("d-flex"); - } else { - articleSelectNum.innerText = `article${error.length === 1 ? "" : "s"}`; - articleSelectList.innerHTML = error - .map( - (elem) => - `
  • ${elem.name} - ${elem.selected} of ${elem.total} items selected
  • ` - ) - .join(""); - articleSelectWarning.classList.remove("d-none"); - articleSelectWarning.classList.add("d-flex"); - } -}); -assignModal.addEventListener("hidden.bs.modal", () => { - selectShopSection.clear(); -}); - -assignArticlesForm.addEventListener("submit", async (e) => { - e.preventDefault(); - - const section = String( - new FormData(e.target).get("target-shop-section") - ).trim(); - const numSection = parseInt(section); - - const shopSection = `${ - !(section === "null" || Number.isInteger(numSection)) ? "new_" : "" - }shop_section=${section}`; - - const items = document.querySelectorAll(`input[id^="move-item"]:checked`); - - const data = `${shopSection}${Array.from(items) - .map((elem) => `&article=${elem.dataset.articleId}`) - .join("")}`; - const url = - location.pathname + - (location.pathname.endsWith("/") ? "" : "/") + - "assign_articles_to_shop_section"; - const res = await fetch(url, { - method: "post", - headers: { - Accept: "text/html", - "Content-Type": "application/x-www-form-urlencoded", - }, - body: data, - }); - - if (!res.ok) { - showMessage( - "An error occured during moving the selected articles", - "danger" - ); - } else { - document.getElementById("list-container").innerHTML = await res.text(); - showMessage("Successfully moved the selected articles", "success"); - init(); - selectShopSection.options = getShopSectionOptions(); - selectShopSection.clear(); - } - - bootstrap.Modal.getInstance(assignModal).hide(); -}); diff --git a/root/static/js/script.js b/root/static/js/script.js index cb87d3161..d4c3338d1 100644 --- a/root/static/js/script.js +++ b/root/static/js/script.js @@ -170,3 +170,33 @@ function getJsonData(id) { return undefined; } } + +function getBtn(selector, enabledMsg, disabledMsg) { + const btn = document.querySelector(selector); + + if (btn === null) { + return null; + } + + const tt = btn.parentElement; + + const changeTooltip = (msg) => { + const bsTt = bootstrap.Tooltip.getInstance(tt); + bsTt.dispose(); + tt.dataset.bsTitle = msg; + bootstrap.Tooltip.getOrCreateInstance(tt); + }; + + return { + elem: btn, + disable: (msg = disabledMsg) => { + btn.disabled = true; + changeTooltip(msg); + }, + enable: (msg = enabledMsg) => { + btn.disabled = false; + changeTooltip(msg); + }, + changeTooltip, + }; +} diff --git a/root/templates/purchase_list/_edit_table.tt b/root/templates/purchase_list/_edit_table.tt index dcf7037b3..bb3320066 100644 --- a/root/templates/purchase_list/_edit_table.tt +++ b/root/templates/purchase_list/_edit_table.tt @@ -48,7 +48,17 @@ [% display_unit( item.unit, {html=>1} ) %] - + [% tooltip_button( { + variant => 'outline-primary', + icon => 'done', + tooltip => { + content => 'Update amount', + }, + attrs => { + type => 'submit', + }, + button_classes => 'ms-1 pl-form', + } ) %] [% ELSE; IF item.offset < 0; '⊖'; @@ -58,10 +68,20 @@ END; IF item.convertible_into.size %] -
    +
    [% FOREACH unit IN item.convertible_into %]
    - + [% tooltip_button( { + variant => 'outline-secondary', + text => display_value_unit(unit.total, unit, {html=>1}), + tooltip => { + content => 'Convert to ' _ display_value_unit(unit.total, unit, {print=>1}), + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    [% END %]
    @@ -125,7 +145,18 @@ [% IF item.update_offset_url %]
    - + [% tooltip_button( { + variant => 'outline-danger', + icon => 'undo', + tooltip => { + content => 'Reset rounding', + }, + attrs => { + type => 'submit', + name => 'offset', + value => '0', + }, + } ) %]
    [% END %] diff --git a/root/templates/purchase_list/edit.tt b/root/templates/purchase_list/edit.tt index 721e57fc8..1be7fc637 100644 --- a/root/templates/purchase_list/edit.tt +++ b/root/templates/purchase_list/edit.tt @@ -9,23 +9,47 @@ js.push('/lib/coocook-web-components/dist/autocomplete/autocomplete.es.js'); js_push_template_path(); %] - -[% IF purchase_lists.size > 1 %] - - -[% END %] - - +
    + [% tooltip_button( { + icon => 'print', + tooltip => { content => 'Print' }, + attrs => { onclick => 'print()' }, + } ); -
    + IF purchase_lists.size > 1; + tooltip_button( { + disabled => 1, + icon => 'drive_file_move', + text => 'Move items/ingredients to other purchase list', + tooltip => { + content => 'Move selected items/ingredients to different purchase list', + disabled_content => 'No items/ingredients selected to move', + }, + attrs => { + id => 'move-btn', + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#move-items', + }, + } ); + END; + + tooltip_button( { + disabled => 1, + icon => 'move_to_inbox', + text => 'Assign articles to shop section', + tooltip => { + content => 'Assign selected articles to a shop section', + disabled_content => 'No articles selected to assign', + }, + attrs => { + id => 'assign-btn', + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#assign-articles', + }, + } ) %] +
    + +
    [% INCLUDE purchase_list/_edit_table.tt %]
    -- GitLab From a350f3c2aef6af3c0b274e545d8586f5e3202254 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 11:23:10 +0200 Subject: [PATCH 06/26] Use button macro for faq pages --- root/templates/admin/faq/edit.tt | 13 +++++++++++-- root/templates/admin/faq/index.tt | 19 +++++++++++++++++-- root/templates/faq/index.tt | 11 ++++++++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/root/templates/admin/faq/edit.tt b/root/templates/admin/faq/edit.tt index f465b63e6..22d8fc7cc 100644 --- a/root/templates/admin/faq/edit.tt +++ b/root/templates/admin/faq/edit.tt @@ -3,7 +3,11 @@ [% IF faq %]
    - Show the FAQ entry + [% button( { + text => 'Show the FAQ entry', + variant => 'outline-primary', + href => faq.url, + } ) %]
    [% END %] @@ -34,7 +38,12 @@
    - + [% button( { + text => (faq.in_storage ? 'Update' : 'Create') _ ' Entry', + attrs => { + type => 'submit', + }, + } ) %]
    diff --git a/root/templates/admin/faq/index.tt b/root/templates/admin/faq/index.tt index 81b6a053d..66c410bdd 100644 --- a/root/templates/admin/faq/index.tt +++ b/root/templates/admin/faq/index.tt @@ -14,13 +14,28 @@ USE Markdown; %] [% faq.question_md | markdown %] - edit + [% tooltip_button( { + icon => 'edit', + tooltip => { + content => 'Edit FAQ entry', + }, + href => faq.url, + button_classes => 'btn-sm', + } ) %] [% END %] - add + [% tooltip_button( { + variant => 'outline-primary', + icon => 'add', + tooltip => { + content => 'Create FAQ entry', + }, + href => new_faq_url, + container_classes => 'd-grid', + } ) %] diff --git a/root/templates/faq/index.tt b/root/templates/faq/index.tt index fb53d2bd6..0766303ec 100644 --- a/root/templates/faq/index.tt +++ b/root/templates/faq/index.tt @@ -21,7 +21,16 @@ FOR faq IN faqs %] [% IF faq.edit_url %]
    - + [% tooltip_button( { + icon => 'edit', + tooltip => { + content => 'Edit', + }, + container_classes => 'ms-1', + attrs => { + type => 'submit', + }, + } ) %]
    [% END %] -- GitLab From 8661855beefb2b4e7d2ab518113d9f87386104fb Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 12:01:23 +0200 Subject: [PATCH 07/26] Use button macro on admin term pages --- root/templates/admin/terms/edit.tt | 11 ++++++-- root/templates/admin/terms/index.tt | 41 ++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/root/templates/admin/terms/edit.tt b/root/templates/admin/terms/edit.tt index 463554c8c..8afbe7db4 100644 --- a/root/templates/admin/terms/edit.tt +++ b/root/templates/admin/terms/edit.tt @@ -3,7 +3,7 @@

    Valid from: - +

    @@ -11,5 +11,12 @@
    -

    +
    + [% button( { + text => (terms.in_storage ? 'Update' : 'Create') _ ' terms', + attrs => { + type => 'submit', + }, + } ) %] +
    diff --git a/root/templates/admin/terms/index.tt b/root/templates/admin/terms/index.tt index e7f4388b3..5918f923e 100644 --- a/root/templates/admin/terms/index.tt +++ b/root/templates/admin/terms/index.tt @@ -5,22 +5,45 @@
    [% IF item.edit_url %]
    - + [% tooltip_button( { + variant => 'outline-dark', + icon => 'edit', + tooltip => { + content => 'Edit terms', + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    [% END; IF item.delete_url %]
    - + [% tooltip_button( { + variant => 'outline-danger', + icon => 'delete_forever', + tooltip => { + content => 'Delete terms', + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    [% END %]
    [% END; list(terms, 'term_item', { item_classes => 'd-flex gap-3 justify-content-between align-items-center parent'}) %] -
    - add -
    +[% tooltip_button( { + variant => 'outline-primary', + icon => 'add', + tooltip => { + content => 'Create new terms', + }, + href => new_url, + container_classes => 'd-grid mt-4', + button_classes => 'btn-no-square', +} ) %] -- GitLab From a3dbac784ecd62d91beb76f869670022370e0060 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 13:23:21 +0200 Subject: [PATCH 08/26] Use button macro on article pages --- root/templates/article/edit.tt | 7 ++++++- root/templates/article/index.tt | 31 +++++++++++++++++++++---------- t/controller_Article.t | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/root/templates/article/edit.tt b/root/templates/article/edit.tt index 3316f862a..ba092a9be 100644 --- a/root/templates/article/edit.tt +++ b/root/templates/article/edit.tt @@ -116,7 +116,12 @@ IF article; escape_title( 'Article', article.name ); ELSE; title="New Article";
    - + [% button( { + text => (article ? 'Update' : 'Create'), + attrs => { + type => 'submit', + }, + } ) %] diff --git a/root/templates/article/index.tt b/root/templates/article/index.tt index a91f84e6a..8a9ccf7d1 100644 --- a/root/templates/article/index.tt +++ b/root/templates/article/index.tt @@ -1,7 +1,13 @@ [% title = "Articles" %] [% new = BLOCK %] -

    add New article

    +

    + [% button( { + icon => 'add', + text => 'New article', + href => new_url, + } ) %] +

    [% END; new %] [% WRAPPER table length=articles.size classes='align-middle' %] @@ -42,17 +48,22 @@ - [% IF article.delete_url %]
    - + [% article_name = article.name | html; + tooltip_button( { + variant => 'outline-danger', + icon => 'delete_forever', + disabled => !article.delete_url, + tooltip => { + content => 'Delete ' _ article_name _ '', + disabled_content => 'This article is used in dishes/recipes', + }, + attrs => { + type => article.delete_url ? 'submit' : 'button', + }, + button_classes => 'btn-sm', + } ) %]
    - [% ELSE %] - - [% END %] [% END %] diff --git a/t/controller_Article.t b/t/controller_Article.t index 45cd492d6..a0ea3b491 100644 --- a/t/controller_Article.t +++ b/t/controller_Article.t @@ -13,7 +13,7 @@ $t->login_ok( 'john_doe', 'P@ssw0rd' ); $t->follow_link_ok( { text => 'public Test Project' } ); $t->follow_link_ok( { text => 'Articles' } ); -$t->follow_link_ok( { text => 'add New article' } ); +$t->follow_link_ok( { text => 'addNew article' } ); $t->submit_form_ok( { with_fields => { name => 'aether' } }, "create article" ); -- GitLab From 523a8488a1993b0a5008cc36be9ef7922be47322 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 14:25:46 +0200 Subject: [PATCH 09/26] Use button macro on browse pages --- root/templates/browse/recipe/import.tt | 11 ++++++++--- root/templates/browse/recipe/index.tt | 12 +++++++++++- root/templates/browse/recipe/show.tt | 27 +++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/root/templates/browse/recipe/import.tt b/root/templates/browse/recipe/import.tt index 275e8809e..b77551a51 100644 --- a/root/templates/browse/recipe/import.tt +++ b/root/templates/browse/recipe/import.tt @@ -10,9 +10,14 @@
    [% link_project(proj) %]
    - + [% button( { + text => 'Import', + icon => 'east', + attrs => { + type => 'submit', + }, + icon_after_text => 1, + } ) %]
    diff --git a/root/templates/browse/recipe/index.tt b/root/templates/browse/recipe/index.tt index 4b51a8865..a4fd7b0a7 100644 --- a/root/templates/browse/recipe/index.tt +++ b/root/templates/browse/recipe/index.tt @@ -23,7 +23,17 @@ END %] [% IF item.import_url %]
    - + [% tooltip_button( { + icon => 'east', + text => 'Import', + tooltip => { + content => 'Import this recipe into one of your projects', + }, + attrs => { + type => 'submit', + }, + icon_after_text => 1, + } ) %]
    [% END; END; diff --git a/root/templates/browse/recipe/show.tt b/root/templates/browse/recipe/show.tt index 306bc0bd0..95f2f1cc6 100644 --- a/root/templates/browse/recipe/show.tt +++ b/root/templates/browse/recipe/show.tt @@ -3,7 +3,12 @@ js_push_template_path(); css.push('/css/print.css') %] - +[% tooltip_button( { + icon => 'print', + tooltip => { content => 'Print' }, + attrs => { onclick => 'print()' }, + container_classes => 'd-inline-block my-3 no-print', +} ) %]
    @@ -17,7 +22,17 @@ css.push('/css/print.css') %]

    [% IF import_url %]
    - + [% tooltip_button( { + icon => 'east', + text => 'Import', + tooltip => { + content => 'Import this recipe into one of your projects', + }, + attrs => { + type => 'submit', + }, + icon_after_text => 1, + } ) %]
    [% END %]
    @@ -31,7 +46,13 @@ css.push('/css/print.css') %]
    - + [% button( { + text => 'Show', + icon => 'calculate', + attrs => { + type => 'submit', + }, + } ) %]
    -- GitLab From f6289fbc4bdf808a896333e8473234e1a4f32174 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 14:41:50 +0200 Subject: [PATCH 10/26] Use button macro on dish page --- root/templates/dish/edit.tt | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/root/templates/dish/edit.tt b/root/templates/dish/edit.tt index bff1ffa6f..8f3d34b60 100644 --- a/root/templates/dish/edit.tt +++ b/root/templates/dish/edit.tt @@ -17,9 +17,14 @@ js.push('/js/tagAutocomplete.js');
    - + [% button( { + variant => 'danger', + text => 'Delete', + icon => 'delete_forever', + attrs => { + type => 'submit', + }, + } ) %]
    @@ -85,7 +90,12 @@ js.push('/js/tagAutocomplete.js');
    - + [% button( { + text => 'Update dish', + attrs => { + type => 'submit', + }, + } ) %]
    @@ -110,7 +120,12 @@ class="col-sm-12 py-3">
    - + [% button( { + text => 'Recalculate values', + attrs => { + type => 'submit', + }, + } ) %]
    -- GitLab From e1ad701efa0f0318a1fd42fc92703c9ead2b9c13 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 24 Oct 2024 15:20:29 +0200 Subject: [PATCH 11/26] Use button macro organization pages --- root/templates/organization/members.tt | 34 +++++++++++++++++++--- root/templates/organization/show.tt | 37 ++++++++++++++++-------- root/templates/settings/organizations.tt | 7 ++++- t/organization_lifecycle.t | 2 +- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/root/templates/organization/members.tt b/root/templates/organization/members.tt index 72b5d01f2..e1ad53cc3 100644 --- a/root/templates/organization/members.tt +++ b/root/templates/organization/members.tt @@ -31,7 +31,8 @@ has_admin = 0 %] [% END %] - + [%# We don't use the button macro here. Because when we would use the macro the styling would break %] + [% ELSE %] [% o_u.role %] @@ -42,12 +43,32 @@ has_admin = 0 %] [% IF o_u.transfer_ownership_url; has_admin = 1 %]
    - + [% tooltip_button( { + variant => 'outline-secondary', + icon => 'sync_alt', + tooltip => { + content => 'Transfer ownership', + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    [% END %] [% IF o_u.remove_url %]
    - + [% tooltip_button( { + variant => 'outline-danger', + icon => 'delete_forever', + tooltip => { + content => 'Remove', + }, + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    [% END %] @@ -93,7 +114,12 @@ has_admin = 0 %]
    - + [% button( { + text => 'Add', + attrs => { + type => 'submit', + }, + } ) %]
    [% ELSE %] diff --git a/root/templates/organization/show.tt b/root/templates/organization/show.tt index e8eb8cd49..0718bce4f 100644 --- a/root/templates/organization/show.tt +++ b/root/templates/organization/show.tt @@ -12,7 +12,12 @@ - + [% button( { + text => 'Change display name', + attrs => { + type => 'submit', + }, + } ) %] [% ELSE %]
    @@ -35,7 +40,12 @@
    - + [% button( { + text => 'Update description', + attrs => { + type => 'submit', + }, + } ) %] [% ELSE %] [% USE Markdown; organization.description_md | markdown %] @@ -56,15 +66,12 @@ BLOCK project_item ~%]

    Members

    - [% IF members_url %] - - manage_accounts Manage memberships - - [% ELSE %] - - manage_accounts Manage memberships - - [% END %] + [% button( { + text => 'Manage memberships', + icon => 'manage_accounts', + href => members_url, + disabled => members_url, + } ) %]
    [% list(organizations_users, 'user_item', {list_classes => 'list-group-flush rounded'}) %] @@ -98,7 +105,13 @@ BLOCK project_item ~%]

    Deleting an organization also removes all memberships and the organization’s permissions on projects.

    - + [% button( { + variant => 'danger', + text => 'Delete organization', + attrs => { + type => 'submit', + }, + } ) %]
    diff --git a/root/templates/settings/organizations.tt b/root/templates/settings/organizations.tt index 8c5759c4d..dd9a0711d 100644 --- a/root/templates/settings/organizations.tt +++ b/root/templates/settings/organizations.tt @@ -14,7 +14,12 @@ BLOCK new_org %]
    - + [% button( { + text => 'Create organization', + attrs => { + name => 'create' + } + } ) %]
    diff --git a/t/organization_lifecycle.t b/t/organization_lifecycle.t index 12d6d564e..79e723f1e 100644 --- a/t/organization_lifecycle.t +++ b/t/organization_lifecycle.t @@ -36,7 +36,7 @@ $t->text_lacks( my $display_name = 'Test Orga' ); $t->submit_form_ok( { with_fields => { display_name => $display_name } } ); $t->text_contains("Organization $display_name"); -$t->follow_link_ok( { text => 'manage_accounts Manage memberships' } ); +$t->follow_link_ok( { text => 'manage_accountsManage memberships' } ); $t->submit_form_ok( { form_id => 'add-member', with_fields => { name => 'other', role => 'member' } } ); -- GitLab From 1ab42d0d9f0b80a9867038ab54e8fd69123e85c2 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Thu, 14 Nov 2024 11:08:29 +0100 Subject: [PATCH 12/26] Add button macro to print day page --- root/templates/print/day.tt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/root/templates/print/day.tt b/root/templates/print/day.tt index 11da82873..d31cb15d6 100644 --- a/root/templates/print/day.tt +++ b/root/templates/print/day.tt @@ -1,6 +1,12 @@ [% css.push('/css/print.css') %] - +
    + [% tooltip_button( { + icon => 'print', + tooltip => { content => 'Print' }, + attrs => { onclick => 'print()' }, + } ) %] +
    [% FOR meal IN meals %]

    [% meal.name | html %]

    -- GitLab From 9d2cc65a07e90a93ee348721477b36fa7f8bfe7e Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sat, 1 Mar 2025 07:50:02 +0100 Subject: [PATCH 13/26] Use button macro on project pages --- lib/Coocook/Controller/Permission.pm | 10 +++- root/static/css/style.css | 9 ++- root/static/js/project/show.js | 29 +++++++--- root/templates/project/import.tt | 10 +++- root/templates/project/permissions.tt | 80 +++++++++++++++++---------- root/templates/project/show.tt | 22 ++++++-- 6 files changed, 112 insertions(+), 48 deletions(-) diff --git a/lib/Coocook/Controller/Permission.pm b/lib/Coocook/Controller/Permission.pm index 1f0745b20..594f6b5e9 100644 --- a/lib/Coocook/Controller/Permission.pm +++ b/lib/Coocook/Controller/Permission.pm @@ -39,6 +39,9 @@ sub index : GET HEAD Chained('/project/submenu') PathPart('permissions') Args(0) my $projects_users = $c->project->projects_users->prefetch('user'); while ( my $project_user = $projects_users->next ) { + my $can_transfer_onership = + $c->has_capability( 'transfer_project_ownership', { permission => $project_user } ); + push @permissions, { role => $project_user->role, sort_key => $project_user->user->name_fc, @@ -51,8 +54,7 @@ sub index : GET HEAD Chained('/project/submenu') PathPart('permissions') Args(0) ? $c->project_uri( $self->action_for('edit'), $project_user->user->name ) : undef, - make_owner_url => - $c->has_capability( 'transfer_project_ownership', { permission => $project_user } ) + make_owner_url => $can_transfer_onership ? $c->project_uri( $self->action_for('make_owner'), $project_user->user->name ) : undef, @@ -60,6 +62,10 @@ sub index : GET HEAD Chained('/project/submenu') PathPart('permissions') Args(0) ? $c->project_uri( $self->action_for('revoke'), $project_user->user->name ) : undef, }; + + if ($can_transfer_onership) { + $c->stash( can_transfer_ownership => 1 ); + } } } diff --git a/root/static/css/style.css b/root/static/css/style.css index 48de23d01..e95fba649 100644 --- a/root/static/css/style.css +++ b/root/static/css/style.css @@ -367,13 +367,20 @@ cc-autocomplete { .extra-col button { font-weight: bold; - padding: 1px; +} + +.extra-col > * { + height: 34px; } .print { display: none; } +.role-form select { + width: unset; +} + /* Show/hide action buttons on hover/no hover */ .parent:hover .action-btn, .purchase-list-table:hover .pl-form { diff --git a/root/static/js/project/show.js b/root/static/js/project/show.js index 0995be311..5cfebd4a8 100644 --- a/root/static/js/project/show.js +++ b/root/static/js/project/show.js @@ -18,23 +18,34 @@ const openEdit = (e) => { const addCol = (e) => { e.preventDefault(); - let name = document.getElementById("col-name").value; - let head = table.querySelector("th:last-child"); - let btn = document.createElement("button"); + const name = document.getElementById("col-name").value; + const head = table.querySelector("th:last-child"); + + const btn = document.createElement("button"); btn.innerText = name; - btn.className = "btn btn-outline-dark"; - btn.title = "Edit column"; + btn.className = + "btn btn-outline-dark btn-sm d-inline-flex justify-content-center align-items-center"; btn.onclick = openEdit; - let th = document.createElement("th"); + + const btnContainer = document.createElement("span"); + btnContainer.className = "d-grid"; + btnContainer.append(btn); + + const th = document.createElement("th"); th.className = "extra-col"; - th.append(btn); + th.append(btnContainer); + head.parentElement.insertBefore(th, head); addModal.hide(); - for (let row of table.querySelectorAll("tbody tr")) { - let tmp = document.createElement("td"); + for (const row of table.querySelectorAll("tbody tr")) { + const tmp = document.createElement("td"); tmp.innerHTML = " "; row.append(tmp); } + + bootstrap.Tooltip.getOrCreateInstance(btnContainer, { + title: "Edit column", + }); }; const saveCol = (e) => { diff --git a/root/templates/project/import.tt b/root/templates/project/import.tt index 1dcad3a8d..152ef155d 100644 --- a/root/templates/project/import.tt +++ b/root/templates/project/import.tt @@ -41,8 +41,14 @@ js_push_template_path() %] [% END %] - - +
    + [% button( { + text => 'Import', + attrs => { + type => 'submit', + }, + } ) %] +
    [% END %] diff --git a/root/templates/project/permissions.tt b/root/templates/project/permissions.tt index 043a3a54b..3b18d813c 100644 --- a/root/templates/project/permissions.tt +++ b/root/templates/project/permissions.tt @@ -10,12 +10,12 @@ has_admin = 0 %] User/Organization Role - + [% FOR permission IN permissions %] - + [% IF permission.organization; link_organization(permission.organization); @@ -26,41 +26,56 @@ has_admin = 0 %] END %] - [% IF permission.edit_url %] -
    -
    -
    + [% IF permission.edit_url %] + +
    -
    -
    - -
    + [% tooltip_button( { + icon => 'check', + tooltip => { content => 'Save' }, + attrs => { + 'type' => 'submit', + }, + container_classes => 'action-btn', + } ) %]
    - [% ELSE %] + [% ELSE %] [% permission.role %] - [% END %] + [% END %] -
    - [% IF permission.revoke_url %] -
    -
    - -
    -
    - [% END %] - [% IF permission.make_owner_url %] -
    -
    - -
    -
    - [% END %] +
    + [% IF permission.make_owner_url %] +
    + [% tooltip_button( { + variant => 'outline-primary', + icon => 'transfer_within_a_station', + tooltip => { content => 'Transfer ownership' }, + attrs => { + 'type' => 'submit', + }, + button_classes => 'btn-sm', + } ) %] +
    + [% END; + IF permission.revoke_url %] +
    + [% tooltip_button( { + variant => 'outline-danger', + icon => 'delete_forever', + tooltip => { content => 'Revoke permission' }, + attrs => { + 'type' => 'submit', + }, + button_classes => 'btn-sm', + } ) %] +
    + [% END %]
    @@ -103,7 +118,12 @@ has_admin = 0 %]
    - + [% button( { + text => 'Add', + attrs => { + type => 'submit', + }, + } ) %]
    [% ELSE %] diff --git a/root/templates/project/show.tt b/root/templates/project/show.tt index 7be863739..bdf24102d 100644 --- a/root/templates/project/show.tt +++ b/root/templates/project/show.tt @@ -57,7 +57,13 @@ IF project.archived;

    [% project.name | html %]

    - +
    + [% tooltip_button( { + icon => 'print', + tooltip => { content => 'Print' }, + attrs => { onclick => 'print()' }, + } ) %] +
    [% USE Markdown; @@ -71,9 +77,17 @@ IF project.archived; Meal Dishes -
    - -
    + [% tooltip_button( { + variant => 'outline-primary', + icon => 'add', + tooltip => { content => 'Add extra column' }, + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#addCol', + }, + button_classes => 'btn-sm btn-no-quad', + container_classes => 'd-grid', + } ) %] -- GitLab From aef984984ad2bc3b1a618aa20dc237292cdf8d98 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 10:19:20 +0100 Subject: [PATCH 14/26] Fix formatting --- root/static/js/recipe/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/root/static/js/recipe/index.js b/root/static/js/recipe/index.js index 663524c41..fa186decd 100644 --- a/root/static/js/recipe/index.js +++ b/root/static/js/recipe/index.js @@ -8,5 +8,13 @@ const setDuplicateFormValues = (actionURL, name) => { recipeNameBodyElement.textContent = name; } -document.getElementById("duplicate-recipe-modal").addEventListener("shown.bs.modal", () => document.getElementById("new-name").focus()); -document.getElementById("create-recipe-modal").addEventListener("shown.bs.modal", () => document.getElementById("create-name").focus()); +document + .getElementById("duplicate-recipe-modal") + .addEventListener("shown.bs.modal", () => + document.getElementById("new-name").focus() + ); +document + .getElementById("create-recipe-modal") + .addEventListener("shown.bs.modal", () => + document.getElementById("create-name").focus() + ); -- GitLab From 3159c049f6d3b7d578e8fab9ac36bbff79c9ba49 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 10:20:02 +0100 Subject: [PATCH 15/26] Use button macro on recipe pages --- root/static/js/recipe/index.js | 13 +++-- root/templates/recipe/edit.tt | 59 ++++++++++++++++----- root/templates/recipe/importable_recipes.tt | 14 +++-- root/templates/recipe/index.tt | 54 ++++++++++++++----- 4 files changed, 109 insertions(+), 31 deletions(-) diff --git a/root/static/js/recipe/index.js b/root/static/js/recipe/index.js index fa186decd..8d98d25c7 100644 --- a/root/static/js/recipe/index.js +++ b/root/static/js/recipe/index.js @@ -2,11 +2,18 @@ const formElement = document.getElementById("duplicate-form"); const recipeNameTitleElement = document.getElementById("recipe-name-title"); const recipeNameBodyElement = document.getElementById("recipe-name-body"); -const setDuplicateFormValues = (actionURL, name) => { - formElement.setAttribute("action", actionURL); +const setDuplicateFormValues = (e) => { + const url = e.currentTarget.dataset.url; + const name = e.currentTarget.dataset.name; + + formElement.setAttribute("action", url); recipeNameTitleElement.textContent = name; recipeNameBodyElement.textContent = name; -} +}; + +document + .querySelectorAll(".duplicate-trigger") + .forEach((elem) => elem.addEventListener("click", setDuplicateFormValues)); document .getElementById("duplicate-recipe-modal") diff --git a/root/templates/recipe/edit.tt b/root/templates/recipe/edit.tt index 1297f9b3d..641f19b8e 100644 --- a/root/templates/recipe/edit.tt +++ b/root/templates/recipe/edit.tt @@ -10,22 +10,50 @@ IF import_url %]
    - + [% + button_label = 'Used in ' _ dishes.size _ ' dish' _ (dishes.size != 1 ? 'es' : ''); + button( { + text => button_label, + attrs => { + 'data-bs-toggle' => 'collapse', + 'data-bs-target' => '#usage', + 'aria-expanded' => 'false', + 'aria-controls' => 'usage', + }, + button_classes => 'dropdown-toggle align-items-center', + disabled => dishes.size == 0, + } ) + %]
    [% IF public_url %] - - - - [% END; - IF import_url %]
    - + [% button( { + text => 'Share link', + href => public_url, + variant => 'secondary', + attrs => { + 'data-bs-toggle' => 'collapse', + 'data-bs-target' => '#usage', + 'aria-expanded' => 'false', + 'aria-controls' => 'usage', + }, + } ) %]
    - [% END %] + [% END; + IF import_url; + tooltip_button( { + variant => 'secondary', + text => 'Import into another project', + icon => 'east', + icon_after_text => 1, + tooltip => { content => 'Import this recipe into another one of your projects' }, + attrs => { + type =>'submit', + form => 'import', + }, + container_classes => 'btn-group col-xxl-3 col-md-4 col-sm-6', + } ); + END %]
    [% IF dishes.size > 0 %] @@ -86,7 +114,12 @@ IF import_url %]
    - + [% button( { + text => 'Update recipe', + attrs => { + type => 'submit', + }, + } ) %]
    diff --git a/root/templates/recipe/importable_recipes.tt b/root/templates/recipe/importable_recipes.tt index 27aa33085..1033f0667 100644 --- a/root/templates/recipe/importable_recipes.tt +++ b/root/templates/recipe/importable_recipes.tt @@ -13,9 +13,17 @@ [% IF recipe.import_url %]
    - + [% + project_name = project.name | html; + tooltip_button( { + text => 'Import', + icon => 'east', + tooltip => { content => 'Import this recipe into project "' _ project_name _ '"' }, + attrs => { + type => 'submit', + }, + } ) + %]
    [% END %] diff --git a/root/templates/recipe/index.tt b/root/templates/recipe/index.tt index e2ec4f577..bc10565fa 100644 --- a/root/templates/recipe/index.tt +++ b/root/templates/recipe/index.tt @@ -4,12 +4,25 @@ js_push_template_path(); new = BLOCK %]

    - - - east Import recipe - +[% + tooltip_button( { + icon => 'add', + text => 'New recipe', + tooltip => { content => 'Add new recipe' }, + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#create-recipe-modal', + }, + } ); + + tooltip_button( { + icon => 'east', + text => 'Import recipe', + variant => 'secondary', + href => import_recipe_url, + tooltip => { content => 'Import recipe' }, + } ); +%]

    [% END; new %] @@ -32,13 +45,30 @@ new = BLOCK %]
    - + [% + recipe_name = recipe.name | html; + tooltip_button( { + icon => 'file_copy', + variant => 'outline-dark', + tooltip => { content => 'Duplicate "' _ recipe_name _ '"' }, + attrs => { + 'data-name' => recipe_name, + 'data-url' => recipe.duplicate_url, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#duplicate-recipe-modal', + }, + button_classes => 'btn-sm duplicate-trigger', + } ) + %]
    - + [% button( { + icon => 'delete_forever', + variant => 'outline-danger', + attrs => { + type => 'submit', + }, + button_classes => 'btn-sm', + } ) %]
    -- GitLab From e116fa1c1f3f4c2f32a5b8b9cc768e54088546d6 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 10:23:03 +0100 Subject: [PATCH 16/26] Use button macro on sign in page --- root/templates/session/login.tt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/root/templates/session/login.tt b/root/templates/session/login.tt index 7a607ec48..467997ec5 100644 --- a/root/templates/session/login.tt +++ b/root/templates/session/login.tt @@ -20,7 +20,9 @@ - + [% button( { + text => 'Sign in', + } ) %] -- GitLab From d488f5b11876b30db45057990a73ade146270928 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 11:06:12 +0100 Subject: [PATCH 17/26] Use button macro on setting pages --- root/templates/settings/account.tt | 47 ++++++++++++++---------- root/templates/settings/organizations.tt | 34 +++++++++++------ 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/root/templates/settings/account.tt b/root/templates/settings/account.tt index 4d00b4a35..c1b2fdc9a 100644 --- a/root/templates/settings/account.tt +++ b/root/templates/settings/account.tt @@ -5,19 +5,21 @@ js.push('/lib/zxcvbn.js'); %] [% IF confirm_email_change_url %] -
    -
    -
    - You can confirm to change your email address to - [% user.new_email_fc | html %] - -
    -
    -
    +
    + You can confirm to change your email address to + [% user.new_email_fc | html %] + [% button( { + text => 'Confirm email change', + } ) %] +
    [% END %]

    - View your profile + [% button( { + text => 'View your profile', + variant => 'outline-primary', + href => profile_url, + } ) %]

    @@ -41,7 +43,9 @@ js.push('/lib/zxcvbn.js');
    - + [% button( { + text => 'Change display name', + } ) %] @@ -67,7 +71,10 @@ js.push('/lib/zxcvbn.js'); - + [% button( { + text => 'Change password', + button_classes => 'mb-2', + } ) %]
    If you don’t know your current password anymore, you can @@ -90,12 +97,12 @@ js.push('/lib/zxcvbn.js');
    [% IF user.new_email_fc %] -
    -
    - You’ve requested change to [% user.new_email_fc | html %] - at [% display_datetime(user.token_created, {short=>1}) %] UTC - -
    + + You’ve requested change to [% user.new_email_fc | html %] + at [% display_datetime(user.token_created, {short=>1}) %] UTC + [% button( { + text => 'Cancel', + } ) %]
    [% END %] @@ -105,7 +112,9 @@ js.push('/lib/zxcvbn.js');
    - + [% button( { + text => 'Change email address', + } ) %]
    diff --git a/root/templates/settings/organizations.tt b/root/templates/settings/organizations.tt index dd9a0711d..6dba001ba 100644 --- a/root/templates/settings/organizations.tt +++ b/root/templates/settings/organizations.tt @@ -17,8 +17,8 @@ BLOCK new_org %] [% button( { text => 'Create organization', attrs => { - name => 'create' - } + name => 'create', + }, } ) %] @@ -33,21 +33,33 @@ BLOCK user_item %] [% link_organization(item.organization) %] ([% item.role %]) [% IF item.leave_url %]
    - + [% button( { + text => 'Leave organization', + icon => 'logout', + variant => 'outline-danger', + } ) %]
    -[% ELSE %] - -[% END; +[% ELSE; + tooltip_button( { + text => 'Leave organization', + icon => 'logout', + variant => 'outline-danger', + tooltip => { + content => 'You are the owner of this organization', + }, + disabled => 1, + button_classes => 'action-btn', + } ); +END; END; IF organizations_users.size > 0 %]

    Before you can leave an organization you need to transfer organization ownership to another organization member.

    - [% list(organizations_users, 'user_item', {item_classes => 'd-flex gap-3 justify-content-between align-items-center flex-wrap parent'}) %] + [% list(organizations_users, 'user_item', { + list_classes => 'mb-3', + item_classes => 'd-flex gap-3 justify-content-between align-items-center flex-wrap parent', + }) %] [% ELSE %]

    You are not member of any organization yet.

    [% END; -- GitLab From 5273383b96aa45d196e9a64d5540363f12781998 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 11:24:50 +0100 Subject: [PATCH 18/26] Use button macro on shop section page --- root/templates/shop_section/index.tt | 55 ++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/root/templates/shop_section/index.tt b/root/templates/shop_section/index.tt index e879e9a3b..284956459 100644 --- a/root/templates/shop_section/index.tt +++ b/root/templates/shop_section/index.tt @@ -5,9 +5,19 @@ js_push_template_path(); BLOCK new_section %] -
    - -
    + [% tooltip_button( { + variant => 'outline-primary', + icon => 'add', + tooltip => { + content => 'Create shop section', + }, + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#add-section', + }, + container_classes => 'd-grid', + button_classes => 'btn-no-quad', + } ) %] [% END %] @@ -28,20 +38,33 @@ BLOCK new_section %] [% section.articles_count %]
    - - [% IF section.delete_url %] -
    - + [% + section_name = section.name | html; + tooltip_button( { + variant => 'outline-dark', + icon => 'edit', + tooltip => { content => 'Rename shop section', line_through => 1 }, + attrs => { + 'data-url' => section.update_url, + 'data-name' => section_name, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#update-section', + }, + button_classes => 'btn-sm update', + } ) + %] + + [% tooltip_button( { + variant => 'outline-danger', + icon => 'delete_forever', + disabled => !section.delete_url, + tooltip => { + content => 'Delete shop section', + disabled_content => numerus(section.articles_count, 'article', 'articles') _ ' are assigned to this shop section', + }, + button_classes => 'btn-sm', + } ) %]
    - [% ELSE %] - - [% END %]
    -- GitLab From 863c357e6e5e7b7bc517bc36b4afd2dbc518d2eb Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 12:09:14 +0100 Subject: [PATCH 19/26] Use button macro on tag pages --- root/templates/tag/edit.tt | 30 ++++---- root/templates/tag/index.tt | 133 ++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/root/templates/tag/edit.tt b/root/templates/tag/edit.tt index e034f5a9b..1504d4be6 100644 --- a/root/templates/tag/edit.tt +++ b/root/templates/tag/edit.tt @@ -2,20 +2,26 @@ js_push_template_path() %]
    - - [% IF is_in_use %] - - [% ELSE %] + [% button( { + text => 'Edit', + icon => 'edit', + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#edit-tag', + }, + button_classes => 'update', + } ) %]
    - + [% button( { + text => 'Delete', + icon => 'delete', + disabled => is_in_use, + variant => 'danger', + attrs => { + id => 'delete-btn', + }, + } ) %]
    - [% END %]
    diff --git a/root/templates/tag/index.tt b/root/templates/tag/index.tt index 23964924d..02c73c14a 100644 --- a/root/templates/tag/index.tt +++ b/root/templates/tag/index.tt @@ -1,63 +1,83 @@ [% title = "Tags"; -js_push_template_path() %] +js_push_template_path(); -

    - - -

    +BLOCK new_element %] + +
    +[% + button( { + text => 'New Tag', + icon => 'add', + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#add-tag', + }, + } ); + button( { + text => 'New Tag group', + icon => 'add', + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#add-tg', + }, + } ); +%] +
    +[% END; + +PROCESS new_element; -[% FOR group IN groups %] +FOR group IN groups %]

    Tag Group [% group.name | html %]

    - - - [% IF group.tags.size %] -
    - -
    - [% ELSE %] + [% + group_name = group.name | html; + group_comment = group.comment | html; + tooltip_button( { + variant => 'outline-dark', + icon => 'add', + tooltip => { + content => 'Add tag to tag group '_ group_name, + }, + attrs => { + 'data-id' => group.id, + 'data-name' => group_name, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#add-to-group', + }, + button_classes => 'btn-sm atg', + } ); + tooltip_button( { + variant => 'outline-dark', + icon => 'edit', + tooltip => { + content => 'Edit tag group '_ group_name, + }, + attrs => { + 'data-url' => group.update_url, + 'data-name' => group_name, + 'data-comment' => group_comment, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#tg-edit', + }, + button_classes => 'btn-sm edit-tg', + } ) + %]
    - + [% tooltip_button( { + variant => 'outline-danger', + icon => 'delete', + disabled => group.tags.size, + tooltip => { + content => 'Delete tag group '_ group_name, + disabled_content => "Can't delete tag group which contains tags", + }, + button_classes => 'btn-sm', + } ) %]
    - [% END %]
    @@ -83,18 +103,11 @@ js_push_template_path() %] [% END %]
    -[% END %] +[% END; -

    - - -

    +PROCESS new_element; -[% WRAPPER includes/infobox.tt %] +WRAPPER includes/infobox.tt %] Tags are keywords that can be attached to recipes, dishes and articles. They can be used to filter them during search or to highlight important aspects.

    -- GitLab From e54c3b32c751ab9d18022b096ad6a6d111bf3ba7 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 13:04:33 +0100 Subject: [PATCH 20/26] Use button macro on unit pages --- root/templates/unit/edit.tt | 31 +++++++++++++++++++++++-------- root/templates/unit/index.tt | 30 ++++++++++++++++++------------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/root/templates/unit/edit.tt b/root/templates/unit/edit.tt index 583985fca..2c2141bcf 100644 --- a/root/templates/unit/edit.tt +++ b/root/templates/unit/edit.tt @@ -14,7 +14,9 @@ END %]
    - + [% button( { + text => 'Update', + } ) %]

    Conversions

    @@ -40,7 +42,10 @@ END %]
    - + [% button( { + text => 'Add', + button_classes => 'btn-sm', + } ) %]
    @@ -53,7 +58,7 @@ END %]
    1 [% unit.long_name | html %] is equal to
    [% IF conversion.update_url %] -
    +
    [% ELSE; @@ -62,13 +67,23 @@ END %] [% conversion.long_name | html %]
    - [% IF conversion.update_url %] - - [% END %] + [% IF conversion.update_url; + button( { + text => 'Update', + button_classes => 'btn-sm', + attrs => { + form => 'update-conversion-to-unit-' _ conversion.id, + }, + } ); + END; - [% IF conversion.delete_url %] + IF conversion.delete_url %]
    - + [% button( { + text => 'Delete', + variant => 'danger', + button_classes => 'btn-sm', + } ) %]
    [% END %] [% ELSE %] diff --git a/root/templates/unit/index.tt b/root/templates/unit/index.tt index f0f0a118a..8076fcb6c 100644 --- a/root/templates/unit/index.tt +++ b/root/templates/unit/index.tt @@ -4,9 +4,14 @@ js_push_template_path(); new = BLOCK %]

    - + [% button( { + text => 'New unit', + icon => 'add', + attrs => { + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#create-unit-modal', + }, + } ) %]

    [% END; new %] @@ -45,17 +50,18 @@ new = BLOCK %] [% END; IF loop.first %] - [% IF unit.delete_url %]
    - + [% tooltip_button( { + icon => 'delete', + variant => 'outline-danger', + disabled => !unit.delete_url, + tooltip => { + content => 'Delete unit', + disabled_content => 'This is used for dish/recipe ingredients or purchase list items', + }, + button_classes => 'btn-sm', + } ) %]
    - [% ELSE %] - - [% END %] [% END %] -- GitLab From 08bdd3475333809c09cac62a29049136b7a6482f Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 13:24:04 +0100 Subject: [PATCH 21/26] Use button macro on user pages --- root/templates/user/recover.tt | 5 ++++- root/templates/user/register.tt | 5 ++++- root/templates/user/reset_password.tt | 5 ++++- root/templates/user/show.tt | 22 ++++++++++++++++------ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/root/templates/user/recover.tt b/root/templates/user/recover.tt index 69e808dd6..d156d2437 100644 --- a/root/templates/user/recover.tt +++ b/root/templates/user/recover.tt @@ -14,7 +14,10 @@
    - + + [% button( { + text => 'Send recovery link', + } ) %] diff --git a/root/templates/user/register.tt b/root/templates/user/register.tt index 4b133ed08..c989dff98 100644 --- a/root/templates/user/register.tt +++ b/root/templates/user/register.tt @@ -51,7 +51,10 @@ IF terms %] By clicking [% label %] you accept our terms as of [% display_date(terms.valid_from, {html=>1}) %]. [% END %] You will receive an email with a web link to verify your email address. - + + [% button( { + text => label, + } ) %] diff --git a/root/templates/user/reset_password.tt b/root/templates/user/reset_password.tt index bdab62d3a..43adcc6e5 100644 --- a/root/templates/user/reset_password.tt +++ b/root/templates/user/reset_password.tt @@ -23,7 +23,10 @@ js.push('/lib/zxcvbn.js');
    - + + [% button( { + text => 'Reset password', + } ) %] diff --git a/root/templates/user/show.tt b/root/templates/user/show.tt index bab048182..0969b2970 100644 --- a/root/templates/user/show.tt +++ b/root/templates/user/show.tt @@ -1,13 +1,23 @@ [% escape_title( 'User', user_object.display_name ) %]

    - [% IF my_settings_url %] - edit Edit your profile - [% END %] + [% IF my_settings_url; + button( { + text => 'Edit your profile', + icon => 'edit', + variant => 'outline-primary', + href => my_settings_url, + } ); + END %] - [% IF profile_admin_url %] - supervisor_account View profile as admin - [% END %] + [% IF profile_admin_url; + button( { + text => 'View profile as admin', + icon => 'supervisor_account', + variant => 'outline-secondary', + href => profile_admin_url, + } ); + END %]

    Registered: [% display_date(user_object.created) %]

    -- GitLab From ed6abc98fffe93ddd8f871efa9e4c8de6fcf9bc0 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Sun, 2 Mar 2025 13:25:54 +0100 Subject: [PATCH 22/26] Use button macro on dashboard page --- root/templates/dashboard.tt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/root/templates/dashboard.tt b/root/templates/dashboard.tt index c95272a86..e0aa0b256 100644 --- a/root/templates/dashboard.tt +++ b/root/templates/dashboard.tt @@ -16,7 +16,9 @@
    - + [% button( { + text => 'Create project', + } ) %]
    -- GitLab From 9ee27520888fe4c58521baaedabb25283b19dc50 Mon Sep 17 00:00:00 2001 From: Kurt Roscher Date: Mon, 24 Mar 2025 17:11:30 +0100 Subject: [PATCH 23/26] Rename btn-quad to btn-square and btn-no-quad to btn-no-square --- root/common_templates/macros.tt | 2 +- root/static/css/style.css | 8 ++++---- root/templates/faq/index.tt | 10 ++++++---- root/templates/organization/members.tt | 2 +- root/templates/project/show.tt | 2 +- root/templates/purchase_list/index.tt | 2 +- root/templates/shop_section/index.tt | 2 +- root/templates/terms/show.tt | 8 ++++---- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/root/common_templates/macros.tt b/root/common_templates/macros.tt index e678d31c1..9ccf68495 100644 --- a/root/common_templates/macros.tt +++ b/root/common_templates/macros.tt @@ -128,7 +128,7 @@ MACRO button(opts) BLOCK; icon = '' _ opts.icon _ ''; END ~%] <[% opts.href ? 'a' : 'button' %] - class="btn btn-[% opts.variant || 'primary' %] [% 'btn-quad' IF !opts.text %] d-inline-flex justify-content-center gap-1 [% opts.button_classes.list.join(' ') %]" + class="btn btn-[% opts.variant || 'primary' %] [% 'btn-square' IF !opts.text %] d-inline-flex justify-content-center gap-1 [% opts.button_classes.list.join(' ') %]" [%~ ' disabled ' IF opts.disabled; FOR attr IN opts.attrs; attr.key %]="[% attr.value | html %]" diff --git a/root/static/css/style.css b/root/static/css/style.css index e95fba649..9d3576ab9 100644 --- a/root/static/css/style.css +++ b/root/static/css/style.css @@ -17,19 +17,19 @@ textarea { resize: vertical; } -.btn-quad { +.btn-square { aspect-ratio: 1; } -.btn-no-quad { +.btn-no-square { aspect-ratio: unset; } -.btn.btn-quad { +.btn.btn-square { padding: 0.375em; } -.btn.btn-sm.btn-quad { +.btn.btn-sm.btn-square { padding: 0.25rem; } diff --git a/root/templates/faq/index.tt b/root/templates/faq/index.tt index 0766303ec..96167fb89 100644 --- a/root/templates/faq/index.tt +++ b/root/templates/faq/index.tt @@ -2,10 +2,12 @@ html_title = 'Frequently Asked Questions' %] [% IF admin_faq_url %] -
    -
    - Edit FAQ -
    +
    + [% button( { + text => 'Edit FAQ', + variant => 'outline-primary', + href => admin_faq_url, + } ) %]
    [% END; diff --git a/root/templates/organization/members.tt b/root/templates/organization/members.tt index e1ad53cc3..95519e749 100644 --- a/root/templates/organization/members.tt +++ b/root/templates/organization/members.tt @@ -32,7 +32,7 @@ has_admin = 0 %] [% END %] [%# We don't use the button macro here. Because when we would use the macro the styling would break %] - + [% ELSE %] [% o_u.role %] diff --git a/root/templates/project/show.tt b/root/templates/project/show.tt index bdf24102d..23619e0b5 100644 --- a/root/templates/project/show.tt +++ b/root/templates/project/show.tt @@ -85,7 +85,7 @@ IF project.archived; 'data-bs-toggle' => 'modal', 'data-bs-target' => '#addCol', }, - button_classes => 'btn-sm btn-no-quad', + button_classes => 'btn-sm btn-no-square', container_classes => 'd-grid', } ) %] diff --git a/root/templates/purchase_list/index.tt b/root/templates/purchase_list/index.tt index 72228fa8b..edc60dbce 100644 --- a/root/templates/purchase_list/index.tt +++ b/root/templates/purchase_list/index.tt @@ -18,7 +18,7 @@ BLOCK new_list %] 'data-bs-target' => '#addList', }, container_classes => 'd-grid', - button_classes => 'btn-no-quad', + button_classes => 'btn-no-square', } ) %] diff --git a/root/templates/shop_section/index.tt b/root/templates/shop_section/index.tt index 284956459..0917613bd 100644 --- a/root/templates/shop_section/index.tt +++ b/root/templates/shop_section/index.tt @@ -16,7 +16,7 @@ BLOCK new_section %] 'data-bs-target' => '#add-section', }, container_classes => 'd-grid', - button_classes => 'btn-no-quad', + button_classes => 'btn-no-square', } ) %] diff --git a/root/templates/terms/show.tt b/root/templates/terms/show.tt index 9f829f119..d18758b92 100644 --- a/root/templates/terms/show.tt +++ b/root/templates/terms/show.tt @@ -2,11 +2,11 @@
    [% IF previous_url %] - + chevron_left [% ELSE %] - + chevron_left [% END %] @@ -15,11 +15,11 @@ ' until ' _ display_date( terms.valid_until, {html=>1} ) IF terms.valid_until %]

    [% IF next_url %] - + chevron_right [% ELSE %] - + chevron_right [% END %] -- GitLab From 3c8a6adf680183b50c578b4a9acc36dbd0a1995c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6hmer?= Date: Mon, 24 Mar 2025 17:30:30 +0100 Subject: [PATCH 24/26] fixup! Respect project state und capabilities in purchase list index - apostrophe easily confused with single quote --- root/templates/purchase_list/index.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/root/templates/purchase_list/index.tt b/root/templates/purchase_list/index.tt index edc60dbce..6c4e4d3cb 100644 --- a/root/templates/purchase_list/index.tt +++ b/root/templates/purchase_list/index.tt @@ -94,7 +94,7 @@ BLOCK new_list %] disabled => !purchase_list.delete_url || (purchase_lists.size > 1 && purchase_list.is_default), icon => 'delete_forever', tooltip => { - content => purchase_lists.size > 1 && purchase_list.is_default ? 'Can’t delete default purchase list' : 'Delete purchase list', + content => purchase_lists.size > 1 && purchase_list.is_default ? "Can’t delete default purchase list" : "Delete purchase list", line_through => purchase_lists.size > 1 && purchase_list.is_default ? 0 : 1, }, attrs => is_in_use ? { -- GitLab From 2ebfbb32b886912b0f5b6548bcc53664c47a4c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6hmer?= Date: Mon, 24 Mar 2025 17:25:24 +0100 Subject: [PATCH 25/26] Unify TT macros button() and tooltip_button() --- root/common_templates/macros.tt | 62 ++++++++++----------- root/templates/admin/faq/index.tt | 4 +- root/templates/admin/terms/index.tt | 6 +- root/templates/article/index.tt | 2 +- root/templates/browse/recipe/index.tt | 2 +- root/templates/browse/recipe/show.tt | 4 +- root/templates/organization/members.tt | 4 +- root/templates/print/day.tt | 2 +- root/templates/project/permissions.tt | 6 +- root/templates/project/show.tt | 4 +- root/templates/purchase_list/_edit_table.tt | 6 +- root/templates/purchase_list/edit.tt | 6 +- root/templates/purchase_list/index.tt | 8 +-- root/templates/recipe/edit.tt | 2 +- root/templates/recipe/importable_recipes.tt | 2 +- root/templates/recipe/index.tt | 6 +- root/templates/settings/organizations.tt | 2 +- root/templates/shop_section/index.tt | 6 +- root/templates/tag/index.tt | 6 +- root/templates/unit/index.tt | 2 +- t/template_macros.t | 22 +++++++- 21 files changed, 91 insertions(+), 73 deletions(-) diff --git a/root/common_templates/macros.tt b/root/common_templates/macros.tt index 9ccf68495..13f2e3837 100644 --- a/root/common_templates/macros.tt +++ b/root/common_templates/macros.tt @@ -105,51 +105,51 @@ length=items.size ~%] [%~ END; -# Arguments for button and tooltip_button -# opts: +# Arguments for button # - variant: optional, str - one of the bootstrap variants, default: primary # - disabled: bool, optional - triggering the "disabled" HTML attribute, default: false # - href: str, optional - if set the button is rendered as Tag with the given href instead of + EOT + + is_tt '[% button({text => "foo"}) %]', {} => <<~EOT, "with text"; + + EOT + + is_tt '[% button({text => "foo", tooltip => {content => "bar"} }) %]', {} => <<~EOT, "with tooltip"; + + EOT +}; + is_tt '[% display_project({name => "My Project"}) %]', {} => <lock My Project EOT @@ -80,8 +94,12 @@ sub is_tt ( $tt_code, $stash, $expected_output, $name = undef ) { $tt->process( \$tt_code, $stash, \my $output ) or die $tt->error; + $expected_output =~ m/\n\z/ and $output .= " "; # heredocs always end with \n + # TODO test HTML equivalence instead of string equivalence - $output =~ s/\S\K +/ /g; # reduce multiple spaces to one - $expected_output =~ m/\n\z/ and $output .= "\n"; # heredocs always end with \n + for ( $output, $expected_output ) { + s/\s+/ /gm; # reduce multiple spaces to one (very basic normalization) + } + is $output => $expected_output, $name; } -- GitLab From 189607d9e3dea0a38f062e1f26723fa382b761e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=B6hmer?= Date: Mon, 24 Mar 2025 19:50:16 +0100 Subject: [PATCH 26/26] Improve URL handling in Controller::Permission->index() - use project_uri_if_permitted() instead of checking capability by hand - use same scope variable for whole permission loop and use more descriptive variable name --- lib/Coocook/Controller/Permission.pm | 37 +++++++++++++++------------ root/templates/project/permissions.tt | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/Coocook/Controller/Permission.pm b/lib/Coocook/Controller/Permission.pm index 594f6b5e9..eecf68720 100644 --- a/lib/Coocook/Controller/Permission.pm +++ b/lib/Coocook/Controller/Permission.pm @@ -36,12 +36,10 @@ sub index : GET HEAD Chained('/project/submenu') PathPart('permissions') Args(0) } { - my $projects_users = $c->project->projects_users->prefetch('user'); + my $can_transfer_ownership_to_anyone = ''; + my $projects_users = $c->project->projects_users->prefetch('user'); while ( my $project_user = $projects_users->next ) { - my $can_transfer_onership = - $c->has_capability( 'transfer_project_ownership', { permission => $project_user } ); - push @permissions, { role => $project_user->role, sort_key => $project_user->user->name_fc, @@ -49,24 +47,29 @@ sub index : GET HEAD Chained('/project/submenu') PathPart('permissions') Args(0) url => $c->uri_for_action( '/user/show', [ $project_user->user->name ] ), ), - edit_url => - $c->has_capability( edit_user_permission => { permission => $project_user, role => 'viewer' } ) - ? $c->project_uri( $self->action_for('edit'), $project_user->user->name ) - : undef, + edit_url => $c->project_uri_if_permitted( + $self->action_for('edit'), #perltidy + { permission => $project_user, role => 'viewer' }, + $project_user->user->name + ), - make_owner_url => $can_transfer_onership - ? $c->project_uri( $self->action_for('make_owner'), $project_user->user->name ) - : undef, + make_owner_url => my $make_owner_url = $c->project_uri_if_permitted( + $self->action_for('make_owner'), + { permission => $project_user }, + $project_user->user->name + ), - revoke_url => $c->has_capability( 'revoke_project_permission', { permission => $project_user } ) - ? $c->project_uri( $self->action_for('revoke'), $project_user->user->name ) - : undef, + revoke_url => $c->project_uri_if_permitted( + $self->action_for('revoke'), + { permission => $project_user }, + $project_user->user->name + ), }; - if ($can_transfer_onership) { - $c->stash( can_transfer_ownership => 1 ); - } + $make_owner_url and $can_transfer_ownership_to_anyone = 1; } + + $c->stash( can_transfer_ownership_to_anyone => $can_transfer_ownership_to_anyone ); } @permissions = sort { $a->{sort_key} cmp $b->{sort_key} } @permissions; diff --git a/root/templates/project/permissions.tt b/root/templates/project/permissions.tt index fc1af1c3c..c0c1a2b3a 100644 --- a/root/templates/project/permissions.tt +++ b/root/templates/project/permissions.tt @@ -10,7 +10,7 @@ has_admin = 0 %] User/Organization Role - + -- GitLab