2727 />
2828 <ConfirmDialog />
2929 <AccessUrlChooser v-if =" ! showAccessUrlChosserLayout " />
30+
31+ <!-- Do not show docked chat in embedded contexts (iframes/pickers/dialogs) -->
3032 <DockedChat v-if =" showGlobalChat " />
3133 </component >
3234
@@ -101,13 +103,43 @@ const showAccessUrlChosserLayout = computed(
101103 () => securityStore .isAuthenticated && ! securityStore .isAdmin && accessUrlChooserVisible .value ,
102104)
103105
106+ // ---- Embedded context detection (iframe/dialog/picker) ----
107+ const queryParams = computed (() => new URLSearchParams (window .location .search ))
108+
109+ const isPickerContext = computed (() => {
110+ const picker = String (queryParams .value .get (" picker" ) || " " ).toLowerCase ()
111+ return picker === " tinymce" || picker === " ckeditor"
112+ })
113+
114+ const isIframeContext = computed (() => {
115+ // Safe checks: if cross-origin, accessing window.top can throw.
116+ try {
117+ return window .self !== window .top
118+ } catch (e) {
119+ // If we cannot access window.top, we assume we are inside an iframe.
120+ return true
121+ }
122+ })
123+
124+ const isDialogContext = computed (() => {
125+ // allow explicit opt-out via query param.
126+ // Example: ?hideChat=1
127+ const hideChat = String (queryParams .value .get (" hideChat" ) || " " ).toLowerCase ()
128+ return hideChat === " 1" || hideChat === " true"
129+ })
130+
131+ const isEmbeddedContext = computed (() => {
132+ // In embedded contexts, we must not render global docked chat to avoid duplicated UI.
133+ return isPickerContext .value || isIframeContext .value || isDialogContext .value
134+ })
135+
104136const layout = computed (() => {
105137 if (showAccessUrlChosserLayout .value ) {
106138 return AccessUrlChooserLayout
107139 }
108140
109- const queryParams = new URLSearchParams ( window . location . search )
110- const picker = String (queryParams .get (" picker" ) || " " ).toLowerCase ()
141+ const qp = queryParams . value
142+ const picker = String (qp .get (" picker" ) || " " ).toLowerCase ()
111143
112144 // Force EmptyLayout for embedded editor pickers (TinyMCE/CKEditor)
113145 if (picker === " tinymce" || picker === " ckeditor" ) {
@@ -119,8 +151,8 @@ const layout = computed(() => {
119151 }
120152
121153 if (
122- (queryParams .has (" lp_id" ) && " view" === queryParams .get (" action" )) ||
123- (queryParams .has (" origin" ) && " learnpath" === queryParams .get (" origin" ))
154+ (qp .has (" lp_id" ) && " view" === qp .get (" action" )) ||
155+ (qp .has (" origin" ) && " learnpath" === qp .get (" origin" ))
124156 ) {
125157 return EmptyLayout
126158 }
@@ -244,7 +276,8 @@ const allowGlobalChat = computed(() => {
244276})
245277
246278const showGlobalChat = computed (() => {
247- return securityStore .isAuthenticated && allowGlobalChat .value
279+ // Do not render global chat when the app is embedded (iframe/dialog/picker).
280+ return securityStore .isAuthenticated && allowGlobalChat .value && ! isEmbeddedContext .value
248281})
249282
250283watch (forbiddenMsg, (msg ) => {
0 commit comments