История коммитов

.
fix(collections): build item URL from its own section path in root listing
The root listing shows items from every section, but the detail URL was built
from the current listing path, so a sectioned item shown at the collection root
got /collection/code.html without its section segment and 404'd. PublicItemDTO
now carries sectionId, and the listing resolves each item's section path
(getPathTo, memoized per page) to build /collection/section-path/code.html.
.
feat(collections): auto-generate item slug from name
When the item code is left empty it is generated as a slug of the name
(Str::slug, transliterating Cyrillic) in SaveCollectionItemUseCase and made
unique within the collection/section with a numeric suffix on collision
(base, base-2, ...). An explicit code is still rejected on duplicate. The admin
validation now treats code as optional; the facade addItem generates it too.
.
feat(collections): use CKEditor for item preview text
Preview text becomes rich HTML like detail text: the admin item form renders it
through the CKEditor partial (scripts loaded once, on the preview editor), and
the public output sanitizes it via ItemContentFormatter in both the listing
(ListPublicItemsUseCase) and the detail page, rendering purified HTML instead of
nl2br+escape.
.
feat(collections): add public flag to gate URL access
Separate 'reachable by URL' from 'active': a new collections.public boolean
(default 1). A private collection stays fully manageable in admin and readable
via the API/facade, but mints no front URL and is excluded from the sitemap.

URL resolver and sitemap now go through getPublicCodeMap() (active AND public,
renamed from getActiveCodeMap); the code cache stores the filtered map. Admin
form gains a Public checkbox. Reserved-code validation stays flag-independent
so a private code can go public without a segment clash.
.
feat(collections): public CollectionsApi facade for third-party modules
Add CollectionsApi (CollectionsApiInterface), a stable DI-public facade over
repositories and item use cases. Read:
findCollection/getItems/countItems/getItem/getItemByCode. Write: addItem
(returns new id), updateItem, deleteItem, reusing code-uniqueness checks and
EAV value rebuild.

SaveCollectionItemUseCase::execute now returns the item id (int) instead of
bool; the admin controller derives the create/update flash from $id.
.
feat(collections): rich detail text with CKEditor and output sanitization
Completes iteration 4 (public output).

- The item admin form edits detail_text with CKEditor via a module ckeditor
partial (the admin theme ships CkeditorInputComponent.vue; system::app/ckeditor
only exists in the default theme, so collections carries its own).
- Content is stored raw; GetPublicItemUseCase sanitizes detail_text on output
through ItemContentFormatter (injects \HTMLPurifier) and the detail template
renders it as-is. preview_text stays plain (nl2br + escape).
- ItemContentFormatter test (real HTMLPurifier strips script/on* handlers, keeps
safe tags); GetPublicItemUseCase test updated with a passthrough purifier.
.
feat(collections): add sitemap provider for public collection pages
Registers a johncms.sitemap_provider that emits URLs for each active
collection root, every reachable active section (paths built by walking the
tree; sections behind an inactive ancestor are skipped) and every published
item (getVisibleForSitemap streams them with a cursor), with lastmod from
updated_at. Items under an inactive section are skipped. Item repository gains
getVisibleForSitemap; DB test covers active-only output and unreachable skips.
.
feat(collections): show child sections in the public listing
Makes the section tree browsable on the public site.

- Section repository gains getActiveChildren(collectionId, parent): active
sections of a level, parent-scoped and sorted.
- CollectionRouterController lists the active child sections of the current
level as links above the items, so the root shows root sections and a section
shows its subsections. The empty-list notice appears only when neither
sections nor items exist.
- Section repository DB tests (active filter, parent scope, ordering).
.
feat(collections): reject reserved codes when saving a collection
Guards the public URL resolver against unreachable collections.

- ReservedCodeChecker (interface + impl) inspects the compiled Symfony
RouteCollection and collects claimed top-level segments: the literal first
segment of every route (placeholder-first paths like /{route} are skipped)
plus a static list of filesystem/entry paths (admin, assets, upload, system,
data, install, vendor, index.php).
- SaveCollectionUseCase checks isReserved() before persisting and throws
CollectionCodeReservedException; the admin controller surfaces it as a form
error. Tests cover the checker over sample routes and the reserved branch.
.
feat(collections): add public URL resolver with listing and detail
First public-output increment of iteration 4.

- Low-priority catch-all route /{route} (priority -1000, custom requirement for
nested paths and the .html suffix), registered outside the admin rights guard.
- CollectionRouterController parses the path: first segment resolves a collection
code (cached), a .html suffix renders the item detail, otherwise the section or
root listing. Breadcrumbs via NavChain, PageMeta and Pagination (per_page from
settings); missing collection/section/item -> pageNotFound.
- CollectionCodeCache (interface + Johncms\Cache impl) caches the active
code->id map so the resolver, which is also the catch-all 404 path, keeps
misses off the database; invalidated on collection save/delete.
- ListPublicItemsUseCase / GetPublicItemUseCase (visible-by-code with publish
window, values by field label); PublicItemDTO/PublicItemDetailDTO; repository
gains getActiveCodeMap and findVisibleByCode. Public listing/detail templates
(escape + nl2br).
- Real routes win by priority; the catch-all only handles otherwise-unmatched
URLs. Use case tests (listing mock, detail DB test).