@ -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" ) ) ;
report Warning ( "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,7 +184,23 @@ 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 {