-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.js
683 lines (598 loc) · 28.7 KB
/
Main.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
// ==UserScript==
// @name Chat GPT Helper
// @namespace http://tampermonkey.net/
// @version 2024-03-05
// @description Enhances interaction with ChatGPT by adding features for saving and hiding chat entries.
// @author Marc Davis
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @include https://chat.openai.com/c/*
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
var sessionList = []; // Array to store saved chat entries
var sessionIconsAdded = []; // Tracks chat entries with added save icons
var sessionLinks = []; // Links UI section elements to sessionList elements
var categories = []; // Array to categorize saved chat entries
var mainLoopStarted = false; // Indicates if main monitoring loop is active
var saveWarnedUser = false; // Ensures user is warned once per session about saving
var visualIconAdded = false; // Indicates if save entries visual icon is added
var lastUrl = window.location.href; // Stores current window location URL
var activeDropDownToolTip = false; // Tracks visibility state of dropdown tooltip
var activeCategory = "General"; // Currently active/selected category
var activeOverlay = false; // Manages presence of an active overlay (e.g., modal window)
var persistantOverlayData = false;
console.log("Entire page fully loaded, parsing page in 5 seconds."); // Log statement
class sessionManager {
static storageKey = 'sessionList';
static getUrlIdentifier() {
return window.location.pathname;
}
// Method to convert sessionList array into a single HTML string
static convertArrayToHtmlString(sessionList) {
let htmlString = '';
// Iterate through each session element
for (let element of sessionList) {
// Convert element to its outerHTML representation
let elementHtml = element instanceof HTMLElement ? element.outerHTML : '';
htmlString += elementHtml; // Concatenate into the htmlString
}
return htmlString;
}
// Simplified save method (accepts sessionList array and converts it internally)
static saveSessionList() {
const urlIdentifier = this.getUrlIdentifier();
const storageKey = `${this.storageKey}_${urlIdentifier}`;
const sessionListHtml = this.convertArrayToHtmlString(sessionList);
console.log("saving session html: " + sessionListHtml);
localStorage.setItem(storageKey, sessionListHtml);
persistantOverlayData = localStorage.getItem(storageKey);
}
// Load method remains the same, returning the HTML string directly from storage
static loadSessionList() {
const urlIdentifier = this.getUrlIdentifier();
const storageKey = `${this.storageKey}_${urlIdentifier}`;
var data = localStorage.getItem(storageKey);
persistantOverlayData = data;
console.log("loading html: " + data);
return data;
}
}
// Start the main loop to deploy icons periodically
function mainLoop() {
setInterval(function() {
deployIcons();
buttonizeIcons();
}, 5000); // Set interval to 5 seconds
}
mainLoop();
// Function to hide a chat entry from view
function hideEntry(el) {
el.style.display = "none"; // CSS to make the element invisible
}
// Function to show a hidden chat entry, unused but ready for future implementation
function showEntry(el) {
el.style.display = "inline"; // CSS to make the element visible inline
}
// Saves a chat entry to a list for later access
function saveEntryToList(el) {
if(sessionList.indexOf(el) >= 0) {
alert("Entry already added to list, category: " + getCategoryByElement(el)); // Prevent duplicate entries
} else {
if (!saveWarnedUser) {
saveWarnedUser = true; // Ensure the warning is only shown once
alert("Saved entry under category: " + activeCategory + ". To view, click the Notebook, in the bottom left-hand corner. This message only shows once per session.");
}
el.setAttribute("data-url", sessionManager.getUrlIdentifier());
sessionList.push(el); // Add the entry to the list
sessionManager.saveSessionList();
}
}
// Returns the category associated with a given element
function getCategoryByElement(element) {
return element.getAttribute("data-category");
}
// Returns an array of elements that belong to a specific category
function getElementsByCategory(category) {
var items = sessionList; // Access category links to find elements by category
const mains = []; // Array to hold elements belonging to the specified category
for (const item of items) {
if (item.main.getAttribute("data-category") == category) {//)item.category === category) {
mains.push(item.main); // Add matching elements to the mains array
}
}
return mains; // Returns array of elements, can be empty if no matches found
}
// Deploys save and hide icons next to eligible chat entries
function deployIcons() {
var query = document.querySelectorAll(".text-gray-400.flex.self-end"); // Query to find elements to add icons
query.forEach(function(el) {
if(sessionIconsAdded.indexOf(el) < 0) { // Check if icons have not been added
var saveIcon = createImageIcon("https://cdn1.iconfinder.com/data/icons/medicato/32/cross_round-256.png", 1);
var hideIcon = createImageIcon("https://icon-library.com/images/hide-icon/hide-icon-13.jpg", 3);
el.appendChild(saveIcon); // Append save icon
el.appendChild(hideIcon); // Append hide icon
sessionIconsAdded.push(el); // Mark as icons added
}
});
// Add a visual icon for accessing saved entries if not already added
if(!visualIconAdded || lastUrl != window.location.href) {
if(lastUrl != window.location.href) {
sessionList = [];
sessionLinks = [];
categories = [];
activeCategory = "General";
// Load session elements
const loadedSessionListHTML = sessionManager.loadSessionList();
if (loadedSessionListHTML) {
// Add paragraphs for each saved entry
var htmlstr = loadedSessionListHTML
if(htmlstr) {
var temp = document.createElement("div");
temp.innerHTML = htmlstr;
for(var i = 0; i < temp.childNodes.length; i++) {
var el = temp.childNodes[i];
var elcat = el.getAttribute("data-category");
var elurl = el.getAttribute("data-url");
if (elurl != sessionManager.getUrlIdentifier()) {
continue;
}
if(categories.indexOf(elcat) == -1 && elcat != "General") {
categories.push(elcat);
}
saveEntryToList(el)
}
}
}
}
lastUrl = window.location.href;
visualIconAdded = true;
var query2 = document.querySelector(".stretch.mx-2.flex.flex-row.gap-3"); // Find a suitable place to add the icon
var viewIcon = createImageIcon("https://cdn0.iconfinder.com/data/icons/healthcare-science-and-government/64/notepad-paper-notebook-education-diary-256.png", 2);
query2.parentNode.appendChild(viewIcon); // Add the view icon to the page
}
}
// Creates an image element to be used as an interactive icon
function createImageIcon(imageUrl, functionTypeAsInteger) {
const image = document.createElement('img');
Object.assign(image, {
src: imageUrl,
style: `width: 25px; height: 25px; object-fit: cover; border-radius: 10%; cursor: pointer;`
});
// Add event listener based on the function type
// Type 1: Save entry icon
if(functionTypeAsInteger === 1) {
image.title = "Save this section under the specified category";
image.addEventListener('mouseenter', function(event) {
createHoverCategoryMenu(image);
});
image.addEventListener('click', function() {
var associatedContext = this.parentNode.parentNode.parentNode.firstChild.firstChild;
if(sessionList.indexOf(associatedContext) >= 0) {
return;
}
associatedContext.setAttribute("data-category",activeCategory);
saveEntryToList(associatedContext); // Save the entry on click
closeActiveDropDownToolTip();
}, false);
}
// Type 2: View saved entries icon
else if(functionTypeAsInteger === 2) {
image.style.width = "60px";
image.style.height = "60px";
image.style.backgroundColor = 'rgba(0,0,0,0)';
image.setAttribute("title","Show & manage your saved sections");
image.addEventListener('click', function() {
closeActiveDropDownToolTip();
createOverlayDiv(); // Show saved entries on click
}, false);
}
// Type 3: Hide entry icon
else if(functionTypeAsInteger === 3) {
image.title = "Hide this section from view (collapse)";
image.addEventListener('click', function() {
var associatedContext = this.parentNode.parentNode.parentNode;
hideEntry(associatedContext); // Hide the entry on click
closeActiveDropDownToolTip();
}, false);
}
return image; // Return the newly created icon
}
//Toggles an element's size between its original dimensions and 100% height and width on click events.
function toggleElementSize(element) {
// Check if the original size has been stored; if not, store it
if (!element.hasAttribute('data-original-width') || !element.hasAttribute('data-original-height')) {
element.setAttribute('data-original-width', element.style.width);
element.setAttribute('data-original-height', element.style.height);
element.setAttribute('data-original-mleft', element.style.marginLeft);
}
// Define the toggle function
const toggleSize = () => {
// Check the current size; if it's not at 100%, set it to 100%
if (element.style.width !== '100%' && element.style.height !== '100%') {
element.style.width = '100%';
element.style.height = '100%';
element.style.marginLeft = "0px";
} else {
// Otherwise, revert to the original size
element.style.width = element.getAttribute('data-original-width');
element.style.height = element.getAttribute('data-original-height');
element.style.marginLeft = element.getAttribute('data-original-mleft');
}
};
// Register the toggle function as an onclick event handler
element.onclick = toggleSize;
}
// Returns modified array without value provided
function filterArrayByValue(arr, value) {
var na = []; // Initialize a new array to hold filtered elements
arr.forEach(function(el) {
if (el == value) return; // Skip the element if it matches the value to be filtered out
na.push(el); // Add element to new array if it doesn't match the value
});
return na; // Return the newly created array without the specified value
}
// Gets the linked element for the session
function getSessionLink(element) {
var linkreturned = false;
sessionLinks.forEach(function(link) {
var linkedElement = link.main;
if(linkedElement == element) {
linkreturned = link.link;
return;
}
});
return linkreturned;
}
// Remove the link data from the main chat section to the workbook section
function removeSessionLink(element) {
var linkreturned = false;
sessionLinks.forEach(function(link) {
var linkedElement = link.main;
if(linkedElement == element) {
linkreturned = link;
return;
}
});
if(linkreturned != false) {
sessionLinks = filterArrayByValue(sessionLinks, linkreturned);
}
return linkreturned;
}
// Turns the chat icons into clickable buttons that restore hidden chat sections
function buttonizeIcons() {
// Select chat icons based on specific class names
var icons = document.querySelectorAll(".flex-shrink-0.flex.flex-col.relative.items-end");
icons.forEach(function(el) {
if (el.style.cursor != "pointer") { // Make icons clickable if not already
el.style.cursor = "pointer"; // Indicate clickable items
el.title = "Click to restore the hidden section"; // Tooltip for functionality
addClickListener(el, function() { // Add click listener to reveal hidden chat sections
var restore = el.parentNode.querySelector(".relative.flex.w-full.flex-col.agent-turn"); // Find parent chat section
restore = restore.querySelector(".flex-col.gap-1"); // Narrow down to specific hidden part
restore.style.display = "inline"; // Make the hidden part visible
});
}
});
}
// Adds a left-click listener to element provided and runs action (anon function)
function addClickListener(el, action) {
// Get the element by its ID
const element = el;
// Check if the element exists to avoid errors
if (element) {
// Attach a click event listener to the element
element.addEventListener('click', function(event) {
// Execute the specified action
action();
});
} else {
console.error('Element with id ' + element + ' not found.');
}
}
// Basic right click menu popup, used when removing sections in the category manager
function attachRightClickMenu(element) {
element.addEventListener('contextmenu', function(e) {
e.preventDefault(); // Prevent the default context menu
// Remove any existing custom context menus
const existingMenu = document.querySelector('.custom-context-menu');
if (existingMenu) {
existingMenu.remove();
}
// Create the context menu container
const menu = document.createElement('div');
Object.assign(menu, {
className: 'custom-context-menu',
style: `z-index:1005;position: fixed; left: ${e.pageX}px; top: ${e.pageY}px; background-color: #fff; border: 1px solid #ddd; padding: 10px; box-shadow: 2px 2px 6px rgba(0,0,0,0.2); zIndex: 1005;`
});
// Add text message
const message = document.createElement('p');
message.textContent = 'Remove Selection?';
message.style.color = '#333'; // Dark text color for contrast
message.style.marginBottom = '15px'; // Space between message and buttons
menu.appendChild(message);
// Create container for buttons to allow for even spacing
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.justifyContent = 'space-around'; // Evenly space out buttons
// Create Yes button
const yesButton = document.createElement('button');
Object.assign(yesButton, {
textContent: 'Yes',
style: `background-color: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;`
});
yesButton.onclick = function() {
var link = getSessionLink(element);
if(!link) {
alert("no link found for element: " + element);
}
sessionList = filterArrayByValue(sessionList, link);
// need to remove obj from session links as well
removeSessionLink(element);
element.remove();
menu.remove(); // Remove the context menu
sessionManager.saveSessionList();
};
buttonsContainer.appendChild(yesButton);
// Create No button
const noButton = document.createElement('button');
Object.assign(noButton, {
textContent: 'No',
style: `background-color: #F44336; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;`
});
noButton.onclick = function() {
menu.remove(); // Remove the context menu
};
buttonsContainer.appendChild(noButton);
// Add the buttons container to the menu
menu.appendChild(buttonsContainer);
// Add the menu to the document
document.body.appendChild(menu);
});
}
// Creates input box menu for when creating a new category
function createInputBoxMenu(text) {
// Create elements
const menu = document.createElement('div');
const description = document.createElement('p');
const subdescription = document.createElement('p');
const inputBox = document.createElement('input');
const cancelButton = document.createElement('button');
// Setup menu
Object.assign(menu.style, {
position: 'fixed', // Use fixed positioning to place it relative to the viewport
top: '50%', // Position the top edge of the element in the middle of the screen vertically
left: '50%', // Position the left edge of the element in the middle of the screen horizontally
transform: 'translate(-50%, -50%)', // Adjust the element's position to truly center it
zIndex: '1010', // Ensure the element is on top of others
padding:"20px"
});
menu.style.position = "absolute";
menu.style.background = "rgba(80,80,80,0.8)"
document.body.appendChild(menu);
// Setup description
description.textContent = text;
description.style.textAlign = "center";
menu.appendChild(description);
// Setup subdescription
subdescription.textContent = "Press enter to submit";
subdescription.style.fontSize = "10px";
subdescription.style.textAlign = "center";
menu.appendChild(subdescription);
// Setup inputBox
inputBox.type = 'text';
inputBox.style.marginTop = '10px';
inputBox.style.color = "gray";
menu.appendChild(inputBox);
// Setup cancelButton
Object.assign(cancelButton.style, {backgroundColor: 'red', color: 'white', display: 'block', margin: '10px auto', padding: '5px 10px', border: 'none', cursor: 'pointer'});
cancelButton.textContent = 'Cancel';
menu.appendChild(cancelButton);
// Event listeners
inputBox.addEventListener('keypress', e => {
if (e.key === 'Enter') {
console.log('Input submitted:', inputBox.value);
var category = inputBox.value;
categories.push(category);
console.log("Saved category: " + category);
menu.remove();
overlayDivRefresh();
}
});
cancelButton.addEventListener('click', () => {
console.log('Cancellation requested');
menu.remove();
});
}
// Created the drop down menu for the categories on the main page
function createCategoriesManager() {
// Create drop down section "container"
const container = Object.assign(document.createElement('div'), {
style: `display: flex; justify-content: center; align-items: center; gap: 20px; padding: 40px;`
});
// Create and style dropdown
var generalSelected = "";
if(activeCategory == "General") {
generalSelected = `selected="selected" `
}
const dropdown = Object.assign(document.createElement('select'), {
innerHTML: `<option ` + generalSelected + `value="General">General</option>`,
style: "padding: 5px 10px;width:140px;color:gray;",
color: "gray"
});
dropdown.title = "Active Category";
categories.forEach(function(cat) {
var selected = "";
if(activeCategory == cat) {
selected = `selected="selected" `
}
dropdown.innerHTML += `<option ` + selected + `value="` + cat + `">` + cat + `</option>`;
});
dropdown.onchange = function() {
updateActiveCategory(dropdown.value);
overlayDivRefresh();
}
// Function to create and style buttons
const createButton = (text, bgColor) => Object.assign(document.createElement('button'), {
textContent: text,
style: `padding: 5px 15px; color: white; background: ` + bgColor + `; border: none; cursor: pointer;`
});
// Create + and - buttons
const addButton = createButton('+', 'rgba(46, 204, 113, 1)');
// Add an event listener for the '+' button click
addButton.title = "Create new Category";
addButton.addEventListener('click', () => {
// Code to execute when '+' button is clicked
console.log('The "+" button was clicked');
createInputBoxMenu("Add new category");
});
const subtractButton = createButton('-', 'rgba(231, 76, 60, 1)');
// Append elements to container and container to body
subtractButton.title = "Remove active Category";
container.append(dropdown, addButton, subtractButton);
//document.body.append(container);
// Style the body for centering
Object.assign(document.body.style, {
display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', margin: '0'
});
return container;
}
// Sets the active category to save sections to
function updateActiveCategory(categoryNow) {
activeCategory = categoryNow;
}
// Close active drop down tool tip
function closeActiveDropDownToolTip() {
if(activeDropDownToolTip != false) {
activeDropDownToolTip.remove();
activeDropDownToolTip = false;
}
}
// Create the hover category menu
function createHoverCategoryMenu(targetElement) {
// Create tooltip container
if(activeDropDownToolTip != false) {
activeDropDownToolTip.remove();
}
const tooltip = Object.assign(document.createElement('div'), {
style: `position: absolute;z-index:1001; color: gray; text-align: center; padding: 10px; background-color: #f0f0f0; border: 1px solid #ccc;` // initially hidden
});
activeDropDownToolTip = tooltip;
// Main text
tooltip.appendChild(Object.assign(document.createElement('div'), {
textContent: 'Active Category',
style: 'font-size: 14px; margin-bottom: 5px;font-weight:bold;'
}));
// Categories label
tooltip.appendChild(Object.assign(document.createElement('div'), {
textContent: 'Categories',
style: 'font-size: 12px; margin-bottom: 5px;'
}));
// Dropdown box
var generalSelected = "";
if(activeCategory == "General") {
generalSelected = 'selected="selected" '
}
var tooltipSelect = Object.assign(document.createElement('select'), {
innerHTML: '<option ' + generalSelected +'value="General">General</option>',
style: 'margin: auto; display: block;'
});
tooltip.appendChild(tooltipSelect);
categories.forEach(function(cat) {
var selected = "";
if(activeCategory == cat) {
selected = `selected="selected" `
}
tooltipSelect.innerHTML += `<option ` + selected + `value="` + cat + `">` + cat + `</option>`;
});
tooltipSelect.onchange = function() {
updateActiveCategory(tooltipSelect.value);
}
// Append tooltip to body
document.body.appendChild(tooltip);
// Function to show tooltip at cursor position
const showTooltip = (e) => {
tooltip.style.display = 'block';
tooltip.style.left = `${e.clientX + 10}px`; // Offset a bit from cursor
tooltip.style.top = `${e.clientY + 10}px`;
};
// Function to hide tooltip
const hideTooltip = () => {
//tooltip.style.display = 'none';
tooltip.remove();
};
// Event listeners for mouse enter/leave
targetElement.addEventListener('mousemove', showTooltip);
tooltip.addEventListener('mouseleave', hideTooltip);
}
// Refresh the overlay div
function overlayDivRefresh() {
activeOverlay.remove();
createOverlayDiv();
}
// Creates a semi-transparent overlay to display saved entries
function createOverlayDiv() {
// Create an overlay div and set styles to cover the entire viewport with specific properties
const overlayDiv = document.createElement('div');
activeOverlay = overlayDiv; // Assign created div to activeOverlay.
// Apply styles using Object.assign for conciseness
Object.assign(overlayDiv.style, {
position: 'fixed', // Fixed position to cover the viewport
top: '0', // Start from the top edge
left: '0', // Start from the left edge
width: '100%', // Span the full width of the viewport
height: '100%', // Span the full height of the viewport
backgroundColor: 'rgba(0,0,0,0.5)', // Semi-transparent black background
zIndex: '1000', // Ensure overlay is above other content
overflowY: 'scroll', // Enable vertical scrolling
});
// Create the close button and apply styles and functionality using Object.assign
const closeButton = document.createElement('button');
closeButton.innerText = 'Close'; // Set button text
Object.assign(closeButton.style, {
position: 'absolute',
top: '20px',
right: '20px',
zIndex: '1001', // Ensure button is above the overlay
color: 'red',
fontWeight: 'bold'
});
// Attach click event listener to remove the overlay when button is clicked
closeButton.addEventListener('click', () => {
overlayDiv.remove();
});
overlayDiv.appendChild(closeButton); // Add close button to overlay
var container = createCategoriesManager();
overlayDiv.append(container);
sessionList.forEach(function(el) {
var cat = getCategoryByElement(el);
// alert("cat1 attempt " + cat + " -> " + el.innerText);
if(cat == false || cat != activeCategory) {
return;
}
var paragraph = document.createElement('p');
paragraph.innerHTML = el.innerHTML;
//var remove = paragraph.querySelector(".mt-1.flex.justify-start.gap-3");
// remove.remove();
// Style paragraph to make it distinguishable
Object.assign(paragraph.style, {
backgroundColor: 'rgba(200,200,200,0.8)',
marginBottom: "20px",
border: "medium solid white",
height: "300px",
overflowY: "scroll",
padding: "80px",
width: "60%",
marginLeft: "20%"
});
attachRightClickMenu(paragraph);
toggleElementSize(paragraph);
overlayDiv.appendChild(paragraph); // Add paragraph to overlay
var link = {"main":paragraph,"link":el};
paragraph.setAttribute("data-category", el.getAttribute("data-category"));
sessionLinks.push(link);
});
document.body.appendChild(overlayDiv); // Add overlay to document body
}
})();