zmiany dla michała
This commit is contained in:
@@ -1382,6 +1382,14 @@ input[type="checkbox"].form-check-input,
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.shopping-item-text .info-line {
|
||||
@@ -2284,3 +2292,35 @@ body:not(.sorting-active) .drag-handle {
|
||||
color: rgba(255,255,255,.66);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
|
||||
.endpoint-view_list .shopping-item-name[data-item-menu-trigger],
|
||||
.endpoint-list .shopping-item-name[data-item-menu-trigger] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.endpoint-view_list .shopping-item-name[data-item-menu-trigger]:hover,
|
||||
.endpoint-view_list .shopping-item-name[data-item-menu-trigger]:focus-visible,
|
||||
.endpoint-list .shopping-item-name[data-item-menu-trigger]:hover,
|
||||
.endpoint-list .shopping-item-name[data-item-menu-trigger]:focus-visible {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#desktopItemMenu {
|
||||
position: fixed;
|
||||
z-index: 1200;
|
||||
min-width: 10rem;
|
||||
display: grid;
|
||||
gap: .35rem;
|
||||
padding: .45rem;
|
||||
border: 1px solid rgba(255, 255, 255, .12);
|
||||
border-radius: .9rem;
|
||||
background: rgba(18, 20, 24, .96);
|
||||
box-shadow: 0 16px 38px rgba(0, 0, 0, .34);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
#desktopItemMenu[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
126
shopping_app/static/js/desktop_item_menu.js
Normal file
126
shopping_app/static/js/desktop_item_menu.js
Normal file
@@ -0,0 +1,126 @@
|
||||
(function () {
|
||||
const DESKTOP_QUERY = '(min-width: 992px) and (pointer: fine)';
|
||||
|
||||
function isDesktopOwnerList() {
|
||||
return window.matchMedia(DESKTOP_QUERY).matches
|
||||
&& !window.IS_SHARE
|
||||
&& (
|
||||
document.body.classList.contains('endpoint-view_list')
|
||||
|| document.body.classList.contains('endpoint-list')
|
||||
);
|
||||
}
|
||||
|
||||
function getMenu() {
|
||||
return document.getElementById('desktopItemMenu');
|
||||
}
|
||||
|
||||
function hideDesktopItemMenu() {
|
||||
const menu = getMenu();
|
||||
if (!menu) return;
|
||||
menu.hidden = true;
|
||||
delete menu.dataset.itemId;
|
||||
delete menu.dataset.itemName;
|
||||
delete menu.dataset.itemQuantity;
|
||||
}
|
||||
|
||||
function positionMenu(menu, clickX, clickY) {
|
||||
const gap = 14;
|
||||
menu.style.left = '0px';
|
||||
menu.style.top = '0px';
|
||||
menu.hidden = false;
|
||||
|
||||
const rect = menu.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let left = clickX - (rect.width / 2);
|
||||
let top = clickY - rect.height - gap;
|
||||
|
||||
if (top < 12) {
|
||||
top = Math.min(viewportHeight - rect.height - 12, clickY + gap);
|
||||
}
|
||||
|
||||
left = Math.max(12, Math.min(left, viewportWidth - rect.width - 12));
|
||||
top = Math.max(12, Math.min(top, viewportHeight - rect.height - 12));
|
||||
|
||||
menu.style.left = `${left}px`;
|
||||
menu.style.top = `${top}px`;
|
||||
}
|
||||
|
||||
function showDesktopItemMenu(trigger, event) {
|
||||
const menu = getMenu();
|
||||
if (!menu) return;
|
||||
|
||||
menu.dataset.itemId = trigger.dataset.itemId || '';
|
||||
menu.dataset.itemName = trigger.dataset.itemName || '';
|
||||
menu.dataset.itemQuantity = trigger.dataset.itemQuantity || '1';
|
||||
|
||||
let clickX = event.clientX || 0;
|
||||
let clickY = event.clientY || 0;
|
||||
|
||||
if (!clickX && !clickY) {
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
clickX = rect.left + (rect.width / 2);
|
||||
clickY = rect.top;
|
||||
}
|
||||
|
||||
positionMenu(menu, clickX, clickY);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
const menu = getMenu();
|
||||
const trigger = event.target.closest('[data-item-menu-trigger="true"]');
|
||||
|
||||
if (trigger && isDesktopOwnerList() && !trigger.disabled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
showDesktopItemMenu(trigger, event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (menu && !menu.hidden && !event.target.closest('#desktopItemMenu')) {
|
||||
hideDesktopItemMenu();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape') {
|
||||
hideDesktopItemMenu();
|
||||
}
|
||||
});
|
||||
|
||||
['scroll', 'resize'].forEach(function (eventName) {
|
||||
window.addEventListener(eventName, hideDesktopItemMenu, true);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const menu = getMenu();
|
||||
if (!menu) return;
|
||||
|
||||
menu.addEventListener('click', function (event) {
|
||||
const actionButton = event.target.closest('[data-menu-action]');
|
||||
if (!actionButton) return;
|
||||
|
||||
const itemId = parseInt(menu.dataset.itemId || '', 10);
|
||||
const itemName = menu.dataset.itemName || '';
|
||||
const itemQuantity = parseInt(menu.dataset.itemQuantity || '1', 10) || 1;
|
||||
|
||||
if (!itemId) {
|
||||
hideDesktopItemMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionButton.dataset.menuAction === 'edit') {
|
||||
openEditItemModal(event, itemId, itemName, itemQuantity);
|
||||
}
|
||||
|
||||
if (actionButton.dataset.menuAction === 'delete') {
|
||||
deleteItem(itemId);
|
||||
}
|
||||
|
||||
hideDesktopItemMenu();
|
||||
});
|
||||
});
|
||||
|
||||
window.hideDesktopItemMenu = hideDesktopItemMenu;
|
||||
})();
|
||||
@@ -385,8 +385,9 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
|
||||
|
||||
const isOwner = window.IS_OWNER === true || window.IS_OWNER === 'true';
|
||||
const isArchived = window.IS_ARCHIVED === true || window.IS_ARCHIVED === 'true';
|
||||
const safeName = escapeHtml(item.name || '');
|
||||
const nameForEdit = JSON.stringify(String(item.name || ''));
|
||||
const rawName = String(item.name || '');
|
||||
const safeName = escapeHtml(rawName);
|
||||
const nameForEdit = JSON.stringify(rawName);
|
||||
const quantity = Number.isInteger(item.quantity) ? item.quantity : parseInt(item.quantity, 10) || 1;
|
||||
const quantityBadge = quantity > 1
|
||||
? `<span class="badge rounded-pill bg-secondary">x${quantity}</span>`
|
||||
@@ -417,6 +418,15 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
|
||||
|
||||
const iconBtn = 'btn btn-outline-light btn-sm shopping-action-btn';
|
||||
const wideBtn = 'btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide';
|
||||
const itemNameHtml = canEditListItem
|
||||
? `<button type="button"
|
||||
id="name-${item.id}"
|
||||
class="shopping-item-name text-white"
|
||||
data-item-id="${item.id}"
|
||||
data-item-name=${JSON.stringify(rawName)}
|
||||
data-item-quantity="${quantity}"
|
||||
${isArchived ? 'disabled aria-disabled="true"' : 'data-item-menu-trigger="true"'}>${safeName}</button>`
|
||||
: `<span id="name-${item.id}" class="shopping-item-name text-white">${safeName}</span>`;
|
||||
let actionButtons = '';
|
||||
|
||||
if (canEditListItem) {
|
||||
@@ -454,7 +464,7 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
|
||||
<div class="shopping-item-content">
|
||||
<div class="shopping-item-head">
|
||||
<div class="shopping-item-text">
|
||||
<span id="name-${item.id}" class="shopping-item-name text-white">${safeName}</span>
|
||||
${itemNameHtml}
|
||||
${quantityBadge}
|
||||
${infoHtml}
|
||||
</div>
|
||||
|
||||
@@ -125,7 +125,13 @@
|
||||
<div class="shopping-item-content">
|
||||
<div class="shopping-item-head">
|
||||
<div class="shopping-item-text">
|
||||
<span id="name-{{ item.id }}" class="shopping-item-name text-white">{{ item.name }}</span>
|
||||
<button type="button"
|
||||
id="name-{{ item.id }}"
|
||||
class="shopping-item-name text-white"
|
||||
data-item-id="{{ item.id }}"
|
||||
data-item-name={{ item.name|tojson }}
|
||||
data-item-quantity="{{ item.quantity or 1 }}"
|
||||
{% if not list.is_archived %}data-item-menu-trigger="true"{% else %}disabled aria-disabled="true"{% endif %}>{{ item.name }}</button>
|
||||
{% if item.quantity and item.quantity > 1 %}
|
||||
<span class="badge rounded-pill bg-secondary">x{{ item.quantity }}</span>
|
||||
{% endif %}
|
||||
@@ -166,6 +172,11 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div id="desktopItemMenu" hidden>
|
||||
<button type="button" class="btn btn-outline-light btn-sm w-100 text-start" data-menu-action="edit">✏️ Edytuj</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm w-100 text-start" data-menu-action="delete">🗑️ Usuń</button>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="editItemModal" tabindex="-1" aria-labelledby="editItemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
@@ -541,6 +552,7 @@
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'access_users.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'category_modal.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'notes.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'desktop_item_menu.js') }}"></script>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user