@@ -7,24 +7,27 @@ let query;
77let isShowingLongValues ;
88let pluginConfiguration ;
99const headersToAlignRight = [ ] ;
10- const loaderElement = document . getElementById ( 'loader' ) ;
11- const modal = document . getElementById ( 'modal' ) ;
12-
13- // close the edit columns modal when clicking outside of it
14- document . querySelector ( 'body' ) . addEventListener ( 'click' , ( ) => {
15- if ( event . target === modal ) {
16- closeModal ( ) ;
17- }
18- } ) ;
19- // close the edit columns modal when escape key is pressed
20- window . onkeyup = ( e ) => {
21- if (
22- e . key === 'Escape' && modal . style . visibility === 'visible'
23- ) {
24- e . preventDefault ( ) ;
25- closeModal ( ) ;
26- }
27- } ;
10+ const globalThisOrWindow = typeof globalThis === 'object' ? globalThis : typeof window === 'object' ? window : null ;
11+ const loaderElement = globalThisOrWindow . document ? document . getElementById ( 'loader' ) : undefined ;
12+ const modal = globalThisOrWindow . document ? document . getElementById ( 'modal' ) : undefined ;
13+
14+ if ( globalThisOrWindow . document ) {
15+ // close the edit columns modal when clicking outside of it
16+ document . querySelector ( 'body' ) . addEventListener ( 'click' , ( ) => {
17+ if ( event . target === modal ) {
18+ closeModal ( ) ;
19+ }
20+ } ) ;
21+ // close the edit columns modal when escape key is pressed
22+ globalThisOrWindow . onkeyup = ( e ) => {
23+ if (
24+ e . key === 'Escape' && modal . style . visibility === 'visible'
25+ ) {
26+ e . preventDefault ( ) ;
27+ closeModal ( ) ;
28+ }
29+ } ;
30+ }
2831
2932/**
3033 * make XMLHttpRequest
@@ -65,7 +68,10 @@ function makeRequest(verb = 'GET', url, body) {
6568 * @param event : Object
6669 */
6770function handleError ( event ) {
68- document . getElementById ( 'error_description' ) . innerText = event . body . message ;
71+ if ( event instanceof Error ) {
72+ event = { body : { message : event . message } } ;
73+ }
74+ document . getElementById ( 'error_description' ) . innerText = event . body ? event . body . message : '' ;
6975 document . getElementById ( 'loading' ) . classList . add ( 'hide' ) ;
7076 document . getElementById ( 'spinner' ) . classList . add ( 'hide' ) ;
7177 document . getElementById ( 'error' ) . classList . remove ( 'hide' ) ;
@@ -639,10 +645,43 @@ function changePaginationButtonState(first, prev, next, last) {
639645 * align the columns header title to the right
640646 */
641647function alignRightHeaders ( ) {
642- document . querySelectorAll ( '.tabulator-col-title' ) . item ( 1 ) . classList . add ( 'align-right' ) ;
643- headersToAlignRight . forEach ( index => {
644- document . querySelectorAll ( '.tabulator-col-title' ) . item ( index ) . classList . add ( 'align-right' ) ;
645- } ) ;
648+ document . querySelectorAll ( '.tabulator-col-title' ) . item ( 1 ) . classList . add ( 'align-right' ) ;
649+ headersToAlignRight . forEach ( index => {
650+ document . querySelectorAll ( '.tabulator-col-title' ) . item ( index ) . classList . add ( 'align-right' ) ;
651+ } ) ;
652+ }
653+
654+ /**
655+ * Note: in 'Numbers' on Mac, a string beginning with space(s) and one of the following characters: = + - @ \t \r
656+ * is interpeted as a formula.
657+ * We decided to not fix all edge cases, but only the most common ones for now, so this method does fix this.
658+ * But this could be a possible improvement in the future if needed.
659+ */
660+ globalThisOrWindow . addSingleQuoteToCsvIfNeeded = function ( str ) {
661+ const specialChars = [ '=' , '+' , '-' , '@' , '\t' , '\r' ] ;
662+ if ( specialChars . some ( char => str . startsWith ( char ) ) ) {
663+ return "'" + str ;
664+ } else {
665+ return str ;
666+ }
667+ }
668+
669+ /**
670+ * Patch the Tabulator CSV downloader logic to add a single quote to values starting with special characters
671+ * This fixes the issue where Excel would interpret these values as formulas, creating a security risk
672+ */
673+ function patchTabulatorCsvDownloader ( ) {
674+ const getFieldValueCopy = Tabulator . prototype . moduleBindings ?. download ?. prototype ?. getFieldValue ;
675+ if ( getFieldValueCopy === undefined ) {
676+ console . error ( 'Ooops, something is wrong. CSV download might not work correctly. LKE-4315' ) ;
677+ }
678+ Tabulator . prototype . moduleBindings . download . prototype . getFieldValue = function ( columnName , rowData ) {
679+ const value = getFieldValueCopy . call ( this , columnName , rowData ) ;
680+ if ( typeof value === 'string' || typeof value === 'number' ) {
681+ return globalThisOrWindow . addSingleQuoteToCsvIfNeeded ( String ( value ) ) ;
682+ }
683+ return value ;
684+ }
646685}
647686
648687/**
@@ -657,6 +696,7 @@ function fillDataTable() {
657696 loaderElement . classList . remove ( 'active' ) ;
658697 document . getElementById ( 'container' ) . classList . remove ( 'hide' ) ;
659698 setTableHeader ( ) ;
699+ patchTabulatorCsvDownloader ( ) ;
660700 // create Tabulator on DOM element with id "example-table"
661701 table = new Tabulator ( '#table' , {
662702 tooltipsHeader : getTooltipsHeader ,
0 commit comments