From d09057fe0aa505518f2792d2f40c51a2bb8ffbd0 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Wed, 11 Feb 2026 17:26:29 +0100 Subject: [PATCH 01/17] style: update article/add integration --- templates/admin/article/add.html.twig | 42 ++++++++++++--------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/templates/admin/article/add.html.twig b/templates/admin/article/add.html.twig index bc6cccabb..124bcb53d 100644 --- a/templates/admin/article/add.html.twig +++ b/templates/admin/article/add.html.twig @@ -7,31 +7,27 @@ {%- endblock -%} {%- block content_body -%} -
+
{%- block content_body_before -%}{%- endblock -%} + {% form_theme form '@RoadizRozier/forms.html.twig' %} - {{ form_start(form, { - 'attr': { - 'id': 'articles-form', - 'class': 'rz-form' - } - }) }}{{ form_widget(form) }} -
- {% apply spaceless %} - {% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} - {{ rz_button.root({ - attributes: { - type: 'submit', - 'data-action-save': '#articles-form', - title: 'articles.form.add'|trans, - }, - label: 'articles.form.add'|trans, - emphasis: 'primary', - icon: 'rz-icon-ri--save-line', - }) }} - {% endapply %} -
+ {{ form_start(form) }} + {{ form_widget(form) }} + {% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} + {{ rz_button.root({ + label: 'articles.form.add'|trans, + emphasis: 'primary', + color: 'success', + onDark: true, + icon: 'rz-icon-ri--save-line', + attributes: { + 'style': 'align-self: flex-start;', + 'type': 'submit', + } + }) }} + {{ form_end(form) }} + {%- block content_body_after -%}{%- endblock -%} -
+ {%- endblock -%} From 53817b2ccac21b89bdfcd6c8f6b1454774e94f7f Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 11:23:59 +0100 Subject: [PATCH 02/17] chore: fix link attr --- .../templates/macros/rz_page_header.html.twig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/RoadizRozierBundle/templates/macros/rz_page_header.html.twig b/lib/RoadizRozierBundle/templates/macros/rz_page_header.html.twig index 9bcacd5cb..d9620aecf 100644 --- a/lib/RoadizRozierBundle/templates/macros/rz_page_header.html.twig +++ b/lib/RoadizRozierBundle/templates/macros/rz_page_header.html.twig @@ -97,14 +97,18 @@ 'translationId': translation.id, 'locale': translation.locale })) %} + + {% set attr = { href: href } %} + {% if current == translation.getId %} + {% set attr = attr|merge({ 'aria-current': 'page' }) %} + {% endif %} + {{ rz_link.root({ class: class, label: translation.preferredLocale, - attributes: { - href: href, - (current == translation.getId) ? aria-current: 'page' : null, - } + attributes: attr, }) }} + {%- endfor -%} {% if pathAddTranslations is defined and pathAddTranslations %} {% set class = 'rz-button rz-button-group__button' %} From 7e11a9b8660ffb978b91418c45ab2ea71317406d Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 11:30:16 +0100 Subject: [PATCH 03/17] refactor: simplify and fix RzTree refresh --- .../templates/nodes/tree.html.twig | 2 + .../widgets/nodeTree/nodeTree.html.twig | 1 + .../widgets/nodeTree/singleNode.html.twig | 24 +-- .../tree/rz_tree_wrapper_auto.html.twig | 9 +- lib/Rozier/app/@types/rozier.d.ts | 8 - lib/Rozier/app/Rozier.ts | 60 +----- lib/Rozier/app/custom-elements/RzAside.ts | 171 +++--------------- .../RzNodeTreeContextualMenu.ts | 13 +- lib/Rozier/app/custom-elements/RzTree.ts | 82 ++++++--- lib/Rozier/app/session-message.ts | 61 +++++++ 10 files changed, 170 insertions(+), 261 deletions(-) create mode 100644 lib/Rozier/app/session-message.ts diff --git a/lib/RoadizRozierBundle/templates/nodes/tree.html.twig b/lib/RoadizRozierBundle/templates/nodes/tree.html.twig index 8a545eccd..70da20842 100644 --- a/lib/RoadizRozierBundle/templates/nodes/tree.html.twig +++ b/lib/RoadizRozierBundle/templates/nodes/tree.html.twig @@ -72,6 +72,8 @@ {% include '@RoadizRozier/widgets/nodeTree/nodeTree.html.twig' with { nodeTree: specificNodeTree, node: specificNodeTree.getRootNode, + isMainTree: false, + layout: 'condensed', } only %} {% else %} diff --git a/lib/RoadizRozierBundle/templates/widgets/nodeTree/nodeTree.html.twig b/lib/RoadizRozierBundle/templates/widgets/nodeTree/nodeTree.html.twig index 6402506a6..0d91348ba 100644 --- a/lib/RoadizRozierBundle/templates/widgets/nodeTree/nodeTree.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/nodeTree/nodeTree.html.twig @@ -16,6 +16,7 @@ "isMainTree": isMainTree, "level": 0, 'canReorder': nodeTree.canReorder, + "layout": layout, } only %} {% endfor %} diff --git a/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig b/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig index 0e123074d..e7506e3b4 100644 --- a/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig @@ -36,12 +36,12 @@ {% set innerClasses = innerClasses|merge(['rz-tree__item__node--not-visible']) %} {% endif %} -
  • 0, icon_class: icon_class, can_reorder: canReorder and not node.isLocked, - has_selection: nodeTree.isStackTree, + has_selection: layout == 'condensed' and not isMainTree and nodeTree.isStackTree, nodeTree: nodeTree, - isMainTree: isMainTree + isMainTree: isMainTree, + contextual_menu_id: 'node-contextual-menu-' ~ (isMainTree ? 'main-' : '') ~ node.id, } only %} {% block icon_content %} - {%- if not isMainTree and not nodeTree.isStackTree -%} + {%- if layout == 'condensed' -%} {% import '@RoadizRozier/macros/rz_button.html.twig' as rz_button %} {{ rz_button.root({ @@ -165,7 +165,7 @@ class: 'tree-contextualmenu-button', attributes: { 'aria-label': 'show.actions'|trans, - popovertarget: contextual_id ~ '-popover', + popovertarget: contextual_menu_id ~ '-popover', type: 'button', } })}} diff --git a/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig b/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig index 61fc5edc2..d73994146 100644 --- a/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig @@ -81,20 +81,23 @@ {% if tree_type == 'node' %} {% include '@RoadizRozier/widgets/nodeTree/nodeTree.html.twig' with { nodeTree: tree, - isMainTree: true, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), linkedTypes: linkedTypes|default(null), + layout: 'condensed', } %} {% elseif tree_type == 'tag' %} {% include '@RoadizRozier/widgets/tagTree/tagTree.html.twig' with { tagTree: tree, - isMainTree: true, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + layout: 'condensed', } %} {% elseif tree_type == 'folder' %} {% include '@RoadizRozier/widgets/folderTree/folderTree.html.twig' with { folderTree: tree, - isMainTree: true, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + layout: 'condensed', } %} {% endif %} {% endblock %} diff --git a/lib/Rozier/app/@types/rozier.d.ts b/lib/Rozier/app/@types/rozier.d.ts index e466dc875..19ea992d0 100644 --- a/lib/Rozier/app/@types/rozier.d.ts +++ b/lib/Rozier/app/@types/rozier.d.ts @@ -8,7 +8,6 @@ export default class Rozier { canvasLoader: CanvasLoader | null lazyload: Lazyload vueApp: VueApp - nodeStatuses?: unknown onDocumentReady(): void bindMainTrees(): void @@ -20,13 +19,6 @@ export default class Rozier { } declare global { - interface RzAsideElement extends HTMLElement { - bindMainTrees?: () => void - refreshMainNodeTree?: (translationId?: number) => Promise | void - refreshMainTagTree?: (translationId?: number) => Promise | void - refreshMainFolderTree?: (translationId?: number) => Promise | void - } - interface CanvasLoader { setColor(color: string): void setShape(shape: string): void diff --git a/lib/Rozier/app/Rozier.ts b/lib/Rozier/app/Rozier.ts index 161b25702..441ef9579 100644 --- a/lib/Rozier/app/Rozier.ts +++ b/lib/Rozier/app/Rozier.ts @@ -1,5 +1,7 @@ import Lazyload from '~/Lazyload' import VueApp from '~/App' +import { dispatchSessionToast } from '~/session-message' +import type RzAside from './custom-elements/RzAside' /** * Rozier root entry @@ -40,11 +42,11 @@ export default class Rozier { } getAsideElement() { - return document.querySelector('rz-aside') as RzAsideElement | null + return document.querySelector('rz-aside') as RzAside | null } bindMainTrees() { - this.getAsideElement()?.bindMainTrees?.() + // this.getAsideElement()?.bindMainTrees?.() } refreshMainNodeTree(translationId?: number) { @@ -76,60 +78,8 @@ export default class Rozier { this.canvasLoader.setFPS(30) } - async fetchSessionMessages() { - const query = new URLSearchParams({ - _csrf_token: window.RozierConfig.ajaxToken, - }) - const url = - window.RozierConfig.routes.ajaxSessionMessages + - '?' + - query.toString() - const response = await fetch(url, { - method: 'GET', - headers: { - 'X-Requested-With': 'XMLHttpRequest', - Accept: 'application/json', - }, - }) - const data = (await response.json()) as { messages?: unknown } - if (!data.messages) { - return [] - } - return data.messages - } - - /** - * Get messages. - */ async getMessages() { - const messages = (await this.fetchSessionMessages()) as { - confirm?: string[] - error?: string[] - } - if (messages.confirm && messages.confirm.length > 0) { - messages.confirm.forEach((message) => { - window.dispatchEvent( - new CustomEvent('pushToast', { - detail: { - message: message, - status: 'success', - }, - }), - ) - }) - } - if (messages.error && messages.error.length > 0) { - messages.error.forEach((message) => { - window.dispatchEvent( - new CustomEvent('pushToast', { - detail: { - message: message, - status: 'danger', - }, - }), - ) - }) - } + await dispatchSessionToast() } resize() { diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index 6f509e532..7c8c408bb 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -3,27 +3,15 @@ import { fadeIn, fadeOut } from '~/utils/animation' import { sleep } from '~/utils/sleep' export default class RzAside extends RoadizElement { - private onPageShowEnd: () => void - private onAllNodeTreeChange: () => void - private onMainTreeRefresh: () => void - private onBindMainTreesRequest: () => void - private onAjaxLinkBindRequest: () => void - private onMessagesRefresh: () => void private onLangButtonClick: (event: Event) => void - private onMainTreeContextMenu: (event: Event) => void + private currentTranslationId: number | null = null constructor() { super() - this.onPageShowEnd = this.handlePageShowEnd.bind(this) - this.onAllNodeTreeChange = this.handleAllNodeTreeChange.bind(this) - this.onMainTreeRefresh = this.handleMainTreeRefresh.bind(this) - this.onBindMainTreesRequest = this.handleBindMainTreesRequest.bind(this) - this.onAjaxLinkBindRequest = this.handleAjaxLinkBindRequest.bind(this) - this.onMessagesRefresh = this.handleMessagesRefresh.bind(this) + this.onPageShowEnd = this.onPageShowEnd.bind(this) this.onLangButtonClick = this.handleLangButtonClick.bind(this) - this.onMainTreeContextMenu = - this.maintreeElementNameRightClick.bind(this) + this.handleMessagesRefresh = this.handleMessagesRefresh.bind(this) } private get rozier() { @@ -33,31 +21,10 @@ export default class RzAside extends RoadizElement { connectedCallback() { this.listen(this, 'click', this.onLangButtonClick) this.refreshAsideMainTree() - window.addEventListener('pageshowend', this.onPageShowEnd) - window.addEventListener( - 'requestAllNodeTreeChange', - this.onAllNodeTreeChange, - ) - window.addEventListener( - 'requestAllNodeTreeRefresh', - this.onAllNodeTreeChange, - ) - window.addEventListener( - 'requestMainTreeRefresh', - this.onMainTreeRefresh, - ) - window.addEventListener( - 'requestBindMainTrees', - this.onBindMainTreesRequest, - ) - window.addEventListener( - 'requestAjaxLinkBind', - this.onAjaxLinkBindRequest, - ) window.addEventListener( 'requestMessagesRefresh', - this.onMessagesRefresh, + this.handleMessagesRefresh, ) } @@ -65,42 +32,13 @@ export default class RzAside extends RoadizElement { super.disconnectedCallback() window.removeEventListener('pageshowend', this.onPageShowEnd) - window.removeEventListener( - 'requestAllNodeTreeChange', - this.onAllNodeTreeChange, - ) - window.removeEventListener( - 'requestAllNodeTreeRefresh', - this.onAllNodeTreeChange, - ) - window.removeEventListener( - 'requestMainTreeRefresh', - this.onMainTreeRefresh, - ) - window.removeEventListener( - 'requestBindMainTrees', - this.onBindMainTreesRequest, - ) - window.removeEventListener( - 'requestAjaxLinkBind', - this.onAjaxLinkBindRequest, - ) window.removeEventListener( 'requestMessagesRefresh', - this.onMessagesRefresh, + this.handleMessagesRefresh, ) } - private handlePageShowEnd() { - this.refreshAsideMainTree() - } - - private handleAllNodeTreeChange() { - this.refreshAllNodeTrees() - window.dispatchEvent(new CustomEvent('requestMessagesRefresh')) - } - - private handleMainTreeRefresh() { + private onPageShowEnd() { this.refreshAsideMainTree() } @@ -108,14 +46,6 @@ export default class RzAside extends RoadizElement { this.rozier?.getMessages?.() } - private handleBindMainTreesRequest() { - this.bindMainTrees() - } - - private handleAjaxLinkBindRequest() { - this.rozier?.lazyload?.bindAjaxLink?.() - } - private get treeContainer() { return this.querySelector('.rz-aside__body') as HTMLElement | null } @@ -148,74 +78,15 @@ export default class RzAside extends RoadizElement { this.refreshMainTagTree(translationId) } } - - /** - * Bind main trees - */ - bindMainTrees() { - const treeElements = this.querySelectorAll('.tree-element-name') - treeElements.forEach((element) => { - element.removeEventListener( - 'contextmenu', - this.onMainTreeContextMenu, - ) - element.addEventListener('contextmenu', this.onMainTreeContextMenu) - }) - } - - /** - * Main tree element name right click. - * @return {boolean} - */ - maintreeElementNameRightClick(event: Event) { - event.preventDefault() - - document - .querySelectorAll('.tree-contextualmenu') - .forEach((contextualMenu) => { - if (contextualMenu.classList.contains('uk-open')) { - contextualMenu.classList.remove('uk-open') - } - }) - - const target = event.currentTarget as HTMLElement | null - const contextualMenu = target?.parentElement?.querySelector( - '.tree-contextualmenu', - ) - if (contextualMenu) { - if (!contextualMenu.classList.contains('uk-open')) { - contextualMenu.classList.add('uk-open') - } else { - contextualMenu.classList.remove('uk-open') - } - } - - return false - } - - /** - * @param translationId - */ - refreshAllNodeTrees(translationId?: number) { - const promises: Array> = [] - promises.push(this.refreshMainNodeTree(translationId)) - - if (this.rozier?.lazyload?.stackNodeTrees?.treeAvailable?.()) { - promises.push(this.rozier.lazyload.stackNodeTrees.refreshNodeTree()) - } - - return Promise.all(promises) - } - - async refreshAsideTreeContent(treeHTML = '') { + async refreshTreeContent(treeHTML = '') { if (!treeHTML) { - console.warn('No treeHTML provided to refreshAsideTreeContent') + console.warn('No treeHTML provided to refreshTreeContent') return } const treeContainer = this.treeContainer if (!treeContainer) { - console.warn('No tree container found to refreshAsideTreeContent') + console.warn('No tree container found to refreshTreeContent') return } @@ -236,8 +107,6 @@ export default class RzAside extends RoadizElement { await fadeIn(treeContainer) - this.bindMainTrees() - this.rozier?.resize?.() this.rozier?.lazyload?.bindAjaxLink?.() } @@ -297,17 +166,21 @@ export default class RzAside extends RoadizElement { if (data && typeof treeHTML !== 'undefined') { const treeTypeId = - `${data.tree_type || 'node'}-tree` + - (queryOptions?.translationId - ? `-${queryOptions?.translationId}` - : '-main-locale') + `type-${data.tree_type || 'node'}-tree` + + '-t-' + + (this.currentTranslationId || 'main-locale') - if (treeTypeId === treeContainer.getAttribute('data-tree-id')) { - return - } - - this.refreshAsideTreeContent(treeHTML) + await this.refreshTreeContent(treeHTML) treeContainer.setAttribute('data-tree-id', treeTypeId) + + const translationId = + queryOptions?.translationId?.toString() || + this.querySelector( + '.rz-aside__langs button.rz-button--selected', + )?.getAttribute('data-translation-id') || + '' + this.currentTranslationId = + translationId !== '' ? Number(translationId) : null } } diff --git a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts index c0b328b02..1c1b8750e 100644 --- a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts +++ b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts @@ -283,13 +283,11 @@ export default class RzNodeTreeContextualMenu extends HTMLElement { window.dispatchEvent(new CustomEvent('requestLoaderShow')) const nodeTreeElement = (event.currentTarget as HTMLElement).closest( - '.nodetree-element', + '.rz-tree__item', ) const parentNodeId = parseInt( - nodeTreeElement - .closest('ul') - ?.getAttribute('data-parent-node-id') || '', + nodeTreeElement.closest('ul')?.getAttribute('data-parent-id') || '', ) const postData: UpdatePayloadDict = { @@ -349,7 +347,12 @@ export default class RzNodeTreeContextualMenu extends HTMLElement { } else { const data = await response.json() window.dispatchEvent( - new CustomEvent('requestAllNodeTreeChange'), + new CustomEvent('requestAllNodeTreeChange', { + detail: { + nodeId: this.nodeId, + treeElement: this.closest('rz-tree'), + }, + }), ) return data } diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index 5f4095eb8..bf3f9a597 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -12,6 +12,8 @@ export default class RzTree extends HTMLElement { this.sortables = [] this.onSortableChange = this.onSortableChange.bind(this) this.onCommand = this.onCommand.bind(this) + this.onRequestAllNodeTreeChange = + this.onRequestAllNodeTreeChange.bind(this) } get group() { @@ -65,11 +67,27 @@ export default class RzTree extends HTMLElement { this.syncCollapsedState() this.bindExpandButtons() this.addEventListener('command', this.onCommand) + + window.addEventListener( + 'requestAllNodeTreeChange', + this.onRequestAllNodeTreeChange, + ) } disconnectedCallback() { this.removeEventListener('command', this.onCommand) this.destroySortable() + + window.removeEventListener( + 'requestAllNodeTreeChange', + this.onRequestAllNodeTreeChange, + ) + } + + onRequestAllNodeTreeChange(event: Event) { + event.preventDefault() + console.log('onRequestAllNodeTreeChange', event, this) + this.refreshNodeTree() } onCommand(event: CommandEvent) { @@ -78,6 +96,7 @@ export default class RzTree extends HTMLElement { this.onToggleChildren(event) break case '--quick-add-child-node': + // Add child from children-nodes-widget (form) context this.onQuickAddNode(event) break } @@ -102,10 +121,7 @@ export default class RzTree extends HTMLElement { } getRootNodeId() { - const rootList = this.rootList - if (!rootList) return null - - const rootNodeId = rootList.getAttribute('data-parent-id') + const rootNodeId = this.rootList?.getAttribute('data-parent-id') if (!rootNodeId) return null const parsedId = parseInt(rootNodeId, 10) @@ -184,17 +200,22 @@ export default class RzTree extends HTMLElement { } } - async fetchNodeTree( - rootNodeId: number, - linkedTypes: string[], - translationId: number | null = null, - ) { - const params = new URLSearchParams({ + async fetchNodeTree() { + const linkedTypes = this.getRootLinkedTypes() + const translationId = this.getRootTranslationId() + + const options = { _token: window.RozierConfig.ajaxToken, _action: 'requestNodeTree', - parentNodeId: rootNodeId.toString(), translationId: translationId ? translationId.toString() : '', - }) + } + + const rootNodeId = this.getRootNodeId() + if (rootNodeId) { + Object.assign(options, { parentNodeId: rootNodeId.toString() }) + } + const params = new URLSearchParams(options) + linkedTypes.forEach((type, i) => { params.append(`linkedTypes[${i}]`, type) }) @@ -217,23 +238,22 @@ export default class RzTree extends HTMLElement { return await response.json() } - async refreshNodeTree() { - const rootNodeId = this.getRootNodeId() - if (!rootNodeId) { - console.log('No node-tree available.') - return - } - - const linkedTypes = this.getRootLinkedTypes() - const translationId = this.getRootTranslationId() + async refreshOtherTrees() { + document + .querySelectorAll('rz-tree') + ?.forEach(async (instance: RzTree) => { + if (instance !== this) { + await instance.refreshNodeTree() + } + }) + } + async refreshNodeTree() { window.dispatchEvent(new CustomEvent('requestLoaderShow')) + try { - const data = await this.fetchNodeTree( - rootNodeId, - linkedTypes, - translationId, - ) + const data = await this.fetchNodeTree() + if (typeof data.nodeTree !== 'undefined') { const wrapper = document.createElement('div') wrapper.innerHTML = data.nodeTree @@ -270,9 +290,9 @@ export default class RzTree extends HTMLElement { await fadeIn(this) } - window.dispatchEvent(new CustomEvent('requestNestablesInit')) - window.dispatchEvent(new CustomEvent('requestBindMainTrees')) - window.dispatchEvent(new CustomEvent('requestAjaxLinkBind')) + // window.dispatchEvent(new CustomEvent('requestNestablesInit')) + // window.dispatchEvent(new CustomEvent('requestBindMainTrees')) + // window.dispatchEvent(new CustomEvent('requestAjaxLinkBind')) } } catch (error) { await this.pushRequestError(error) @@ -524,10 +544,12 @@ export default class RzTree extends HTMLElement { throw response } const data = await response.json() + const message = this.entityType === 'node' ? data.responseText || data.detail : data.responseText + window.dispatchEvent( new CustomEvent('pushToast', { detail: { @@ -536,6 +558,8 @@ export default class RzTree extends HTMLElement { }, }), ) + + await this.refreshOtherTrees() } catch (response) { const data = await response.json() window.dispatchEvent( diff --git a/lib/Rozier/app/session-message.ts b/lib/Rozier/app/session-message.ts new file mode 100644 index 000000000..4e8845183 --- /dev/null +++ b/lib/Rozier/app/session-message.ts @@ -0,0 +1,61 @@ +type SessionMessagesResponse = { + messages?: { + confirm?: string[] + error?: string[] + } +} + +export async function fetchSessionMessages() { + const query = new URLSearchParams({ + _csrf_token: window.RozierConfig.ajaxToken, + }) + + const url = + window.RozierConfig.routes.ajaxSessionMessages + '?' + query.toString() + + const response = await fetch(url, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + Accept: 'application/json', + }, + }) + + const data = (await response.json()) as SessionMessagesResponse + + if (!data.messages) return null + + return data.messages +} + +export async function dispatchSessionToast() { + const messages = await fetchSessionMessages() + console.log('Session messages:', messages) + + if (!messages) return + + if (messages.confirm && messages.confirm.length > 0) { + messages.confirm.forEach((message) => { + window.dispatchEvent( + new CustomEvent('pushToast', { + detail: { + message: message, + status: 'success', + }, + }), + ) + }) + } + if (messages.error && messages.error.length > 0) { + messages.error.forEach((message) => { + window.dispatchEvent( + new CustomEvent('pushToast', { + detail: { + message: message, + status: 'danger', + }, + }), + ) + }) + } +} From 274dd2a97af7a1700a184906d14059e0a902dc06 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:10:39 +0100 Subject: [PATCH 04/17] refactor: simplify and group file logic --- .../Ajax/Tree/AjaxFolderTreeController.php | 2 +- .../Ajax/Tree/AjaxNodeTreeController.php | 2 +- .../Ajax/Tree/AjaxTagTreeController.php | 2 +- .../widgets/folderTree/folderTree.html.twig | 1 + .../rz_folder_tree_wrapper.html.twig | 32 ----- .../nodeTree/rz_node_tree_wrapper.html.twig | 45 ------- .../templates/widgets/rz_aside.html.twig | 4 +- .../widgets/rz_tree_wrapper.html.twig | 58 --------- .../widgets/rz_tree_wrapper_auto.html.twig | 113 ++++++++++++++++++ .../tagTree/rz_tag_tree_wrapper.html.twig | 31 ----- .../widgets/tagTree/tagTree.html.twig | 1 + .../tree/rz_tree_wrapper_auto.html.twig | 104 ---------------- 12 files changed, 119 insertions(+), 276 deletions(-) delete mode 100644 lib/RoadizRozierBundle/templates/widgets/folderTree/rz_folder_tree_wrapper.html.twig delete mode 100644 lib/RoadizRozierBundle/templates/widgets/nodeTree/rz_node_tree_wrapper.html.twig delete mode 100644 lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper.html.twig create mode 100644 lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig delete mode 100644 lib/RoadizRozierBundle/templates/widgets/tagTree/rz_tag_tree_wrapper.html.twig delete mode 100644 lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig diff --git a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxFolderTreeController.php b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxFolderTreeController.php index c108393cf..6efbb2f84 100644 --- a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxFolderTreeController.php +++ b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxFolderTreeController.php @@ -71,7 +71,7 @@ public function getTreeAction(Request $request): JsonResponse 'statusCode' => '200', 'status' => 'success', 'tree_type' => $assignation['tree_type'], - 'folderTree' => $this->twig->render('@RoadizRozier/widgets/tree/rz_tree_wrapper_auto.html.twig', $assignation), + 'folderTree' => $this->twig->render('@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig', $assignation), ]); } } diff --git a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxNodeTreeController.php b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxNodeTreeController.php index 8582a7b16..ff543f4db 100644 --- a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxNodeTreeController.php +++ b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxNodeTreeController.php @@ -124,7 +124,7 @@ public function getTreeAction(Request $request): JsonResponse 'status' => 'success', 'tree_type' => $assignation['tree_type'], 'linkedTypes' => $linkedTypes, - 'nodeTree' => trim($this->twig->render('@RoadizRozier/widgets/tree/rz_tree_wrapper_auto.html.twig', $assignation)), + 'nodeTree' => trim($this->twig->render('@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig', $assignation)), ]); } } diff --git a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxTagTreeController.php b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxTagTreeController.php index 18cd24b12..6043bbe57 100644 --- a/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxTagTreeController.php +++ b/lib/RoadizRozierBundle/src/Controller/Ajax/Tree/AjaxTagTreeController.php @@ -78,7 +78,7 @@ public function getTreeAction(Request $request): JsonResponse 'statusCode' => '200', 'status' => 'success', 'tree_type' => $assignation['tree_type'], - 'tagTree' => $this->twig->render('@RoadizRozier/widgets/tree/rz_tree_wrapper_auto.html.twig', $assignation), + 'tagTree' => $this->twig->render('@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig', $assignation), ]); } } diff --git a/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig b/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig index 8cebda70f..2668cb43a 100644 --- a/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig @@ -1,6 +1,7 @@ {%- if folderTree -%}
      {% for cfolder in folderTree.folders %} diff --git a/lib/RoadizRozierBundle/templates/widgets/folderTree/rz_folder_tree_wrapper.html.twig b/lib/RoadizRozierBundle/templates/widgets/folderTree/rz_folder_tree_wrapper.html.twig deleted file mode 100644 index 2c2e8c099..000000000 --- a/lib/RoadizRozierBundle/templates/widgets/folderTree/rz_folder_tree_wrapper.html.twig +++ /dev/null @@ -1,32 +0,0 @@ -{% embed '@RoadizRozier/widgets/rz_tree_wrapper.html.twig' with { - has_header: mainFolderTree, - title: 'folderTree'|trans, - display_translations: mainFolderTree, - tree: folderTree, - mainFolderTree: mainFolderTree, - tree_type: 'folder', -} only %} - - {% block head_button %} - {% if tree is defined %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: folderTree.getRootFolder == null ? 'add.a.folder'|trans : 'add.child.folder'|trans, - id: 'foldertree-btn-add', - attributes: { - href: folderTree.getRootFolder == null ? - path('foldersAddPage') - : path('foldersAddChildPage', { folderId : folderTree.rootFolder.id }), - } - }) }} - {% endif %} - {% endblock %} - - {% block tree_content %} - {% include '@RoadizRozier/widgets/folderTree/folderTree.html.twig' with { - folderTree: tree, - mainFolderTree: mainFolderTree, - } only %} - {% endblock %} - -{% endembed %} diff --git a/lib/RoadizRozierBundle/templates/widgets/nodeTree/rz_node_tree_wrapper.html.twig b/lib/RoadizRozierBundle/templates/widgets/nodeTree/rz_node_tree_wrapper.html.twig deleted file mode 100644 index 14c7230fc..000000000 --- a/lib/RoadizRozierBundle/templates/widgets/nodeTree/rz_node_tree_wrapper.html.twig +++ /dev/null @@ -1,45 +0,0 @@ -{% embed '@RoadizRozier/widgets/rz_tree_wrapper.html.twig' with { - has_header: mainNodeTree, - title: 'nodeTree'|trans, - display_translations: mainNodeTree, - tree: nodeTree, - mainNodeTree: mainNodeTree, - linkedTypes: linkedTypes, - tree_type: 'node', -} only %} - - {% block head_button %} - {% if tree is defined %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: not tree.rootNode ? 'add.a.node'|trans : 'add.child.node'|trans, - attributes: { - href: not tree.rootNode ? - path('nodesAddPage', { translationId: tree.translation.id }) - : path('nodesAddChildPage', { nodeId : tree.rootNode.id }), - } - }) }} - {% endif %} - {% endblock %} - - {% block after_title %} - {% import '@RoadizRozier/macros/rz_link.html.twig' as rz_link %} - {{ rz_link.root({ - icon: 'rz-icon-ri--search-line', - class: 'rz-aside__root-tree-link', - attributes: { - href: path('nodesMainTreePage', { translationId: tree.translation.id }), - title: 'see_all'|trans, - } - }) }} - {% endblock %} - - {% block tree_content %} - {% include '@RoadizRozier/widgets/nodeTree/nodeTree.html.twig' with { - nodeTree: tree, - mainNodeTree: mainNodeTree, - linkedTypes: linkedTypes, - } %} - {% endblock %} - -{% endembed %} diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig index 0064a42c9..e244ee9c6 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig @@ -1,7 +1,5 @@
      - {% include '@RoadizRozier/widgets/tree/rz_tree_wrapper_auto.html.twig' with { - root_classes: ['rz-aside__body'], - } %} + {% include '@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig' only %}
      diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper.html.twig deleted file mode 100644 index eb0304d05..000000000 --- a/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper.html.twig +++ /dev/null @@ -1,58 +0,0 @@ -{% set type_prefix = tree_type|default('node') %} -{% set root_classes = ['rz-tree-wrapper', 'tree-widget', type_prefix ~ '-tree-widget'] %} -
      - {% if not tree %} - {% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} - {{ rz_button.root({ - tagName: 'div', - label: 'loading'|trans, - emphasis: 'secondary', - size: 'md', - iconClass: 'rz-spinner', - class: 'rz-aside__loading-button', - attributes: { - 'aria-live': 'polite', - 'role': 'status', - } - }) }} - {% else %} - - {% if has_header is defined and has_header %} -
      - - -

      - {{ title|default('rz_aside.title'|trans) }} - {% block after_title %}{% endblock %} -

      - - {% block head_button %}{% endblock %} -
      - {% endif %} - - {% if display_translations %} - {% include '@RoadizRozier/widgets/rz_language_switcher.html.twig' with { - tree: tree, - type_prefix: type_prefix, - classes: ['rz-aside__langs', type_prefix ~ '-tree-langs'], - } only %} - {% endif %} - - {% set tree_wrapper_classes = ['rz-tree', 'rz-tree--' ~ type_prefix ] %} - {% set tree_id = tree.id ?? tree.parentNode.id ?? tree.rootTag.id ?? tree.rootFolder.id ?? 'root' %} - - {% block tree_content %}{% endblock %} - - {% endif %} -
      - -{% macro button(options) %} - {% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} - {{ rz_button.root(options|merge({ size: 'sm', class: 'rz-aside__button' })) }} -{% endmacro %} diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig new file mode 100644 index 000000000..c1deea569 --- /dev/null +++ b/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig @@ -0,0 +1,113 @@ +{% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} + +{% set type_prefix = tree_type|default('node') %} +{% set root_classes = ['rz-tree-wrapper', 'tree-widget', type_prefix ~ '-tree-widget'] %} + +
      + {% if not tree %} + {{ rz_button.root({ + tagName: 'div', + label: 'loading'|trans, + emphasis: 'secondary', + size: 'md', + iconClass: 'rz-spinner', + class: 'rz-aside__loading-button', + attributes: { + 'aria-live': 'polite', + 'role': 'status', + } + }) }} + {% else %} +
      + +

      + {{- + tree_type == 'node' + ? 'nodeTree'|trans + : tree_type == 'tag' + ? 'tagTree'|trans + : 'folderTree'|trans + -}} +

      + + {% set btn_label = null %} + {% set btn_href = null %} + + {% if tree_type == 'node' %} + {% set btn_label = not tree.rootNode + ? 'add.a.node'|trans + : 'add.child.node'|trans %} + {% set btn_href = not tree.rootNode + ? path('nodesAddPage', { translationId: tree.translation.id }) + : path('nodesAddChildPage', { nodeId: tree.rootNode.id }) %} + + {% elseif tree_type == 'tag' %} + {% set btn_label = tree.getRootTag == null + ? 'add.a.tag'|trans + : 'add.child.tag'|trans %} + {% set btn_href = tree.getRootTag == null + ? path('tagsAddPage') + : path('tagsAddChildPage', { tagId: tree.rootTag.id }) %} + + {% elseif tree_type == 'folder' %} + {% set btn_label = tree.getRootFolder == null + ? 'add.a.folder'|trans + : 'add.child.folder'|trans %} + {% set btn_href = tree.getRootFolder == null + ? path('foldersAddPage') + : path('foldersAddChildPage', { folderId: tree.rootFolder.id }) %} + {% endif %} + + {{ rz_button.root({ + icon: 'rz-icon-ri--add-line', + size: 'sm', + label: btn_label, + class: 'rz-aside__button', + attributes: { + href: btn_href, + } + }) }} +
      + + {% if mainTree %} + {% include '@RoadizRozier/widgets/rz_language_switcher.html.twig' with { + tree: tree, + type_prefix: type_prefix, + classes: ['rz-aside__langs', type_prefix ~ '-tree-langs'], + } only %} + {% endif %} + + {% set tree_wrapper_classes = ['rz-tree', 'rz-tree--' ~ type_prefix ] %} + {% set tree_id = tree.id ?? tree.parentNode.id ?? tree.rootTag.id ?? tree.rootFolder.id ?? 'root' %} + + {% if tree_type == 'node' %} + {% include '@RoadizRozier/widgets/nodeTree/nodeTree.html.twig' with { + nodeTree: tree, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + linkedTypes: linkedTypes|default(null), + layout: 'condensed', + } %} + + {% elseif tree_type == 'tag' %} + {% include '@RoadizRozier/widgets/tagTree/tagTree.html.twig' with { + tagTree: tree, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + layout: 'condensed', + } %} + + {% elseif tree_type == 'folder' %} + {% include '@RoadizRozier/widgets/folderTree/folderTree.html.twig' with { + folderTree: tree, + isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + layout: 'condensed', + } %} + {% endif %} + + {% endif %} +
      diff --git a/lib/RoadizRozierBundle/templates/widgets/tagTree/rz_tag_tree_wrapper.html.twig b/lib/RoadizRozierBundle/templates/widgets/tagTree/rz_tag_tree_wrapper.html.twig deleted file mode 100644 index b9912df4f..000000000 --- a/lib/RoadizRozierBundle/templates/widgets/tagTree/rz_tag_tree_wrapper.html.twig +++ /dev/null @@ -1,31 +0,0 @@ -{% embed '@RoadizRozier/widgets/rz_tree_wrapper.html.twig' with { - has_header: mainTagTree, - title: 'tagTree'|trans, - display_translations: mainTagTree, - tree: tagTree, - mainTagTree: mainTagTree, - tree_type: 'tag', -} only %} - - {% block head_button %} - {% if tree is defined %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: tagTree.getRootTag == null ? 'add.a.tag'|trans : 'add.child.tag'|trans, - attributes: { - href: tagTree.getRootTag == null ? - path('tagsAddPage') - : path('tagsAddChildPage', { tagId : tagTree.rootTag.id }), - } - }) }} - {% endif %} - {% endblock %} - - {% block tree_content %} - {% include '@RoadizRozier/widgets/tagTree/tagTree.html.twig' with { - tagTree: tree, - mainTagTree: mainTagTree, - } only %} - {% endblock %} - -{% endembed %} diff --git a/lib/RoadizRozierBundle/templates/widgets/tagTree/tagTree.html.twig b/lib/RoadizRozierBundle/templates/widgets/tagTree/tagTree.html.twig index cf0823cd8..80825fe45 100644 --- a/lib/RoadizRozierBundle/templates/widgets/tagTree/tagTree.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/tagTree/tagTree.html.twig @@ -1,6 +1,7 @@ {%- if tagTree -%}
        {% for ctag in tagTree.getTags %} diff --git a/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig b/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig deleted file mode 100644 index d73994146..000000000 --- a/lib/RoadizRozierBundle/templates/widgets/tree/rz_tree_wrapper_auto.html.twig +++ /dev/null @@ -1,104 +0,0 @@ -{% embed '@RoadizRozier/widgets/rz_tree_wrapper.html.twig' with { - has_header: mainTree, - title: tree_type == 'node' - ? 'nodeTree'|trans - : tree_type == 'tag' - ? 'tagTree'|trans - : 'folderTree'|trans, - display_translations: mainTree, - tree: tree, - tree_type: tree_type, -} only %} - {# ===================== #} - {# HEADER BUTTON #} - {# ===================== #} - {% block head_button %} - {% if tree is defined %} - {% if tree_type == 'node' %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: not tree.rootNode - ? 'add.a.node'|trans - : 'add.child.node'|trans, - attributes: { - href: not tree.rootNode - ? path('nodesAddPage', { translationId: tree.translation.id }) - : path('nodesAddChildPage', { nodeId: tree.rootNode.id }), - } - }) }} - - {% elseif tree_type == 'tag' %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: tree.getRootTag == null - ? 'add.a.tag'|trans - : 'add.child.tag'|trans, - attributes: { - href: tree.getRootTag == null - ? path('tagsAddPage') - : path('tagsAddChildPage', { tagId: tree.rootTag.id }), - } - }) }} - - {% elseif tree_type == 'folder' %} - {{ _self.button({ - icon: 'rz-icon-ri--add-line', - label: tree.getRootFolder == null - ? 'add.a.folder'|trans - : 'add.child.folder'|trans, - id: 'foldertree-btn-add', - attributes: { - href: tree.getRootFolder == null - ? path('foldersAddPage') - : path('foldersAddChildPage', { folderId: tree.rootFolder.id }), - } - }) }} - {% endif %} - {% endif %} - {% endblock %} - - {# ===================== #} - {# AFTER TITLE (node only) #} - {# ===================== #} - {% block after_title %} - {% if tree_type == 'node' and tree is defined %} - {% import '@RoadizRozier/macros/rz_link.html.twig' as rz_link %} - {{ rz_link.root({ - icon: 'rz-icon-ri--search-line', - class: 'rz-aside__root-tree-link', - attributes: { - href: path('nodesMainTreePage', { translationId: tree.translation.id }), - title: 'see_all'|trans, - } - }) }} - {% endif %} - {% endblock %} - - {# ===================== #} - {# TREE CONTENT #} - {# ===================== #} - {% block tree_content %} - {% if tree_type == 'node' %} - {% include '@RoadizRozier/widgets/nodeTree/nodeTree.html.twig' with { - nodeTree: tree, - isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), - linkedTypes: linkedTypes|default(null), - layout: 'condensed', - } %} - - {% elseif tree_type == 'tag' %} - {% include '@RoadizRozier/widgets/tagTree/tagTree.html.twig' with { - tagTree: tree, - isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), - layout: 'condensed', - } %} - - {% elseif tree_type == 'folder' %} - {% include '@RoadizRozier/widgets/folderTree/folderTree.html.twig' with { - folderTree: tree, - isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), - layout: 'condensed', - } %} - {% endif %} - {% endblock %} -{% endembed %} From 34f61b964ae18dfe80c99d65f29938da7e7d45a0 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:12:02 +0100 Subject: [PATCH 05/17] chore: better method return --- lib/Rozier/app/custom-elements/RzTree.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index bf3f9a597..b37253503 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -239,13 +239,11 @@ export default class RzTree extends HTMLElement { } async refreshOtherTrees() { - document - .querySelectorAll('rz-tree') - ?.forEach(async (instance: RzTree) => { - if (instance !== this) { - await instance.refreshNodeTree() - } - }) + const instances = document.querySelectorAll('rz-tree') + const refreshPromises = Array.from(instances) + .filter((instance) => instance !== this) + .map((instance) => instance.refreshNodeTree()) + await Promise.all(refreshPromises) } async refreshNodeTree() { From 7d1e734ab60c1eb1451b309a251cbfc35c2d8d77 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:13:24 +0100 Subject: [PATCH 06/17] chore: remove logs --- lib/Rozier/app/custom-elements/RzTree.ts | 4 +--- lib/Rozier/app/session-message.ts | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index b37253503..d8a6bdb6d 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -84,9 +84,7 @@ export default class RzTree extends HTMLElement { ) } - onRequestAllNodeTreeChange(event: Event) { - event.preventDefault() - console.log('onRequestAllNodeTreeChange', event, this) + onRequestAllNodeTreeChange() { this.refreshNodeTree() } diff --git a/lib/Rozier/app/session-message.ts b/lib/Rozier/app/session-message.ts index 4e8845183..1beaa4080 100644 --- a/lib/Rozier/app/session-message.ts +++ b/lib/Rozier/app/session-message.ts @@ -30,8 +30,6 @@ export async function fetchSessionMessages() { export async function dispatchSessionToast() { const messages = await fetchSessionMessages() - console.log('Session messages:', messages) - if (!messages) return if (messages.confirm && messages.confirm.length > 0) { From 127767b5bed71ce64241fad46afcc98273b87914 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:17:14 +0100 Subject: [PATCH 07/17] chore: fix condition --- lib/Rozier/app/custom-elements/RzTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index d8a6bdb6d..057b94241 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -209,7 +209,7 @@ export default class RzTree extends HTMLElement { } const rootNodeId = this.getRootNodeId() - if (rootNodeId) { + if (!!rootNodeId || rootNodeId === 0) { Object.assign(options, { parentNodeId: rootNodeId.toString() }) } const params = new URLSearchParams(options) From 690e0c97913f507cf48309f8f36970132713b853 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:28:03 +0100 Subject: [PATCH 08/17] chore: update naming and element id --- lib/Rozier/app/custom-elements/RzAside.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index 7c8c408bb..27cb8f849 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -165,20 +165,20 @@ export default class RzAside extends RoadizElement { data?.['tree'] if (data && typeof treeHTML !== 'undefined') { - const treeTypeId = - `type-${data.tree_type || 'node'}-tree` + - '-t-' + - (this.currentTranslationId || 'main-locale') - - await this.refreshTreeContent(treeHTML) - treeContainer.setAttribute('data-tree-id', treeTypeId) - const translationId = queryOptions?.translationId?.toString() || this.querySelector( '.rz-aside__langs button.rz-button--selected', )?.getAttribute('data-translation-id') || '' + + const asideContainerId = + `type-${data.tree_type || 'node'}-tree` + + `-translation-${translationId || 'main'}` + + await this.refreshTreeContent(treeHTML) + treeContainer.setAttribute('data-tree-id', asideContainerId) + this.currentTranslationId = translationId !== '' ? Number(translationId) : null } From 4f0b656f435606b27f9c12f0084247574493bfcc Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 12:28:16 +0100 Subject: [PATCH 09/17] chore: update naming and element id --- lib/Rozier/app/custom-elements/RzAside.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index 27cb8f849..c668298ad 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -165,6 +165,8 @@ export default class RzAside extends RoadizElement { data?.['tree'] if (data && typeof treeHTML !== 'undefined') { + await this.refreshTreeContent(treeHTML) + const translationId = queryOptions?.translationId?.toString() || this.querySelector( @@ -176,7 +178,6 @@ export default class RzAside extends RoadizElement { `type-${data.tree_type || 'node'}-tree` + `-translation-${translationId || 'main'}` - await this.refreshTreeContent(treeHTML) treeContainer.setAttribute('data-tree-id', asideContainerId) this.currentTranslationId = From 42f017eb3a4a8803fcaabac41ab485ab1f54972d Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 17:13:15 +0100 Subject: [PATCH 10/17] fix: fix regression --- .../widgets/folderTree/folderTree.html.twig | 2 +- .../templates/widgets/rz_aside.html.twig | 2 +- .../widgets/rz_language_switcher.html.twig | 15 +- .../widgets/rz_tree_wrapper_auto.html.twig | 5 +- lib/Rozier/app/@types/rozier.d.ts | 1 + lib/Rozier/app/Rozier.ts | 2 + lib/Rozier/app/custom-elements/RzAside.ts | 159 +++++++++--------- lib/Rozier/app/custom-elements/RzTree.ts | 10 +- lib/Rozier/app/session-message.ts | 9 +- 9 files changed, 106 insertions(+), 99 deletions(-) diff --git a/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig b/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig index 2668cb43a..43723f5ba 100644 --- a/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/folderTree/folderTree.html.twig @@ -1,7 +1,7 @@ {%- if folderTree -%}
          {% for cfolder in folderTree.folders %} diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig index e244ee9c6..ce2a9b579 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig @@ -1,4 +1,4 @@ - +
          {% include '@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig' only %}
          diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig index 5f1965cf4..cbed7b93b 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig @@ -5,13 +5,13 @@
            {%- for translation in translations -%} - {{ _self.item(translation, tree) }} + {{ _self.item(translation, tree, btn_attr) }} {%- endfor -%}
          {% endif %} -{% macro item(translation, tree) %} +{% macro item(translation, tree, btn_attr = {}) %}
        • {% set button_data = { emphasis: 'tertiary', @@ -21,7 +21,10 @@ {% if tree is defined %} {% set button_data = button_data|merge({ selected: translation.id == tree.translation.id }) %} - {% set button_attr = { 'data-translation-id': translation.id } %} + {% set button_attr = { + 'data-translation-id': translation.id, + 'command': '--update-translation', + } %} {% if tree.rootNode is defined %} {% set button_attr = button_attr|merge({'data-children-parent-node': tree.rootNode.id }) %} @@ -31,10 +34,12 @@ {% set button_attr = button_attr|merge({'data-filter-tag': tree.tag.id}) %} {% endif %} - {% set button_data = button_data|merge({attributes: button_attr}) %} {% endif %} {% import "@RoadizRozier/macros/rz_button.html.twig" as rz_button %} - {{ rz_button.root(button_data) }} + {{ rz_button.root(button_data|merge({ + attributes: button_attr|merge(btn_attr) + })) + }}
        • {% endmacro %} diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig index c1deea569..b6c6e7b39 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_tree_wrapper_auto.html.twig @@ -74,6 +74,7 @@ tree: tree, type_prefix: type_prefix, classes: ['rz-aside__langs', type_prefix ~ '-tree-langs'], + btn_attr: { 'commandfor': 'rz-aside' }, } only %} {% endif %} @@ -97,14 +98,14 @@ {% elseif tree_type == 'tag' %} {% include '@RoadizRozier/widgets/tagTree/tagTree.html.twig' with { tagTree: tree, - isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + isMainTree: not tree.parentTag.id or (isMainTree is defined and isMainTree), layout: 'condensed', } %} {% elseif tree_type == 'folder' %} {% include '@RoadizRozier/widgets/folderTree/folderTree.html.twig' with { folderTree: tree, - isMainTree: not tree.rootNode.id or (isMainTree is defined and isMainTree), + isMainTree: not tree.parentFolder.id or (isMainTree is defined and isMainTree), layout: 'condensed', } %} {% endif %} diff --git a/lib/Rozier/app/@types/rozier.d.ts b/lib/Rozier/app/@types/rozier.d.ts index 19ea992d0..80b95f31f 100644 --- a/lib/Rozier/app/@types/rozier.d.ts +++ b/lib/Rozier/app/@types/rozier.d.ts @@ -8,6 +8,7 @@ export default class Rozier { canvasLoader: CanvasLoader | null lazyload: Lazyload vueApp: VueApp + nodeStatuses: null | string onDocumentReady(): void bindMainTrees(): void diff --git a/lib/Rozier/app/Rozier.ts b/lib/Rozier/app/Rozier.ts index 441ef9579..1847c97b3 100644 --- a/lib/Rozier/app/Rozier.ts +++ b/lib/Rozier/app/Rozier.ts @@ -13,6 +13,7 @@ export default class Rozier { canvasLoader: CanvasLoader | null lazyload: Lazyload | null = null vueApp: VueApp | null = null + nodeStatuses = null constructor() { this.windowWidth = null @@ -39,6 +40,7 @@ export default class Rozier { window.addEventListener('requestLoaderHide', () => { this.canvasLoader?.hide() }) + window.addEventListener('requestMessagesRefresh', this.getMessages) } getAsideElement() { diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index c668298ad..c62334a88 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -3,81 +3,110 @@ import { fadeIn, fadeOut } from '~/utils/animation' import { sleep } from '~/utils/sleep' export default class RzAside extends RoadizElement { - private onLangButtonClick: (event: Event) => void private currentTranslationId: number | null = null + private entityType: null | string = null constructor() { super() this.onPageShowEnd = this.onPageShowEnd.bind(this) - this.onLangButtonClick = this.handleLangButtonClick.bind(this) - this.handleMessagesRefresh = this.handleMessagesRefresh.bind(this) + this.onCommand = this.onCommand.bind(this) } private get rozier() { return window.Rozier } + private get treeContainer() { + return this.querySelector('.rz-aside__body') as HTMLElement | null + } + connectedCallback() { - this.listen(this, 'click', this.onLangButtonClick) this.refreshAsideMainTree() + window.addEventListener('pageshowend', this.onPageShowEnd) - window.addEventListener( - 'requestMessagesRefresh', - this.handleMessagesRefresh, - ) + this.addEventListener('command', this.onCommand) } disconnectedCallback() { - super.disconnectedCallback() - window.removeEventListener('pageshowend', this.onPageShowEnd) - window.removeEventListener( - 'requestMessagesRefresh', - this.handleMessagesRefresh, - ) - } - - private onPageShowEnd() { - this.refreshAsideMainTree() + this.removeEventListener('command', this.onCommand) } - private handleMessagesRefresh() { - this.rozier?.getMessages?.() + onCommand(event: CommandEvent) { + switch (event.command) { + case '--update-translation': + this.onLangButtonClick(event) + break + } } - private get treeContainer() { - return this.querySelector('.rz-aside__body') as HTMLElement | null + private onPageShowEnd() { + // TODO: refresh only when new tree will be diferent (translation or type changed) + this.refreshAsideMainTree() + const id = this.treeContainer.getAttribute('data-id') + console.log('onPageShowEnd', id) } - private handleLangButtonClick(event: Event) { - const target = (event.target as HTMLElement | null)?.closest('button') - if (!target || !this.contains(target)) { + private onLangButtonClick(event: CommandEvent) { + const target = event.source as HTMLButtonElement | undefined + if (!target || !this.entityType) { return } - const translationIdRaw = target.getAttribute('data-translation-id') const translationId = translationIdRaw ? parseInt(translationIdRaw, 10) : undefined - if (target.closest('[data-tree-type="node"]')) { - window.dispatchEvent(new CustomEvent('requestLoaderShow')) - this.refreshMainNodeTree(translationId) - return - } + window.dispatchEvent(new CustomEvent('requestLoaderShow')) - if (target.closest('[data-tree-type="folder"]')) { - window.dispatchEvent(new CustomEvent('requestLoaderShow')) + if (this.entityType === 'node') { + this.refreshMainNodeTree(translationId) + } else if (this.entityType === 'folder') { this.refreshMainFolderTree(translationId) - return + } else if (this.entityType === 'tag') { + this.refreshMainTagTree(translationId) } + } - if (target.closest('[data-tree-type="tag"]')) { - window.dispatchEvent(new CustomEvent('requestLoaderShow')) - this.refreshMainTagTree(translationId) + async refreshMainNodeTree(translationId: number | undefined = undefined) { + await this.refreshAsideMainTree( + window.RozierConfig.routes?.nodesTreeAjax || null, + { translationId }, + ) + } + + async refreshMainTagTree(translationId: number | undefined = undefined) { + await this.refreshAsideMainTree( + window.RozierConfig.routes?.tagsTreeAjax || null, + { translationId }, + ) + } + + async refreshMainFolderTree(translationId: number | undefined = undefined) { + await this.refreshAsideMainTree( + window.RozierConfig.routes?.foldersTreeAjax || null, + { translationId }, + ) + } + + async refreshAsideMainTree( + baseUrl: string | null = null, + query: Record = {}, + ) { + try { + await this.refreshMainTree(baseUrl, query) + } catch { + console.debug( + '[RzAside.refreshAsideMainTree] Retrying in 3 seconds', + ) + await sleep(3000) + await this.refreshMainTree(baseUrl, query) } + + window.dispatchEvent(new CustomEvent('requestLoaderHide')) } + async refreshTreeContent(treeHTML = '') { if (!treeHTML) { console.warn('No treeHTML provided to refreshTreeContent') @@ -165,61 +194,31 @@ export default class RzAside extends RoadizElement { data?.['tree'] if (data && typeof treeHTML !== 'undefined') { + this.entityType = data.tree_type + await this.refreshTreeContent(treeHTML) - const translationId = + const translationId = this.getNewContainerId( + data.tree_type, queryOptions?.translationId?.toString() || - this.querySelector( - '.rz-aside__langs button.rz-button--selected', - )?.getAttribute('data-translation-id') || - '' + this.querySelector( + '.rz-aside__langs button.rz-button--selected', + )?.getAttribute('data-translation-id') || + '', + ) const asideContainerId = - `type-${data.tree_type || 'node'}-tree` + + `type-${this.entityType}-tree` + `-translation-${translationId || 'main'}` - treeContainer.setAttribute('data-tree-id', asideContainerId) + treeContainer.setAttribute('data-id', asideContainerId) this.currentTranslationId = translationId !== '' ? Number(translationId) : null } } - async refreshAsideMainTree( - baseUrl: string | null = null, - query: Record = {}, - ) { - try { - await this.refreshMainTree(baseUrl, query) - } catch { - console.debug( - '[RzAside.refreshAsideMainTree] Retrying in 3 seconds', - ) - await sleep(3000) - await this.refreshMainTree(baseUrl, query) - } - - window.dispatchEvent(new CustomEvent('requestLoaderHide')) - } - - async refreshMainNodeTree(translationId: number | undefined = undefined) { - await this.refreshAsideMainTree( - window.RozierConfig.routes?.nodesTreeAjax || null, - { translationId }, - ) - } - - async refreshMainTagTree(translationId: number | undefined = undefined) { - await this.refreshAsideMainTree( - window.RozierConfig.routes?.tagsTreeAjax || null, - { translationId }, - ) - } - - async refreshMainFolderTree(translationId: number | undefined = undefined) { - await this.refreshAsideMainTree( - window.RozierConfig.routes?.foldersTreeAjax || null, - { translationId }, - ) + getNewContainerId(type: string, translation: string) { + return `type-${type}-tree-translation-${translation}` } } diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index 057b94241..f8b4b14f0 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -65,7 +65,7 @@ export default class RzTree extends HTMLElement { } this.syncCollapsedState() - this.bindExpandButtons() + this.bindExpandButtonsId() this.addEventListener('command', this.onCommand) window.addEventListener( @@ -282,13 +282,11 @@ export default class RzTree extends HTMLElement { } else { this.innerHTML = data.nodeTree this.rootNode = this.querySelector('[role="tree"]') - this.bindExpandButtons() await fadeIn(this) } - // window.dispatchEvent(new CustomEvent('requestNestablesInit')) - // window.dispatchEvent(new CustomEvent('requestBindMainTrees')) - // window.dispatchEvent(new CustomEvent('requestAjaxLinkBind')) + this.bindExpandButtonsId() + window.Rozier?.lazyload?.bindAjaxLink?.() } } catch (error) { await this.pushRequestError(error) @@ -346,7 +344,7 @@ export default class RzTree extends HTMLElement { this.updateCollapsedState(item, newValue) } - bindExpandButtons() { + bindExpandButtonsId() { const buttons = this.querySelectorAll('.rz-tree__item__expand-button') if (!buttons.length) return diff --git a/lib/Rozier/app/session-message.ts b/lib/Rozier/app/session-message.ts index 1beaa4080..101bbc52f 100644 --- a/lib/Rozier/app/session-message.ts +++ b/lib/Rozier/app/session-message.ts @@ -21,11 +21,12 @@ export async function fetchSessionMessages() { }, }) - const data = (await response.json()) as SessionMessagesResponse - - if (!data.messages) return null + if (!response.ok) { + return null + } - return data.messages + const data = (await response.json()) as SessionMessagesResponse + return data?.messages } export async function dispatchSessionToast() { From 11129a843c3ee144eecb52340732f913b94063db Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Thu, 12 Feb 2026 17:17:48 +0100 Subject: [PATCH 11/17] chore: revert type modification --- lib/Rozier/app/@types/rozier.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Rozier/app/@types/rozier.d.ts b/lib/Rozier/app/@types/rozier.d.ts index 80b95f31f..59479a846 100644 --- a/lib/Rozier/app/@types/rozier.d.ts +++ b/lib/Rozier/app/@types/rozier.d.ts @@ -8,7 +8,7 @@ export default class Rozier { canvasLoader: CanvasLoader | null lazyload: Lazyload vueApp: VueApp - nodeStatuses: null | string + nodeStatuses?: unknown onDocumentReady(): void bindMainTrees(): void From 9183db3c992f5ee7d8ec8c8db943822e43bdedb0 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 09:14:01 +0100 Subject: [PATCH 12/17] refactor: prevent to rerender content when content is identical --- .../templates/widgets/rz_aside.html.twig | 7 +- lib/Rozier/app/custom-elements/RzAside.ts | 92 ++++++++----------- 2 files changed, 44 insertions(+), 55 deletions(-) diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig index ce2a9b579..8b6b8a03e 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_aside.html.twig @@ -1,4 +1,9 @@ - +{% set default_translation = getAvailableTranslations()|find(t => t.defaultTranslation) %} +
          {% include '@RoadizRozier/widgets/rz_tree_wrapper_auto.html.twig' only %}
          diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index c62334a88..7c515955b 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeOut } from '~/utils/animation' import { sleep } from '~/utils/sleep' export default class RzAside extends RoadizElement { - private currentTranslationId: number | null = null + private currentTranslationId: string | null = null private entityType: null | string = null constructor() { @@ -13,10 +13,6 @@ export default class RzAside extends RoadizElement { this.onCommand = this.onCommand.bind(this) } - private get rozier() { - return window.Rozier - } - private get treeContainer() { return this.querySelector('.rz-aside__body') as HTMLElement | null } @@ -24,6 +20,9 @@ export default class RzAside extends RoadizElement { connectedCallback() { this.refreshAsideMainTree() + this.currentTranslationId = this.getAttribute( + 'data-default-translation-id', + ) window.addEventListener('pageshowend', this.onPageShowEnd) this.addEventListener('command', this.onCommand) } @@ -42,10 +41,7 @@ export default class RzAside extends RoadizElement { } private onPageShowEnd() { - // TODO: refresh only when new tree will be diferent (translation or type changed) this.refreshAsideMainTree() - const id = this.treeContainer.getAttribute('data-id') - console.log('onPageShowEnd', id) } private onLangButtonClick(event: CommandEvent) { @@ -53,10 +49,7 @@ export default class RzAside extends RoadizElement { if (!target || !this.entityType) { return } - const translationIdRaw = target.getAttribute('data-translation-id') - const translationId = translationIdRaw - ? parseInt(translationIdRaw, 10) - : undefined + const translationId = target.getAttribute('data-translation-id') window.dispatchEvent(new CustomEvent('requestLoaderShow')) @@ -69,21 +62,21 @@ export default class RzAside extends RoadizElement { } } - async refreshMainNodeTree(translationId: number | undefined = undefined) { + async refreshMainNodeTree(translationId: string | undefined = undefined) { await this.refreshAsideMainTree( window.RozierConfig.routes?.nodesTreeAjax || null, { translationId }, ) } - async refreshMainTagTree(translationId: number | undefined = undefined) { + async refreshMainTagTree(translationId: string | undefined = undefined) { await this.refreshAsideMainTree( window.RozierConfig.routes?.tagsTreeAjax || null, { translationId }, ) } - async refreshMainFolderTree(translationId: number | undefined = undefined) { + async refreshMainFolderTree(translationId: string | undefined = undefined) { await this.refreshAsideMainTree( window.RozierConfig.routes?.foldersTreeAjax || null, { translationId }, @@ -92,7 +85,7 @@ export default class RzAside extends RoadizElement { async refreshAsideMainTree( baseUrl: string | null = null, - query: Record = {}, + query: Record = {}, ) { try { await this.refreshMainTree(baseUrl, query) @@ -135,44 +128,47 @@ export default class RzAside extends RoadizElement { } await fadeIn(treeContainer) - - this.rozier?.lazyload?.bindAjaxLink?.() + window.Rozier?.lazyload?.bindAjaxLink?.() } async refreshMainTree( baseUrl?: string | null, - queryOptions: Record = {}, + queryOptions: Record = {}, ) { const treeContainer = this.treeContainer if (!treeContainer) { return } - const currentRootTree = treeContainer.querySelector('.rz-tree-wrapper') - - if (currentRootTree && !queryOptions?.translationId) { - const translationId = currentRootTree.getAttribute( - 'data-translation-id', - ) - if (translationId) { - queryOptions.translationId = translationId - } - } - - const query = new URLSearchParams({ + const options = { _token: window.RozierConfig.ajaxToken, _action: 'requestMainTree', url: window.location.pathname, ...queryOptions, - }) + } + + const translationId = + queryOptions?.translationId || + treeContainer + .querySelector('.rz-tree-wrapper') + ?.getAttribute('data-translation-id') || + '' + + if (translationId) { + Object.assign(options, { translationId }) + } + + const query = new URLSearchParams(options) const fetchUrl = baseUrl || (window.RozierConfig.routes as { treeAjaxGateway?: string }) ?.treeAjaxGateway + if (!fetchUrl) { return } + const treeResponse = await fetch(`${fetchUrl}?${query.toString()}`, { method: 'GET', headers: { @@ -194,31 +190,19 @@ export default class RzAside extends RoadizElement { data?.['tree'] if (data && typeof treeHTML !== 'undefined') { - this.entityType = data.tree_type + // refresh only when new tree will be diferent (translation or type changed) + if ( + (!this.entityType && !this.currentTranslationId) || + (data.tree_type === this.entityType && + translationId === this.currentTranslationId) + ) { + return + } await this.refreshTreeContent(treeHTML) - const translationId = this.getNewContainerId( - data.tree_type, - queryOptions?.translationId?.toString() || - this.querySelector( - '.rz-aside__langs button.rz-button--selected', - )?.getAttribute('data-translation-id') || - '', - ) - - const asideContainerId = - `type-${this.entityType}-tree` + - `-translation-${translationId || 'main'}` - - treeContainer.setAttribute('data-id', asideContainerId) - - this.currentTranslationId = - translationId !== '' ? Number(translationId) : null + this.entityType = data.tree_type + this.currentTranslationId = translationId } } - - getNewContainerId(type: string, translation: string) { - return `type-${type}-tree-translation-${translation}` - } } From c47342b9b3c79f3078f378a4924e3d6493ab2757 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 09:20:20 +0100 Subject: [PATCH 13/17] refactor: don't extends ROadizElement --- lib/Rozier/app/custom-elements/RzAside.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index 7c515955b..c0505699a 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -1,8 +1,7 @@ -import RoadizElement from '~/utils/custom-element/RoadizElement' import { fadeIn, fadeOut } from '~/utils/animation' import { sleep } from '~/utils/sleep' -export default class RzAside extends RoadizElement { +export default class RzAside extends HTMLElement { private currentTranslationId: string | null = null private entityType: null | string = null From 01a4d698237fec2d2f05fbbe886377d13b504c5b Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 09:37:56 +0100 Subject: [PATCH 14/17] refacotr: rename vars and fix rerender tree --- lib/Rozier/app/custom-elements/RzAside.ts | 49 +++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/Rozier/app/custom-elements/RzAside.ts b/lib/Rozier/app/custom-elements/RzAside.ts index c0505699a..9205778df 100644 --- a/lib/Rozier/app/custom-elements/RzAside.ts +++ b/lib/Rozier/app/custom-elements/RzAside.ts @@ -12,7 +12,7 @@ export default class RzAside extends HTMLElement { this.onCommand = this.onCommand.bind(this) } - private get treeContainer() { + private get innerElement() { return this.querySelector('.rz-aside__body') as HTMLElement | null } @@ -105,40 +105,44 @@ export default class RzAside extends HTMLElement { return } - const treeContainer = this.treeContainer - if (!treeContainer) { + const innerElement = this.innerElement + if (!innerElement) { console.warn('No tree container found to refreshTreeContent') return } - const previousElement = treeContainer.querySelector('.rz-tree-wrapper') + const previousElement = innerElement.querySelector('.rz-tree-wrapper') const temporaryElement = document.createElement('div') temporaryElement.innerHTML = treeHTML const newElement = temporaryElement.querySelector('.rz-tree-wrapper') - await fadeOut(treeContainer) + await fadeOut(innerElement) if (newElement) { - treeContainer.appendChild(newElement) + innerElement.appendChild(newElement) } if (previousElement) { previousElement.remove() } - await fadeIn(treeContainer) + await fadeIn(innerElement) window.Rozier?.lazyload?.bindAjaxLink?.() } + getDisplayedTreeTranslationId() { + return ( + this.getAttribute('data-translation-id') || + this.innerElement + .querySelector('.rz-tree__list') + ?.getAttribute('data-translation-id') + ) + } + async refreshMainTree( baseUrl?: string | null, queryOptions: Record = {}, ) { - const treeContainer = this.treeContainer - if (!treeContainer) { - return - } - const options = { _token: window.RozierConfig.ajaxToken, _action: 'requestMainTree', @@ -148,26 +152,20 @@ export default class RzAside extends HTMLElement { const translationId = queryOptions?.translationId || - treeContainer - .querySelector('.rz-tree-wrapper') - ?.getAttribute('data-translation-id') || - '' + this.getDisplayedTreeTranslationId() || + this.currentTranslationId if (translationId) { Object.assign(options, { translationId }) } const query = new URLSearchParams(options) - const fetchUrl = baseUrl || (window.RozierConfig.routes as { treeAjaxGateway?: string }) ?.treeAjaxGateway - if (!fetchUrl) { - return - } - + if (!fetchUrl) return const treeResponse = await fetch(`${fetchUrl}?${query.toString()}`, { method: 'GET', headers: { @@ -190,11 +188,10 @@ export default class RzAside extends HTMLElement { if (data && typeof treeHTML !== 'undefined') { // refresh only when new tree will be diferent (translation or type changed) - if ( - (!this.entityType && !this.currentTranslationId) || - (data.tree_type === this.entityType && - translationId === this.currentTranslationId) - ) { + const isSameType = data.tree_type === this.entityType + const isSameTranslation = + translationId === this.currentTranslationId + if (isSameType && isSameTranslation) { return } From 2ca295493c1aeab994c3399698b99befc0396921 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 09:39:14 +0100 Subject: [PATCH 15/17] chore: add type attr on button --- .../templates/widgets/rz_language_switcher.html.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig b/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig index cbed7b93b..b21041d3e 100644 --- a/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/rz_language_switcher.html.twig @@ -24,6 +24,7 @@ {% set button_attr = { 'data-translation-id': translation.id, 'command': '--update-translation', + 'type': 'button', } %} {% if tree.rootNode is defined %} From 28921006e7ab0df70c03e9984879787c34d0e986 Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 10:05:20 +0100 Subject: [PATCH 16/17] style: set stack type on specific RzTree --- lib/RoadizRozierBundle/templates/nodes/tree.html.twig | 1 + .../templates/widgets/nodeTree/singleNode.html.twig | 4 +++- lib/Rozier/app/custom-elements/RzTree.ts | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/RoadizRozierBundle/templates/nodes/tree.html.twig b/lib/RoadizRozierBundle/templates/nodes/tree.html.twig index 70da20842..087088056 100644 --- a/lib/RoadizRozierBundle/templates/nodes/tree.html.twig +++ b/lib/RoadizRozierBundle/templates/nodes/tree.html.twig @@ -66,6 +66,7 @@ class="rz-tree" group="nodeTree" data-entity-type="node" + data-stack-tree="true" data-is-sortable="{{ specificNodeTree.canReorder ? 'true' : 'false' }}" id="children-stack-{{ specificNodeTree.id ?? node.id ?? 'root' }}" > diff --git a/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig b/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig index e7506e3b4..c6a86d5d1 100644 --- a/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/nodeTree/singleNode.html.twig @@ -52,6 +52,7 @@ {% if type.isPublishable and source.publishedAt and source.publishedAt > date() %} {% set icon_class = 'rz-icon-ri--history-line' %} {% endif %} + {% set contextual_menu_id = 'node-contextual-menu-' ~ (isMainTree ? 'main-' : '') ~ node.id %} {% embed '@RoadizRozier/widgets/rz_tree_item_inner.html.twig' with { item: node, @@ -61,7 +62,7 @@ has_selection: layout == 'condensed' and not isMainTree and nodeTree.isStackTree, nodeTree: nodeTree, isMainTree: isMainTree, - contextual_menu_id: 'node-contextual-menu-' ~ (isMainTree ? 'main-' : '') ~ node.id, + contextual_menu_id: contextual_menu_id, } only %} {% block icon_content %} {%- if layout == 'condensed' -%} @@ -194,6 +195,7 @@ "level": level + 1, 'canReorder': (node.childrenOrder == 'position'), 'is_last': loop.last, + "layout": layout, } only %} {% endfor %}
        diff --git a/lib/Rozier/app/custom-elements/RzTree.ts b/lib/Rozier/app/custom-elements/RzTree.ts index f8b4b14f0..e75b29ef4 100644 --- a/lib/Rozier/app/custom-elements/RzTree.ts +++ b/lib/Rozier/app/custom-elements/RzTree.ts @@ -208,6 +208,10 @@ export default class RzTree extends HTMLElement { translationId: translationId ? translationId.toString() : '', } + if (this.hasAttribute('data-stack-tree')) { + Object.assign(options, { stackTree: true }) + } + const rootNodeId = this.getRootNodeId() if (!!rootNodeId || rootNodeId === 0) { Object.assign(options, { parentNodeId: rootNodeId.toString() }) From f679004d25c4c416b38beb2868131e0b8721866a Mon Sep 17 00:00:00 2001 From: timothe joubert Date: Fri, 13 Feb 2026 10:10:57 +0100 Subject: [PATCH 17/17] chore: update from review --- lib/Rozier/app/Rozier.ts | 10 ++++++---- .../app/custom-elements/RzNodeTreeContextualMenu.ts | 7 +------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/Rozier/app/Rozier.ts b/lib/Rozier/app/Rozier.ts index 1847c97b3..662104f17 100644 --- a/lib/Rozier/app/Rozier.ts +++ b/lib/Rozier/app/Rozier.ts @@ -40,7 +40,9 @@ export default class Rozier { window.addEventListener('requestLoaderHide', () => { this.canvasLoader?.hide() }) - window.addEventListener('requestMessagesRefresh', this.getMessages) + window.addEventListener('requestMessagesRefresh', () => { + this.getMessages() + }) } getAsideElement() { @@ -51,15 +53,15 @@ export default class Rozier { // this.getAsideElement()?.bindMainTrees?.() } - refreshMainNodeTree(translationId?: number) { + refreshMainNodeTree(translationId?: string) { return this.getAsideElement()?.refreshMainNodeTree?.(translationId) } - refreshMainTagTree(translationId?: number) { + refreshMainTagTree(translationId?: string) { return this.getAsideElement()?.refreshMainTagTree?.(translationId) } - refreshMainFolderTree(translationId?: number) { + refreshMainFolderTree(translationId?: string) { return this.getAsideElement()?.refreshMainFolderTree?.(translationId) } diff --git a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts index 1c1b8750e..3323b2bab 100644 --- a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts +++ b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts @@ -347,12 +347,7 @@ export default class RzNodeTreeContextualMenu extends HTMLElement { } else { const data = await response.json() window.dispatchEvent( - new CustomEvent('requestAllNodeTreeChange', { - detail: { - nodeId: this.nodeId, - treeElement: this.closest('rz-tree'), - }, - }), + new CustomEvent('requestAllNodeTreeChange'), ) return data }