mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Reopen HelpScout beacon to the last-opened article
Summary: - When opening HelpScout beacon to an article ("answers"), avoid a 'navigate' call to let the beacon show the previously open article. - Work around a bug with reloading a page with a beacon article open: HelpScout renders the last state without triggering usual events. - Report errors to server when beacon fails to load. - reportWarning() method now reports the message to the server. Test Plan: Added a test case Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3345
This commit is contained in:
parent
499e24b744
commit
d55bdbcdf3
@ -15,7 +15,8 @@
|
||||
|
||||
// tslint:disable:unified-signatures
|
||||
|
||||
import {AppModel, reportError} from 'app/client/models/AppModel';
|
||||
import {AppModel} from 'app/client/models/AppModel';
|
||||
import {reportWarning} from 'app/client/models/errors';
|
||||
import {IAppError} from 'app/client/models/NotifyModel';
|
||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {timeFormat} from 'app/common/timeFormat';
|
||||
@ -64,7 +65,8 @@ export function Beacon(method: 'navigate', route: string): void;
|
||||
export function Beacon(method: 'identify', userObj: IUserObj): void;
|
||||
export function Beacon(method: 'prefill', formObj: IFormObj): void;
|
||||
export function Beacon(method: 'config', configObj: object): void;
|
||||
export function Beacon(method: 'on'|'off'|'once', event: 'open'|'close', callback: () => void): void;
|
||||
export function Beacon(method: 'on'|'once', event: string, callback: () => void): void;
|
||||
export function Beacon(method: 'off', event: string, callback?: () => void): void;
|
||||
export function Beacon(method: 'session-data', data: ISessionData): void;
|
||||
export function Beacon(method: BeaconCmd): void;
|
||||
export function Beacon(method: BeaconCmd, options?: unknown, data?: unknown) {
|
||||
@ -85,21 +87,30 @@ function initBeacon(): void {
|
||||
const beaconId = gristConfig && gristConfig.helpScoutBeaconId;
|
||||
if (beaconId) {
|
||||
(window as any).Beacon = _beacon;
|
||||
document.head.appendChild(dom('script', {
|
||||
document.head.appendChild(dom('script',
|
||||
{
|
||||
type: 'text/javascript',
|
||||
src: 'https://beacon-v2.helpscout.net',
|
||||
async: true,
|
||||
}));
|
||||
},
|
||||
// Report when the beacon fails to load so that the user knows something is wrong, and we
|
||||
// have a log of the error. (Note: might not report all failures due to ad-blockers.)
|
||||
dom.on('error', (e) => {
|
||||
reportWarning("Support form failed to load. " +
|
||||
"Please email support@getgrist.com with questions instead.");
|
||||
}),
|
||||
));
|
||||
_beacon('init', beaconId);
|
||||
_beacon('config', {display: {style: "manual"}});
|
||||
} else {
|
||||
(window as any).Beacon = () => null;
|
||||
reportError(new Error("Support form is not configured"));
|
||||
reportWarning("Support form is not configured");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastOpenType: 'error' | 'message' = 'message';
|
||||
let lastRoute: BeaconRoute|null = null;
|
||||
|
||||
/**
|
||||
* Helper to open a beacon, taking care of setting focus appropriately. Calls optional onOpen
|
||||
@ -118,31 +129,33 @@ function _beaconOpen(userObj: IUserObj|null, options: IBeaconOpenOptions) {
|
||||
lastOpenType = openType;
|
||||
}
|
||||
|
||||
const route: BeaconRoute = options.route || (errors?.length ? '/ask/message/' : '/answers/');
|
||||
// If beacon was and still is being opened for help articles, avoid the 'navigate' call
|
||||
// altogether, to keep the beacon at the last article it was on.
|
||||
const skipNav = (route === lastRoute && route === '/answers/');
|
||||
lastRoute = route;
|
||||
|
||||
Beacon('once', 'open', () => {
|
||||
const iframe = document.querySelector('#beacon-container iframe') as HTMLIFrameElement;
|
||||
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 <base href>.
|
||||
//
|
||||
// Here we set a <base href> 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: ''}));
|
||||
});
|
||||
// Fix base-href tag when opening an article.
|
||||
Beacon('once', 'article-viewed', () => fixBeaconBaseHref());
|
||||
// We duplicate this check for 'ready' event, because 'open' and 'article-viewed' events don't
|
||||
// trigger on page reload when a beacon article is already open (seems to be a HelpScout bug).
|
||||
Beacon('once', 'ready', () => fixBeaconBaseHref());
|
||||
|
||||
Beacon('once', 'close', () => {
|
||||
const iframe = document.querySelector('#beacon-container iframe') as HTMLIFrameElement;
|
||||
if (iframe) { iframe.blur(); }
|
||||
Beacon('off', 'article-viewed');
|
||||
});
|
||||
if (userObj) {
|
||||
Beacon('identify', userObj);
|
||||
}
|
||||
|
||||
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.
|
||||
@ -162,10 +175,8 @@ function _beaconOpen(userObj: IUserObj|null, options: IBeaconOpenOptions) {
|
||||
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', {
|
||||
@ -173,8 +184,24 @@ function _beaconOpen(userObj: IUserObj|null, options: IBeaconOpenOptions) {
|
||||
...attrs,
|
||||
});
|
||||
Beacon('open');
|
||||
if (!skipNav) {
|
||||
Beacon('navigate', route);
|
||||
}
|
||||
}
|
||||
|
||||
function fixBeaconBaseHref() {
|
||||
// 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 <base href>.
|
||||
//
|
||||
// Here we set a <base href> 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;
|
||||
const iframeDoc = iframe?.contentDocument;
|
||||
if (iframeDoc && !iframeDoc.querySelector('head > base')) {
|
||||
iframeDoc.head.appendChild(dom('base', {href: ''}));
|
||||
}
|
||||
}
|
||||
|
||||
export interface IBeaconOpenOptions {
|
||||
appModel: AppModel|null;
|
||||
|
@ -53,10 +53,14 @@ export function reportMessage(msg: string, options?: Partial<INotifyOptions>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows warning toast notification (with yellow styling).
|
||||
* Shows warning toast notification (with yellow styling), and log to server and to console. Pass
|
||||
* {level: 'error'} for same behavior with adjusted styling.
|
||||
*/
|
||||
export function reportWarning(msg: string, options?: Partial<INotifyOptions>) {
|
||||
reportMessage(msg, {level: 'warning', ...options});
|
||||
options = {level: 'warning', ...options};
|
||||
log.warn(`${options.level}: `, msg);
|
||||
_logError(msg);
|
||||
reportMessage(msg, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user