ExporterCardPDF.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. // exporter maybe is broken since Gridfs introduced, add fs and path
  2. import { createWorkbook } from './createWorkbook';
  3. class ExporterCardPDF {
  4. constructor(boardId) {
  5. this._boardId = boardId;
  6. }
  7. build(res) {
  8. /*
  9. const fs = Npm.require('fs');
  10. const os = Npm.require('os');
  11. const path = Npm.require('path');
  12. const byBoard = {
  13. boardId: this._boardId,
  14. };
  15. const byBoardNoLinked = {
  16. boardId: this._boardId,
  17. linkedId: {
  18. $in: ['', null],
  19. },
  20. };
  21. // we do not want to retrieve boardId in related elements
  22. const noBoardId = {
  23. fields: {
  24. boardId: 0,
  25. },
  26. };
  27. const result = {
  28. _format: 'wekan-board-1.0.0',
  29. };
  30. _.extend(
  31. result,
  32. Boards.findOne(this._boardId, {
  33. fields: {
  34. stars: 0,
  35. },
  36. }),
  37. );
  38. result.lists = Lists.find(byBoard, noBoardId).fetch();
  39. result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
  40. result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
  41. result.customFields = CustomFields.find(
  42. {
  43. boardIds: {
  44. $in: [this.boardId],
  45. },
  46. },
  47. {
  48. fields: {
  49. boardId: 0,
  50. },
  51. },
  52. ).fetch();
  53. result.comments = CardComments.find(byBoard, noBoardId).fetch();
  54. result.activities = Activities.find(byBoard, noBoardId).fetch();
  55. result.rules = Rules.find(byBoard, noBoardId).fetch();
  56. result.checklists = [];
  57. result.checklistItems = [];
  58. result.subtaskItems = [];
  59. result.triggers = [];
  60. result.actions = [];
  61. result.cards.forEach((card) => {
  62. result.checklists.push(
  63. ...Checklists.find({
  64. cardId: card._id,
  65. }).fetch(),
  66. );
  67. result.checklistItems.push(
  68. ...ChecklistItems.find({
  69. cardId: card._id,
  70. }).fetch(),
  71. );
  72. result.subtaskItems.push(
  73. ...Cards.find({
  74. parentId: card._id,
  75. }).fetch(),
  76. );
  77. });
  78. result.rules.forEach((rule) => {
  79. result.triggers.push(
  80. ...Triggers.find(
  81. {
  82. _id: rule.triggerId,
  83. },
  84. noBoardId,
  85. ).fetch(),
  86. );
  87. result.actions.push(
  88. ...Actions.find(
  89. {
  90. _id: rule.actionId,
  91. },
  92. noBoardId,
  93. ).fetch(),
  94. );
  95. });
  96. // we also have to export some user data - as the other elements only
  97. // include id but we have to be careful:
  98. // 1- only exports users that are linked somehow to that board
  99. // 2- do not export any sensitive information
  100. const users = {};
  101. result.members.forEach((member) => {
  102. users[member.userId] = true;
  103. });
  104. result.lists.forEach((list) => {
  105. users[list.userId] = true;
  106. });
  107. result.cards.forEach((card) => {
  108. users[card.userId] = true;
  109. if (card.members) {
  110. card.members.forEach((memberId) => {
  111. users[memberId] = true;
  112. });
  113. }
  114. if (card.assignees) {
  115. card.assignees.forEach((memberId) => {
  116. users[memberId] = true;
  117. });
  118. }
  119. });
  120. result.comments.forEach((comment) => {
  121. users[comment.userId] = true;
  122. });
  123. result.activities.forEach((activity) => {
  124. users[activity.userId] = true;
  125. });
  126. result.checklists.forEach((checklist) => {
  127. users[checklist.userId] = true;
  128. });
  129. const byUserIds = {
  130. _id: {
  131. $in: Object.getOwnPropertyNames(users),
  132. },
  133. };
  134. // we use whitelist to be sure we do not expose inadvertently
  135. // some secret fields that gets added to User later.
  136. const userFields = {
  137. fields: {
  138. _id: 1,
  139. username: 1,
  140. 'profile.initials': 1,
  141. 'profile.avatarUrl': 1,
  142. },
  143. };
  144. result.users = Users.find(byUserIds, userFields)
  145. .fetch()
  146. .map((user) => {
  147. // user avatar is stored as a relative url, we export absolute
  148. if ((user.profile || {}).avatarUrl) {
  149. user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
  150. }
  151. return user;
  152. });
  153. //init exceljs workbook
  154. const workbook = createWorkbook();
  155. workbook.creator = TAPi18n.__('export-board');
  156. workbook.lastModifiedBy = TAPi18n.__('export-board');
  157. workbook.created = new Date();
  158. workbook.modified = new Date();
  159. workbook.lastPrinted = new Date();
  160. const filename = `${result.title}.xlsx`;
  161. //init worksheet
  162. const worksheet = workbook.addWorksheet(result.title, {
  163. properties: {
  164. tabColor: {
  165. argb: 'FFC0000',
  166. },
  167. },
  168. pageSetup: {
  169. paperSize: 9,
  170. orientation: 'landscape',
  171. },
  172. });
  173. //get worksheet
  174. const ws = workbook.getWorksheet(result.title);
  175. ws.properties.defaultRowHeight = 20;
  176. //init columns
  177. //Excel font. Western: Arial. zh-CN: 宋体
  178. ws.columns = [
  179. {
  180. key: 'a',
  181. width: 14,
  182. },
  183. {
  184. key: 'b',
  185. width: 40,
  186. },
  187. {
  188. key: 'c',
  189. width: 60,
  190. },
  191. {
  192. key: 'd',
  193. width: 40,
  194. },
  195. {
  196. key: 'e',
  197. width: 20,
  198. },
  199. {
  200. key: 'f',
  201. width: 20,
  202. style: {
  203. font: {
  204. name: TAPi18n.__('excel-font'),
  205. size: '10',
  206. },
  207. numFmt: 'yyyy/mm/dd hh:mm:ss',
  208. },
  209. },
  210. {
  211. key: 'g',
  212. width: 20,
  213. style: {
  214. font: {
  215. name: TAPi18n.__('excel-font'),
  216. size: '10',
  217. },
  218. numFmt: 'yyyy/mm/dd hh:mm:ss',
  219. },
  220. },
  221. {
  222. key: 'h',
  223. width: 20,
  224. style: {
  225. font: {
  226. name: TAPi18n.__('excel-font'),
  227. size: '10',
  228. },
  229. numFmt: 'yyyy/mm/dd hh:mm:ss',
  230. },
  231. },
  232. {
  233. key: 'i',
  234. width: 20,
  235. style: {
  236. font: {
  237. name: TAPi18n.__('excel-font'),
  238. size: '10',
  239. },
  240. numFmt: 'yyyy/mm/dd hh:mm:ss',
  241. },
  242. },
  243. {
  244. key: 'j',
  245. width: 20,
  246. style: {
  247. font: {
  248. name: TAPi18n.__('excel-font'),
  249. size: '10',
  250. },
  251. numFmt: 'yyyy/mm/dd hh:mm:ss',
  252. },
  253. },
  254. {
  255. key: 'k',
  256. width: 20,
  257. style: {
  258. font: {
  259. name: TAPi18n.__('excel-font'),
  260. size: '10',
  261. },
  262. numFmt: 'yyyy/mm/dd hh:mm:ss',
  263. },
  264. },
  265. {
  266. key: 'l',
  267. width: 20,
  268. },
  269. {
  270. key: 'm',
  271. width: 20,
  272. },
  273. {
  274. key: 'n',
  275. width: 20,
  276. },
  277. {
  278. key: 'o',
  279. width: 20,
  280. },
  281. {
  282. key: 'p',
  283. width: 20,
  284. },
  285. {
  286. key: 'q',
  287. width: 20,
  288. },
  289. {
  290. key: 'r',
  291. width: 20,
  292. },
  293. ];
  294. //add title line
  295. ws.mergeCells('A1:H1');
  296. ws.getCell('A1').value = result.title;
  297. ws.getCell('A1').style = {
  298. font: {
  299. name: TAPi18n.__('excel-font'),
  300. size: '20',
  301. },
  302. };
  303. ws.getCell('A1').alignment = {
  304. vertical: 'middle',
  305. horizontal: 'center',
  306. };
  307. ws.getRow(1).height = 40;
  308. //get member and assignee info
  309. let jmem = '';
  310. let jassig = '';
  311. const jmeml = {};
  312. const jassigl = {};
  313. for (const i in result.users) {
  314. jmem = `${jmem + result.users[i].username},`;
  315. jmeml[result.users[i]._id] = result.users[i].username;
  316. }
  317. jmem = jmem.substr(0, jmem.length - 1);
  318. for (const ia in result.users) {
  319. jassig = `${jassig + result.users[ia].username},`;
  320. jassigl[result.users[ia]._id] = result.users[ia].username;
  321. }
  322. jassig = jassig.substr(0, jassig.length - 1);
  323. //get kanban list info
  324. const jlist = {};
  325. for (const klist in result.lists) {
  326. jlist[result.lists[klist]._id] = result.lists[klist].title;
  327. }
  328. //get kanban swimlanes info
  329. const jswimlane = {};
  330. for (const kswimlane in result.swimlanes) {
  331. jswimlane[result.swimlanes[kswimlane]._id] =
  332. result.swimlanes[kswimlane].title;
  333. }
  334. //get kanban label info
  335. const jlabel = {};
  336. var isFirst = 1;
  337. for (const klabel in result.labels) {
  338. // console.log(klabel);
  339. if (isFirst == 0) {
  340. jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`;
  341. } else {
  342. isFirst = 0;
  343. jlabel[result.labels[klabel]._id] = result.labels[klabel].name;
  344. }
  345. }
  346. //add data +8 hours
  347. function addTZhours(jdate) {
  348. const curdate = new Date(jdate);
  349. const checkCorrectDate = moment(curdate);
  350. if (checkCorrectDate.isValid()) {
  351. return curdate;
  352. } else {
  353. return ' ';
  354. }
  355. ////Do not add 8 hours to GMT. Use GMT instead.
  356. ////Could not yet figure out how to get localtime.
  357. //return new Date(curdate.setHours(curdate.getHours() + 8));
  358. //return curdate;
  359. }
  360. //add blank row
  361. ws.addRow().values = ['', '', '', '', '', ''];
  362. //add kanban info
  363. ws.addRow().values = [
  364. TAPi18n.__('createdAt'),
  365. addTZhours(result.createdAt),
  366. TAPi18n.__('modifiedAt'),
  367. addTZhours(result.modifiedAt),
  368. TAPi18n.__('members'),
  369. jmem,
  370. ];
  371. ws.getRow(3).font = {
  372. name: TAPi18n.__('excel-font'),
  373. size: 10,
  374. bold: true,
  375. };
  376. ws.mergeCells('F3:R3');
  377. ws.getCell('B3').style = {
  378. font: {
  379. name: TAPi18n.__('excel-font'),
  380. size: '10',
  381. bold: true,
  382. },
  383. numFmt: 'yyyy/mm/dd hh:mm:ss',
  384. };
  385. //cell center
  386. function cellCenter(cellno) {
  387. ws.getCell(cellno).alignment = {
  388. vertical: 'middle',
  389. horizontal: 'center',
  390. wrapText: true,
  391. };
  392. }
  393. function cellLeft(cellno) {
  394. ws.getCell(cellno).alignment = {
  395. vertical: 'middle',
  396. horizontal: 'left',
  397. wrapText: true,
  398. };
  399. }
  400. cellCenter('A3');
  401. cellCenter('B3');
  402. cellCenter('C3');
  403. cellCenter('D3');
  404. cellCenter('E3');
  405. cellLeft('F3');
  406. ws.getRow(3).height = 20;
  407. //all border
  408. function allBorder(cellno) {
  409. ws.getCell(cellno).border = {
  410. top: {
  411. style: 'thin',
  412. },
  413. left: {
  414. style: 'thin',
  415. },
  416. bottom: {
  417. style: 'thin',
  418. },
  419. right: {
  420. style: 'thin',
  421. },
  422. };
  423. }
  424. allBorder('A3');
  425. allBorder('B3');
  426. allBorder('C3');
  427. allBorder('D3');
  428. allBorder('E3');
  429. allBorder('F3');
  430. //add blank row
  431. ws.addRow().values = [
  432. '',
  433. '',
  434. '',
  435. '',
  436. '',
  437. '',
  438. '',
  439. '',
  440. '',
  441. '',
  442. '',
  443. '',
  444. '',
  445. '',
  446. '',
  447. ];
  448. //add card title
  449. //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
  450. //this is where order in which the excel file generates
  451. ws.addRow().values = [
  452. TAPi18n.__('number'),
  453. TAPi18n.__('title'),
  454. TAPi18n.__('description'),
  455. TAPi18n.__('parent-card'),
  456. TAPi18n.__('owner'),
  457. TAPi18n.__('createdAt'),
  458. TAPi18n.__('last-modified-at'),
  459. TAPi18n.__('card-received'),
  460. TAPi18n.__('card-start'),
  461. TAPi18n.__('card-due'),
  462. TAPi18n.__('card-end'),
  463. TAPi18n.__('list'),
  464. TAPi18n.__('swimlane'),
  465. TAPi18n.__('assignee'),
  466. TAPi18n.__('members'),
  467. TAPi18n.__('labels'),
  468. TAPi18n.__('overtime-hours'),
  469. TAPi18n.__('spent-time-hours'),
  470. ];
  471. ws.getRow(5).height = 20;
  472. allBorder('A5');
  473. allBorder('B5');
  474. allBorder('C5');
  475. allBorder('D5');
  476. allBorder('E5');
  477. allBorder('F5');
  478. allBorder('G5');
  479. allBorder('H5');
  480. allBorder('I5');
  481. allBorder('J5');
  482. allBorder('K5');
  483. allBorder('L5');
  484. allBorder('M5');
  485. allBorder('N5');
  486. allBorder('O5');
  487. allBorder('P5');
  488. allBorder('Q5');
  489. allBorder('R5');
  490. cellCenter('A5');
  491. cellCenter('B5');
  492. cellCenter('C5');
  493. cellCenter('D5');
  494. cellCenter('E5');
  495. cellCenter('F5');
  496. cellCenter('G5');
  497. cellCenter('H5');
  498. cellCenter('I5');
  499. cellCenter('J5');
  500. cellCenter('K5');
  501. cellCenter('L5');
  502. cellCenter('M5');
  503. cellCenter('N5');
  504. cellCenter('O5');
  505. cellCenter('P5');
  506. cellCenter('Q5');
  507. cellCenter('R5');
  508. ws.getRow(5).font = {
  509. name: TAPi18n.__('excel-font'),
  510. size: 12,
  511. bold: true,
  512. };
  513. //add blank row
  514. //add card info
  515. for (const i in result.cards) {
  516. const jcard = result.cards[i];
  517. //get member info
  518. let jcmem = '';
  519. for (const j in jcard.members) {
  520. jcmem += jmeml[jcard.members[j]];
  521. jcmem += ' ';
  522. }
  523. //get assignee info
  524. let jcassig = '';
  525. for (const ja in jcard.assignees) {
  526. jcassig += jassigl[jcard.assignees[ja]];
  527. jcassig += ' ';
  528. }
  529. //get card label info
  530. let jclabel = '';
  531. for (const jl in jcard.labelIds) {
  532. jclabel += jlabel[jcard.labelIds[jl]];
  533. jclabel += ' ';
  534. }
  535. //get parent name
  536. if (jcard.parentId) {
  537. const parentCard = result.cards.find(
  538. (card) => card._id === jcard.parentId,
  539. );
  540. jcard.parentCardTitle = parentCard ? parentCard.title : '';
  541. }
  542. //add card detail
  543. const t = Number(i) + 1;
  544. ws.addRow().values = [
  545. t.toString(),
  546. jcard.title,
  547. jcard.description,
  548. jcard.parentCardTitle,
  549. jmeml[jcard.userId],
  550. addTZhours(jcard.createdAt),
  551. addTZhours(jcard.dateLastActivity),
  552. addTZhours(jcard.receivedAt),
  553. addTZhours(jcard.startAt),
  554. addTZhours(jcard.dueAt),
  555. addTZhours(jcard.endAt),
  556. jlist[jcard.listId],
  557. jswimlane[jcard.swimlaneId],
  558. jcassig,
  559. jcmem,
  560. jclabel,
  561. jcard.isOvertime ? 'true' : 'false',
  562. jcard.spentTime,
  563. ];
  564. const y = Number(i) + 6;
  565. //ws.getRow(y).height = 25;
  566. allBorder(`A${y}`);
  567. allBorder(`B${y}`);
  568. allBorder(`C${y}`);
  569. allBorder(`D${y}`);
  570. allBorder(`E${y}`);
  571. allBorder(`F${y}`);
  572. allBorder(`G${y}`);
  573. allBorder(`H${y}`);
  574. allBorder(`I${y}`);
  575. allBorder(`J${y}`);
  576. allBorder(`K${y}`);
  577. allBorder(`L${y}`);
  578. allBorder(`M${y}`);
  579. allBorder(`N${y}`);
  580. allBorder(`O${y}`);
  581. allBorder(`P${y}`);
  582. allBorder(`Q${y}`);
  583. allBorder(`R${y}`);
  584. cellCenter(`A${y}`);
  585. ws.getCell(`B${y}`).alignment = {
  586. wrapText: true,
  587. };
  588. ws.getCell(`C${y}`).alignment = {
  589. wrapText: true,
  590. };
  591. ws.getCell(`M${y}`).alignment = {
  592. wrapText: true,
  593. };
  594. ws.getCell(`N${y}`).alignment = {
  595. wrapText: true,
  596. };
  597. ws.getCell(`O${y}`).alignment = {
  598. wrapText: true,
  599. };
  600. }
  601. workbook.xlsx.write(res).then(function () {});
  602. */
  603. var doc = new PDFDocument({size: 'A4', margin: 50});
  604. doc.fontSize(12);
  605. doc.text('Some test text', 10, 30, {align: 'center', width: 200});
  606. this.response.writeHead(200, {
  607. 'Content-type': 'application/pdf',
  608. 'Content-Disposition': "attachment; filename=test.pdf"
  609. });
  610. this.response.end( doc.outputSync() );
  611. }
  612. canExport(user) {
  613. const board = Boards.findOne(this._boardId);
  614. return board && board.isVisibleBy(user);
  615. }
  616. }
  617. export { ExporterCardPDF };