Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7d23d9435 | ||
|
|
9f64d50011 | ||
|
|
f926f5e500 | ||
|
|
c9f6fb202c | ||
|
|
7830fec604 | ||
|
|
ba04e0b678 | ||
|
|
8625a00918 | ||
|
|
16e638aeb7 |
@@ -109,6 +109,15 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openArticle(slug) {
|
||||||
|
if (!slug) {
|
||||||
|
throw new Error('Article slug is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
IFrameHelper.events.toggleBubble('open');
|
||||||
|
IFrameHelper.sendMessage('open-article', { slug });
|
||||||
|
},
|
||||||
|
|
||||||
setUser(identifier, user) {
|
setUser(identifier, user) {
|
||||||
if (typeof identifier !== 'string' && typeof identifier !== 'number') {
|
if (typeof identifier !== 'string' && typeof identifier !== 'number') {
|
||||||
throw new Error('Identifier should be a string or a number');
|
throw new Error('Identifier should be a string or a number');
|
||||||
|
|||||||
@@ -38,3 +38,24 @@ export const getMatchingLocale = (selectedLocale = '', allowedLocales = []) => {
|
|||||||
// Return the first match that exists in the allowed list, or null
|
// Return the first match that exists in the allowed list, or null
|
||||||
return priorityMatches.find(l => l && allowedLocales.includes(l)) ?? null;
|
return priorityMatches.find(l => l && allowedLocales.includes(l)) ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the link consumed by the in-widget article viewer, appending the query
|
||||||
|
* params it expects (plain layout, theme and locale).
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {string} options.link Relative article/portal path (e.g. `hc/slug/articles/foo`).
|
||||||
|
* @param {(string|null)} [options.locale] Resolved portal locale.
|
||||||
|
* @param {boolean} [options.prefersDarkMode] Whether the widget is in dark mode.
|
||||||
|
* @returns {string} The link with the article viewer query params appended.
|
||||||
|
*/
|
||||||
|
export const buildArticleViewerLink = ({ link, locale, prefersDarkMode }) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
show_plain_layout: 'true',
|
||||||
|
theme: prefersDarkMode ? 'dark' : 'light',
|
||||||
|
...(locale && { locale }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${link}?${params.toString()}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ import { useRouter } from 'vue-router';
|
|||||||
import { useAvailability } from 'widget/composables/useAvailability';
|
import { useAvailability } from 'widget/composables/useAvailability';
|
||||||
import { SDK_SET_BUBBLE_VISIBILITY } from '../shared/constants/sharedFrameEvents';
|
import { SDK_SET_BUBBLE_VISIBILITY } from '../shared/constants/sharedFrameEvents';
|
||||||
import { emitter } from 'shared/helpers/mitt';
|
import { emitter } from 'shared/helpers/mitt';
|
||||||
|
import {
|
||||||
|
getMatchingLocale,
|
||||||
|
buildArticleViewerLink,
|
||||||
|
} from 'shared/helpers/portalHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
@@ -160,6 +164,26 @@ export default {
|
|||||||
this.$root.$i18n.locale = localeWithoutVariation;
|
this.$root.$i18n.locale = localeWithoutVariation;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openArticle(slug) {
|
||||||
|
const { portal } = window.chatwootWebChannel;
|
||||||
|
if (!portal || !slug) return;
|
||||||
|
|
||||||
|
const locale = getMatchingLocale(
|
||||||
|
this.$root.$i18n.locale,
|
||||||
|
portal.config?.allowed_locales
|
||||||
|
);
|
||||||
|
const link = buildArticleViewerLink({
|
||||||
|
link: `hc/${portal.slug}/articles/${slug}`,
|
||||||
|
locale,
|
||||||
|
prefersDarkMode: this.prefersDarkMode,
|
||||||
|
});
|
||||||
|
// Add a timestamp so the route always changes, even when the same article
|
||||||
|
// is requested again or the iframe was browsed to another page.
|
||||||
|
this.router.push({
|
||||||
|
name: 'article-viewer',
|
||||||
|
query: { link, v: Date.now() },
|
||||||
|
});
|
||||||
|
},
|
||||||
registerUnreadEvents() {
|
registerUnreadEvents() {
|
||||||
emitter.on(ON_AGENT_MESSAGE_RECEIVED, () => {
|
emitter.on(ON_AGENT_MESSAGE_RECEIVED, () => {
|
||||||
const { name: routeName } = this.$route;
|
const { name: routeName } = this.$route;
|
||||||
@@ -316,6 +340,8 @@ export default {
|
|||||||
this.setBubbleLabel();
|
this.setBubbleLabel();
|
||||||
} else if (message.event === 'set-color-scheme') {
|
} else if (message.event === 'set-color-scheme') {
|
||||||
this.setColorScheme(message.darkMode);
|
this.setColorScheme(message.darkMode);
|
||||||
|
} else if (message.event === 'open-article') {
|
||||||
|
this.openArticle(message.slug);
|
||||||
} else if (message.event === 'toggle-open') {
|
} else if (message.event === 'toggle-open') {
|
||||||
this.$store.dispatch('appConfig/toggleWidgetOpen', message.isOpen);
|
this.$store.dispatch('appConfig/toggleWidgetOpen', message.isOpen);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { useRouter } from 'vue-router';
|
|||||||
import { useStore } from 'dashboard/composables/store';
|
import { useStore } from 'dashboard/composables/store';
|
||||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||||
import { getMatchingLocale } from 'shared/helpers/portalHelper';
|
import {
|
||||||
|
getMatchingLocale,
|
||||||
|
buildArticleViewerLink,
|
||||||
|
} from 'shared/helpers/portalHelper';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -38,14 +41,11 @@ const fetchArticles = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openArticleInArticleViewer = link => {
|
const openArticleInArticleViewer = link => {
|
||||||
const params = new URLSearchParams({
|
const linkToOpen = buildArticleViewerLink({
|
||||||
show_plain_layout: 'true',
|
link,
|
||||||
theme: prefersDarkMode.value ? 'dark' : 'light',
|
locale: locale.value,
|
||||||
...(locale.value && { locale: locale.value }),
|
prefersDarkMode: prefersDarkMode.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Combine link with query parameters
|
|
||||||
const linkToOpen = `${link}?${params.toString()}`;
|
|
||||||
router.push({ name: 'article-viewer', query: { link: linkToOpen } });
|
router.push({ name: 'article-viewer', query: { link: linkToOpen } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white h-full">
|
<div class="bg-white h-full">
|
||||||
<IframeLoader :url="$route.query.link" />
|
<!--
|
||||||
|
Key by fullPath (not just the link) so the iframe remounts on every
|
||||||
|
navigation here, including re-opening the same article via the SDK after
|
||||||
|
the iframe was browsed to another help-center page. See App.vue#openArticle.
|
||||||
|
-->
|
||||||
|
<IframeLoader :key="$route.fullPath" :url="$route.query.link" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user