mirror of
https://github.com/chenasraf/nextcloud-pantry.git
synced 2026-05-17 17:28:01 +00:00
feat(checklist): trash mode toggle to view deleted items
This commit is contained in:
@@ -200,6 +200,34 @@ final class ChecklistController extends OCSController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List soft-deleted items in a checklist (trash)
|
||||||
|
*
|
||||||
|
* Returns items whose deleted_at is set, most recently deleted first.
|
||||||
|
*
|
||||||
|
* @param int $houseId House id.
|
||||||
|
* @param int $listId List id.
|
||||||
|
* @param int<1, 1000> $limit Maximum number of items to return.
|
||||||
|
* @param int<0, max> $offset Number of items to skip.
|
||||||
|
*
|
||||||
|
* @return DataResponse<Http::STATUS_OK, list<PantryListItem>, array{}>
|
||||||
|
*
|
||||||
|
* 200: Deleted items returned
|
||||||
|
*/
|
||||||
|
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/lists/{listId}/items/trash')]
|
||||||
|
#[NoAdminRequired]
|
||||||
|
public function indexDeletedItems(int $houseId, int $listId, int $limit = 200, int $offset = 0): DataResponse {
|
||||||
|
return $this->runAction(function () use ($houseId, $listId, $limit, $offset): DataResponse {
|
||||||
|
$this->auth->requireMember($houseId, $this->requireUid());
|
||||||
|
$list = $this->lists->getList($listId);
|
||||||
|
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||||
|
$all = $this->lists->listDeletedItems($listId);
|
||||||
|
$sliced = array_slice($all, max(0, $offset), max(0, $limit));
|
||||||
|
$items = array_map(fn ($i) => $i->jsonSerialize(), $sliced);
|
||||||
|
return new DataResponse($items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an item to a list
|
* Add an item to a list
|
||||||
*
|
*
|
||||||
@@ -401,6 +429,84 @@ final class ChecklistController extends OCSController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a soft-deleted item back into the active list
|
||||||
|
*
|
||||||
|
* @param int $houseId House id.
|
||||||
|
* @param int $listId List id.
|
||||||
|
* @param int $itemId Item id.
|
||||||
|
*
|
||||||
|
* @return DataResponse<Http::STATUS_OK, PantryListItem, array{}>
|
||||||
|
*
|
||||||
|
* 200: Item restored
|
||||||
|
*/
|
||||||
|
#[ApiRoute(verb: 'POST', url: '/api/houses/{houseId}/lists/{listId}/items/{itemId}/restore')]
|
||||||
|
#[NoAdminRequired]
|
||||||
|
public function restoreItem(int $houseId, int $listId, int $itemId): DataResponse {
|
||||||
|
return $this->runAction(function () use ($houseId, $listId, $itemId): DataResponse {
|
||||||
|
$this->auth->requireMember($houseId, $this->requireUid());
|
||||||
|
$item = $this->lists->getItem($itemId, includeDeleted: true);
|
||||||
|
$list = $this->lists->getList($item->getListId());
|
||||||
|
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||||
|
if ($item->getListId() !== $listId) {
|
||||||
|
throw new NotFoundException('Item does not belong to this list');
|
||||||
|
}
|
||||||
|
$restored = $this->lists->restoreItem($itemId);
|
||||||
|
return new DataResponse($restored->jsonSerialize());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permanently delete an item, bypassing the trash
|
||||||
|
*
|
||||||
|
* Works on both live items and items already in trash.
|
||||||
|
*
|
||||||
|
* @param int $houseId House id.
|
||||||
|
* @param int $listId List id.
|
||||||
|
* @param int $itemId Item id.
|
||||||
|
*
|
||||||
|
* @return DataResponse<Http::STATUS_OK, PantrySuccess, array{}>
|
||||||
|
*
|
||||||
|
* 200: Item permanently deleted
|
||||||
|
*/
|
||||||
|
#[ApiRoute(verb: 'DELETE', url: '/api/houses/{houseId}/lists/{listId}/items/{itemId}/permanent')]
|
||||||
|
#[NoAdminRequired]
|
||||||
|
public function permanentlyDeleteItem(int $houseId, int $listId, int $itemId): DataResponse {
|
||||||
|
return $this->runAction(function () use ($houseId, $listId, $itemId): DataResponse {
|
||||||
|
$this->auth->requireMember($houseId, $this->requireUid());
|
||||||
|
$item = $this->lists->getItem($itemId, includeDeleted: true);
|
||||||
|
$list = $this->lists->getList($item->getListId());
|
||||||
|
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||||
|
if ($item->getListId() !== $listId) {
|
||||||
|
throw new NotFoundException('Item does not belong to this list');
|
||||||
|
}
|
||||||
|
$this->lists->permanentlyDeleteItem($itemId);
|
||||||
|
return new DataResponse(['success' => true]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty a list's trash, permanently deleting every soft-deleted item
|
||||||
|
*
|
||||||
|
* @param int $houseId House id.
|
||||||
|
* @param int $listId List id.
|
||||||
|
*
|
||||||
|
* @return DataResponse<Http::STATUS_OK, PantrySuccess, array{}>
|
||||||
|
*
|
||||||
|
* 200: Trash emptied
|
||||||
|
*/
|
||||||
|
#[ApiRoute(verb: 'DELETE', url: '/api/houses/{houseId}/lists/{listId}/items/trash')]
|
||||||
|
#[NoAdminRequired]
|
||||||
|
public function emptyTrash(int $houseId, int $listId): DataResponse {
|
||||||
|
return $this->runAction(function () use ($houseId, $listId): DataResponse {
|
||||||
|
$this->auth->requireMember($houseId, $this->requireUid());
|
||||||
|
$list = $this->lists->getList($listId);
|
||||||
|
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||||
|
$this->lists->emptyTrash($listId);
|
||||||
|
return new DataResponse(['success' => true]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch reorder items in a list
|
* Batch reorder items in a list
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -135,10 +135,37 @@ class ChecklistItemMapper extends QBMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find soft-deleted items in a list, most recently deleted first.
|
||||||
|
*
|
||||||
|
* @return ChecklistItem[]
|
||||||
|
*/
|
||||||
|
public function findDeletedByList(int $listId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->isNotNull('deleted_at'))
|
||||||
|
->orderBy('deleted_at', 'DESC');
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
public function deleteByList(int $listId): void {
|
public function deleteByList(int $listId): void {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->delete($this->getTableName())
|
$qb->delete($this->getTableName())
|
||||||
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)));
|
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)));
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hard-delete every soft-deleted item in the list.
|
||||||
|
*/
|
||||||
|
public function emptyTrashForList(int $listId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->isNotNull('deleted_at'));
|
||||||
|
$qb->executeStatement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,9 +106,18 @@ class ChecklistService {
|
|||||||
return $this->itemMapper->findByList($listId, $sortBy);
|
return $this->itemMapper->findByList($listId, $sortBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getItem(int $itemId): ChecklistItem {
|
/**
|
||||||
|
* List soft-deleted items for a list. Most recently deleted first.
|
||||||
|
*
|
||||||
|
* @return ChecklistItem[]
|
||||||
|
*/
|
||||||
|
public function listDeletedItems(int $listId): array {
|
||||||
|
return $this->itemMapper->findDeletedByList($listId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getItem(int $itemId, bool $includeDeleted = false): ChecklistItem {
|
||||||
try {
|
try {
|
||||||
return $this->itemMapper->findById($itemId);
|
return $this->itemMapper->findById($itemId, $includeDeleted);
|
||||||
} catch (DoesNotExistException) {
|
} catch (DoesNotExistException) {
|
||||||
throw new NotFoundException('Item not found');
|
throw new NotFoundException('Item not found');
|
||||||
}
|
}
|
||||||
@@ -360,6 +369,33 @@ class ChecklistService {
|
|||||||
$this->itemMapper->update($item);
|
$this->itemMapper->update($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permanently remove an item, regardless of whether it is currently in
|
||||||
|
* trash. Bypasses the soft-delete row and erases it from the table.
|
||||||
|
*/
|
||||||
|
public function permanentlyDeleteItem(int $itemId): void {
|
||||||
|
$item = $this->getItem($itemId, includeDeleted: true);
|
||||||
|
$this->itemMapper->delete($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a soft-deleted item by clearing its deleted_at marker.
|
||||||
|
*/
|
||||||
|
public function restoreItem(int $itemId): ChecklistItem {
|
||||||
|
$item = $this->getItem($itemId, includeDeleted: true);
|
||||||
|
$item->setDeletedAt(null);
|
||||||
|
$item->setUpdatedAt(time());
|
||||||
|
$this->itemMapper->update($item);
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hard-delete every soft-deleted item in the list.
|
||||||
|
*/
|
||||||
|
public function emptyTrash(int $listId): void {
|
||||||
|
$this->itemMapper->emptyTrashForList($listId);
|
||||||
|
}
|
||||||
|
|
||||||
private function strOrNull(mixed $v): ?string {
|
private function strOrNull(mixed $v): ?string {
|
||||||
if (!is_string($v)) {
|
if (!is_string($v)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
490
openapi.json
490
openapi.json
@@ -336,7 +336,8 @@
|
|||||||
"imageUploadedBy",
|
"imageUploadedBy",
|
||||||
"sortOrder",
|
"sortOrder",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedAt"
|
"updatedAt",
|
||||||
|
"deletedAt"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -410,6 +411,11 @@
|
|||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"deletedAt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2100,6 +2106,249 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/trash": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "checklist-index-deleted-items",
|
||||||
|
"summary": "List soft-deleted items in a checklist (trash)",
|
||||||
|
"description": "Returns items whose deleted_at is set, most recently deleted first.",
|
||||||
|
"tags": [
|
||||||
|
"checklist"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer_auth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"basic_auth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "houseId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "House id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "listId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "List id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Maximum number of items to return.",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"default": 200,
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items to skip.",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OCS-APIRequest",
|
||||||
|
"in": "header",
|
||||||
|
"description": "Required to be true for the API request to pass",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Deleted items returned",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ListItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Current user is not logged in",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "checklist-empty-trash",
|
||||||
|
"summary": "Empty a list's trash, permanently deleting every soft-deleted item",
|
||||||
|
"tags": [
|
||||||
|
"checklist"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer_auth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"basic_auth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "houseId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "House id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "listId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "List id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OCS-APIRequest",
|
||||||
|
"in": "header",
|
||||||
|
"description": "Required to be true for the API request to pass",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Trash emptied",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Current user is not logged in",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/{itemId}": {
|
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/{itemId}": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"operationId": "checklist-update-item",
|
"operationId": "checklist-update-item",
|
||||||
@@ -2531,6 +2780,245 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/{itemId}/restore": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "checklist-restore-item",
|
||||||
|
"summary": "Restore a soft-deleted item back into the active list",
|
||||||
|
"tags": [
|
||||||
|
"checklist"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer_auth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"basic_auth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "houseId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "House id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "listId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "List id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "itemId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Item id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OCS-APIRequest",
|
||||||
|
"in": "header",
|
||||||
|
"description": "Required to be true for the API request to pass",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Item restored",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/ListItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Current user is not logged in",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/{itemId}/permanent": {
|
||||||
|
"delete": {
|
||||||
|
"operationId": "checklist-permanently-delete-item",
|
||||||
|
"summary": "Permanently delete an item, bypassing the trash",
|
||||||
|
"description": "Works on both live items and items already in trash.",
|
||||||
|
"tags": [
|
||||||
|
"checklist"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer_auth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"basic_auth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "houseId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "House id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "listId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "List id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "itemId",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Item id.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OCS-APIRequest",
|
||||||
|
"in": "header",
|
||||||
|
"description": "Required to be true for the API request to pass",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Item permanently deleted",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Current user is not logged in",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ocs"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ocs": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"meta",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"$ref": "#/components/schemas/OCSMeta"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/reorder": {
|
"/ocs/v2.php/apps/pantry/api/houses/{houseId}/lists/{listId}/items/reorder": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "checklist-reorder-items",
|
"operationId": "checklist-reorder-items",
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ export async function listItems(
|
|||||||
return resp.data ?? []
|
return resp.data ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listDeletedItems(houseId: number, listId: number): Promise<ChecklistItem[]> {
|
||||||
|
const resp = await ocs.get<ChecklistItem[]>(`/houses/${houseId}/lists/${listId}/items/trash`)
|
||||||
|
return resp.data ?? []
|
||||||
|
}
|
||||||
|
|
||||||
export interface ItemInput {
|
export interface ItemInput {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -98,6 +103,29 @@ export async function deleteItem(houseId: number, listId: number, itemId: number
|
|||||||
await ocs.delete(`/houses/${houseId}/lists/${listId}/items/${itemId}`)
|
await ocs.delete(`/houses/${houseId}/lists/${listId}/items/${itemId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function permanentlyDeleteItem(
|
||||||
|
houseId: number,
|
||||||
|
listId: number,
|
||||||
|
itemId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
await ocs.delete(`/houses/${houseId}/lists/${listId}/items/${itemId}/permanent`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restoreItem(
|
||||||
|
houseId: number,
|
||||||
|
listId: number,
|
||||||
|
itemId: number,
|
||||||
|
): Promise<ChecklistItem> {
|
||||||
|
const resp = await ocs.post<ChecklistItem>(
|
||||||
|
`/houses/${houseId}/lists/${listId}/items/${itemId}/restore`,
|
||||||
|
)
|
||||||
|
return resp.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function emptyTrash(houseId: number, listId: number): Promise<void> {
|
||||||
|
await ocs.delete(`/houses/${houseId}/lists/${listId}/items/trash`)
|
||||||
|
}
|
||||||
|
|
||||||
export async function reorderItems(
|
export async function reorderItems(
|
||||||
houseId: number,
|
houseId: number,
|
||||||
listId: number,
|
listId: number,
|
||||||
|
|||||||
@@ -52,11 +52,17 @@
|
|||||||
</template>
|
</template>
|
||||||
{{ strings.moveItem }}
|
{{ strings.moveItem }}
|
||||||
</NcActionButton>
|
</NcActionButton>
|
||||||
|
<NcActionButton v-if="trashMode" close-after-click @click="$emit('restore', item.id)">
|
||||||
|
<template #icon>
|
||||||
|
<DeleteRestoreIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
{{ strings.restoreItem }}
|
||||||
|
</NcActionButton>
|
||||||
<NcActionButton close-after-click @click="$emit('remove', item.id)">
|
<NcActionButton close-after-click @click="$emit('remove', item.id)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DeleteIcon :size="20" />
|
<DeleteIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
{{ strings.removeItem }}
|
{{ trashMode ? strings.deletePermanently : strings.removeItem }}
|
||||||
</NcActionButton>
|
</NcActionButton>
|
||||||
</NcActions>
|
</NcActions>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,6 +80,7 @@ import RepeatIcon from '@icons/Repeat.vue'
|
|||||||
import PencilIcon from '@icons/Pencil.vue'
|
import PencilIcon from '@icons/Pencil.vue'
|
||||||
import EyeIcon from '@icons/Eye.vue'
|
import EyeIcon from '@icons/Eye.vue'
|
||||||
import DeleteIcon from '@icons/Delete.vue'
|
import DeleteIcon from '@icons/Delete.vue'
|
||||||
|
import DeleteRestoreIcon from '@icons/DeleteRestore.vue'
|
||||||
import ArrowRightIcon from '@icons/ArrowRight.vue'
|
import ArrowRightIcon from '@icons/ArrowRight.vue'
|
||||||
import { categoryIconComponent } from '@/components/CategoryPicker'
|
import { categoryIconComponent } from '@/components/CategoryPicker'
|
||||||
import { itemImagePreviewUrl } from '@/api/images'
|
import { itemImagePreviewUrl } from '@/api/images'
|
||||||
@@ -86,8 +93,9 @@ const props = withDefaults(
|
|||||||
category: Category | null
|
category: Category | null
|
||||||
houseId: number
|
houseId: number
|
||||||
reorderEnabled?: boolean
|
reorderEnabled?: boolean
|
||||||
|
trashMode?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{ reorderEnabled: false },
|
{ reorderEnabled: false, trashMode: false },
|
||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -96,6 +104,7 @@ const emit = defineEmits<{
|
|||||||
edit: [item: ChecklistItem]
|
edit: [item: ChecklistItem]
|
||||||
move: [item: ChecklistItem]
|
move: [item: ChecklistItem]
|
||||||
remove: [id: number]
|
remove: [id: number]
|
||||||
|
restore: [id: number]
|
||||||
preview: [item: ChecklistItem]
|
preview: [item: ChecklistItem]
|
||||||
'drag-start': [itemId: number]
|
'drag-start': [itemId: number]
|
||||||
'reorder-over': [itemId: number, event: MouseEvent]
|
'reorder-over': [itemId: number, event: MouseEvent]
|
||||||
@@ -143,6 +152,8 @@ const strings = {
|
|||||||
editItem: t('pantry', 'Edit item'),
|
editItem: t('pantry', 'Edit item'),
|
||||||
moveItem: t('pantry', 'Move to list'),
|
moveItem: t('pantry', 'Move to list'),
|
||||||
removeItem: t('pantry', 'Remove item'),
|
removeItem: t('pantry', 'Remove item'),
|
||||||
|
deletePermanently: t('pantry', 'Delete permanently'),
|
||||||
|
restoreItem: t('pantry', 'Restore'),
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -82,13 +82,16 @@ export function useChecklistItems(houseId: number, listId: number) {
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const sortBy = ref<ChecklistItemSort>('custom')
|
const sortBy = ref<ChecklistItemSort>('custom')
|
||||||
|
const trashMode = ref(false)
|
||||||
|
|
||||||
async function load(sort?: ChecklistItemSort): Promise<void> {
|
async function load(sort?: ChecklistItemSort): Promise<void> {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
const s = sort ?? sortBy.value
|
const s = sort ?? sortBy.value
|
||||||
try {
|
try {
|
||||||
items.value = await api.listItems(houseId, listId, s)
|
items.value = trashMode.value
|
||||||
|
? await api.listDeletedItems(houseId, listId)
|
||||||
|
: await api.listItems(houseId, listId, s)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = (e as Error).message
|
error.value = (e as Error).message
|
||||||
} finally {
|
} finally {
|
||||||
@@ -150,6 +153,25 @@ export function useChecklistItems(houseId: number, listId: number) {
|
|||||||
items.value = items.value.filter((i) => i.id !== itemId)
|
items.value = items.value.filter((i) => i.id !== itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removePermanently(itemId: number): Promise<void> {
|
||||||
|
await api.permanentlyDeleteItem(houseId, listId, itemId)
|
||||||
|
items.value = items.value.filter((i) => i.id !== itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restore(itemId: number): Promise<void> {
|
||||||
|
await api.restoreItem(houseId, listId, itemId)
|
||||||
|
// The item leaves the current view: in trash mode it returns to the active
|
||||||
|
// list (and stays hidden here); in active mode it was never visible.
|
||||||
|
items.value = items.value.filter((i) => i.id !== itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emptyTrash(): Promise<void> {
|
||||||
|
await api.emptyTrash(houseId, listId)
|
||||||
|
if (trashMode.value) {
|
||||||
|
items.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function uploadImage(itemId: number, file: File): Promise<void> {
|
async function uploadImage(itemId: number, file: File): Promise<void> {
|
||||||
const updated = await api.uploadItemImage(houseId, listId, itemId, file)
|
const updated = await api.uploadItemImage(houseId, listId, itemId, file)
|
||||||
items.value = items.value.map((i) => (i.id === itemId ? updated : i))
|
items.value = items.value.map((i) => (i.id === itemId ? updated : i))
|
||||||
@@ -165,12 +187,16 @@ export function useChecklistItems(houseId: number, listId: number) {
|
|||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
sortBy,
|
sortBy,
|
||||||
|
trashMode,
|
||||||
load,
|
load,
|
||||||
add,
|
add,
|
||||||
update,
|
update,
|
||||||
toggle,
|
toggle,
|
||||||
reorderItems,
|
reorderItems,
|
||||||
remove,
|
remove,
|
||||||
|
removePermanently,
|
||||||
|
restore,
|
||||||
|
emptyTrash,
|
||||||
uploadImage,
|
uploadImage,
|
||||||
clearImage,
|
clearImage,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</NcButton>
|
</NcButton>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<NcActions :aria-label="strings.sortLabel" type="tertiary">
|
<NcActions :aria-label="strings.sortLabel" :title="strings.sortLabel" type="tertiary">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<SortIcon :size="20" />
|
<SortIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
@@ -30,6 +30,18 @@
|
|||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</NcActionButton>
|
</NcActionButton>
|
||||||
</NcActions>
|
</NcActions>
|
||||||
|
<NcButton
|
||||||
|
:variant="trashMode ? 'primary' : 'tertiary'"
|
||||||
|
:aria-label="strings.trashLabel"
|
||||||
|
:title="strings.trashLabel"
|
||||||
|
:aria-pressed="trashMode"
|
||||||
|
@click="toggleTrash"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<TrashCanIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
{{ strings.trashLabel }}
|
||||||
|
</NcButton>
|
||||||
<NcButton variant="primary" @click="showCategoryManager = true">
|
<NcButton variant="primary" @click="showCategoryManager = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<TagIcon :size="20" />
|
<TagIcon :size="20" />
|
||||||
@@ -57,15 +69,24 @@
|
|||||||
|
|
||||||
<NcEmptyContent
|
<NcEmptyContent
|
||||||
v-else-if="items.length === 0"
|
v-else-if="items.length === 0"
|
||||||
:name="strings.emptyTitle"
|
:name="trashMode ? strings.trashEmptyTitle : strings.emptyTitle"
|
||||||
:description="strings.emptyBody"
|
:description="trashMode ? strings.trashEmptyBody : strings.emptyBody"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<component :is="checklistIconComponent(list?.icon)" />
|
<TrashCanIcon v-if="trashMode" />
|
||||||
|
<component :is="checklistIconComponent(list?.icon)" v-else />
|
||||||
</template>
|
</template>
|
||||||
</NcEmptyContent>
|
</NcEmptyContent>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<div v-if="trashMode" class="pantry-detail__trash-bar">
|
||||||
|
<NcButton variant="error" @click="confirmingEmptyTrash = true">
|
||||||
|
<template #icon>
|
||||||
|
<TrashCanIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
{{ strings.emptyTrashAction }}
|
||||||
|
</NcButton>
|
||||||
|
</div>
|
||||||
<ul v-if="uncheckedItems.length > 0" ref="uncheckedListRef" class="pantry-detail__items">
|
<ul v-if="uncheckedItems.length > 0" ref="uncheckedListRef" class="pantry-detail__items">
|
||||||
<template v-for="gi in uncheckedGridItems" :key="gi.key">
|
<template v-for="gi in uncheckedGridItems" :key="gi.key">
|
||||||
<li
|
<li
|
||||||
@@ -80,11 +101,13 @@
|
|||||||
:category="categoryFor(gi.item.categoryId)"
|
:category="categoryFor(gi.item.categoryId)"
|
||||||
:house-id="houseIdNum"
|
:house-id="houseIdNum"
|
||||||
:reorder-enabled="isCustomSort"
|
:reorder-enabled="isCustomSort"
|
||||||
|
:trash-mode="trashMode"
|
||||||
@toggle="handleToggle"
|
@toggle="handleToggle"
|
||||||
@view="openView"
|
@view="openView"
|
||||||
@edit="startEdit"
|
@edit="startEdit"
|
||||||
@move="startMoveItem"
|
@move="startMoveItem"
|
||||||
@remove="handleRemove"
|
@remove="handleRemove"
|
||||||
|
@restore="handleRestore"
|
||||||
@preview="openPreview"
|
@preview="openPreview"
|
||||||
@drag-start="onItemDragStart"
|
@drag-start="onItemDragStart"
|
||||||
@reorder-over="onReorderOver"
|
@reorder-over="onReorderOver"
|
||||||
@@ -186,6 +209,22 @@
|
|||||||
@update:open="showCreateForMove = $event"
|
@update:open="showCreateForMove = $event"
|
||||||
@save="submitCreateListAndMove"
|
@save="submitCreateListAndMove"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<NcDialog
|
||||||
|
v-if="confirmingEmptyTrash"
|
||||||
|
:name="strings.emptyTrashTitle"
|
||||||
|
:open="confirmingEmptyTrash"
|
||||||
|
close-on-click-outside
|
||||||
|
@update:open="(v) => !v && (confirmingEmptyTrash = false)"
|
||||||
|
>
|
||||||
|
<p>{{ strings.emptyTrashConfirm }}</p>
|
||||||
|
<template #actions>
|
||||||
|
<NcButton @click="confirmingEmptyTrash = false">{{ strings.cancel }}</NcButton>
|
||||||
|
<NcButton variant="error" @click="submitEmptyTrash">
|
||||||
|
{{ strings.emptyTrashAction }}
|
||||||
|
</NcButton>
|
||||||
|
</template>
|
||||||
|
</NcDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -204,6 +243,7 @@ import SortIcon from '@icons/Sort.vue'
|
|||||||
import RadioboxBlankIcon from '@icons/RadioboxBlank.vue'
|
import RadioboxBlankIcon from '@icons/RadioboxBlank.vue'
|
||||||
import RadioboxMarkedIcon from '@icons/RadioboxMarked.vue'
|
import RadioboxMarkedIcon from '@icons/RadioboxMarked.vue'
|
||||||
import TagIcon from '@icons/Tag.vue'
|
import TagIcon from '@icons/Tag.vue'
|
||||||
|
import TrashCanIcon from '@icons/TrashCan.vue'
|
||||||
import PageToolbar from '@/components/PageToolbar'
|
import PageToolbar from '@/components/PageToolbar'
|
||||||
import { ChecklistAddForm } from '@/components/ChecklistAddForm'
|
import { ChecklistAddForm } from '@/components/ChecklistAddForm'
|
||||||
import { ChecklistFilter } from '@/components/ChecklistFilter'
|
import { ChecklistFilter } from '@/components/ChecklistFilter'
|
||||||
@@ -237,10 +277,26 @@ const {
|
|||||||
toggle,
|
toggle,
|
||||||
reorderItems,
|
reorderItems,
|
||||||
remove,
|
remove,
|
||||||
|
removePermanently,
|
||||||
|
restore,
|
||||||
|
emptyTrash,
|
||||||
uploadImage,
|
uploadImage,
|
||||||
clearImage,
|
clearImage,
|
||||||
sortBy,
|
sortBy,
|
||||||
|
trashMode,
|
||||||
} = useChecklistItems(houseIdNum.value, listIdNum.value)
|
} = useChecklistItems(houseIdNum.value, listIdNum.value)
|
||||||
|
|
||||||
|
async function toggleTrash() {
|
||||||
|
trashMode.value = !trashMode.value
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmingEmptyTrash = ref(false)
|
||||||
|
|
||||||
|
async function submitEmptyTrash() {
|
||||||
|
confirmingEmptyTrash.value = false
|
||||||
|
await emptyTrash()
|
||||||
|
}
|
||||||
const categories = useCategories(houseIdNum.value)
|
const categories = useCategories(houseIdNum.value)
|
||||||
|
|
||||||
function categoryFor(id: number | null) {
|
function categoryFor(id: number | null) {
|
||||||
@@ -531,7 +587,15 @@ async function handleToggle(itemId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemove(itemId: number) {
|
async function handleRemove(itemId: number) {
|
||||||
await remove(itemId)
|
if (trashMode.value) {
|
||||||
|
await removePermanently(itemId)
|
||||||
|
} else {
|
||||||
|
await remove(itemId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRestore(itemId: number) {
|
||||||
|
await restore(itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Edit -----
|
// ----- Edit -----
|
||||||
@@ -617,11 +681,21 @@ const strings = {
|
|||||||
back: t('pantry', 'Back to lists'),
|
back: t('pantry', 'Back to lists'),
|
||||||
emptyTitle: t('pantry', 'No items yet'),
|
emptyTitle: t('pantry', 'No items yet'),
|
||||||
emptyBody: t('pantry', 'Add items using the form above.'),
|
emptyBody: t('pantry', 'Add items using the form above.'),
|
||||||
|
trashEmptyTitle: t('pantry', 'Trash is empty'),
|
||||||
|
trashEmptyBody: t('pantry', 'Deleted items will appear here.'),
|
||||||
sortLabel: t('pantry', 'Sort order'),
|
sortLabel: t('pantry', 'Sort order'),
|
||||||
|
trashLabel: t('pantry', 'Trash'),
|
||||||
doneTitle: t('pantry', 'Done'),
|
doneTitle: t('pantry', 'Done'),
|
||||||
manageCategories: t('pantry', 'Manage categories'),
|
manageCategories: t('pantry', 'Manage categories'),
|
||||||
moveToList: t('pantry', 'Move to list'),
|
moveToList: t('pantry', 'Move to list'),
|
||||||
newList: t('pantry', 'New list'),
|
newList: t('pantry', 'New list'),
|
||||||
|
emptyTrashAction: t('pantry', 'Empty trash'),
|
||||||
|
emptyTrashTitle: t('pantry', 'Empty trash?'),
|
||||||
|
emptyTrashConfirm: t(
|
||||||
|
'pantry',
|
||||||
|
'All deleted items in this list will be permanently removed. This cannot be undone.',
|
||||||
|
),
|
||||||
|
cancel: t('pantry', 'Cancel'),
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -652,6 +726,12 @@ const strings = {
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__trash-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
&__placeholder {
|
&__placeholder {
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
border: 3px dashed var(--color-primary-element);
|
border: 3px dashed var(--color-primary-element);
|
||||||
|
|||||||
Reference in New Issue
Block a user