Browse Source

Fix : export CSV, TSV and XLS translation
Feature : add export CSV with semicolon separator

Ben0it-T 3 years ago
parent
commit
11bf4c7c07

+ 5 - 1
client/components/sidebar/sidebar.jade

@@ -400,7 +400,11 @@ template(name="exportBoard")
     li
     li
       a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
       a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
         i.fa.fa-share-alt
         i.fa.fa-share-alt
-        | {{_ 'export-board-csv'}}
+        | {{_ 'export-board-csv'}} ' , '
+    li
+      a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
+        i.fa.fa-share-alt
+        | {{_ 'export-board-csv'}} ' ; '
     li
     li
       a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
       a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
         i.fa.fa-share-alt
         i.fa.fa-share-alt

+ 15 - 0
client/components/sidebar/sidebar.js

@@ -512,6 +512,21 @@ BlazeComponent.extendComponent({
     };
     };
     const queryParams = {
     const queryParams = {
       authToken: Accounts._storedLoginToken(),
       authToken: Accounts._storedLoginToken(),
+      delimiter: ',',
+    };
+    return FlowRouter.path(
+      '/api/boards/:boardId/export/csv',
+      params,
+      queryParams,
+    );
+  },
+  exportScsvUrl() {
+    const params = {
+      boardId: Session.get('currentBoard'),
+    };
+    const queryParams = {
+      authToken: Accounts._storedLoginToken(),
+      delimiter: ';',
     };
     };
     return FlowRouter.path(
     return FlowRouter.path(
       '/api/boards/:boardId/export/csv',
       '/api/boards/:boardId/export/csv',

+ 25 - 12
models/export.js

@@ -167,25 +167,38 @@ if (Meteor.isServer) {
     const exporter = new Exporter(boardId);
     const exporter = new Exporter(boardId);
     if (exporter.canExport(user) || impersonateDone) {
     if (exporter.canExport(user) || impersonateDone) {
       if (impersonateDone) {
       if (impersonateDone) {
-        // TODO: Checking for CSV or TSV export type does not work:
-        //   let exportType = 'export' + params.query.delimiter ? 'CSV' : 'TSV';
-        // So logging export to CSV:
         let exportType = 'exportCSV';
         let exportType = 'exportCSV';
+        if( params.query.delimiter == "\t" ) {
+          exportType = 'exportTSV';
+        }
         ImpersonatedUsers.insert({
         ImpersonatedUsers.insert({
           adminId: adminId,
           adminId: adminId,
           boardId: boardId,
           boardId: boardId,
           reason: exportType,
           reason: exportType,
         });
         });
       }
       }
-
-      body = params.query.delimiter
-        ? exporter.buildCsv(params.query.delimiter)
-        : exporter.buildCsv();
-      //'Content-Length': body.length,
-      res.writeHead(200, {
-        'Content-Type': params.query.delimiter ? 'text/csv' : 'text/tsv',
-      });
-      res.write(body);
+      
+      let userLanguage = 'en';
+      if (user && user.profile) {
+        userLanguage = user.profile.language
+      }
+      
+      if( params.query.delimiter == "\t" ) {
+        // TSV file
+        res.writeHead(200, {
+          'Content-Type': 'text/tsv',
+        });
+      }
+      else {
+        // CSV file (comma or semicolon)
+        res.writeHead(200, {
+          'Content-Type': 'text/csv; charset=utf-8',
+        });
+        // Adding UTF8 BOM to quick fix MS Excel issue
+        // use Uint8Array to prevent from converting bytes to string
+        res.write(new Uint8Array([0xEF, 0xBB, 0xBF]));
+      }
+      res.write(exporter.buildCsv(params.query.delimiter, userLanguage));
       res.end();
       res.end();
     } else {
     } else {
       res.writeHead(403);
       res.writeHead(403);

+ 7 - 2
models/exportExcel.js

@@ -49,8 +49,13 @@ runOnServer(function() {
         isAdmin: true,
         isAdmin: true,
       });
       });
     }
     }
-
-    const exporterExcel = new ExporterExcel(boardId);
+    
+    let userLanguage = 'en';
+    if(user && user.profile){
+      userLanguage = user.profile.language
+    }
+    
+    const exporterExcel = new ExporterExcel(boardId, userLanguage);
     if (exporterExcel.canExport(user) || impersonateDone) {
     if (exporterExcel.canExport(user) || impersonateDone) {
       if (impersonateDone) {
       if (impersonateDone) {
         ImpersonatedUsers.insert({
         ImpersonatedUsers.insert({

+ 30 - 73
models/exporter.js

@@ -197,65 +197,43 @@ export class Exporter {
     return result;
     return result;
   }
   }
 
 
-  buildCsv(delimiter = ',') {
+  buildCsv(userDelimiter = ',', userLanguage='en') {
     const result = this.build();
     const result = this.build();
     const columnHeaders = [];
     const columnHeaders = [];
     const cardRows = [];
     const cardRows = [];
 
 
     const papaconfig = {
     const papaconfig = {
-      delimiter, // get parameter (was: auto-detect)
-      worker: true,
-    };
-
-    /*
-      newline: "",	// auto-detect
+      quotes: true,
       quoteChar: '"',
       quoteChar: '"',
       escapeChar: '"',
       escapeChar: '"',
+      delimiter: userDelimiter,
       header: true,
       header: true,
-      transformHeader: undefined,
-      dynamicTyping: false,
-      preview: 0,
-      encoding: "",
-      comments: false,
-      step: undefined,
-      complete: undefined,
-      error: undefined,
-      download: false,
-      downloadRequestHeaders: undefined,
-      downloadRequestBody: undefined,
-      skipEmptyLines: false,
-      chunk: undefined,
-      chunkSize: undefined,
-      fastMode: undefined,
-      beforeFirstChunk: undefined,
-      withCredentials: undefined,
-      transform: undefined
+      newline: "\r\n",
+      skipEmptyLines: false, 
+      escapeFormulae: true,
     };
     };
-    */
-
-    //delimitersToGuess: [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]
 
 
     columnHeaders.push(
     columnHeaders.push(
-      'Title',
-      'Description',
-      'Status',
-      'Swimlane',
-      'Owner',
-      'Requested by',
-      'Assigned by',
-      'Members',
-      'Assignees',
-      'Labels',
-      'Start at',
-      'Due at',
-      'End at',
-      'Over time',
-      'Spent time (hours)',
-      'Created at',
-      'Last modified at',
-      'Last activity',
-      'Vote',
-      'Archived',
+      TAPi18n.__('title','',userLanguage),
+      TAPi18n.__('description','',userLanguage),
+      TAPi18n.__('list','',userLanguage),
+      TAPi18n.__('swimlane','',userLanguage),
+      TAPi18n.__('owner','',userLanguage),
+      TAPi18n.__('requested-by','',userLanguage),
+      TAPi18n.__('assigned-by','',userLanguage),
+      TAPi18n.__('members','',userLanguage),
+      TAPi18n.__('assignee','',userLanguage),
+      TAPi18n.__('labels','',userLanguage),
+      TAPi18n.__('card-start','',userLanguage),
+      TAPi18n.__('card-due','',userLanguage),
+      TAPi18n.__('card-end','',userLanguage),
+      TAPi18n.__('overtime-hours','',userLanguage),
+      TAPi18n.__('spent-time-hours','',userLanguage),
+      TAPi18n.__('createdAt','',userLanguage),
+      TAPi18n.__('last-modified-at','',userLanguage),
+      TAPi18n.__('last-activity','',userLanguage),
+      TAPi18n.__('voting','',userLanguage),
+      TAPi18n.__('archived','',userLanguage),
     );
     );
     const customFieldMap = {};
     const customFieldMap = {};
     let i = 0;
     let i = 0;
@@ -283,30 +261,8 @@ export class Exporter {
       }
       }
       i++;
       i++;
     });
     });
-    cardRows.push([[columnHeaders]]);
-    /* TODO: Try to get translations working.
-             These currently only bring English translations.
-    TAPi18n.__('title'),
-    TAPi18n.__('description'),
-    TAPi18n.__('status'),
-    TAPi18n.__('swimlane'),
-    TAPi18n.__('owner'),
-    TAPi18n.__('requested-by'),
-    TAPi18n.__('assigned-by'),
-    TAPi18n.__('members'),
-    TAPi18n.__('assignee'),
-    TAPi18n.__('labels'),
-    TAPi18n.__('card-start'),
-    TAPi18n.__('card-due'),
-    TAPi18n.__('card-end'),
-    TAPi18n.__('overtime-hours'),
-    TAPi18n.__('spent-time-hours'),
-    TAPi18n.__('createdAt'),
-    TAPi18n.__('last-modified-at'),
-    TAPi18n.__('last-activity'),
-    TAPi18n.__('voting'),
-    TAPi18n.__('archived'),
-    */
+    //cardRows.push([[columnHeaders]]);
+    cardRows.push(columnHeaders);
 
 
     result.cards.forEach((card) => {
     result.cards.forEach((card) => {
       const currentRow = [];
       const currentRow = [];
@@ -409,7 +365,8 @@ export class Exporter {
           currentRow.push(customFieldValuesToPush[valueIndex]);
           currentRow.push(customFieldValuesToPush[valueIndex]);
         }
         }
       }
       }
-      cardRows.push([[currentRow]]);
+      //cardRows.push([[currentRow]]);
+      cardRows.push(currentRow);
     });
     });
 
 
     return Papa.unparse(cardRows, papaconfig);
     return Papa.unparse(cardRows, papaconfig);

+ 33 - 24
models/server/ExporterExcel.js

@@ -3,8 +3,9 @@ import { createWorkbook } from './createWorkbook';
 // exporter maybe is broken since Gridfs introduced, add fs and path
 // exporter maybe is broken since Gridfs introduced, add fs and path
 
 
 class ExporterExcel {
 class ExporterExcel {
-  constructor(boardId) {
+  constructor(boardId, userLanguage) {
     this._boardId = boardId;
     this._boardId = boardId;
+    this.userLanguage = userLanguage;
   }
   }
 
 
   build(res) {
   build(res) {
@@ -157,8 +158,8 @@ class ExporterExcel {
 
 
     //init exceljs workbook
     //init exceljs workbook
     const workbook = createWorkbook();
     const workbook = createWorkbook();
-    workbook.creator = TAPi18n.__('export-board');
-    workbook.lastModifiedBy = TAPi18n.__('export-board');
+    workbook.creator = TAPi18n.__('export-board','',this.userLanguage);
+    workbook.lastModifiedBy = TAPi18n.__('export-board','',this.userLanguage);
     workbook.created = new Date();
     workbook.created = new Date();
     workbook.modified = new Date();
     workbook.modified = new Date();
     workbook.lastPrinted = new Date();
     workbook.lastPrinted = new Date();
@@ -367,11 +368,11 @@ class ExporterExcel {
     ws.addRow().values = ['', '', '', '', '', ''];
     ws.addRow().values = ['', '', '', '', '', ''];
     //add kanban info
     //add kanban info
     ws.addRow().values = [
     ws.addRow().values = [
-      TAPi18n.__('createdAt'),
+      TAPi18n.__('createdAt','',this.userLanguage),
       addTZhours(result.createdAt),
       addTZhours(result.createdAt),
-      TAPi18n.__('modifiedAt'),
+      TAPi18n.__('modifiedAt','',this.userLanguage),
       addTZhours(result.modifiedAt),
       addTZhours(result.modifiedAt),
-      TAPi18n.__('members'),
+      TAPi18n.__('members','',this.userLanguage),
       jmem,
       jmem,
     ];
     ];
     ws.getRow(3).font = {
     ws.getRow(3).font = {
@@ -388,6 +389,14 @@ class ExporterExcel {
       },
       },
       numFmt: 'yyyy/mm/dd hh:mm:ss',
       numFmt: 'yyyy/mm/dd hh:mm:ss',
     };
     };
+    ws.getCell('D3').style = {
+      font: {
+        name: TAPi18n.__('excel-font'),
+        size: '10',
+        bold: true,
+      },
+      numFmt: 'yyyy/mm/dd hh:mm:ss',
+    };
     //cell center
     //cell center
     function cellCenter(cellno) {
     function cellCenter(cellno) {
       ws.getCell(cellno).alignment = {
       ws.getCell(cellno).alignment = {
@@ -455,24 +464,24 @@ class ExporterExcel {
     //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
     //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
     //this is where order in which the excel file generates
     //this is where order in which the excel file generates
     ws.addRow().values = [
     ws.addRow().values = [
-      TAPi18n.__('number'),
-      TAPi18n.__('title'),
-      TAPi18n.__('description'),
-      TAPi18n.__('parent-card'),
-      TAPi18n.__('owner'),
-      TAPi18n.__('createdAt'),
-      TAPi18n.__('last-modified-at'),
-      TAPi18n.__('card-received'),
-      TAPi18n.__('card-start'),
-      TAPi18n.__('card-due'),
-      TAPi18n.__('card-end'),
-      TAPi18n.__('list'),
-      TAPi18n.__('swimlane'),
-      TAPi18n.__('assignee'),
-      TAPi18n.__('members'),
-      TAPi18n.__('labels'),
-      TAPi18n.__('overtime-hours'),
-      TAPi18n.__('spent-time-hours'),
+      TAPi18n.__('number','',this.userLanguage),
+      TAPi18n.__('title','',this.userLanguage),
+      TAPi18n.__('description','',this.userLanguage),
+      TAPi18n.__('parent-card','',this.userLanguage),
+      TAPi18n.__('owner','',this.userLanguage),
+      TAPi18n.__('createdAt','',this.userLanguage),
+      TAPi18n.__('last-modified-at','',this.userLanguage),
+      TAPi18n.__('card-received','',this.userLanguage),
+      TAPi18n.__('card-start','',this.userLanguage),
+      TAPi18n.__('card-due','',this.userLanguage),
+      TAPi18n.__('card-end','',this.userLanguage),
+      TAPi18n.__('list','',this.userLanguage),
+      TAPi18n.__('swimlane','',this.userLanguage),
+      TAPi18n.__('assignee','',this.userLanguage),
+      TAPi18n.__('members','',this.userLanguage),
+      TAPi18n.__('labels','',this.userLanguage),
+      TAPi18n.__('overtime-hours','',this.userLanguage),
+      TAPi18n.__('spent-time-hours','',this.userLanguage),
     ];
     ];
     ws.getRow(5).height = 20;
     ws.getRow(5).height = 20;
     allBorder('A5');
     allBorder('A5');