diff --git a/app/client/lib/helpScout.ts b/app/client/lib/helpScout.ts
index fc083c56..110318ed 100644
--- a/app/client/lib/helpScout.ts
+++ b/app/client/lib/helpScout.ts
@@ -29,6 +29,8 @@ export type BeaconCmd = 'init' | 'destroy' | 'open' | 'close' | 'toggle' | 'sear
'article' | 'navigate' | 'identify' | 'prefill' | 'reset' | 'logout' | 'config' | 'on' | 'off' |
'once' | 'event' | 'session-data';
+export type BeaconRoute = '/ask/message/' | '/answers/';
+
export interface IUserObj {
name?: string;
email?: string;
@@ -70,6 +72,8 @@ export function Beacon(method: BeaconCmd, options?: unknown, data?: unknown) {
(window as any).Beacon(method, options, data);
}
+// This is essentially what's done by the code snippet that HelpScout suggests to install in every
+// page. In Grist app pages, we only load HelpScout code when the beacon is opened.
function _beacon(method: BeaconCmd, options?: unknown, data?: unknown) {
_beacon.readyQueue.push({method, options, data});
}
@@ -103,7 +107,7 @@ let lastOpenType: 'error' | 'message' = 'message';
* If errors is given, prepares a form for submitting an error report, and includes stack traces
* into the session-data.
*/
-function _beaconOpen(userObj: IUserObj|null, options: {onOpen?: () => void, errors?: IAppError[]}) {
+function _beaconOpen(userObj: IUserObj|null, options: IBeaconOpenOptions) {
const {onOpen, errors} = options;
// The beacon remembers its content, so reset it when switching between reporting errors and
@@ -119,6 +123,16 @@ function _beaconOpen(userObj: IUserObj|null, options: {onOpen?: () => void, erro
if (iframe) { iframe.focus(); }
if (onOpen) { onOpen(); }
});
+ Beacon('once', 'article-viewed' as any, () => {
+ // HelpScout creates an iframe with an empty 'src' attribute, then writes to it. In such an
+ // iframe, different browsers interpret relative links differently: Chrome's are relative to
+ // the parent page's URL; Firefox's are relative to the parent page's .
+ //
+ // Here we set a explicitly in the iframe to get consistent behavior of links
+ // relative to the top page's URL (HelpScout then seems to handle clicks on them correctly).
+ const iframe = document.querySelector('#beacon-container iframe') as HTMLIFrameElement;
+ iframe?.contentDocument?.head.appendChild(dom('base', {href: ''}));
+ });
Beacon('once', 'close', () => {
const iframe = document.querySelector('#beacon-container iframe') as HTMLIFrameElement;
if (iframe) { iframe.blur(); }
@@ -128,6 +142,7 @@ function _beaconOpen(userObj: IUserObj|null, options: {onOpen?: () => void, erro
}
const attrs: ISessionData = {};
+ let route: BeaconRoute;
if (errors?.length) {
// If sending errors, prefill part of the message (the user sees this and can add to it), and
// include more detailed errors with stack traces into session-data.
@@ -147,8 +162,10 @@ function _beaconOpen(userObj: IUserObj|null, options: {onOpen?: () => void, erro
attrs[`error-${i}-stack`] = JSON.stringify(error.stack.trim().split('\n'));
}
});
+ route = options.route || '/ask/message/';
} else {
Beacon('config', {messaging: {contactForm: {showSubject: true}}});
+ route = options.route || '/answers/';
}
Beacon('session-data', {
@@ -156,7 +173,7 @@ function _beaconOpen(userObj: IUserObj|null, options: {onOpen?: () => void, erro
...attrs,
});
Beacon('open');
- Beacon('navigate', '/ask/message/');
+ Beacon('navigate', route);
}
export interface IBeaconOpenOptions {
@@ -164,6 +181,7 @@ export interface IBeaconOpenOptions {
includeAppErrors?: boolean;
onOpen?: () => void;
errors?: IAppError[];
+ route?: BeaconRoute;
}
/**
diff --git a/app/client/ui/LeftPanelCommon.ts b/app/client/ui/LeftPanelCommon.ts
index cf9c5a15..b4c82ca8 100644
--- a/app/client/ui/LeftPanelCommon.ts
+++ b/app/client/ui/LeftPanelCommon.ts
@@ -25,23 +25,22 @@ import {dom, DomContents, Observable, styled} from 'grainjs';
* HelpCenter in a new tab.
*/
export function createHelpTools(appModel: AppModel, spacer = true): DomContents {
- const isEfcr = (appModel.topAppModel.productFlavor === 'efcr');
return [
spacer ? cssSpacer() : null,
- cssPageEntry(
- cssPageLink(cssPageIcon('Feedback'),
- cssLinkText('Give Feedback'),
- dom.on('click', () => beaconOpenMessage({appModel})),
+ cssSplitPageEntry(
+ cssPageEntryMain(
+ cssPageLink(cssPageIcon('Help'),
+ cssLinkText('Help Center'),
+ dom.cls('tour-help-center'),
+ dom.on('click', (ev) => beaconOpenMessage({appModel})),
+ testId('left-feedback'),
+ ),
),
- dom.hide(isEfcr),
- testId('left-feedback'),
- ),
- cssPageEntry(
- cssPageLink(cssPageIcon('Help'), {href: commonUrls.help, target: '_blank'},
- cssLinkText('Help Center'),
- dom.cls('tour-help-center')
- ),
- dom.hide(isEfcr),
+ cssPageEntrySmall(
+ cssPageLink(cssPageIcon('FieldLink'),
+ {href: commonUrls.help, target: '_blank'},
+ ),
+ )
),
];
}
@@ -156,3 +155,28 @@ export const cssPageIcon = styled(icon, `
export const cssSpacer = styled('div', `
height: 18px;
`);
+
+const cssSplitPageEntry = styled('div', `
+ display: flex;
+ align-items: center;
+`);
+
+const cssPageEntryMain = styled(cssPageEntry, `
+ flex: auto;
+ margin: 0;
+`);
+
+const cssPageEntrySmall = styled(cssPageEntry, `
+ flex: none;
+ border-radius: 3px;
+ --icon-color: ${colors.lightGreen};
+ & > .${cssPageLink.className} {
+ padding: 0 8px 0 16px;
+ }
+ &:hover {
+ --icon-color: ${colors.darkGreen};
+ }
+ .${cssTools.className}-collapsed & {
+ display: none;
+ }
+`);
diff --git a/app/client/ui/NotifyUI.ts b/app/client/ui/NotifyUI.ts
index b2fa9b26..ca3fcbca 100644
--- a/app/client/ui/NotifyUI.ts
+++ b/app/client/ui/NotifyUI.ts
@@ -145,7 +145,7 @@ function buildNotifyDropdown(ctl: IOpenController, notifier: Notifier, appModel:
cssDropdownFeedbackLink(
cssDropdownFeedbackIcon('Feedback'),
'Give feedback',
- dom.on('click', () => beaconOpenMessage({appModel, onOpen: () => ctl.close()})),
+ dom.on('click', () => beaconOpenMessage({appModel, onOpen: () => ctl.close(), route: '/ask/message/'})),
testId('feedback'),
)
),
diff --git a/app/gen-server/ApiServer.ts b/app/gen-server/ApiServer.ts
index 4e02f7d4..08adeab8 100644
--- a/app/gen-server/ApiServer.ts
+++ b/app/gen-server/ApiServer.ts
@@ -39,7 +39,7 @@ export function getOrgFromRequest(req: Request): string|null {
* HelpScout the user identity for identifying customer information and conversation history.
*/
function helpScoutSign(email: string): string|undefined {
- const secretKey = process.env.HELP_SCOUT_SECRET_KEY;
+ const secretKey = process.env.HELP_SCOUT_SECRET_KEY_V2;
if (!secretKey) { return undefined; }
return crypto.createHmac('sha256', secretKey).update(email).digest('hex');
}
diff --git a/app/server/lib/sendAppPage.ts b/app/server/lib/sendAppPage.ts
index b52005ba..99a0b204 100644
--- a/app/server/lib/sendAppPage.ts
+++ b/app/server/lib/sendAppPage.ts
@@ -41,7 +41,7 @@ export function makeGristConfig(homeUrl: string|null, extra: Partial