tbonelee commented on code in PR #5261:
URL: https://github.com/apache/zeppelin/pull/5261#discussion_r3346296027


##########
zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts:
##########
@@ -271,6 +271,33 @@ export class NotebookParagraphResultComponent implements 
OnInit, AfterViewInit,
     }
   }
 
+  copyToClipboard(type: 'tsv' | 'csv'): void {
+    if (!this.tableData || !this.tableData.rows) {
+      return;
+    }
+    const delimiter = type === 'tsv' ? '\t' : ',';
+    const { columns, rows } = this.tableData;
+    const escape = (value: unknown): string => {
+      const str = String(value ?? '');
+      return str.includes(delimiter) || str.includes('"') || 
str.includes('\n') ? `"${str.replace(/"/g, '""')}"` : str;
+    };
+    const lines = [
+      columns.map(escape).join(delimiter),
+      ...rows.map(row => columns.map(col => escape(row[col])).join(delimiter))
+    ];
+    const text = lines.join('\n');
+    navigator.clipboard.writeText(text).catch(() => {
+      const el = document.createElement('textarea');
+      el.value = text;
+      el.style.position = 'absolute';
+      el.style.left = '-9999px';
+      document.body.appendChild(el);
+      el.select();
+      document.execCommand('copy');
+      document.body.removeChild(el);
+    });

Review Comment:
   ```suggestion
       // TODO: Refactor the duplicated copy-to-clipboard logics
       const fallbackCopy = () => {
         const el = document.createElement('textarea');
         el.value = text;
         el.style.position = 'absolute';
         el.style.left = '-9999px';
         document.body.appendChild(el);
         el.select();
         document.execCommand('copy');
         document.body.removeChild(el);
       };
       // navigator.clipboard is undefined in non-secure contexts (e.g. plain 
HTTP),
       // where writeText would throw synchronously before the catch could run.
       if (navigator.clipboard) {
         navigator.clipboard.writeText(text).catch(fallbackCopy);
       } else {
         fallbackCopy();
       }
   ```



##########
zeppelin-web/src/app/notebook/paragraph/result/result.controller.js:
##########
@@ -914,6 +914,49 @@ function ResultCtrl($scope, $rootScope, $route, $window, 
$routeParams, $location
     saveAsService.saveAs(dsv, exportedFileName, extension);
   };
 
+  $scope.copyToClipboard = function(delimiter) {
+    let text = '';
+    for (let titleIndex in tableData.columns) {
+      if (tableData.columns.hasOwnProperty(titleIndex)) {
+        text += tableData.columns[titleIndex].name + delimiter;

Review Comment:
   We should escape not only the row value but the header value also. You could 
extract the `escape` function inside the closure and use that for this.



##########
zeppelin-web-angular/src/app/visualizations/table/table-visualization.component.ts:
##########
@@ -75,6 +75,30 @@ export class TableVisualizationComponent implements OnInit {
     writeFile(wb, `export.${type}`);
   }
 
+  copyToClipboard(type: 'tsv' | 'csv', all = true) {
+    const delimiter = type === 'tsv' ? '\t' : ',';
+    const sourceRows = all ? this.rows : [...this.nzTable.data];
+    const escape = (value: unknown): string => {
+      const str = String(value ?? '');
+      return str.includes(delimiter) || str.includes('"') || 
str.includes('\n') ? `"${str.replace(/"/g, '""')}"` : str;
+    };
+    const lines = [
+      this.columns.map(escape).join(delimiter),
+      ...sourceRows.map(row => this.columns.map(col => 
escape(row[col])).join(delimiter))
+    ];
+    const text = lines.join('\n');
+    navigator.clipboard.writeText(text).catch(() => {
+      const el = document.createElement('textarea');
+      el.value = text;
+      el.style.position = 'absolute';
+      el.style.left = '-9999px';
+      document.body.appendChild(el);
+      el.select();
+      document.execCommand('copy');
+      document.body.removeChild(el);
+    });

Review Comment:
   ```suggestion
       // TODO: Refactor the duplicated copy-to-clipboard logics
       const fallbackCopy = () => {
         const el = document.createElement('textarea');
         el.value = text;
         el.style.position = 'absolute';
         el.style.left = '-9999px';
         document.body.appendChild(el);
         el.select();
         document.execCommand('copy');
         document.body.removeChild(el);
       };
       // navigator.clipboard is undefined in non-secure contexts (e.g. plain 
HTTP),
       // where writeText would throw synchronously before the catch could run.
       if (navigator.clipboard) {
         navigator.clipboard.writeText(text).catch(fallbackCopy);
       } else {
         fallbackCopy();
       }
   ```



##########
zeppelin-web/src/app/notebook/paragraph/result/result.controller.js:
##########
@@ -914,6 +914,49 @@ function ResultCtrl($scope, $rootScope, $route, $window, 
$routeParams, $location
     saveAsService.saveAs(dsv, exportedFileName, extension);
   };
 
+  $scope.copyToClipboard = function(delimiter) {
+    let text = '';
+    for (let titleIndex in tableData.columns) {
+      if (tableData.columns.hasOwnProperty(titleIndex)) {
+        text += tableData.columns[titleIndex].name + delimiter;
+      }
+    }
+    text = text.substring(0, text.length - 1) + '\n';
+    for (let r in tableData.rows) {
+      if (tableData.rows.hasOwnProperty(r)) {
+        let row = tableData.rows[r];
+        let dsvRow = '';
+        for (let index in row) {
+          if (row.hasOwnProperty(index)) {
+            let stringValue = (row[index]).toString();

Review Comment:
   Null check is needed at here.
   ```suggestion
               let stringValue = (value === null || value === undefined) ? '' : 
String(value);
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to