datastake-daf 0.6.463 → 0.6.465

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Packer } from 'docx';
3
+ import { saveAs } from 'file-saver';
4
+ import { renderAsync } from 'docx-preview';
5
+ import createDocument from '../createDocument';
6
+
7
+ const WordDocumentPreview = ({ title = "React Word Document Example", content = "This is a sample Word document created with docx library.", fileName = "document.docx", config = [] }) => {
8
+ const previewRef = useRef(null);
9
+
10
+ // Helper function to fetch image from URL and convert to buffer
11
+ const fetchImageAsBuffer = async (url) => {
12
+ try {
13
+ const response = await fetch(url);
14
+ const arrayBuffer = await response.arrayBuffer();
15
+ return arrayBuffer;
16
+ } catch (error) {
17
+ console.error('Error fetching image:', error);
18
+ return null;
19
+ }
20
+ };
21
+
22
+ // Process config to fetch images from URLs
23
+ const processConfig = async (configArray) => {
24
+ const processedConfig = await Promise.all(
25
+ configArray.map(async (item) => {
26
+ // Handle imageWithText type
27
+ if (item.type === 'imageWithText' && item.data?.imageUrl && !item.data.image) {
28
+ const imageBuffer = await fetchImageAsBuffer(item.data.imageUrl);
29
+ return {
30
+ ...item,
31
+ data: {
32
+ ...item.data,
33
+ image: imageBuffer
34
+ }
35
+ };
36
+ }
37
+
38
+ // Handle documentHeader type with icon URL
39
+ if (item.type === 'documentHeader' && item.data?.icon && typeof item.data.icon === 'string') {
40
+ const iconBuffer = await fetchImageAsBuffer(item.data.icon);
41
+ return {
42
+ ...item,
43
+ data: {
44
+ ...item.data,
45
+ icon: iconBuffer
46
+ }
47
+ };
48
+ }
49
+
50
+ return item;
51
+ })
52
+ );
53
+ return processedConfig;
54
+ };
55
+
56
+ const downloadDocument = async () => {
57
+ const processedConfig = await processConfig(config);
58
+ const doc = createDocument({ title, content, config: processedConfig });
59
+ const blob = await Packer.toBlob(doc);
60
+ saveAs(blob, fileName);
61
+ };
62
+
63
+ useEffect(() => {
64
+ const renderPreview = async () => {
65
+ if (previewRef.current) {
66
+ const processedConfig = await processConfig(config);
67
+ const doc = createDocument({ title, content, config: processedConfig });
68
+ const blob = await Packer.toBlob(doc);
69
+
70
+ await renderAsync(blob, previewRef.current, null, {
71
+ className: "docx-preview",
72
+ inWrapper: true,
73
+ ignoreWidth: false,
74
+ ignoreHeight: false,
75
+ renderHeaders: true,
76
+ renderFooters: true,
77
+ });
78
+ }
79
+ };
80
+
81
+ renderPreview();
82
+ }, [title, content, config]);
83
+
84
+ return (
85
+ <div style={{ padding: '20px' }}>
86
+ <div style={{ marginBottom: '20px' }}>
87
+ <button
88
+ onClick={downloadDocument}
89
+ style={{
90
+ padding: '10px 20px',
91
+ fontSize: '16px',
92
+ backgroundColor: '#4CAF50',
93
+ color: 'white',
94
+ border: 'none',
95
+ borderRadius: '4px',
96
+ cursor: 'pointer'
97
+ }}
98
+ >
99
+ Download Word Document
100
+ </button>
101
+ </div>
102
+
103
+ <div
104
+ ref={previewRef}
105
+ style={{
106
+ border: '1px solid #ddd',
107
+ borderRadius: '4px',
108
+ padding: '20px',
109
+ backgroundColor: 'white',
110
+ minHeight: '400px',
111
+ maxWidth: '800px'
112
+ }}
113
+ />
114
+ </div>
115
+ );
116
+ };
117
+
118
+ export default WordDocumentPreview;
@@ -0,0 +1,565 @@
1
+ import * as docx from 'docx';
2
+
3
+ /**
4
+ * Renders different types of document elements based on type
5
+ * @param {Object} item - Configuration item
6
+ * @param {string} item.type - Type of element (paragraph, heading, table, image, etc.)
7
+ * @param {any} item.data - Data for the element
8
+ * @param {Object} item.meta - Metadata/styling for text runs
9
+ * @param {Object} item.style - Style properties for the element
10
+ * @returns {docx element} The rendered document element
11
+ */
12
+ const renderElement = (item) => {
13
+ const { type = 'paragraph', data, meta = {}, style = {} } = item;
14
+
15
+ switch (type) {
16
+ case 'paragraph':
17
+ return new docx.Paragraph({
18
+ children: [
19
+ new docx.TextRun({ text: data, ...meta }),
20
+ ],
21
+ ...style
22
+ });
23
+
24
+ case 'heading':
25
+ return new docx.Paragraph({
26
+ text: data,
27
+ heading: style.heading || docx.HeadingLevel.HEADING_1,
28
+ ...style
29
+ });
30
+
31
+ case 'table':
32
+ return new docx.Table({
33
+ rows: data.map(row =>
34
+ new docx.TableRow({
35
+ children: row.map(cell =>
36
+ new docx.TableCell({
37
+ children: [new docx.Paragraph(cell)]
38
+ })
39
+ )
40
+ })
41
+ ),
42
+ ...style
43
+ });
44
+
45
+ case 'image':
46
+ return new docx.Paragraph({
47
+ children: [
48
+ new docx.ImageRun({
49
+ data: data,
50
+ transformation: {
51
+ width: style.width || 100,
52
+ height: style.height || 100,
53
+ },
54
+ ...meta
55
+ })
56
+ ],
57
+ ...style
58
+ });
59
+
60
+ case 'imageWithText': {
61
+ // Creates a table with image and text side by side
62
+ // imagePosition: 'left' (default) or 'right'
63
+ const imagePosition = data.imagePosition || 'left';
64
+
65
+ // Create image cell
66
+ const imageCell = new docx.TableCell({
67
+ children: [
68
+ ...(data.image ? [
69
+ new docx.Paragraph({
70
+ children: [
71
+ new docx.ImageRun({
72
+ data: data.image,
73
+ transformation: {
74
+ width: data.imageWidth || 250,
75
+ height: data.imageHeight || 200,
76
+ }
77
+ })
78
+ ]
79
+ })
80
+ ] : [
81
+ new docx.Paragraph({
82
+ text: '[Image placeholder]',
83
+ shading: {
84
+ fill: 'F0F0F0',
85
+ }
86
+ })
87
+ ])
88
+ ],
89
+ width: {
90
+ size: 50,
91
+ type: docx.WidthType.PERCENTAGE
92
+ },
93
+ borders: {
94
+ top: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
95
+ bottom: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
96
+ left: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
97
+ right: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' }
98
+ }
99
+ });
100
+
101
+ // Create text cell
102
+ const textCell = new docx.TableCell({
103
+ children: [
104
+ new docx.Paragraph({
105
+ children: [
106
+ new docx.TextRun({
107
+ text: data.text || '',
108
+ size: 22
109
+ })
110
+ ]
111
+ })
112
+ ],
113
+ width: {
114
+ size: 50,
115
+ type: docx.WidthType.PERCENTAGE
116
+ },
117
+ borders: {
118
+ top: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
119
+ bottom: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
120
+ left: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' },
121
+ right: { style: docx.BorderStyle.SINGLE, size: 1, color: 'CCCCCC' }
122
+ }
123
+ });
124
+
125
+ // Arrange cells based on image position
126
+ const cells = imagePosition === 'right' ? [textCell, imageCell] : [imageCell, textCell];
127
+
128
+ const table = new docx.Table({
129
+ rows: [
130
+ new docx.TableRow({
131
+ children: cells
132
+ })
133
+ ],
134
+ width: {
135
+ size: 100,
136
+ type: docx.WidthType.PERCENTAGE
137
+ },
138
+ ...style
139
+ });
140
+
141
+ // Add spacing paragraph after the table
142
+ return [table, ...(data.hasSpacing ? [new docx.Paragraph({ spacing: { after: 200 } })] : [])];
143
+ }
144
+
145
+ case 'borderedSection': {
146
+ // Creates a bordered section (like the editable areas)
147
+ const table = new docx.Table({
148
+ rows: [
149
+ new docx.TableRow({
150
+ children: [
151
+ new docx.TableCell({
152
+ children: [
153
+ new docx.Paragraph({
154
+ children: [
155
+ new docx.TextRun({
156
+ text: data.title || '',
157
+ bold: true,
158
+ size: 24
159
+ })
160
+ ],
161
+ spacing: { after: 200 }
162
+ }),
163
+ new docx.Paragraph({
164
+ children: [
165
+ new docx.TextRun({
166
+ text: data.content || 'Editable - to be filled by the user',
167
+ italic: true,
168
+ size: 22,
169
+ color: '666666'
170
+ })
171
+ ]
172
+ })
173
+ ],
174
+ borders: {
175
+ top: { style: docx.BorderStyle.SINGLE, size: 6, color: 'CCCCCC' },
176
+ bottom: { style: docx.BorderStyle.SINGLE, size: 6, color: 'CCCCCC' },
177
+ left: { style: docx.BorderStyle.SINGLE, size: 6, color: 'CCCCCC' },
178
+ right: { style: docx.BorderStyle.SINGLE, size: 6, color: 'CCCCCC' }
179
+ },
180
+ shading: {
181
+ fill: 'F9F9F9'
182
+ }
183
+ })
184
+ ]
185
+ })
186
+ ],
187
+ width: {
188
+ size: 100,
189
+ type: docx.WidthType.PERCENTAGE
190
+ },
191
+ ...style
192
+ });
193
+
194
+ // Add spacing paragraph after the table
195
+ return [table, new docx.Paragraph({ spacing: { after: 400 } })];
196
+ }
197
+
198
+ case 'documentHeader': {
199
+ // Creates a document header with title, optional icon, and dynamic info fields
200
+ // data structure: { title, icon, infoFields: [{ label, value }] }
201
+ const { title, icon, infoFields = [], titleTextColor = '000000' } = data;
202
+
203
+ const headerCells = [];
204
+
205
+ // Title cell (left side)
206
+ const titleCell = new docx.TableCell({
207
+ children: [
208
+ new docx.Paragraph({
209
+ children: [
210
+ new docx.TextRun({
211
+ text: title || 'Document Title',
212
+ bold: true,
213
+ size: 28,
214
+ color: titleTextColor
215
+ })
216
+ ],
217
+ alignment: docx.AlignmentType.LEFT
218
+ })
219
+ ],
220
+ verticalAlign: docx.VerticalAlign.CENTER,
221
+ margins: {
222
+ top: 0,
223
+ bottom: 0,
224
+ left: 0,
225
+ right: 0
226
+ },
227
+ borders: {
228
+ top: { style: docx.BorderStyle.NONE },
229
+ bottom: { style: docx.BorderStyle.NONE },
230
+ left: { style: docx.BorderStyle.NONE },
231
+ right: { style: docx.BorderStyle.NONE }
232
+ }
233
+ });
234
+
235
+ headerCells.push(titleCell);
236
+
237
+ // Icon cell (right side) - optional
238
+ if (icon) {
239
+ const iconCell = new docx.TableCell({
240
+ children: [
241
+ new docx.Paragraph({
242
+ children: [
243
+ new docx.ImageRun({
244
+ data: icon,
245
+ transformation: {
246
+ width: data.iconWidth || 80,
247
+ height: data.iconHeight || 80,
248
+ }
249
+ })
250
+ ],
251
+ alignment: docx.AlignmentType.RIGHT
252
+ })
253
+ ],
254
+ verticalAlign: docx.VerticalAlign.CENTER,
255
+ width: {
256
+ size: 15,
257
+ type: docx.WidthType.PERCENTAGE
258
+ },
259
+ margins: {
260
+ top: 0,
261
+ bottom: 0,
262
+ left: 0,
263
+ right: 0
264
+ },
265
+ borders: {
266
+ top: { style: docx.BorderStyle.NONE },
267
+ bottom: { style: docx.BorderStyle.NONE },
268
+ left: { style: docx.BorderStyle.NONE },
269
+ right: { style: docx.BorderStyle.NONE }
270
+ }
271
+ });
272
+ headerCells.push(iconCell);
273
+ }
274
+
275
+ // Create title row
276
+ const titleRow = new docx.TableRow({
277
+ children: headerCells
278
+ });
279
+
280
+ // Create info fields row if provided
281
+ const rows = [titleRow];
282
+
283
+ if (infoFields && infoFields.length > 0) {
284
+ // Build the info text with dynamic fields
285
+ const infoTextRuns = [];
286
+ infoFields.forEach((field, index) => {
287
+ // Add label (bold)
288
+ infoTextRuns.push(
289
+ new docx.TextRun({
290
+ text: field.label,
291
+ bold: true,
292
+ size: 20
293
+ })
294
+ );
295
+ // Add value (normal)
296
+ infoTextRuns.push(
297
+ new docx.TextRun({
298
+ text: ` ${field.value}`,
299
+ size: 20
300
+ })
301
+ );
302
+ // Add separator if not last item
303
+ if (index < infoFields.length - 1) {
304
+ infoTextRuns.push(
305
+ new docx.TextRun({
306
+ text: ' | ',
307
+ size: 20
308
+ })
309
+ );
310
+ }
311
+ });
312
+
313
+ const infoRow = new docx.TableRow({
314
+ children: [
315
+ new docx.TableCell({
316
+ children: [
317
+ new docx.Paragraph({
318
+ children: infoTextRuns,
319
+ spacing: { before: 50, after: 0 }
320
+ })
321
+ ],
322
+ columnSpan: headerCells.length,
323
+ margins: {
324
+ top: 0,
325
+ bottom: 0,
326
+ left: 0,
327
+ right: 0
328
+ },
329
+ borders: {
330
+ top: { style: docx.BorderStyle.NONE },
331
+ bottom: { style: docx.BorderStyle.NONE },
332
+ left: { style: docx.BorderStyle.NONE },
333
+ right: { style: docx.BorderStyle.NONE }
334
+ }
335
+ })
336
+ ]
337
+ });
338
+
339
+ rows.push(infoRow);
340
+ }
341
+
342
+ const table = new docx.Table({
343
+ rows: rows,
344
+ width: {
345
+ size: 100,
346
+ type: docx.WidthType.PERCENTAGE
347
+ },
348
+ borders: {
349
+ top: { style: docx.BorderStyle.NONE },
350
+ bottom: { style: docx.BorderStyle.NONE },
351
+ left: { style: docx.BorderStyle.NONE },
352
+ right: { style: docx.BorderStyle.NONE },
353
+ insideHorizontal: { style: docx.BorderStyle.NONE },
354
+ insideVertical: { style: docx.BorderStyle.NONE }
355
+ },
356
+ ...style
357
+ });
358
+
359
+ // Add spacing paragraph after the header
360
+ return [table, new docx.Paragraph({ spacing: { after: 200 } })];
361
+ }
362
+
363
+ case 'commentSection': {
364
+ // Creates a bordered section with heading and auto-filled comments
365
+ const sectionChildren = [];
366
+
367
+ // Create title paragraph with percentage if provided
368
+ if (data.percentage !== undefined) {
369
+ sectionChildren.push(
370
+ new docx.Paragraph({
371
+ children: [
372
+ new docx.TextRun({
373
+ text: data.title,
374
+ bold: true,
375
+ size: 24
376
+ }),
377
+ new docx.TextRun({
378
+ text: "\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
379
+ }),
380
+ new docx.TextRun({
381
+ text: `○ ${data.percentage}%`,
382
+ color: 'FFA500',
383
+ size: 20,
384
+ bold: false
385
+ })
386
+ ],
387
+ spacing: { after: 100 }
388
+ })
389
+ );
390
+ } else {
391
+ sectionChildren.push(
392
+ new docx.Paragraph({
393
+ text: data.title,
394
+ bold: true,
395
+ size: 24,
396
+ spacing: { after: 100 }
397
+ })
398
+ );
399
+ }
400
+
401
+ // Add description with bottom border
402
+ sectionChildren.push(
403
+ new docx.Paragraph({
404
+ children: [
405
+ new docx.TextRun({
406
+ text: data.description,
407
+ size: 20
408
+ })
409
+ ],
410
+ spacing: { after: 150 },
411
+ border: {
412
+ bottom: { style: docx.BorderStyle.SINGLE, size: 6, color: 'CCCCCC' }
413
+ }
414
+ })
415
+ );
416
+
417
+ // Add bullet points
418
+ if (data.comments && Array.isArray(data.comments)) {
419
+ data.comments.forEach((comment, index) => {
420
+ sectionChildren.push(
421
+ new docx.Paragraph({
422
+ text: comment,
423
+ bullet: { level: 0 },
424
+ spacing: {
425
+ before: index === 0 ? 100 : 0,
426
+ after: index === data.comments.length - 1 ? 100 : 50
427
+ }
428
+ })
429
+ );
430
+ });
431
+ }
432
+
433
+ // Wrap everything in a bordered table
434
+ const table = new docx.Table({
435
+ rows: [
436
+ new docx.TableRow({
437
+ children: [
438
+ new docx.TableCell({
439
+ children: sectionChildren,
440
+ borders: {
441
+ top: { style: docx.BorderStyle.SINGLE, size: 6, color: '000000' },
442
+ bottom: { style: docx.BorderStyle.SINGLE, size: 6, color: '000000' },
443
+ left: { style: docx.BorderStyle.SINGLE, size: 6, color: '000000' },
444
+ right: { style: docx.BorderStyle.SINGLE, size: 6, color: '000000' }
445
+ },
446
+ margins: {
447
+ top: 100,
448
+ bottom: 100,
449
+ left: 100,
450
+ right: 100
451
+ }
452
+ })
453
+ ]
454
+ })
455
+ ],
456
+ width: {
457
+ size: 100,
458
+ type: docx.WidthType.PERCENTAGE
459
+ },
460
+ ...style
461
+ });
462
+
463
+ // Add minimal spacing after comment sections (they look good close together)
464
+ return table;
465
+ }
466
+
467
+ case 'bulletList':
468
+ return new docx.Paragraph({
469
+ text: data,
470
+ bullet: {
471
+ level: style.level || 0,
472
+ },
473
+ ...style
474
+ });
475
+
476
+ case 'numberedList':
477
+ return new docx.Paragraph({
478
+ text: data,
479
+ numbering: {
480
+ reference: style.reference || "default-numbering",
481
+ level: style.level || 0,
482
+ },
483
+ ...style
484
+ });
485
+
486
+ case 'pageBreak':
487
+ return new docx.Paragraph({
488
+ children: [new docx.PageBreak()],
489
+ });
490
+
491
+ case 'textRun':
492
+ return new docx.Paragraph({
493
+ children: [
494
+ new docx.TextRun({ text: data, ...meta }),
495
+ ],
496
+ ...style
497
+ });
498
+
499
+ default:
500
+ // Default to paragraph if type is unknown
501
+ return new docx.Paragraph({
502
+ children: [
503
+ new docx.TextRun({ text: data, ...meta }),
504
+ ],
505
+ ...style
506
+ });
507
+ }
508
+ };
509
+
510
+ /**
511
+ * Creates a Word document with the provided content
512
+ * @param {Object} options - Configuration options for the document
513
+ * @param {string} options.title - The main title of the document
514
+ * @param {string} options.content - Additional content to include
515
+ * @param {Array} options.config - Array of configuration items with type, data, meta, style, and optional title
516
+ * @returns {docx.Document} The created Word document
517
+ */
518
+ export const createDocument = ({ title = "React Word Document Example", content = "This is a sample Word document created with docx library.", config = [] }) => {
519
+ // Process config items - add section titles if provided
520
+ const children = config.flatMap((item) => {
521
+ const elements = [];
522
+
523
+ // If item has a title property, add it as a section heading
524
+ if (item.title) {
525
+ elements.push(
526
+ new docx.Paragraph({
527
+ children: [
528
+ new docx.TextRun({
529
+ text: item.title,
530
+ bold: true,
531
+ size: 26,
532
+ color: '000000'
533
+ })
534
+ ],
535
+ spacing: {
536
+ before: item.titleSpacing?.before || 300,
537
+ after: item.titleSpacing?.after || 150
538
+ },
539
+ ...item.titleStyle
540
+ })
541
+ );
542
+ }
543
+
544
+ // Add the actual element content
545
+ const renderedElement = renderElement(item);
546
+ if (Array.isArray(renderedElement)) {
547
+ elements.push(...renderedElement);
548
+ } else {
549
+ elements.push(renderedElement);
550
+ }
551
+
552
+ return elements;
553
+ });
554
+
555
+ return new docx.Document({
556
+ sections: [
557
+ {
558
+ children: children
559
+ }
560
+ ],
561
+ });
562
+ };
563
+
564
+ export default createDocument;
565
+
@@ -0,0 +1,9 @@
1
+ import createDocument from './createDocument';
2
+
3
+ const WordDocument = ({ title, content, config } = {}) => {
4
+ const doc = createDocument({ title, content, config });
5
+ return doc;
6
+ };
7
+
8
+ export default WordDocument;
9
+ export { createDocument };