-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathre.js
1462 lines (1313 loc) · 56.7 KB
/
re.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*jshint esversion: 11 */
/* ECMASCRIPT_2020 */
(function (document) {
'use strict';
/**
* Manages drag-and-drop functionality for draggable elements within a container.
*/
class DraggableManager {
/**
* Creates a DraggableManager instance.
* @param {string} containerSelector - CSS selector for the container element.
* @param {string} draggableSelector - CSS selector for the draggable elements.
*/
constructor(containerSelector, draggableSelector) {
this.container = document.querySelector(containerSelector);
this.draggable = Array.from(this.container.querySelectorAll(draggableSelector));
this.draggedElement = null; // The currently dragged element
}
/**
* Initializes the drag-and-drop functionality by attaching event listeners.
*/
initialize() {
this.draggable.forEach(draggable => {
// Enable drag-and-drop events on each draggable element
// draggable.setAttribute('draggable', 'true'); // Uncomment if elements aren't draggable by default
draggable.addEventListener('dragstart', this.handleDragStart.bind(this));
draggable.addEventListener('dragover', this.handleDragOver.bind(this));
draggable.addEventListener('drop', this.handleDrop.bind(this));
});
}
/**
* Handles the drag start event.
* @param {DragEvent} event - The dragstart event.
*/
handleDragStart(event) {
this.draggedElement = event.target; // Store reference to the element being dragged
event.dataTransfer.setData('text/plain', ''); // Required for Firefox compatibility
}
/**
* Handles the drag over event.
* @param {DragEvent} event - The dragover event.
*/
handleDragOver(event) {
event.preventDefault(); // Allow dropping by preventing the default behavior
}
/**
* Handles the drop event, swaps elements, and updates the DOM.
* @param {DragEvent} event - The drop event.
*/
handleDrop(event) {
const targetElement = event.currentTarget; // The element on which the drop occurred
// Ensure there's a valid dragged element and avoid dropping on itself
if (this.draggedElement && this.draggedElement !== targetElement && this.draggedElement.tagName === 'DIV') {
const targetIndex = this.draggable.indexOf(targetElement); // Get index of the drop target
const draggedIndex = this.draggable.indexOf(this.draggedElement); // Get index of the dragged element
// Swap elements in the array to maintain logical order
[this.draggable[draggedIndex], this.draggable[targetIndex]] = [this.draggable[targetIndex], this.draggable[draggedIndex]];
// Update the visual order of the draggable elements in the container
this.container.innerHTML = ''; // Clear the container
this.draggable.forEach(draggable => this.container.appendChild(draggable)); // Re-append in new order
}
}
}
// Initialize DraggableManager
const draggableManager = new DraggableManager('#cv', '.blokas');
draggableManager.initialize();
/**
* Helper function to create an HTML element with optional text and attributes.
* @param {string} tag - The tag name of the HTML element (e.g., 'div', 'span').
* @param {string} [text=''] - The text content to be set for the element (optional).
* @param {Object} [attributes={}] - An optional object of key-value pairs for setting element attributes.
* @returns {HTMLElement} The created HTML element.
*/
function createHTMLElement(tag, text = '', attributes = {}) {
// Create the HTML element using the provided tag name
const element = document.createElement(tag);
// If text is provided, set the text content of the element
if (text) {
element.textContent = text;
}
// If attributes are provided and is an object, loop through and set them
if (attributes && typeof attributes === 'object') {
Object.entries(attributes).forEach(([key, value]) => {
// Set each attribute on the element
element.setAttribute(key, value);
});
}
// Return the created element
return element;
}
// Initialize elements if the stylesheet is not already added
if (!document.getElementById('styl')) {
// Get the <head> element of the document
const head = document.getElementsByTagName('head')[0];
// Create a <link> element for the stylesheet
const link = document.createElement('link');
// Set attributes for the <link> element
link.id = 'styl'; // Unique ID for the link element
link.rel = 'stylesheet'; // Set the relation to 'stylesheet'
link.type = 'text/css'; // Specify the type of the linked file
link.href = 'style.css?v=2'; // Path to the CSS file (with versioning query parameter)
link.media = 'all'; // The stylesheet applies to all media types
link.className = 'remove'; // Class name for possible future removal/styling
// Append the <link> element to the <head> of the document
head.appendChild(link);
// Disable the context menu (right-click) on the document
document.oncontextmenu = function () {
return false; // Prevent default context menu
};
}
// Initialize the body and select the first '.blokas' element
const body = document.body;
const block = document.querySelectorAll('.blokas:first-child');
// Button configuration for 'close' button in blocks
const button = {
class: 'shd brdr w24 remove',
href: '#',
id: 'close',
};
// Prepend 'close' button to the first block
block.forEach(e => e.before(createHTMLElement('button', 'i', button)));
// Create and configure the 'Save CV' button
const issaugoti = 'Save CV...';
const buttonIsaugoti = {
class: 'save fixe brdr shd remove',
href: '#',
};
// Create 'Save CV' button and append to the body
const elems = createHTMLElement('button', issaugoti, buttonIsaugoti);
body.appendChild(elems);
elems.onclick = htmls; // Attach click event for saving
// Create 'info' div and its children for additional information display
const infoDiv = createHTMLElement('div', '', {
id: 'info',
class: 'remove',
style: 'display:none', // Initially hidden
});
const infocDiv = createHTMLElement('div', '', { id: 'infoc' });
const spanElement = createHTMLElement('span', 'CLOSE', {
class: 'btn brdr shd',
});
infocDiv.appendChild(spanElement);
// Info text content explaining user instructions
const infoTextContent = `Please do not close this window until your work is saved, as no information is stored in the database. To save your text, simply press 'Enter' after editing. You can change the language level by double-clicking with your mouse. For the best experience, please ensure your photo is sized at 100x128 pixels. To optimize loading times, reduce the file size of your photo as much as possible. You can rearrange blocks by dragging.`;
const infoTextDiv = createHTMLElement('div', infoTextContent, {
id: 'info-text',
});
// Append the info content to the infoDiv and insert at the beginning of the body
infoDiv.appendChild(infocDiv);
infoDiv.appendChild(infoTextDiv);
document.body.insertBefore(infoDiv, document.body.firstChild);
// Add 'minus' and 'plus' buttons to other blocks for manipulation
const elem = document.querySelectorAll('.blokas');
const buttonPlus = {
href: '#',
id: 'dubl',
};
elem.forEach((e, i) => {
if (i > 0) {
// 'minus' button
buttonPlus.class = 'remove shd brdr wbg w24 rem';
e.appendChild(createHTMLElement('button', '-', buttonPlus));
// 'plus' button
buttonPlus.class += 'remove shd brdr wbg w24 add';
e.appendChild(createHTMLElement('button', '+', buttonPlus));
}
});
// Create the fake file input button for file selection
const fakeButtonHtml = createHTMLElement('div', 'Select File', {
id: 'fakebtn',
class: 'remove brdr',
});
const fileInput = createHTMLElement('input', '', {
id: 'image-file',
name: 'image-file',
type: 'file',
});
fileInput.onchange = fileSelected; // Handle file change event
fakeButtonHtml.appendChild(fileInput);
// Create and configure the 'Import JSON' button
const importButtonText = 'Import JSON';
const importButtonConfig = {
class: 'impo fixe brdr shd remove',
id: 'importButton',
};
// Create the hidden file input for JSON files
const fileInputConfig = {
type: 'file',
id: 'fileInput',
accept: '.json',
style: 'display: none;', // Hidden input field
class: 'remove',
};
// Create the "Import JSON" button and hidden file input
const importButton = createHTMLElement('button', importButtonText, importButtonConfig);
const fileJSONInput = createHTMLElement('input', '', fileInputConfig);
// Append both the import button and file input to the body
document.body.appendChild(importButton);
document.body.appendChild(fileJSONInput);
// HTML structure for the upload form with several hidden fields and progress info
const uploadFormHtml = `
<div id="dele" class="remove">
<form action="" enctype="multipart/form-data" id="upload_form" method="post" name="upload_form">
<div id="fileinfo">
<div id="filename"></div>
<div id="filesize"></div>
<div id="filetype"></div>
<div id="filedim"></div>
</div>
<div id="error">Failas nepalaikomas! bmp, gif, jpeg, png, tiff</div>
<div id="error2">An error occurred while uploading the file</div>
<div id="abort">The upload has been canceled</div>
<div id="warnsize">The file is too large.</div>
<div id="progress_info">
<div id="progress"></div>
<div id="progress_percent"> </div>
<div class="clear_both"></div>
<div>
<div id="speed"> </div>
<div id="remaining"> </div>
<div id="b_transfered"> </div>
<div class="clear_both"></div>
</div>
<div id="upload_response"></div>
</div>
</form>
</div>
`;
// Insert the upload form HTML in the header
document.getElementById('header').insertAdjacentHTML('afterbegin', uploadFormHtml);
// Add the file selection button to the photo section
const heading = document.getElementById('he');
const preview = document.getElementById('preview');
const photo = document.getElementById('photo');
photo.appendChild(fakeButtonHtml);
// Add click event listener to toggle the visibility of the 'info' div
document.querySelectorAll('#close, #infoc').forEach(function (element) {
element.addEventListener('click', function (e) {
e.preventDefault();
// Toggle the display of the info div
infoDiv.style.display = infoDiv.style.display === 'none' ? '' : 'none';
});
});
/**
* Helper function to create a link element based on input value and its id.
* @param {HTMLInputElement} input - The input element containing the value to be used for the link.
* @returns {HTMLAnchorElement} The created <a> tag with the appropriate href and text.
*/
const createLink = input => {
let href = '';
// Check the input's id to determine the type of link to create
if (input.id === 'email') {
// For email input, create a 'mailto' link
href = `mailto:${input.value.replace(/\s+/g, '')}`;
} else if (input.id === 'number') {
// For phone number input, create a 'tel' link
href = `tel:${input.value.replace(/\s+/g, '')}`;
} else if (input.id === 'url') {
// For URL input, check if the value starts with 'http://' or 'https://'
if (/^https?:\/\//.test(input.value)) {
// If the input already contains a valid URL with 'http://' or 'https://', use it as is
href = input.value;
} else {
// Otherwise, prepend 'http://' to the value
href = `http://${input.value}`;
}
} else if (input.value && /https?:\/\//.test(input.value)) {
// For any input value that matches a simple URL pattern (http/https)
href = input.value;
}
// Create the <a> tag with the constructed href
const link = document.createElement('a');
link.href = href; // Set the href attribute of the <a> tag
link.textContent = input.value; // Set the visible text of the link to the input value
link.target = '_blank'; // Ensure the link opens in a new tab
link.rel = 'noopener noreferrer nofollow'; // Apply safe and nofollow attributes for security
// Return the created <a> element
return link;
};
/**
* Function to replace an input element with a heading (h2 or h3) and preserve its class, id, and value.
* @param {HTMLInputElement} input - The input element to be replaced.
* @param {string} tagName - The tag name of the heading (either 'h2' or 'h3').
* @returns {HTMLElement} The newly created heading element (h2 or h3).
*/
function replaceElementWithHeading(input, tagName) {
// Create a new heading element (either h2 or h3)
const heading = document.createElement(tagName);
// Preserve the class and id attributes from the input element (if they exist)
if (input.className) heading.setAttribute('class', input.className); // Preserve class on the heading
if (input.id) heading.setAttribute('id', input.id); // Preserve id on the heading
// Set the text content of the heading from the input value (trim any surrounding whitespace)
if (input.value) heading.textContent = input.value.trim();
// Replace the input element with the newly created heading
input.parentNode.replaceChild(heading, input);
// Return the newly created heading element
return heading;
}
/**
* Function to process form inputs and replace certain elements with headings or links.
* It updates the document title with the name value and replaces text inputs with h2 or h3 headings.
* It also handles the creation of links for email, phone number, and URLs.
*/
function outf() {
// Select all 'select' elements and 'input' elements excluding the file input
const cvInputs = document.querySelectorAll('select, input:not(#image-file)');
// Select the element with the ID 'name' for title and heading updates
const nameElement = document.querySelector('#name');
// If the name element exists, update the document title and heading
if (nameElement) {
// Get the name from the input's value or text content
const name = nameElement.value || nameElement.textContent;
// Update the document's title to the name
document.title = name;
// Update the heading element's text content with the name
heading.children[0].textContent = name;
// Optionally, you can save the data to localStorage (commented out)
// localStorage.setItem(nameElement.innerText.replace(/\s+/g, ''), JSON.stringify(jsonData));
}
// Iterate over each input or select element
cvInputs.forEach(input => {
let tagName = input.classList.contains('left') ? 'h2' : 'h3'; // Decide whether to use h2 or h3 based on class
// Handle text input elements
if (input.tagName === 'INPUT' && input.type === 'text') {
// Replace the text input with a heading (h2 or h3)
const newElement = replaceElementWithHeading(input, tagName);
// If the input is an email or number, replace it with a link
if (input.id === 'email' || input.id === 'number') {
newElement.innerHTML = ''; // Clear any existing content in the heading
newElement.appendChild(createLink(input)); // Add a link inside the heading
}
// If the input is a URL (http/https), create a regular link
else if (input.id === 'url' || (input.value && /https?:\/\//.test(input.value))) {
newElement.innerHTML = ''; // Clear existing content
newElement.appendChild(createLink(input)); // Append the link inside the heading
}
}
// Handle select elements by replacing them with the selected option's value
else if (input.tagName === 'SELECT') {
input.parentElement.innerText = input.options[input.selectedIndex].value; // Set parent element's text to selected option
}
});
}
// let oTimer = 0;
//let iBytesUploaded = 0;
//let iBytesTotal = 0;
//let iPreviousBytesLoaded = 0;
const iMaxFilesize = 1048576;
let sResultFileSize = '';
/* function secondsToTime(secs) {
let hr = Math.floor(secs / 3600);
let min = Math.floor((secs - hr * 3600) / 60);
let sec = Math.floor(secs - hr * 3600 - min * 60);
if (hr < 10) {
hr = '0' + hr;
}
if (min < 10) {
min = '0' + min;
}
if (sec < 10) {
sec = '0' + sec;
}
if (hr) {
hr = '00';
}
return hr + ':' + min + ':' + sec;
}*/
const fileinfo = document.getElementById('fileinfo');
const upload_response = document.getElementById('upload_response');
const error = document.getElementById('error');
const error2 = document.getElementById('error2');
const abort = document.getElementById('abort');
const warnsize = document.getElementById('warnsize');
function bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB'];
if (bytes === 0) return 'n/a';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
}
function fileSelected() {
fileinfo.style.display = 'none';
upload_response.style.display = 'none';
error.style.display = 'none';
error2.style.display = 'none';
abort.style.display = 'none';
warnsize.style.display = 'none';
const oFile = document.getElementById('image-file').files[0];
const rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;
if (!rFilter.test(oFile.type)) {
error.style.display = 'block';
return;
}
if (oFile.size > iMaxFilesize) {
warnsize.style.display = 'block';
return;
}
const oImage = document.getElementById('preview');
const oReader = new FileReader();
oReader.onload = function (e) {
oImage.src = e.target.result;
oImage.onload = function () {
sResultFileSize = bytesToSize(oFile.size);
fileinfo.style.display = 'block';
document.getElementById('filename').innerHTML = 'Name: ' + oFile.name;
document.getElementById('filesize').innerHTML = 'Size: ' + sResultFileSize;
document.getElementById('filetype').innerHTML = 'Type: ' + oFile.type;
document.getElementById('filedim').innerHTML = 'Dimension: ' + oImage.naturalWidth + ' x ' + oImage.naturalHeight;
};
};
oReader.readAsDataURL(oFile);
}
/* function startUploading() {
iPreviousBytesLoaded = 0;
document.getElementById('upload_response').style.display = 'none';
document.getElementById('error').style.display = 'none';
document.getElementById('error2').style.display = 'none';
document.getElementById('abort').style.display = 'none';
document.getElementById('warnsize').style.display = 'none';
document.getElementById('progress_percent').innerHTML = '';
const oProgress = document.getElementById('progress');
oProgress.style.display = 'block';
oProgress.style.width = '0px';
const vFD = new FormData(document.getElementById('upload_form'));
const oXHR = new XMLHttpRequest();
oXHR.upload.addEventListener('progress', uploadProgress, !1);
oXHR.addEventListener('load', uploadFinish, !1);
oXHR.addEventListener('error', uploadError, !1);
oXHR.addEventListener('abort', uploadAbort, !1);
oXHR.open('POST', 'upload.php');
oXHR.send(vFD);
oTimer = setInterval(doInnerUpdates, 300);
} */
/*
function doInnerUpdates() {
const iCB = iBytesUploaded;
let iDiff = iCB - iPreviousBytesLoaded;
if (iDiff === 0) return;
iPreviousBytesLoaded = iCB;
iDiff = iDiff * 2;
const iBytesRem = iBytesTotal - iPreviousBytesLoaded;
const secondsRemaining = iBytesRem / iDiff;
let iSpeed = iDiff.toString() + 'B/s';
if (iDiff > 1024 * 1024) {
iSpeed =
(Math.round((iDiff * 100) / (1024 * 1024)) / 100).toString() + 'MB/s';
} else if (iDiff > 1024) {
iSpeed = (Math.round((iDiff * 100) / 1024) / 100).toString() + 'KB/s';
}
document.getElementById('speed').innerHTML = iSpeed;
document.getElementById('remaining').innerHTML =
'| ' + secondsToTime(secondsRemaining);
}
function uploadProgress(e) {
if (e.lengthComputable) {
iBytesUploaded = e.loaded;
iBytesTotal = e.total;
const iPercentComplete = Math.round((e.loaded * 100) / e.total);
const iBytesTransfered = bytesToSize(iBytesUploaded);
document.getElementById('progress_percent').innerHTML =
iPercentComplete.toString() + '%';
document.getElementById('progress').style.width =
(iPercentComplete * 4).toString() + 'px';
document.getElementById('b_transfered').innerHTML = iBytesTransfered;
if (iPercentComplete === 100) {
const oUploadResponse = document.getElementById('upload_response');
oUploadResponse.innerHTML = '<h1>Please wait...processing</h1>';
oUploadResponse.style.display = 'block';
}
} else {
document.getElementById('progress').innerHTML = 'unable to compute';
}
}
function uploadFinish(e) {
const oUploadResponse = document.getElementById('upload_response');
oUploadResponse.innerHTML = e.target.responseText;
oUploadResponse.style.display = 'block';
document.getElementById('progress_percent').innerHTML = '100%';
document.getElementById('progress').style.width = '400px';
document.getElementById('filesize').innerHTML = sResultFileSize;
document.getElementById('remaining').innerHTML = '| 00:00:00';
clearInterval(oTimer);
}
function uploadError(e) {
document.getElementById('error2').style.display = 'block';
clearInterval(oTimer);
}
function uploadAbort(e) {
document.getElementById('abort').style.display = 'block';
clearInterval(oTimer);
} */
/**
* Function to dynamically generate a title based on the given key and parent section.
* It uses a set of mappings for different sections (e.g., basics, education, work) and returns a capitalized title.
*
* @param {string} key - The specific key for the item (e.g., 'name', 'email').
* @param {string} parent - The section that the key belongs to (e.g., 'basics', 'education').
* @returns {string} The dynamic title based on the key and parent.
*/
const getDynamicTitle = (key, parent) => {
// Convert key or parent to lowercase for consistent handling (if either is undefined, use the other)
const text = key || parent;
// Mappings for specific sections with titles for keys
const sections = {
basics: {
name: 'Name / Surname',
label: 'Profession Field',
email: 'Email Address',
phone: 'Phone Number',
url: 'Homepage',
website: 'Website',
summary: 'Summary',
default: 'Personal Details',
},
education: {
area: 'Key Subjects / Professional Skills',
studyType: 'Qualification',
institution: 'Institution Name',
score: 'Qualification Level',
url: "Institution's Homepage",
default: 'Education',
},
work: {
website: "Employer's Homepage",
location: 'Employer Address',
name: 'Employer Name',
position: 'Profession or Position',
url: "Employer's Homepage",
summary: 'Main Activities and Responsibilities',
highlights: 'Key Achievements',
default: 'Work Experience',
},
skills: {
name: 'Skill Name',
level: 'Skill Level',
keywords: 'Skill Keywords',
default: 'Skills',
},
languages: {
language: 'Language',
fluency: 'Fluency Level',
default: 'Languages',
},
projects: {
name: 'Project Name',
description: 'Project Description',
startDate: 'Start Date',
endDate: 'End Date',
highlights: 'Key Highlights',
url: 'Project URL',
default: 'Projects',
},
awards: {
title: 'Award Title',
date: 'Award Date',
awarder: 'Awarder',
summary: 'Award Summary',
default: 'Awards',
},
certificates: {
name: 'Certificate Name',
date: 'Date Issued',
issuer: 'Issued By',
url: 'Certificate URL',
default: 'Certificates',
},
};
// Defaults for sections outside the main ones
const defaults = {
url: 'Webpage',
network: 'Online Presence',
};
// Retrieve the title based on the section and key
const sectionTitles = sections[parent];
const result = sectionTitles ? sectionTitles[key] || sectionTitles.default : defaults[text] || text;
// Capitalize the first letter of the resulting text and return it
return result.charAt(0).toUpperCase() + result.slice(1);
};
/**
* Function to loop through the top-level keys of an object and apply a handler to each key-value pair.
*
* @param {Object} obj - The object whose top-level keys and values will be processed.
* @param {Function} handlers - A function to process each key-value pair. It should accept the key and value as parameters and return a result.
* @returns {Array} An array containing the results of applying the handler to each key-value pair.
*/
function getTopLevelKeys(obj, handlers) {
const result = []; // Initialize an empty array to store the processed data
// Return an empty array if the provided object is not a valid object
if (typeof obj !== 'object' || obj === null) {
return result;
}
// Loop through all top-level keys of the object
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// Ensure the key belongs to the object, not its prototype
const data = handlers(key, obj[key]); // Call the handler function with key and value
result.push(data); // Add the result of the handler to the result array
}
}
return result; // Return the array of processed key-value pairs
}
/**
* Function to join nested arrays and objects into a single string.
*
* @param {any} input - The input can be a primitive value, an array, or an object.
* @returns {string} A string representing the joined data, with nested structures flattened into a single string.
*/
function joinData(input) {
if (Array.isArray(input)) {
// If it's an array, recursively join each element and then join the array elements with a comma
return input.map(item => joinData(item)).join(', ');
} else if (typeof input === 'object' && input !== null) {
// If it's an object, recursively join each key-value pair with "key: value" format
return Object.entries(input)
.map(([key, value]) => `${key}: ${joinData(value)}`) // Join key-value pairs
.join(', '); // Join all pairs with a comma
} else {
// For primitive values (e.g., string, number, boolean), convert them to a string
return String(input);
}
}
/**
* Handles dynamic creation of HTML elements based on data passed.
* It processes each top-level key-value pair, skipping certain keys (e.g., 'meta'),
* and recursively processes nested structures like arrays or objects.
*
* @param {string} key - The key of the current section being processed (e.g., 'basics', 'education').
* @param {Object|Array|string|number|boolean} value - The value associated with the key. Can be an object, array, or primitive value.
*
* @returns {void}
* This function does not return a value. It directly modifies the DOM by appending generated HTML elements.
*
* @example
* const data = { basics: { name: 'John Doe', email: '[email protected]' } };
* handlers('basics', data.basics);
* // This will create and append HTML elements like <div class="blokas">...</div> to the DOM.
*/
function handlers(key, value) {
if (value.length === 0 || key === 'meta') {
return; // Skip 'meta' key or empty array (all block)
}
const blockDiv = createHTMLElement('div', '', {
class: 'blokas',
draggable: 'true',
});
const section = createHTMLElement('section');
section.appendChild(
createHTMLElement('h2', getDynamicTitle('', key), {
class: 'left num hm bold',
})
);
blockDiv.appendChild(section);
const dateTracker = createDateTracker(); // Create once for the whole object
// Recursive function to loop through objects or arrays
const loopObjectOrArray = (value, parentKey) => {
// Create a container for nested structures
const inner = createHTMLElement('div', '', { class: key });
if (Array.isArray(value)) {
// Handle arrays
value.forEach(item => loopObjectOrArray(item, parentKey));
return;
}
if (typeof value === 'object' && value !== null) {
// Iterate through object keys
for (const subKey in value) {
if (value.hasOwnProperty(subKey)) {
const subValue = value[subKey];
if (subKey === 'image') {
preview.src = subValue;
continue; // Skip 'image' key
}
// Handle non-date key-value pairs
const title = getDynamicTitle(subKey, parentKey);
const pairSection = createHTMLElement('section');
if (typeof subValue === 'object' && subValue !== null) {
if (subValue.length && subKey !== 'profiles') {
let combinedData = joinData(JSON.parse(JSON.stringify(subValue)));
pairSection.appendChild(
createHTMLElement('h2', subKey, {
class: 'left',
})
);
pairSection.appendChild(
createHTMLElement('h3', combinedData, {
class: 'righ',
})
);
inner.appendChild(pairSection);
blockDiv.appendChild(inner);
continue;
}
// Recursive call for nested structures
loopObjectOrArray(subValue, subKey);
} else {
// Handle primitive values
if (subKey === 'startDate' || subKey === 'endDate') {
const date = dateTracker(String(subValue), subKey);
if (date) {
const dateSection = createHTMLElement('section');
dateSection.appendChild(
createHTMLElement('h2', 'Dates', {
class: 'left',
})
);
dateSection.appendChild(
createHTMLElement('h3', date, {
class: 'righ',
})
);
inner.appendChild(dateSection);
}
continue;
}
const boldFields = ['name', 'email', 'phone']; // These fields should be bolded
const object = {
class: 'righ',
};
if (boldFields.includes(subKey)) {
object.class += ' bold'; // Add 'bold' to the existing class string if in boldFields
} else {
object.class = object.class.replace(' bold', ''); // Remove 'bold' class if not in boldFields
}
// working with custom top values
if (parentKey === 'basics') {
object.id = subKey;
if (subKey === 'name') {
document.title = subValue;
}
}
pairSection.appendChild(
createHTMLElement('h2', title, {
class: 'left',
})
);
pairSection.appendChild(createHTMLElement('h3', subValue, object));
inner.appendChild(pairSection);
blockDiv.appendChild(inner); // Append inner block to main block
}
}
}
}
};
// Start recursive processing
loopObjectOrArray(value, key);
// Append the completed block to the container
const container = document.getElementById('cv');
container.appendChild(blockDiv);
}
/**
* Generates and appends HTML content to the DOM based on the provided JSON data.
* This function clears the current content inside the container and then processes the data
* to generate dynamic HTML elements according to the structure of the JSON object.
* It relies on the `getTopLevelKeys` function and the `handlers` function for processing the data.
*
* @param {Object} data - The JSON object containing the data to generate HTML from.
* The structure of the data is expected to have top-level keys representing different sections
* (e.g., "basics", "education", "work", etc.), each containing nested data.
*
* @returns {void}
* This function does not return a value. It directly modifies the DOM by clearing the
* existing content and appending the newly generated HTML elements.
*
* @example
* const jsonData = {
* basics: {
* name: 'John Doe',
* email: '[email protected]',
* phone: '123-456-7890',
* },
* education: {
* institution: 'University of Example',
* qualification: 'Bachelor of Example Studies',
* },
* };
* generateHTMLFromJson(jsonData);
* // This will clear the current content and generate HTML from the provided `jsonData`.
*/
function generateHTMLFromJson(data) {
const container = document.getElementById('cv');
container.innerHTML = ''; // Clear existing content
getTopLevelKeys(data, handlers); // Generate HTML based on the data
}
/**
* Creates a date tracker that can track date ranges (start and end dates).
* This function returns a closure that can be used to record start and end dates,
* pairing them together when appropriate, and handling cases where an end date
* is provided before the start date.
*
* It stores unpaired start dates until an end date is provided, then pairs them together.
* If an end date is provided without a matching start date, it temporarily holds the end date
* until the corresponding start date is given.
*
* @returns {function} The date tracker function. This function takes two parameters:
* `date` (the date to be recorded) and `type` (either 'startDate' or 'endDate').
* It returns the formatted date range if both start and end dates are paired,
* or null if a complete date range is not yet formed.
*
* @example
* const dateTracker = createDateTracker();
* const startDate = dateTracker('2024-01-01', 'startDate'); // No pair, returns null
* const endDate = dateTracker('2024-12-31', 'endDate'); // Pairs with startDate, returns "2024-01-01 — 2024-12-31"
*
* const startDate2 = dateTracker('2025-01-01', 'startDate'); // No pair, returns null
* const endDate2 = dateTracker('2025-12-31', 'endDate'); // Pairs with startDate2, returns "2025-01-01 — 2025-12-31"
*/
function createDateTracker() {
let pendingEndDate = null; // To hold an endDate if given before a startDate
const dateRanges = []; // To track complete start and end date pairs
return function (date, type) {
if (type === 'startDate') {
// If there's a pending endDate, create a pair immediately
if (pendingEndDate) {
const range = {
startDate: date,
endDate: pendingEndDate,
};
dateRanges.push(range);
pendingEndDate = null; // Clear pending endDate
return `${range.startDate} — ${range.endDate}`; // Return the pair
}
// Otherwise, start a new range
dateRanges.push({
startDate: date,
endDate: null,
});
return null; // No complete pair to return yet
}
if (type === 'endDate') {
// Find the last unpaired range with a startDate
const lastRange = dateRanges.find(range => range.startDate && !range.endDate);
if (lastRange) {
// Pair this endDate with the unpaired startDate
lastRange.endDate = date;
return `${lastRange.startDate} — ${lastRange.endDate}`; // Return the pair
} else {
// No unpaired startDate, store the endDate temporarily
pendingEndDate = date;
return null; // Wait for a matching startDate
}
}
// If input is invalid, return null
return null;
};
}
/**
* Event listener for the "Import" button. Triggers the file input's click event
* when the "Import" button is clicked, allowing the user to select a file.
*/
document.getElementById('importButton').addEventListener('click', () => {
document.getElementById('fileInput').click(); // Trigger file input on button click
});
/**
* Event listener for the file input change event. This function is triggered
* when a user selects a file. It reads the file content, attempts to parse it as JSON,
* and then generates HTML from the parsed JSON data.
*
* @param {Event} event - The change event from the file input.
*/
document.getElementById('fileInput').addEventListener('change', function (event) {
const file = event.target.files[0]; // Get the selected file
if (file) {
const reader = new FileReader(); // Create a new FileReader to read the file
reader.onload = function (e) {
try {
const jsonData = JSON.parse(e.target.result); // Parse the JSON file content
generateHTMLFromJson(jsonData); // Generate HTML from JSON
} catch (err) {
alert('Invalid JSON file'); // Notify user if the JSON is invalid
console.error('Error parsing JSON:', err); // Log the error for debugging
}
};
reader.readAsText(file); // Read the file content as text
}
});
/**
* The main data structure representing a user's profile, including their personal
* details, work experience, education, skills, and more. This structure is intended
* to be populated with specific data and used to generate HTML content dynamically.
*
* @typedef {Object} JsonData
* @property {Object} basics - Contains the basic information of the user.
* @property {string} basics.name - The user's name.
* @property {string} basics.label - The user's professional title or role.
* @property {string} basics.image - The URL of the user's profile image.
* @property {string} basics.email - The user's email address.
* @property {string} basics.phone - The user's phone number.
* @property {string} basics.url - The user's personal or professional website URL.
* @property {string} basics.summary - A brief summary or description of the user.
* @property {Object} basics.location - The user's location details.
* @property {string} basics.location.address - The user's address.
* @property {string} basics.location.postalCode - The postal code of the user's location.
* @property {string} basics.location.city - The city of the user's location.
* @property {string} basics.location.countryCode - The country code of the user's location.
* @property {string} basics.location.region - The region or state of the user's location.
* @property {Array} basics.profiles - A list of the user's social or professional profiles (e.g., LinkedIn, GitHub).
* @property {Array} work - An array of the user's work experience.
* @property {Array} volunteer - An array of the user's volunteer experiences.
* @property {Array} education - An array of the user's educational history.
* @property {Array} awards - An array of the user's awards and recognitions.
* @property {Array} certificates - An array of certificates earned by the user.
* @property {Array} publications - An array of the user's publications or articles.
* @property {Array} skills - An array of the user's professional skills.
* @property {Array} languages - An array of languages spoken by the user.
* @property {Array} interests - An array of the user's personal interests.