| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894 | import { ReactiveCache } from '/imports/reactiveCache';import moment from 'moment/min/moment-with-locales';import { TAPi18n } from '/imports/i18n';import { createWorkbook } from './createWorkbook';// exporter maybe is broken since Gridfs introduced, add fs and pathclass ExporterExcel {  constructor(boardId, userLanguage) {    this._boardId = boardId;    this.userLanguage = userLanguage;  }  build(res) {    const fs = Npm.require('fs');    const os = Npm.require('os');    const path = Npm.require('path');    const byBoard = {      boardId: this._boardId,    };    const byBoardNoLinked = {      boardId: this._boardId,      linkedId: {        $in: ['', null],      },    };    // we do not want to retrieve boardId in related elements    const noBoardId = {      fields: {        boardId: 0,      },    };    const result = {      _format: 'wekan-board-1.0.0',    };    _.extend(      result,      ReactiveCache.getBoard(this._boardId, {        fields: {          stars: 0,        },      }),    );    result.lists = ReactiveCache.getLists(byBoard, noBoardId);    result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId);    result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId);    result.customFields = ReactiveCache.getCustomFields(      {        boardIds: {          $in: [this.boardId],        },      },      {        fields: {          boardId: 0,        },      },    );    result.comments = ReactiveCache.getCardComments(byBoard, noBoardId);    result.activities = ReactiveCache.getActivities(byBoard, noBoardId);    result.rules = ReactiveCache.getRules(byBoard, noBoardId);    result.checklists = [];    result.checklistItems = [];    result.subtaskItems = [];    result.triggers = [];    result.actions = [];    result.cards.forEach((card) => {      result.checklists.push(        ...ReactiveCache.getChecklists({          cardId: card._id,        }),      );      result.checklistItems.push(        ...ReactiveCache.getChecklistItems({          cardId: card._id,        }),      );      result.subtaskItems.push(        ...ReactiveCache.getCards({          parentId: card._id,        }),      );    });    result.rules.forEach((rule) => {      result.triggers.push(        ...ReactiveCache.getTriggers(          {            _id: rule.triggerId,          },          noBoardId,        ),      );      result.actions.push(        ...ReactiveCache.getActions(          {            _id: rule.actionId,          },          noBoardId,        ),      );    });    // we also have to export some user data - as the other elements only    // include id but we have to be careful:    // 1- only exports users that are linked somehow to that board    // 2- do not export any sensitive information    const users = {};    result.members.forEach((member) => {      users[member.userId] = true;    });    result.lists.forEach((list) => {      users[list.userId] = true;    });    result.cards.forEach((card) => {      users[card.userId] = true;      if (card.members) {        card.members.forEach((memberId) => {          users[memberId] = true;        });      }      if (card.assignees) {        card.assignees.forEach((memberId) => {          users[memberId] = true;        });      }    });    result.comments.forEach((comment) => {      users[comment.userId] = true;    });    result.activities.forEach((activity) => {      users[activity.userId] = true;    });    result.checklists.forEach((checklist) => {      users[checklist.userId] = true;    });    const byUserIds = {      _id: {        $in: Object.getOwnPropertyNames(users),      },    };    // we use whitelist to be sure we do not expose inadvertently    // some secret fields that gets added to User later.    const userFields = {      fields: {        _id: 1,        username: 1,        'profile.initials': 1,        'profile.avatarUrl': 1,      },    };    result.users = ReactiveCache.getUsers(byUserIds, userFields)      .map((user) => {        // user avatar is stored as a relative url, we export absolute        if ((user.profile || {}).avatarUrl) {          user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);        }        return user;      });    //init exceljs workbook    const workbook = createWorkbook();    workbook.creator = TAPi18n.__('export-board','',this.userLanguage);    workbook.lastModifiedBy = TAPi18n.__('export-board','',this.userLanguage);    workbook.created = new Date();    workbook.modified = new Date();    workbook.lastPrinted = new Date();    const filename = `${result.title}.xlsx`;    //init worksheet    let worksheetTitle = result.title;    if (worksheetTitle.length > 31) {      // MS Excel doesn't allow worksheet name longer than 31 chars      // Exceljs truncate names to 31 chars      let words = worksheetTitle.split(' ');      let tmpTitle = '';      for (let i=0;i<words.length; i++) {        if (words[0].length > 27) {          // title has no spaces          tmpTitle = words[0].substr(0,27) + ' ';          break;        }        if(tmpTitle.length + words[i].length < 27) {          tmpTitle += words[i] + ' ';        }        else {          break;        }      }      worksheetTitle = tmpTitle + '...';    }    const worksheet = workbook.addWorksheet(worksheetTitle, {      properties: {        tabColor: {          argb: 'FFC0000',        },      },      pageSetup: {        paperSize: 9,        orientation: 'landscape',      },    });    //get worksheet    const ws = workbook.getWorksheet(worksheetTitle);    ws.properties.defaultRowHeight = 20;    //init columns    //Excel font. Western: Arial. zh-CN: 宋体    ws.columns = [      {        key: 'a',        width: 14,      },      {        key: 'b',        width: 40,      },      {        key: 'c',        width: 60,      },      {        key: 'd',        width: 40,      },      {        key: 'e',        width: 20,      },      {        key: 'f',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'g',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'h',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'i',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'j',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'k',        width: 20,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'l',        width: 20,      },      {        key: 'm',        width: 20,      },      {        key: 'n',        width: 20,      },      {        key: 'o',        width: 20,      },      {        key: 'p',        width: 20,      },      {        key: 'q',        width: 20,      },      {        key: 'r',        width: 20,      },    ];    //add title line    ws.mergeCells('A1:H1');    ws.getCell('A1').value = result.title;    ws.getCell('A1').style = {      font: {        name: TAPi18n.__('excel-font'),        size: '20',      },    };    ws.getCell('A1').alignment = {      vertical: 'middle',      horizontal: 'left',    };    ws.getRow(1).height = 40;    //get member and assignee info    let jmem = '';    let jassig = '';    const jmeml = {};    const jassigl = {};    for (const i in result.users) {      jmem = `${jmem + result.users[i].username},`;      jmeml[result.users[i]._id] = result.users[i].username;    }    jmem = jmem.substr(0, jmem.length - 1);    for (const ia in result.users) {      jassig = `${jassig + result.users[ia].username},`;      jassigl[result.users[ia]._id] = result.users[ia].username;    }    jassig = jassig.substr(0, jassig.length - 1);    //get kanban list info    const jlist = {};    for (const klist in result.lists) {      jlist[result.lists[klist]._id] = result.lists[klist].title;    }    //get kanban swimlanes info    const jswimlane = {};    for (const kswimlane in result.swimlanes) {      jswimlane[result.swimlanes[kswimlane]._id] =        result.swimlanes[kswimlane].title;    }    //get kanban label info    const jlabel = {};    var isFirst = 1;    for (const klabel in result.labels) {      // console.log(klabel);      if (isFirst == 0) {        jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`;      } else {        isFirst = 0;        jlabel[result.labels[klabel]._id] = result.labels[klabel].name;      }    }    //add data +8 hours    function addTZhours(jdate) {      if (!jdate) { return ' '; }      const curdate = new Date(jdate);      const checkCorrectDate = moment(curdate);      if (checkCorrectDate.isValid()) {        return curdate;      } else {        return ' ';      }      ////Do not add 8 hours to GMT. Use GMT instead.      ////Could not yet figure out how to get localtime.      //return new Date(curdate.setHours(curdate.getHours() + 8));      //return curdate;    }    //cell center    function cellCenter(cellno) {      ws.getCell(cellno).alignment = {        vertical: 'middle',        horizontal: 'center',        wrapText: true,      };    }    function cellLeft(cellno) {      ws.getCell(cellno).alignment = {        vertical: 'middle',        horizontal: 'left',        wrapText: true,      };    }    // cell Card alignment    function cellCardAlignment(cellno) {      ws.getCell(cellno).alignment = {        vertical: 'top',        horizontal: 'left',        wrapText: true,      };    }    //all border    function allBorder(cellno) {      ws.getCell(cellno).border = {        top: {          style: 'thin',        },        left: {          style: 'thin',        },        bottom: {          style: 'thin',        },        right: {          style: 'thin',        },      };    }    //add blank row    ws.addRow().values = ['', '', '', '', '', ''];    //add board description    ws.addRow().values = [      TAPi18n.__('description','',this.userLanguage),      result.description,    ];    ws.mergeCells('B3:H3');    ws.getRow(3).height = 40;    // In MS Excel, we can't use the AutoFit feature on a column that contains a cell merged with cells in other columns.    // Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows.    ws.getRow(3).font = {      name: TAPi18n.__('excel-font'),      size: 10,    };    ws.getCell('A3').style = {      font: {        name: TAPi18n.__('excel-font'),        size: '10',        bold: true,      },    };    ws.getCell(`B3`).alignment = {      wrapText: true,      vertical: 'middle',    };    cellCenter('A3');    //add blank row    ws.addRow().values = ['', '', '', '', '', ''];    //add kanban info    ws.addRow().values = [      TAPi18n.__('createdAt','',this.userLanguage),      addTZhours(result.createdAt),      TAPi18n.__('modifiedAt','',this.userLanguage),      addTZhours(result.modifiedAt),      TAPi18n.__('members','',this.userLanguage),      jmem,    ];    ws.getRow(5).font = {      name: TAPi18n.__('excel-font'),      size: 10,      bold: true,    };    ws.mergeCells('F5:R5');    ws.getCell('B5').style = {      font: {        name: TAPi18n.__('excel-font'),        size: '10',        bold: true,      },      numFmt: 'yyyy/mm/dd hh:mm:ss',    };    ws.getCell('D5').style = {      font: {        name: TAPi18n.__('excel-font'),        size: '10',        bold: true,      },      numFmt: 'yyyy/mm/dd hh:mm:ss',    };    cellCenter('A5');    cellCenter('B5');    cellCenter('C5');    cellCenter('D5');    cellCenter('E5');    cellLeft('F5');    ws.getRow(5).height = 20;    allBorder('A5');    allBorder('B5');    allBorder('C5');    allBorder('D5');    allBorder('E5');    allBorder('F5');    //add blank row    ws.addRow().values = [      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',      '',    ];    //add card title    //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];    //this is where order in which the excel file generates    ws.addRow().values = [      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(7).height = 20;    allBorder('A7');    allBorder('B7');    allBorder('C7');    allBorder('D7');    allBorder('E7');    allBorder('F7');    allBorder('G7');    allBorder('H7');    allBorder('I7');    allBorder('J7');    allBorder('K7');    allBorder('L7');    allBorder('M7');    allBorder('N7');    allBorder('O7');    allBorder('P7');    allBorder('Q7');    allBorder('R7');    cellCenter('A7');    cellCenter('B7');    cellCenter('C7');    cellCenter('D7');    cellCenter('E7');    cellCenter('F7');    cellCenter('G7');    cellCenter('H7');    cellCenter('I7');    cellCenter('J7');    cellCenter('K7');    cellCenter('L7');    cellCenter('M7');    cellCenter('N7');    cellCenter('O7');    cellCenter('P7');    cellCenter('Q7');    cellCenter('R7');    ws.getRow(7).font = {      name: TAPi18n.__('excel-font'),      size: 10,      bold: true,    };    //add blank row    //add card info    for (const i in result.cards) {      const jcard = result.cards[i];      //get member info      let jcmem = '';      for (const j in jcard.members) {        jcmem += jmeml[jcard.members[j]];        jcmem += ' ';      }      //get assignee info      let jcassig = '';      for (const ja in jcard.assignees) {        jcassig += jassigl[jcard.assignees[ja]];        jcassig += ' ';      }      //get card label info      let jclabel = '';      for (const jl in jcard.labelIds) {        jclabel += jlabel[jcard.labelIds[jl]];        jclabel += ' ';      }      //get parent name      if (jcard.parentId) {        const parentCard = result.cards.find(          (card) => card._id === jcard.parentId,        );        jcard.parentCardTitle = parentCard ? parentCard.title : '';      }      //add card detail      const t = Number(i) + 1;      ws.addRow().values = [        t.toString(),        jcard.title,        jcard.description,        jcard.parentCardTitle,        jmeml[jcard.userId],        addTZhours(jcard.createdAt),        addTZhours(jcard.dateLastActivity),        addTZhours(jcard.receivedAt),        addTZhours(jcard.startAt),        addTZhours(jcard.dueAt),        addTZhours(jcard.endAt),        jlist[jcard.listId],        jswimlane[jcard.swimlaneId],        jcassig,        jcmem,        jclabel,        jcard.isOvertime ? 'true' : 'false',        jcard.spentTime,      ];      const y = Number(i) + 8;      //ws.getRow(y).height = 25;      ws.getRow(y).font = {        name: TAPi18n.__('excel-font'),        size: 10,      };      // Border      allBorder(`A${y}`);      allBorder(`B${y}`);      allBorder(`C${y}`);      allBorder(`D${y}`);      allBorder(`E${y}`);      allBorder(`F${y}`);      allBorder(`G${y}`);      allBorder(`H${y}`);      allBorder(`I${y}`);      allBorder(`J${y}`);      allBorder(`K${y}`);      allBorder(`L${y}`);      allBorder(`M${y}`);      allBorder(`N${y}`);      allBorder(`O${y}`);      allBorder(`P${y}`);      allBorder(`Q${y}`);      allBorder(`R${y}`);      // Alignment      ws.getCell(`A${y}`).alignment = {        vertical: 'top',        horizontal: 'right',        wrapText: true,      };      cellCardAlignment(`B${y}`);      cellCardAlignment(`C${y}`);      cellCardAlignment(`D${y}`);      cellCardAlignment(`E${y}`);      cellCardAlignment(`F${y}`);      cellCardAlignment(`G${y}`);      cellCardAlignment(`H${y}`);      cellCardAlignment(`I${y}`);      cellCardAlignment(`J${y}`);      cellCardAlignment(`K${y}`);      cellCardAlignment(`L${y}`);      cellCardAlignment(`M${y}`);      cellCardAlignment(`N${y}`);      cellCardAlignment(`O${y}`);      cellCardAlignment(`P${y}`);      ws.getCell(`Q${y}`).alignment = {        vertical: 'top',        horizontal: 'center',        wrapText: true,      };      ws.getCell(`R${y}`).alignment = {        vertical: 'top',        horizontal: 'center',        wrapText: true,      };    }    //Activities worksheet    //init worksheet    const worksheet2 = workbook.addWorksheet(TAPi18n.__('activity','',this.userLanguage), {      properties: {        tabColor: {          argb: 'FFC0000',        },      },      pageSetup: {        paperSize: 9,        orientation: 'landscape',      },    });    //get worksheet    const ws2 = workbook.getWorksheet(TAPi18n.__('activity','',this.userLanguage));    ws2.properties.defaultRowHeight = 20;    //init columns    ws2.columns = [      {        key: 'a',        width: 14,      },      {        key: 'b',        width: 60,      },      {        key: 'c',        width: 40,      },      {        key: 'd',        width: 40,      },      {        key: 'e',        width: 30,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },      {        key: 'f',        width: 30,        style: {          font: {            name: TAPi18n.__('excel-font'),            size: '10',          },          numFmt: 'yyyy/mm/dd hh:mm:ss',        },      },    ];    // cell Card alignment    function cellCardAlignmentWs2(cellno) {      ws2.getCell(cellno).alignment = {        vertical: 'top',        horizontal: 'left',        wrapText: true,      };    }    //all border    function allBorderWs2(cellno) {      ws2.getCell(cellno).border = {        top: {          style: 'thin',        },        left: {          style: 'thin',        },        bottom: {          style: 'thin',        },        right: {          style: 'thin',        },      };    }    //add title line    ws2.mergeCells('A1:F1');    ws2.getCell('A1').value = result.title;    ws2.getCell('A1').style = {      font: {        name: TAPi18n.__('excel-font'),        size: '20',      },    };    ws2.getCell('A1').alignment = {      vertical: 'middle',      horizontal: 'center',      wrapText: true,    };    ws2.getRow(1).height = 40;    //add blank row    ws2.addRow().values = ['', '', '', '', '', ''];    //add comment title    ws2.addRow().values = [      TAPi18n.__('number','',this.userLanguage),      TAPi18n.__('activity','',this.userLanguage),      TAPi18n.__('card','',this.userLanguage),      TAPi18n.__('owner','',this.userLanguage),      TAPi18n.__('createdAt','',this.userLanguage),      TAPi18n.__('last-modified-at','',this.userLanguage),    ];    ws2.getRow(3).height = 20;    ws2.getRow(3).font = {      name: TAPi18n.__('excel-font'),      size: 10,      bold: true,    };    ws2.getRow(3).alignment = {      vertical: 'middle',      horizontal: 'center',      wrapText: true,    };    allBorderWs2('A3');    allBorderWs2('B3');    allBorderWs2('C3');    allBorderWs2('D3');    allBorderWs2('E3');    allBorderWs2('F3');    //add comment info    let commentcnt = 0;    for (const i in result.comments) {      const jcomment = result.comments[i];      //card title      const parentCard = result.cards.find(        (card) => card._id === jcomment.cardId,      );      jcomment.cardTitle = parentCard ? parentCard.title : '';      if (jcomment.cardTitle == '') {        continue;      }      //add comment detail      commentcnt++;      ws2.addRow().values = [        commentcnt.toString(),        jcomment.text,        jcomment.cardTitle,        jmeml[jcomment.userId],        addTZhours(jcomment.createdAt),        addTZhours(jcomment.modifiedAt),      ];      const y = commentcnt + 3;      ws2.getRow(y).font = {        name: TAPi18n.__('excel-font'),        size: 10,      };      // Border      allBorderWs2(`A${y}`);      allBorderWs2(`B${y}`);      allBorderWs2(`C${y}`);      allBorderWs2(`D${y}`);      allBorderWs2(`E${y}`);      allBorderWs2(`F${y}`);      // Alignment      ws2.getCell(`A${y}`).alignment = {        vertical: 'top',        horizontal: 'right',        wrapText: true,      };      cellCardAlignmentWs2(`B${y}`);      cellCardAlignmentWs2(`C${y}`);      cellCardAlignmentWs2(`D${y}`);      cellCardAlignmentWs2(`E${y}`);      cellCardAlignmentWs2(`F${y}`);    }    workbook.xlsx.write(res).then(function () {});  }  canExport(user) {    const board = ReactiveCache.getBoard(this._boardId);    return board && board.isVisibleBy(user);  }}export { ExporterExcel };
 |