import { BrtClickLogArg } from '@bruit/types' ; const SKIP_TAG = [ 'BR' , 'WBR' , 'STYLE' , 'SCRIPT' , 'HEAD' , 'HTML' , 'META' ] ; const SKIP_CHILDREN = [ 'SVG' ] ; export class ClickTool { param path return static init ( ) { window . addEventListener ( 'click' , event => { this . logClick ( < BrtClickLogArg > { xpath : this . getXPath ( event [ 'path' ] ) , partialDom : ClickTool . domToString ( event . srcElement || event . target ) } ) ; } ) ; } private static domToString ( element , maxLevel = 3 ) { const elements = [ ] ; let currentElement = this . getChildElement ( element as Element ) ; if ( currentElement ) { while ( elements . length <= maxLevel ) { if ( ! SKIP_TAG . includes ( currentElement . tagName . toUpperCase ( ) ) ) { elements . push ( currentElement ) ; } currentElement = this . getChildElement ( currentElement ) ; if ( ! currentElement ) { break ; } } } return elements . map ( elem => ClickTool . elementToString ( elem ) ) . map ( ( elem , idx ) => `${ '&nbsp;' . repeat ( 2 * idx ) }${ elem }` ) . join ( '<br>' ) ; } private static elementToString ( element ) { if ( element . tagName ) { switch ( element . tagName . toUpperCase ( ) ) { case 'SVG' : case 'IMG' : case 'CANVAS' : case 'EMBED' : return ClickTool . elementToTagString ( element ) ; case 'P' : case 'CENTER' : case 'EM' : case 'STRONG' : case 'SUB' : case 'SUP' : case 'B' : case 'SMALL' : case 'ABBR' : return this . getTextContent ( element ) ; default : return `${ ClickTool . elementToTagString ( element ) } ${ ClickTool . getTextContent ( element ) }` ; } } return '<unknown>' ; } private static getTextContent ( element ) { if ( element . innerHTML ) { const indexOfFirstTag = element . innerHTML . indexOf ( '<' ) ; let txt ; if ( indexOfFirstTag > 0 ) { txt = element . innerHTML . slice ( 0 , element . innerHTML . indexOf ( '<' ) ) ; } else if ( indexOfFirstTag === 0 ) { txt = '' ; } else { txt = element . innerHTML ; } return txt . replace ( /\s+/g , ' ' ) ; } else { return '' ; } } private static elementToTagString ( element ) { let string = '' ; if ( element . tagName ) string += element . tagName . toLowerCase ( ) ; if ( element . id ) string += ` id="${ element . id }"` ; if ( element . className && typeof element . className === 'string' && element . className . length ) string += ` class="${ element . className . split ( ' ' ) [ 0 ] }..."` ; if ( element . getAttribute ( 'type' ) ) string += ` type="${ element . getAttribute ( 'type' ) }"` ; if ( element . getAttribute ( 'name' ) ) string += ` name="${ element . getAttribute ( 'name' ) }"` ; if ( element . getAttribute ( 'href' ) && element . getAttribute ( 'href' ) !== '#' ) string += ` href="${ element . getAttribute ( 'href' ) }"` ; if ( element . getAttribute ( 'value' ) ) string += ` value="${ element . getAttribute ( 'value' ) }"` ; return `&lt;${ string }&gt;` ; } param path return private static getXPath ( path ) { if ( Array . isArray ( path ) && path . length >= 3 ) { path . pop ( ) ; path . pop ( ) ; path . pop ( ) ; const xpathElements = [ ] ; path . some ( element => { xpathElements . push ( ClickTool . getElementXpath ( element ) ) ; return ! ! element . id ; } ) ; return '//' + xpathElements . reverse ( ) . join ( '/' ) ; } else { return '//' ; } } param elem return private static getElementXpath ( elem ) { if ( elem . id ) { return `${ elem . nodeName }[@id="${ elem . id }"]` ; } else if ( elem . previousElementSibling || elem . nextElementSibling ) { return `${ elem . nodeName }[${ ClickTool . indexOfElement ( elem ) }]` ; } else { return elem . nodeName ; } } param elem return private static indexOfElement ( elem ) { var indexOfElement = 0 ; let child = elem ; while ( ( child = child . previousElementSibling ) != null ) { if ( child . nodeName === elem . nodeName ) { indexOfElement ++ ; } } return indexOfElement ; } param element return private static getChildElement ( element ) { var currentElement = element ; if ( currentElement . hasChildNodes ) { let firstChild = null ; for ( let i = 0 ; i < currentElement . children . length ; i ++ ) { const child = currentElement . children . item ( i ) ; if ( ! SKIP_TAG . includes ( child . tagName . toUpperCase ( ) ) && ! SKIP_CHILDREN . includes ( currentElement . tagName . toUpperCase ( ) ) ) { firstChild = child ; break ; } } currentElement = firstChild ; } else { currentElement = null ; } return currentElement ; } static logClick ( ... args ) { if ( ( < any > console ) . click ) { ( < any > console ) . click ( ... args ) ; } else { console . log ( ... args ) ; } } }