1
0

feat: extract bookmarks as standalone module with MintyPHP\Module\Bookmarks namespace

Moves bookmarks from core hardcoding into modules/bookmarks/ as a
fully self-contained module, following the same pattern as addressbook.

Module contributions via platform slots:
- aside.tab_panel: bookmark sidebar panel
- topbar.right_item: bookmark toggle button
- layout.body_end_template: bookmark/group dialogs
- layout.head_style: bookmark CSS (form + sidebar)
- runtime.component: bookmark-save and bookmark-panel (phase: late)
- search.resource_item: bookmarks in global search (user-scoped via {{userId}})

Backend fully in module namespace (MintyPHP\Module\Bookmarks\*):
- Service: BookmarkService, BookmarkServicesFactory, BookmarkRepositoryFactory
- Repository: BookmarkRepository, BookmarkGroupRepository, BookmarkNavigationRepository
- Support: BookmarkUrlNormalizer
- Providers: BookmarksSessionProvider, BookmarksLayoutProvider, BookmarksSearchProvider

Core cleanup:
- Removed all bookmark-specific markup from core templates
- Removed core DI registrations for bookmark services
- Removed core bookmark pages, JS, CSS
- AuthSessionTenantContextService delegates to module SessionProvider

Architecture guards:
- NoBookmarksHardcodingTest: verifies zero bookmark references in core
- testModuleClassesUseModuleNamespace: prevents core namespace leakage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 22:20:20 +01:00
parent c7b8fd516a
commit 4871c6032e
37 changed files with 1514 additions and 294 deletions

View File

@@ -1,96 +0,0 @@
<?php
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Service\Bookmark\BookmarkService;
$bmSessionData = is_array(app(SessionStoreInterface::class)->get('user_bookmarks')) ? app(SessionStoreInterface::class)->get('user_bookmarks') : [];
$bmSessionGroups = is_array($bmSessionData['groups'] ?? null) ? $bmSessionData['groups'] : [];
?>
<dialog
data-app-bookmark-dialog
data-app-component="bookmark-save"
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>