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]