Global Boomarks

This commit is contained in:
2026-03-14 21:45:58 +01:00
parent 921977bdd3
commit 9688848401
53 changed files with 4232 additions and 966 deletions

View File

@@ -86,7 +86,9 @@ if ($bufferTitle !== '') {
<?php require __DIR__ . '/partials/app-confirm-dialog.phtml'; ?>
<?php if ($isLoggedIn): ?>
<?php require __DIR__ . '/partials/app-session-warning-dialog.phtml'; ?>
<?php require __DIR__ . '/partials/app-bookmark-dialog.phtml'; ?>
<script type="module" src="<?php e(assetVersion('js/components/app-session-warning.js')); ?>"></script>
<script type="module" src="<?php e(assetVersion('js/components/app-bookmark-init.js')); ?>"></script>
<?php endif; ?>
<script type="module" src="<?php e(assetVersion('js/app-init.js')); ?>"></script>
</body>

View File

@@ -0,0 +1,94 @@
<?php
use MintyPHP\Service\Bookmark\BookmarkService;
$bmSessionData = is_array($_SESSION['user_bookmarks'] ?? null) ? $_SESSION['user_bookmarks'] : [];
$bmSessionGroups = is_array($bmSessionData['groups'] ?? null) ? $bmSessionData['groups'] : [];
?>
<dialog
data-app-bookmark-dialog
data-max-message="<?php e(t('Maximum bookmarks reached')); ?>"
data-title-create="<?php e(t('Bookmarks')); ?>"
data-title-edit="<?php e(t('Bookmarks')); ?>"
data-label-save="<?php e(t('Save')); ?>"
data-label-update="<?php e(t('Save')); ?>"
data-msg-saved="<?php e(t('Bookmark saved')); ?>"
data-msg-updated="<?php e(t('Bookmark updated')); ?>"
data-msg-group-created="<?php e(t('Group created')); ?>"
data-msg-error-generic="<?php e(t('Bookmark action failed')); ?>"
data-msg-group-create-failed="<?php e(t('Group action failed')); ?>"
data-msg-name-required="<?php e(t('Bookmark name is required')); ?>"
data-msg-url-required="<?php e(t('Bookmark URL is invalid')); ?>"
data-msg-group-not-found="<?php e(t('Group not found')); ?>"
aria-labelledby="app-bookmark-dialog-title"
>
<article class="app-bookmark-dialog">
<header>
<h2 id="app-bookmark-dialog-title"><?php e(t('Bookmarks')); ?></h2>
<button type="button" class="close" data-bookmark-dialog-close aria-label="<?php e(t('Close')); ?>" title="<?php e(t('Close')); ?>"></button>
</header>
<form data-bookmark-save-form>
<label for="bookmark-name"><?php e(t('Name')); ?></label>
<input type="text" id="bookmark-name" name="name" maxlength="120" required
placeholder="<?php e(t('Bookmark name')); ?>">
<label for="bookmark-group"><?php e(t('Assign to group')); ?></label>
<select id="bookmark-group" name="group_id" data-bookmark-group-select>
<option value=""><?php e(t('No group')); ?></option>
<?php foreach ($bmSessionGroups as $grp):
$grpId = (int) ($grp['id'] ?? 0);
$grpName = trim((string) ($grp['name'] ?? ''));
if ($grpId <= 0 || $grpName === '') { continue; }
?>
<option value="<?php e($grpId); ?>"><?php e($grpName); ?></option>
<?php endforeach; ?>
</select>
<button type="button" class="outline secondary app-bookmark-new-group-btn" data-bookmark-new-group>
<i class="bi bi-plus"></i> <?php e(t('New group')); ?>
</button>
<footer>
<button type="submit" class="app-action-success" data-bookmark-submit-label><?php e(t('Save')); ?></button>
</footer>
</form>
</article>
</dialog>
<dialog
data-app-bookmark-group-dialog
data-title-create="<?php e(t('New group')); ?>"
data-title-edit="<?php e(t('Edit group')); ?>"
data-label-save="<?php e(t('Save')); ?>"
data-label-update="<?php e(t('Save')); ?>"
data-max-message="<?php e(t('Maximum groups reached')); ?>"
data-msg-created="<?php e(t('Group created')); ?>"
data-msg-updated="<?php e(t('Group updated')); ?>"
data-msg-error-generic="<?php e(t('Group action failed')); ?>"
data-msg-name-required="<?php e(t('Group name is required')); ?>"
aria-labelledby="app-bookmark-group-dialog-title"
>
<article class="app-bookmark-dialog app-bookmark-group-dialog">
<header>
<h2 id="app-bookmark-group-dialog-title"><?php e(t('New group')); ?></h2>
<button type="button" class="close" data-bookmark-group-dialog-close aria-label="<?php e(t('Close')); ?>" title="<?php e(t('Close')); ?>"></button>
</header>
<form data-bookmark-group-save-form>
<label for="bookmark-group-name"><?php e(t('Group name')); ?></label>
<input type="text" id="bookmark-group-name" name="name" maxlength="100" required
placeholder="<?php e(t('Group name')); ?>">
<label><?php e(t('Icon')); ?></label>
<div class="app-bookmark-group-icon-picker" data-bookmark-group-icon-picker>
<?php foreach (BookmarkService::allowedGroupIcons() as $allowedIcon): ?>
<button type="button" class="app-bookmark-group-icon-option"
data-icon="<?php e($allowedIcon); ?>"
aria-label="<?php e($allowedIcon); ?>">
<i class="bi <?php e($allowedIcon); ?>"></i>
</button>
<?php endforeach; ?>
</div>
<input type="hidden" name="icon" value="bi-folder">
<footer>
<button type="submit" class="app-action-success" data-bookmark-group-submit-label><?php e(t('Save')); ?></button>
</footer>
</form>
</article>
</dialog>

View File

@@ -35,10 +35,11 @@ $addressBookUrl = lurl('address-book');
</li>
<?php endif; ?>
<li>
<button type="button" id="aside-tab-files" data-aside-target="files" role="tab" aria-selected="false"
aria-controls="aside-panel-files" aria-label="<?php e(t('Files')); ?>"
data-tooltip="<?php e(t('Files')); ?>" data-tooltip-pos="right">
<i class="bi bi-folder"></i>
<button type="button" id="aside-tab-bookmarks" data-aside-target="bookmarks" role="tab"
aria-selected="false" aria-controls="aside-panel-bookmarks"
aria-label="<?php e(t('Bookmarks')); ?>"
data-tooltip="<?php e(t('Bookmarks')); ?>" data-tooltip-pos="right">
<i class="bi bi-bookmark-star"></i>
</button>
</li>
<?php if ($hasAdminPanel): ?>

View File

@@ -1,6 +1,7 @@
<?php
use MintyPHP\Service\Docs\DocsCatalogService;
use MintyPHP\Support\BookmarkUrlNormalizer;
$layoutAuth = is_array($viewAuth['layout'] ?? null) ? $viewAuth['layout'] : [];
$canViewTenants = (bool) ($layoutAuth['can_view_tenants'] ?? false);
@@ -261,7 +262,6 @@ $activeAddressDepartments = is_array($addressBook['activeDepartments'] ?? null)
$activeAddressRoles = is_array($addressBook['activeRoles'] ?? null) ? $addressBook['activeRoles'] : [];
$activeAddressCustomFilters = is_array($addressBook['activeCustomFilters'] ?? null) ? $addressBook['activeCustomFilters'] : [];
$peopleGroups = is_array($addressBook['peopleGroups'] ?? null) ? $addressBook['peopleGroups'] : [];
$savedAddressFilters = is_array($addressBook['savedFilters'] ?? null) ? $addressBook['savedFilters'] : [];
$csrfKey = trim((string) ($layoutNav['csrfKey'] ?? \MintyPHP\Session::$csrfSessionKey));
$csrfToken = (string) ($layoutNav['csrfToken'] ?? '');
?>
@@ -315,87 +315,6 @@ $csrfToken = (string) ($layoutNav['csrfToken'] ?? '');
data-aside-details-storage="aside-people-tenant"
data-aside-title="<?php e(t('Address book')); ?>" role="tabpanel" aria-labelledby="aside-tab-people" hidden>
<ul>
<?php if ($savedAddressFilters): ?>
<li class="app-sidebar-group app-sidebar-saved-filters">
<small class="muted"><?php e(t('Saved filters')); ?></small>
<ul class="app-sidebar-saved-filter-list">
<?php foreach ($savedAddressFilters as $savedFilter): ?>
<?php
$savedUuid = trim((string) ($savedFilter['uuid'] ?? ''));
$savedName = trim((string) ($savedFilter['name'] ?? ''));
$savedQuery = $savedFilter['query'] ?? [];
$savedQuery = is_array($savedQuery) ? $savedQuery : [];
if ($savedUuid === '' || $savedName === '') {
continue;
}
$savedSearch = trim((string) ($savedQuery['search'] ?? ''));
$savedTenants = appNormalizeStringList($savedQuery['tenants'] ?? []);
$savedDepartments = appNormalizePositiveIntList($savedQuery['departments'] ?? []);
$savedRoles = appNormalizePositiveIntList($savedQuery['roles'] ?? []);
$savedCustomFilters = appNormalizeAddressBookCustomFilterQuery($savedQuery);
$savedQueryParams = [];
if ($savedSearch !== '') {
$savedQueryParams['search'] = $savedSearch;
}
if ($savedTenants) {
$savedQueryParams['tenants'] = implode(',', $savedTenants);
}
if ($savedDepartments) {
$savedQueryParams['departments'] = implode(',', $savedDepartments);
}
if ($savedRoles) {
$savedQueryParams['roles'] = implode(',', $savedRoles);
}
foreach ($savedCustomFilters as $customKey => $customValue) {
if (is_array($customValue)) {
$savedQueryParams[$customKey] = implode(',', $customValue);
} else {
$savedQueryParams[$customKey] = (string) $customValue;
}
}
$savedHref = $addressBookUrl;
if ($savedQueryParams) {
$savedHref .= '?' . http_build_query($savedQueryParams);
}
$isSavedFilterActive = $addressBookActive['isActive']
&& $savedSearch === $activeAddressSearch
&& $savedTenants === $activeAddressTenants
&& $savedDepartments === $activeAddressDepartments
&& $savedRoles === $activeAddressRoles
&& $savedCustomFilters === $activeAddressCustomFilters;
?>
<li class="app-sidebar-saved-filter-item">
<a href="<?php e($savedHref); ?>" class="<?php e($isSavedFilterActive ? 'active' : ''); ?>"
<?php echo $isSavedFilterActive ? 'aria-current="page"' : ''; ?>>
<?php e($savedName); ?>
</a>
<form method="post" action="<?php e(lurl('address-book/saved-filter-delete')); ?>"
class="app-sidebar-saved-filter-delete"
data-confirm-message="<?php e(t('Delete saved filter')); ?>?">
<input type="hidden" name="<?php e($csrfKey); ?>" value="<?php e($csrfToken); ?>">
<input type="hidden" name="uuid" value="<?php e($savedUuid); ?>">
<input type="hidden" name="search" value="<?php e($activeAddressSearch); ?>">
<input type="hidden" name="tenants" value="<?php e(implode(',', $activeAddressTenants)); ?>">
<input type="hidden" name="departments" value="<?php e(implode(',', $activeAddressDepartments)); ?>">
<input type="hidden" name="roles" value="<?php e(implode(',', $activeAddressRoles)); ?>">
<?php foreach ($activeAddressCustomFilters as $customKey => $customValue): ?>
<input type="hidden" name="<?php e($customKey); ?>" value="<?php e(is_array($customValue) ? implode(',', $customValue) : (string) $customValue); ?>">
<?php endforeach; ?>
<button type="submit" class="transparent"
title="<?php e(t('Delete saved filter')); ?>"
aria-label="<?php e(t('Delete saved filter')); ?>">
<i class="bi bi-trash"></i>
</button>
</form>
</li>
<?php endforeach; ?>
</ul>
</li>
<?php endif; ?>
<?php if (!$peopleGroups): ?>
<li>
<a href="<?php e($addressBookUrl); ?>" class="<?php e($addressBookActive['class'] ?? ''); ?>"
@@ -620,24 +539,242 @@ $csrfToken = (string) ($layoutNav['csrfToken'] ?? '');
</nav>
</div>
<nav id="aside-panel-files" class="app-sidebar-panel" data-aside-panel="files"
data-aside-title="<?php e(t('Files')); ?>" role="tabpanel" aria-labelledby="aside-tab-files" hidden>
<ul>
<li>
<a href="#"><?php e(t('All files')); ?></a>
</li>
<li>
<a href="#"><?php e(t('My files')); ?></a>
</li>
<li>
<a href="#"><?php e(t('Shared with me')); ?></a>
</li>
</ul>
<div class="app-sidebar-panel-tools" data-aside-panel-tools hidden>
<button type="button" class="icon-button" aria-label="<?php e(t('New folder')); ?>">
<i class="bi bi-folder-plus"></i>
</button>
</div>
<?php
$bookmarkData = is_array($layoutNav['bookmarks'] ?? null) ? $layoutNav['bookmarks'] : ['groups' => [], 'ungrouped' => []];
$bookmarkGroups = is_array($bookmarkData['groups'] ?? null) ? $bookmarkData['groups'] : [];
$bookmarkUngrouped = is_array($bookmarkData['ungrouped'] ?? null) ? $bookmarkData['ungrouped'] : [];
$currentPath = BookmarkUrlNormalizer::canonicalizeRequestUri((string) ($_SERVER['REQUEST_URI'] ?? ''), localeBase());
?>
<nav id="aside-panel-bookmarks" class="app-sidebar-panel" data-aside-panel="bookmarks"
data-aside-title="<?php e(t('Bookmarks')); ?>"
data-aside-details-storage="aside-bookmarks-groups-v1"
data-bookmark-reorder-url="<?php e(lurl('bookmarks/reorder-data')); ?>"
data-bookmark-group-delete-url="<?php e(lurl('bookmarks/group-delete-data')); ?>"
data-bookmark-delete-url="<?php e(lurl('bookmarks/delete-data')); ?>"
data-bookmark-msg-group-delete-confirm="<?php e(t('Delete group and keep bookmarks?')); ?>"
data-bookmark-msg-group-deleted="<?php e(t('Group deleted')); ?>"
data-bookmark-msg-group-updated="<?php e(t('Group updated')); ?>"
data-bookmark-msg-bookmark-delete-confirm="<?php e(t('Delete this bookmark?')); ?>"
data-bookmark-msg-bookmark-deleted="<?php e(t('Bookmark deleted')); ?>"
data-bookmark-msg-group-action-failed="<?php e(t('Group action failed')); ?>"
data-bookmark-msg-bookmark-action-failed="<?php e(t('Bookmark action failed')); ?>"
data-bookmark-msg-group-delete-failed="<?php e(t('Group delete failed')); ?>"
data-bookmark-msg-bookmark-delete-failed="<?php e(t('Bookmark action failed')); ?>"
data-bookmark-msg-reorder-saved="<?php e(t('Reorder saved')); ?>"
data-bookmark-msg-reorder-failed="<?php e(t('Reorder failed')); ?>"
data-bookmark-label-delete="<?php e(t('Delete')); ?>"
role="tabpanel" aria-labelledby="aside-tab-bookmarks" hidden>
<?php if (!$bookmarkGroups && !$bookmarkUngrouped): ?>
<?php
$emptyState = [
'message' => t('No bookmarks yet'),
'size' => 'compact',
'align' => 'center',
];
require templatePath('partials/app-empty-state.phtml');
?>
<?php else: ?>
<?php
$bookmarkRootItems = [];
foreach ($bookmarkUngrouped as $bm) {
$bmId = (int) ($bm['id'] ?? 0);
if ($bmId <= 0) {
continue;
}
$bookmarkRootItems[] = [
'type' => 'bookmark',
'id' => $bmId,
'sort_order' => (int) ($bm['sort_order'] ?? 0),
'bookmark' => $bm,
];
}
foreach ($bookmarkGroups as $group) {
$gId = (int) ($group['id'] ?? 0);
if ($gId <= 0) {
continue;
}
$bookmarkRootItems[] = [
'type' => 'group',
'id' => $gId,
'sort_order' => (int) ($group['sort_order'] ?? 0),
'group' => $group,
];
}
usort($bookmarkRootItems, static function (array $a, array $b): int {
$sortCmp = ((int) ($a['sort_order'] ?? 0)) <=> ((int) ($b['sort_order'] ?? 0));
if ($sortCmp !== 0) {
return $sortCmp;
}
$typeCmp = strcmp((string) ($a['type'] ?? ''), (string) ($b['type'] ?? ''));
if ($typeCmp !== 0) {
return $typeCmp;
}
return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
});
$rootCount = count($bookmarkRootItems);
?>
<ul data-bookmark-root-list>
<?php foreach ($bookmarkRootItems as $rootIndex => $rootItem): ?>
<?php
$rootType = (string) ($rootItem['type'] ?? '');
$rootFirst = $rootIndex === 0;
$rootLast = $rootIndex === ($rootCount - 1);
?>
<?php if ($rootType === 'bookmark'): ?>
<?php
$bm = is_array($rootItem['bookmark'] ?? null) ? $rootItem['bookmark'] : [];
$bmId = (int) ($bm['id'] ?? 0);
$bmName = trim((string) ($bm['name'] ?? ''));
$bmUrl = trim((string) ($bm['url'] ?? ''));
$bmCanonicalUrl = BookmarkUrlNormalizer::canonicalizeRelative($bmUrl);
$bmActive = $bmCanonicalUrl !== '' && $currentPath === $bmCanonicalUrl;
?>
<li class="app-sidebar-bookmark-root-item app-sidebar-bookmark-item"
data-bookmark-root-kind="bookmark"
data-bookmark-root-id="<?php e($bmId); ?>"
data-bookmark-id="<?php e($bmId); ?>"
data-bookmark-name="<?php e($bmName); ?>"
data-bookmark-group-id="">
<a href="<?php e(lurl($bmUrl)); ?>"
class="<?php e($bmActive ? 'active' : ''); ?>"
<?php echo $bmActive ? 'aria-current="page"' : ''; ?>>
<?php e($bmName); ?>
</a>
<details class="app-sidebar-bookmark-action-menu" data-bookmark-action-menu>
<summary class="icon-button" title="<?php e(t('Actions')); ?>" aria-label="<?php e(t('Actions')); ?>">
<i class="bi bi-three-dots"></i>
</summary>
<ul class="app-sidebar-bookmark-action-menu-list" role="menu" aria-label="<?php e(t('Bookmark actions')); ?>">
<li>
<button type="button" role="menuitem" data-bookmark-item-action="edit">
<?php e(t('Edit')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-item-action="move-up" data-bookmark-item-action-scope="root" <?php if ($rootFirst): ?>disabled<?php endif; ?>>
<?php e(t('Move bookmark up')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-item-action="move-down" data-bookmark-item-action-scope="root" <?php if ($rootLast): ?>disabled<?php endif; ?>>
<?php e(t('Move bookmark down')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" class="app-sidebar-bookmark-action-danger" data-bookmark-item-action="delete">
<?php e(t('Delete bookmark')); ?>
</button>
</li>
</ul>
</details>
</li>
<?php else: ?>
<?php
$group = is_array($rootItem['group'] ?? null) ? $rootItem['group'] : [];
$gId = (int) ($group['id'] ?? 0);
$gName = trim((string) ($group['name'] ?? ''));
$gIcon = trim((string) ($group['icon'] ?? 'bi-folder'));
$gBookmarks = is_array($group['bookmarks'] ?? null) ? $group['bookmarks'] : [];
?>
<li class="app-sidebar-group app-sidebar-bookmark-group app-sidebar-bookmark-root-item"
data-bookmark-root-kind="group"
data-bookmark-root-id="<?php e($gId); ?>"
data-bookmark-group-id="<?php e($gId); ?>"
data-bookmark-group-name="<?php e($gName); ?>"
data-bookmark-group-icon="<?php e($gIcon); ?>">
<div class="app-sidebar-bookmark-group-shell">
<div class="app-sidebar-bookmark-group-header">
<small class="app-sidebar-bookmark-group-label">
<i class="bi <?php e($gIcon); ?>" aria-hidden="true"></i>
<span><?php e($gName); ?></span>
</small>
<details class="app-sidebar-bookmark-action-menu app-sidebar-bookmark-group-menu" data-bookmark-action-menu>
<summary class="icon-button" title="<?php e(t('Actions')); ?>" aria-label="<?php e(t('Actions')); ?>">
<i class="bi bi-three-dots"></i>
</summary>
<ul class="app-sidebar-bookmark-action-menu-list" role="menu" aria-label="<?php e(t('Group actions')); ?>">
<li>
<button type="button" role="menuitem" data-bookmark-group-action="edit">
<?php e(t('Edit')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-group-action="move-up" <?php if ($rootFirst): ?>disabled<?php endif; ?>>
<?php e(t('Move group up')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-group-action="move-down" <?php if ($rootLast): ?>disabled<?php endif; ?>>
<?php e(t('Move group down')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" class="app-sidebar-bookmark-action-danger" data-bookmark-group-action="delete">
<?php e(t('Delete group')); ?>
</button>
</li>
</ul>
</details>
</div>
<?php if ($gBookmarks): ?>
<ul data-bookmark-group-bookmark-list>
<?php $groupBookmarkCount = count($gBookmarks); ?>
<?php foreach ($gBookmarks as $bookmarkIndex => $bm): ?>
<?php
$bmId = (int) ($bm['id'] ?? 0);
$bmName = trim((string) ($bm['name'] ?? ''));
$bmUrl = trim((string) ($bm['url'] ?? ''));
$bmCanonicalUrl = BookmarkUrlNormalizer::canonicalizeRelative($bmUrl);
$bmActive = $bmCanonicalUrl !== '' && $currentPath === $bmCanonicalUrl;
$bookmarkFirst = $bookmarkIndex === 0;
$bookmarkLast = $bookmarkIndex === ($groupBookmarkCount - 1);
?>
<li class="app-sidebar-bookmark-item"
data-bookmark-id="<?php e($bmId); ?>"
data-bookmark-name="<?php e($bmName); ?>"
data-bookmark-group-id="<?php e($gId); ?>">
<a href="<?php e(lurl($bmUrl)); ?>"
class="<?php e($bmActive ? 'active' : ''); ?>"
<?php echo $bmActive ? 'aria-current="page"' : ''; ?>>
<?php e($bmName); ?>
</a>
<details class="app-sidebar-bookmark-action-menu" data-bookmark-action-menu>
<summary class="icon-button" title="<?php e(t('Actions')); ?>" aria-label="<?php e(t('Actions')); ?>">
<i class="bi bi-three-dots"></i>
</summary>
<ul class="app-sidebar-bookmark-action-menu-list" role="menu" aria-label="<?php e(t('Bookmark actions')); ?>">
<li>
<button type="button" role="menuitem" data-bookmark-item-action="edit">
<?php e(t('Edit')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-item-action="move-up" <?php if ($bookmarkFirst): ?>disabled<?php endif; ?>>
<?php e(t('Move bookmark up')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" data-bookmark-item-action="move-down" <?php if ($bookmarkLast): ?>disabled<?php endif; ?>>
<?php e(t('Move bookmark down')); ?>
</button>
</li>
<li>
<button type="button" role="menuitem" class="app-sidebar-bookmark-action-danger" data-bookmark-item-action="delete">
<?php e(t('Delete bookmark')); ?>
</button>
</li>
</ul>
</details>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</nav>
</div>
</aside>

View File

@@ -1,6 +1,7 @@
<?php
use MintyPHP\Session;
use MintyPHP\Support\BookmarkUrlNormalizer;
$user = $_SESSION['user'] ?? [];
$accountUrl = accountUrl();
@@ -22,6 +23,23 @@ if ($tenantLabel === '') {
$canSwitchTenant = is_array($currentTenant) && count($availableTenants) > 1;
$allowUserTheme = allowUserTheme();
// Bookmark toggle: check if current page is already bookmarked
$bmData = is_array($_SESSION['user_bookmarks'] ?? null) ? $_SESSION['user_bookmarks'] : ['groups' => [], 'ungrouped' => []];
$bmCurrentUrl = BookmarkUrlNormalizer::canonicalizeRequestUri((string) ($_SERVER['REQUEST_URI'] ?? ''), localeBase());
$bmExisting = null;
foreach (($bmData['ungrouped'] ?? []) as $bm) {
$bmUrl = BookmarkUrlNormalizer::canonicalizeRelative((string) ($bm['url'] ?? ''));
if ($bmUrl !== '' && $bmUrl === $bmCurrentUrl) { $bmExisting = $bm; break; }
}
if ($bmExisting === null) {
foreach (($bmData['groups'] ?? []) as $grp) {
foreach (($grp['bookmarks'] ?? []) as $bm) {
$bmUrl = BookmarkUrlNormalizer::canonicalizeRelative((string) ($bm['url'] ?? ''));
if ($bmUrl !== '' && $bmUrl === $bmCurrentUrl) { $bmExisting = $bm; break 2; }
}
}
}
?>
<header class="app-header">
<nav class="app-topbar">
@@ -75,6 +93,18 @@ $allowUserTheme = allowUserTheme();
</details>
</li>
<?php endif; ?>
<li>
<button type="button" data-bookmark-open-dialog
aria-label="<?php e($bmExisting ? t('Edit bookmark') : t('Bookmarks')); ?>"
data-tooltip="<?php e($bmExisting ? t('Edit bookmark') : t('Bookmarks')); ?>" data-tooltip-pos="bottom"
<?php if ($bmExisting): ?>
data-bookmark-existing-id="<?php e((int) ($bmExisting['id'] ?? 0)); ?>"
data-bookmark-existing-name="<?php e(trim((string) ($bmExisting['name'] ?? ''))); ?>"
data-bookmark-existing-group="<?php e($bmExisting['group_id'] ?? ''); ?>"
<?php endif; ?>>
<i class="bi <?php e($bmExisting ? 'bi-bookmark-check-fill' : 'bi-bookmark-plus'); ?>"></i>
</button>
</li>
<li>
<details class="dropdown app-topbar-user-menu"
<?php if ($allowUserTheme): ?>