Repository navigation
feat(content-drive): replace Content Search and Site Browser for Content Drive users (#37759) - #37832
Conversation
…ntent Drive (#37827) When a user's menu has Content Drive but not Content Search or Site Browser, MenuGuardService now sends their URLs to the Content Drive equivalent instead of the first portlet: - /c/content and /c/site-browser open Content Drive - /c/content?filter=<variable> opens it filtered by that content type - /c/content/<inode> looks the content up and opens it by identifier and language (?editContent=&editContentLang=) - /c/content/new/<type> opens ?createContent=<type> Edit and create links restore the CD_ params Content Drive adds when it hands off to the legacy editor. Edit and create links that come from Content Drive itself are not redirected, since Content Drive still sends legacy-editor content to those URLs and redirecting would loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… double navigation (#37759) - Containers and templates stored as files now pass their folder to goToSiteBrowser(path). The menu guard turns //<host>/<folder>/ into Content Drive's ?path=, switching to that site first when it is not the current one. The legacy Site Browser still reads the folder from the session, so users who have it see no change. - When the inode lookup fails and the error handler has already navigated (a 401 goes to login), the guard now stops instead of also going to the first portlet. Rejections with no Content Drive equivalent are told apart with null. - The variant id is cleared on the Content Drive redirect too, not only when going to the first portlet. - The loop protection now covers edit links only. Nothing reads createContent yet, so a legacy create from Content Drive stays in Content Drive instead of jumping to the first portlet. - The Content Search filter goes over as contentType only, with no lookup: the Content Types chip now fills in the base type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#37759) The chip lists content types under their selected base type, so a link that names only a content type (filters=contentType:Blog) filtered the grid but left the chip empty. The chip now adds the base types of the host's content types, resolved from the content types it already loads, so no extra request. Content types the server does not return are dropped from the host's selection after a successful load, so a deleted type or a typo in an old link does not keep filtering with nothing to clear. Content Drive's init effect no longer tracks the default language: it re-ran when the language landed and re-read the page's original URL over any filter changed in between, including that cleanup. loadDefaultLanguage already seeds the language into the current filters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- menu-guard spec: the guard result starts undefined until the guard emits. - container list spec: narrow the file container found in the mock list. - Content Drive store: pass SYSTEM_HOST while the site has not loaded, as initContentDrive already falls back to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
…ith the stack (#37759) - Follow the stack's numbering: route redirects (#37829) are Part 1, the legacy-editor side panel is Part 2. - Renumber the functional requirements so they increase in document order. - State the create location once: the folder Content Drive is showing, the `path` of a `createContent` link, or the site root. - Record that a workflow wizard action reports a save like a plain save (checked in edit_contentlet_js_inc.jsp), and add the rejected-save case. - Align with what #37829 implemented: the redirect keeps folder and filters, CD_ params are added on every legacy hand-off until this work, and page creates follow the same create flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l spec The stack with #37829 was closed and its commits brought into this branch, so the spec now says Part 1 is already on the branch and Part 2's implementation follows in the same PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…7759) - Open the Scope with what this work delivers. - Legacy-editor content always opens in the panel, whatever the side-panel flag says, so the CD_ round trip from #33726 and the guard's loop check are removed (FR-006, FR-026, FR-027). - The URL follows the open panel for both editors: editContent + editContentLang for edits, createContent=<type> for creates, switching to the edit params after the first save, all removed on close (US7, FR-020, FR-024, FR-025). - Record that push publish reaches the legacy editor through workflow actions and that it has no Add to bundle action of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de-panel spec (#37759) New Folder, Folder Settings and Edit Permissions write createFolder=true, editFolder=<identifier> and folderPermissions=<identifier> while open, reopen from the URL and remove the param on close (US8, FR-030 to FR-033). Tests move to FR-034. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Technical notes for #37759These are the implementation notes that lived in the #37759 description. The issue now describes only the business rules, for QA, so the notes move here to stay at hand for Part 2 (not implemented yet)
Code pointers
Part 1 (implemented in this PR): notes and the inventory of links it fixes
Content Drive URL format. Content Drive's Links this fixes Legacy editor links (
Content Search list links (
Site Browser links (
Not covered: server-rendered JSP and Java links that use |
zJaaal
left a comment
There was a problem hiding this comment.
Second pass on the spec only, now that the earlier threads are addressed. Six things to tighten: language sync in the legacy panel, compare plus Bring Back, the rationale for refreshing on close, clearing incompatible URL params (and the default language for createContent), listening only to the legacy events we need, and a wording slip in SC-001.
Written by Claude Code on behalf of @zJaaal.
…e panel (#37759) - editContentLang follows a language switch inside the legacy panel. - Bring Back in the compare dialog restores the version, closes the dialog and refreshes the list. - The panel listens only to the legacy editor events its requirements need; page-editor links stay plain links. - A URL with params that can't hold together opens nothing. - Every create starts in Content Drive's default language. - State the refresh on close as a plain rule and fix SC-001's scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rjvelazco
left a comment
There was a problem hiding this comment.
See the discussion here: https://dotcms.slack.com/archives/C06M2Q9PY7R/p1790864379308299
zJaaal
left a comment
There was a problem hiding this comment.
Narrowing the spec down after a discussion about the side-panel flag. Two decisions: the legacy panel copies the new-editor panel's p-drawer pattern with the legacy editor in an iframe, and Content Drive stops reading FEATURE_FLAG_EDIT_CONTENT_SIDE_PANEL (both editors always open in a panel). UVE, Query Tool and the relationship field are untouched, and the flag itself stays as it is. The wizard, push publish and event handling stay as the spec already has them.
Written by Claude Code on behalf of @zJaaal.
…he spec (#37759) - Content Drive stops reading FEATURE_FLAG_EDIT_CONTENT_SIDE_PANEL: both editors always open in a panel, chosen only by the type's editor setting (FR-005 to FR-007, FR-022). The flag stays for UVE, Query Tool and the relationship field. - Remove every flag-off and flag-unresolved case. - The legacy panel copies the new-editor panel's drawer, with the legacy editor in an iframe (FR-012). - Record that the legacy panel stays separate so it is easy to remove. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rjvelazco
left a comment
There was a problem hiding this comment.
You have some alerts, can you check them?
…l work (#37759) The PR's strict typecheck gate reported 31 violations on lines this branch wrote, plus one TypeScript only surfaced once those were gone. - withDialog: declare dialog/dialogDrillDown as `X | undefined` instead of optional, so the store's `dialog` signal itself is never optional. - content.feature: assert contentType in switchToLegacyEditor; the update call has already read its id by then. - Specs: bracket access on deep-link query params, non-null Drawer query, a typed vi.fn() for defaultLanguageId instead of mocking a Signal, and drop the extra argument to SpectatorService.inject. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
zJaaal
left a comment
There was a problem hiding this comment.
Re-review after the override token. This is in good shape: edit-content is back to main except the token, the override branch in content.feature.ts and the language work, and every opener other than Content Drive keeps main's navigation. Having the override return a boolean and match on what the editor was opened with is a nice touch: it also handles nested editors without the relationship field passing anything down.
Two small things inline, neither blocking.
Written by Claude Code on behalf of @zJaaal.
zJaaal
left a comment
There was a problem hiding this comment.
Two structural points worth fixing in this PR. A follow-up suggestion, not for this PR: the shell grew from 2310 to 2734 lines with the URL-driven state (pending create/folder effects, folder-link resolution, Back handling, URL writes), and the navigation service doubled. A dedicated URL-state service or store feature would be a better home later.
Written by Claude Code on behalf of @zJaaal.
… side panel (#37759) - The Folder Settings and Edit Permissions links share one opener: the folder, the user's access and the site are resolved the same way, a folder in another site is refused, and only the permission checked and the dialog opened differ. - The legacy panel's models move from the component folder to `shared/legacy-editor.models.ts`, so shared code no longer imports from a component. - Every piece that goes with the legacy editor says "Remove with the legacy editor", so removing it is one grep: the panel, its models, the `'legacy'` panel request, the navigation service's legacy branches and its `EditContentNavigationOverride` implementation, the shell's block and handlers, and the override token with its two branches in `content.feature.ts`. - The folder-dialog URL tests assert with `toHaveBeenLastCalledWith` instead of reading `createUrlTree.mock.calls`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
- The legacy panel covers its iframe with a spinner until the editor has loaded, and again while the editor reloads itself (a save, a language switch, a restored version), detected through `pagehide` on the editor's window. - The new editor showed nothing on its first load, because the layout renders only once there is content. It now shows the same spinner, over the whole editor. This is the layout every host uses, so the full-page editor, the Query Tool, UVE and the relationship field get it too. Both use the spinner the new editor already shows while it reloads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in (#37759) Both callers, the shell's pending create and the content type picker, passed `currentFolder()`, so the parameter carried nothing the navigation service didn't already know. `createContent` now reads it itself, before the type lookup, so the content lands in the folder the author was looking at when they asked even if they browse while the request is in flight. `currentFolder()` is private now. Its tests go through `createContent`, setting the folder in the store, and one more covers browsing during the lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt-in-the-side-panel
|
|
…37759) - Layout spec: patch the editor store through unprotected(), the @ngrx/signals/testing helper, instead of writing a protected store. - Navigation spec: give the mocked selected folder the id, path and hostname its node type requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The one PR for #37759: Content Drive replaces Content Search and Site Browser for users who only have Content Drive in their menu. The issue has two parts, and they were first split into stacked PRs. We closed the stack (#37829) to keep things simple, so both parts land here.
This PR now carries both parts, implemented:
750e33d645,0936c76c1f,7cefdd247d,bbd5a22c10). Part 2 later removed theCD_restoration and the loop check from them (see below).specs/37759-content-drive-legacy-side-panel/spec.md), its data model and contracts, and the implementation.Please review the Part 2 code. The spec was reviewed in the first revision.
Why
When a user's menu has Content Drive but not Content Search or Site Browser, every
/c/content…and/c/site-browser…URL hitsMenuGuardService. The guard finds the portlet missing and sends the user to their first portlet with no explanation. That breaks every link from other screens. Inside Content Drive, it breaks editing and creating any content whose type still uses the legacy editor. The server does not block the legacy editor (UVE already embeds it); only the Angular route does.Part 1: route redirects
When the portlet is missing and Content Drive is in the menu,
MenuGuardServicereturns the Content Drive equivalent instead of going to the first portlet. Every/c/:idroute goes through this guard, so internal navigations,routerLinks, and full-page links from JSPs, emails and bookmarks are all covered./c/content/content-drive/c/content?filter=<variable>/content-drive?filters=contentType:<variable>/c/content/<inode>/content-drive?editContent=<identifier>&editContentLang=<languageId>(looked up withGET /api/v1/content/<inode>)/c/content/new/<type>/content-drive?createContent=<type>/c/content/new/content-drive/c/site-browser/content-drive/c/site-browser?path=//<host>/<folder>//content-drive?path=/<folder>/, switching to<host>first when it is not the current sitevariantName, the legacyfolderinode) are dropped.DotRouterService.goToSiteBrowser(path), and the guard maps it with the host stripped. The legacy Site Browser still reads the session, so users who have it see no change.filters=contentType:Blog) now shows it in the chip.DotContentTypeFilterComponent(libs/ui, shared with the asset picker) fills in the implied base type and drops content types the server doesn't return. The Content Drive store's init effect no longer re-runs when the default language arrives, which used to overwrite filters changed in between.Part 2: legacy editor in the side panel
Content Drive opens legacy-editor content in its own side panel. The panel copies the new-editor panel and hosts the legacy editor in an iframe. The links Part 1 builds now open in the editor each content type chose. Content Drive stops reading the side-panel flag: both editors always open in a panel.
?editContent=link to legacy content (Query Tool, Publishing Queue, Block Editor, emails)?createContent=<type>link (starter onboarding cards)FEATURE_FLAG_EDIT_CONTENT_SIDE_PANEL) turned offeditContent+editContentLang. Create:editContent=new, without the typeeditContentLangfollows a language switch in either editor's panel, or a save of a new translation. Create:createContent=<type>, switching to the edit params after the first save. All removed on close (US7)createFolder=true,editFolder=<identifier>,folderPermissions=<identifier>, removed on close (US8)CD_params from #33726c/contentmain's behavior (US6)The panel also handles:
Every create starts in the language the list is filtered by, or in Content Drive's default language when there's no language filter (FR-003). Saving a new page in the legacy panel closes it and opens the page editor, as Content Search does (FR-008). The legacy editor's links to the page editor are plain links and keep working.
Where the code is:
libs/portlets/dot-content-drive):DotLegacyEditorSidePanelComponentis the new panel. It hosts the iframe and handles the legacyng-events.DotContentDriveNavigationServicedecides which panel opens. It keeps what the panel shows separate from what the URL says, so the URL can follow a save or a language switch without remounting the editor.resolveContentDriveUrlIntent).libs/edit-content):EDIT_CONTENT_NAVIGATION_OVERRIDEtoken.content.feature.tskeepsmain'srouter.navigatecalls for "switch to the old editor" and a load error, and asks the override first. Only Content Drive provides it.reloadContentcarries the language of a locale switch. The layout reports it once the reload runs, and the side panel emits it through alanguageChangedoutput.DotActionUrlServicemoved out of UVE so Content Drive can use it.DotFolderService.getFolderByIdandDotPermissionsService.getUserAccess, for the folder-dialog links.CD_producer, its readers in the contentlet wrapper and create screens, and the guard'sCD_restoration and loop check are removed.contentDrivebreadcrumb handler.docs/frontend/BREADCRUMBS.mdis updated.Decisions worth a look:
Each content type keeps its own editor. Always opening the new editor in the panel was rejected because it silently switches editors for types that opted out (FR-007). The new-editor setting (
CONTENT_EDITOR2_ENABLEDplus each type's opt-out) is unchanged and is the only thing that decides which panel opens.Content Drive stops reading the side-panel flag (changed on review). Both editors always open in a panel there, and the full-screen fallback for new-editor content goes away. Content Drive never hands off to the full-page legacy editor, so the
CD_round trip and the guard's loop check are removed (FR-006, FR-026, FR-027). The flag itself isn't removed or deprecated.The legacy panel lives in the Content Drive lib, its only consumer. Putting it in
libs/uiwould have risked a dependency cycle withedit-content. Dropping the legacy editor later never touches the new-editor panel, but it is more than that folder: it also removes the panel's models (shared/legacy-editor.models.ts), the'legacy'case ofDotContentDrivePanelRequest, the navigation service's legacy branches and itsEditContentNavigationOverrideimplementation, the shell's legacy block and handlers, and the override token below. Each piece says "Remove with the legacy editor", so one grep finds them all.The panel listens only to the legacy events it needs: close, save, data changed, workflow wizard, push publish and compare versions (FR-008). We're moving away from the legacy editor, so the spec doesn't commit to its whole event contract.
Only Content Drive changes "switch to the old editor" and a load error (changed on review).
main'srouter.navigatecannot work inside Content Drive:Instead, Content Drive provides an optional
EDIT_CONTENT_NAVIGATION_OVERRIDEon its shell. The editor asks it first and otherwise navigates exactly as onmain. Every other opener (Edit Content portlet, Query Tool, UVE, relationship field, asset picker) is unchanged. Content Drive answers only for the panel it opened, matched by what the editor was opened with, which stays the same across a language switch. Nested editors inherit the provider (a related content opened inside the panel), so they are declined and navigate as onmain. The token, its provider and the two branches carry the same "Remove with the legacy editor" marker.main's crash when switching a create that was never saved is unchanged outside Content Drive.The URL follows the language of the new-editor panel too. A language switch reloads the editor in place; once the reload runs, the panel reports the language and Content Drive keeps
editContentLangin step (replaceState), as it already did for the legacy panel.Refresh on every close. The panel closed, so the list refreshes; a delete isn't told apart from any other close. The cost is one extra list request on a close with no changes.
Links are untrusted input:
editContent,editFolderandfolderPermissionsare used only when shaped like a dotCMS identifier, andcreateContentonly when shaped like a type variable or id.Folder-dialog links follow the context menu's rules: Folder Settings needs edit permission and Edit Permissions needs permission to edit permissions. A New Folder link where New Folder isn't offered shows the standard 403. A folder that is gone or in another site shows the standard 404. In every case nothing opens and the param is removed.
A type the user can read but not create: the form opens and the editor refuses the save with its own message. Content Drive adds no permission check of its own.
Out of scope: any change to the side-panel flag outside Content Drive; related content opened from a Block Editor field inside either panel (the bubble menu navigates the whole window); server-rendered JSP/Java
p_p_id=content|site-browserlinks.Checklist
menu-guard.service.spec.tscovers each redirect row, lookup failure and the already-redirected case, folder paths on the current site, another site and an unknown host, and the "portlet still in menu" and "no Content Drive" cases.dot-content-type-filter.component.spec.tscovers the base-type fill-in and the unknown-type drop.dot-content-drive.store.spec.tscovers a filter changed before the default language lands. The router service, container list and template list specs assert the folder is passed.editContentlink orcreateContentlink, with no dependency on the flag.CD_removal, and the switch and load-error paths of the new-editor panel./content-driveroute.Additional Info
specs/37759-content-drive-legacy-side-panel/quickstart.md) were run by hand against a local instance with that menu. The e2e helpers that build/c/content?filter=URLs stay as they are: they run as an admin who still has Content Search, so the guard never redirects them.This PR fixes: #37759
🤖 Generated with Claude Code
This PR fixes: #37759