-
Notifications
You must be signed in to change notification settings - Fork 16
/
content.js
177 lines (160 loc) · 5.59 KB
/
content.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
/**
* This script manages the behavior of an extension that allows users to save and reuse prompts
* within input fields on web pages like ChatGPT and Gemini. It handles single and double click
* events to either paste previously saved content into an input field or append additional text.
* The script also ensures the cursor is placed at the end of the inserted text and adjusts the
* input field to display all content.
*
* The script works by:
* - Listening for click events on the input field.
* - Handling single and double clicks differently to insert or append content.
* - Saving the current input field text to local storage for reuse.
* - Managing the cursor position and content display within the input field.
*/
function debounce(func, wait) {
let timeout;
return function (...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
document.addEventListener("DOMContentLoaded", () => {
const inputFieldChatGPT =
document.querySelector(".ProseMirror[contenteditable='true']") ||
document.querySelector("#prompt-textarea");
const inputFieldGemini = document.querySelector(
'.ql-editor[contenteditable="true"]'
);
const inputField = inputFieldChatGPT || inputFieldGemini;
if (inputField) {
let clickTimeout;
inputField.addEventListener(
"click",
debounce((event) => {
if (clickTimeout) {
clearTimeout(clickTimeout);
clickTimeout = null;
handleDoubleClick(inputField);
} else {
clickTimeout = setTimeout(() => {
handleSingleClick(inputField);
clickTimeout = null;
}, 300);
}
}, 100)
);
inputField.addEventListener(
"contextmenu",
debounce((event) => {
event.preventDefault();
chrome.runtime.sendMessage({ action: "showContextMenu" });
}, 100)
);
}
});
/**
* Handles the single click event by retrieving the saved text from local storage
* and inserting it into the input field. If no saved text is found, it saves the
* current input field text to local storage.
*
* @param {HTMLElement} inputField - The input field element where the text will be inserted.
*/
function handleSingleClick(inputField) {
const url = window.location.href;
chrome.storage.local.get([url], (result) => {
if (result[url]) {
insertText(inputField, result[url]);
moveCursorToEnd(inputField);
} else {
saveCurrentInput(inputField, url);
}
});
}
/**
* Handles the double click event by appending the saved text from local storage
* to the current content of the input field.
*
* @param {HTMLElement} inputField - The input field element where the text will be appended.
*/
function handleDoubleClick(inputField) {
const url = window.location.href;
chrome.storage.local.get([url], (result) => {
if (result[url]) {
appendText(inputField, result[url]);
}
});
}
/**
* Saves the current text of the input field to local storage using the URL as the key.
*
* @param {HTMLElement} inputField - The input field element whose text will be saved.
* @param {string} url - The URL of the current page, used as the key in local storage.
*/
function saveCurrentInput(inputField, url) {
const text =
inputField.tagName === "TEXTAREA" || inputField.tagName === "INPUT"
? inputField.value
: inputField.innerText;
chrome.storage.local.set({ [url]: text });
}
/**
* Inserts the given text into the input field, replacing any existing content.
* For textareas, it ensures proper formatting by adding new lines.
*
* @param {HTMLElement} inputField - The input field element where the text will be inserted.
* @param {string} text - The text to insert into the input field.
*/
function insertText(inputField, text) {
if (inputField.tagName === "TEXTAREA" || inputField.tagName === "INPUT") {
inputField.value = `${text}\n\n`;
} else {
inputField.innerHTML = `<p>${text}</p><p><br></p>`;
}
}
/**
* Appends the given text to the current content of the input field.
* For textareas, it ensures proper formatting by adding new lines.
*
* @param{HTMLElement}inputField - The input field element where the text will be appended.
* @param {string} text - The text to append to the input field.
*/
function appendText(inputField, text) {
if (inputField.tagName === "TEXTAREA" || inputField.tagName === "INPUT") {
inputField.value += `\n\n${text}`;
} else {
inputField.innerHTML += `<p><br></p><p>${text}</p>`;
}
}
/**
* Moves the cursor to the end of the content in the input field.
* This ensures that any newly inserted text is visible and the user can continue typing at the end.
*
* @param {HTMLElement} inputField - The input field element where the cursor will be moved.
*/
function moveCursorToEnd(inputField) {
if (inputField.tagName === "TEXTAREA" || inputField.tagName === "INPUT") {
inputField.focus();
inputField.setSelectionRange(
inputField.value.length,
inputField.value.length
);
} else {
setCaretToEnd(inputField);
}
}
/**
* Sets the caret (cursor) to the end of the content within a contenteditable element.
* This is useful for ensuring the user can continue typing at the end of the inserted content.
*
* @param {HTMLElement} element - The contenteditable element where the caret will be positioned.
*/
function setCaretToEnd(element) {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(element);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
element.focus();
}