Overview
The provided code is a JavaScript bookmarklet that creates a fullscreen, lightweight text input overlay for ChatGPT. It is designed for situations where ChatGPT’s native prompt box feels slow or “heavy,” potentially due to extra processing in the default input UI. Instead of typing directly into the standard prompt area, you type into a simple overlay textarea and then apply the content back into ChatGPT’s prompt field when you are ready.
What the Bookmarklet Does
Creates a Fullscreen Overlay
When executed, the bookmarklet injects a fullscreen white overlay (position: fixed; inset: 0; z-index: 2147483647) into the page. This overlay contains a single “panel” with:
- A header showing a title: “Input (apply to #prompt-textarea on Done)”
- Two buttons: Cancel and Done
- A large multiline
<textarea>for drafting prompts - A small hint line explaining keyboard shortcuts
This overlay is intentionally minimal to reduce rendering overhead and provide a smoother typing experience.
Prevents Double Launch
To avoid creating multiple overlays, the script checks for an existing overlay element by ID:
__bm_fullscreen_prompt_overlay__
If it already exists, the bookmarklet exits immediately.
How It Interacts with ChatGPT’s Prompt Field
Targets #prompt-textarea
The script assumes ChatGPT’s prompt input area is a DOM element with the ID prompt-textarea. On launch, it reads the current content from that element and pre-fills the overlay textarea.
It collects the existing prompt by iterating through document.getElementById("prompt-textarea").childNodes, extracting each node’s textContent, and joining them with newline characters. This gives you a multi-line editable copy of the current prompt content inside the overlay.
Applies Text While Preserving Blank Lines
When you click Done (or use the shortcut), the script:
- Splits the overlay textarea value into lines, preserving blank lines.
- Appends each line to
#prompt-textareaas a<p>element.
- If the line is empty, it inserts a
<br>inside the<p>to preserve spacing. - Otherwise, it sets
p.textContent = promptLine.
This approach ensures that multi-line prompts and blank lines remain intact when transferred back into ChatGPT’s input area.
Replaces Existing Content Cleanly
Before inserting the new lines, the script saves the current child nodes of #prompt-textarea. After appending the newly created <p> elements, it removes the previously saved nodes from the prompt field. This effectively replaces the old content with the newly applied content.
Finally, it performs a sleep(0) (a minimal async yield) and scrolls the last inserted line into view if possible, then closes the overlay.
Usability Features
Keyboard Shortcuts
The overlay listens for key events at the document level:
- Esc: closes the overlay immediately
- Ctrl+Enter (or Cmd+Enter on macOS): triggers the same behavior as clicking Done
These shortcuts help you work quickly without needing to reach for the mouse.
Focus Management
On creation, the script automatically focuses the overlay textarea, so you can start typing immediately.
Cancel Behavior
Clicking Cancel (or pressing Esc) removes the overlay and unregisters the keyboard listener, returning the page to its original state without changing the prompt.
Why This Helps When the Native Input Feels Slow
A primary motivation of this bookmarklet is performance and responsiveness. If ChatGPT’s native prompt area is laggy—possibly due to rich text handling, event listeners, or other runtime processing—typing into a plain <textarea> can feel significantly smoother. You can draft and edit freely in a simplified UI, then apply the final text back into the official prompt area only once.
Practical Notes and Limitations
- The bookmarklet depends on the existence of
#prompt-textarea. If the element is not present or the page structure changes, it will show an alert and cannot apply the text. - The script operates by manipulating DOM nodes (
<p>and<br>), which is consistent with how many rich-text prompt areas represent multi-line content, but it may not match every future UI change. - Because it replaces the prompt content by removing old nodes after inserting new ones, it is intended for full prompt replacement rather than incremental editing inside the original field.
javascript:!function(){const OVERLAY_ID=%22__bm_fullscreen_prompt_overlay__%22;if(document.getElementById(OVERLAY_ID))return;const overlay=document.createElement(%22div%22);overlay.id=OVERLAY_ID;overlay.style.cssText=[%22position:fixed%22,%22inset:0%22,%22z-index:2147483647%22,%22background:#ffffff%22,%22display:flex%22,%22flex-direction:column%22,%22padding:16px%22,%22gap:12px%22].join(%22;%22);const%20panel=document.createElement(%22div%22);panel.style.cssText=[%22flex:1%22,%22display:flex%22,%22flex-direction:column%22,%22gap:12px%22,%22background:#ffffff%22,%22border:1px%20solid%20#ddd%22,%22border-radius:10px%22,%22padding:12px%22,%22min-height:0%22].join(%22;%22);const%20header=document.createElement(%22div%22);header.style.cssText=%22display:flex;%20gap:8px;%20align-items:center;%20justify-content:space-between;%22;const%20title=document.createElement(%22div%22);title.textContent=%22Input%20(apply%20to%20#prompt-textarea%20on%20Done)%22;title.style.cssText=%22color:#000;%20font:600%2014px/1.2%20system-ui,%20-apple-system,%20Segoe%20UI,%20sans-serif;%22;const%20buttons=document.createElement(%22div%22);buttons.style.cssText=%22display:flex;%20gap:8px;%22;const%20btnCancel=document.createElement(%22button%22);btnCancel.textContent=%22Cancel%22;btnCancel.style.cssText=[%22padding:8px%2012px%22,%22border-radius:8px%22,%22border:1px%20solid%20#bbb%22,%22background:#f3f3f3%22,%22color:#000%22,%22cursor:pointer%22,%22font:13px%20system-ui%22].join(%22;%22);const%20btnDone=document.createElement(%22button%22);btnDone.textContent=%22Done%22;btnDone.style.cssText=[%22padding:8px%2012px%22,%22border-radius:8px%22,%22border:1px%20solid%20#2a7%22,%22background:#2a7%22,%22color:#fff%22,%22cursor:pointer%22,%22font:13px%20system-ui%22].join(%22;%22);const%20textarea=document.createElement(%22textarea%22);textarea.placeholder=%22Enter%20multi-line%20text%20here%20(blank%20lines%20are%20preserved).%22;textarea.style.cssText=[%22flex:1%22,%22width:100%25%22,%22resize:none%22,%22border-radius:8px%22,%22border:1px%20solid%20#bbb%22,%22background:#ffffff%22,%22color:#000%22,%22padding:10px%22,%22font:13px/1.5%20ui-monospace,%20SFMono-Regular,%20Menlo,%20Consolas,%20monospace%22,%22outline:none%22,%22min-height:0%22].join(%22;%22);textarea.value=Array.from(document.getElementById(%22prompt-textarea%22).childNodes).map((value,index)=%3Evalue.textContent).join(%22\n%22);const%20hint=document.createElement(%22div%22);hint.style.cssText=%22color:#333;%20font:12px/1.4%20system-ui;%20opacity:0.9;%22;hint.textContent=%22Press%20Esc%20to%20close%20/%20Ctrl+Enter%20to%20apply%22;buttons.appendChild(btnCancel);buttons.appendChild(btnDone);header.appendChild(title);header.appendChild(buttons);panel.appendChild(header);panel.appendChild(textarea);panel.appendChild(hint);overlay.appendChild(panel);document.body.appendChild(overlay);const%20close=()=%3E{document.removeEventListener(%22keydown%22,onKeyDown,!0);overlay.remove()};const%20onKeyDown=e=%3E{if(%22Escape%22!==e.key)if(%22Enter%22!==e.key||!e.ctrlKey&&!e.metaKey);else{e.preventDefault();btnDone.click()}else{e.preventDefault();close()}};document.addEventListener(%22keydown%22,onKeyDown,!0);btnCancel.addEventListener(%22click%22,close);btnDone.addEventListener(%22click%22,async()=%3E{const%20promptTextarea=document.querySelector(%22#prompt-textarea%22);if(!promptTextarea){alert(%22#prompt-textarea%20was%20not%20found.%20This%20page%20may%20not%20contain%20the%20target%20element.%22);return}const%20promptInputList=textarea.value.replace(/\r\n/g,%22\n%22).split(%22\n%22);const%20childNodesSaved=Array.from(promptTextarea.childNodes);for(const%20promptLine%20of%20promptInputList){const%20p=document.createElement(%22p%22);%22%22===promptLine?p.appendChild(document.createElement(%22br%22)):p.textContent=promptLine;promptTextarea.appendChild(p)}for(const%20childNodeSaved%20of%20childNodesSaved)promptTextarea.removeChild(childNodeSaved);await(msec=%3Enew%20Promise(resolve=%3EsetTimeout(resolve,msec)))(0);promptTextarea.lastChild&&promptTextarea.lastChild.scrollIntoView&&promptTextarea.lastChild.scrollIntoView();close()});textarea.focus()}();
(function () {
const OVERLAY_ID = "__bm_fullscreen_prompt_overlay__";
// Prevent double-launch
if (document.getElementById(OVERLAY_ID)) return;
const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
// Create overlay
const overlay = document.createElement("div");
overlay.id = OVERLAY_ID;
overlay.style.cssText = [
"position:fixed",
"inset:0",
"z-index:2147483647",
"background:#ffffff", // white background
"display:flex",
"flex-direction:column",
"padding:16px",
"gap:12px",
].join(";");
const panel = document.createElement("div");
panel.style.cssText = [
"flex:1",
"display:flex",
"flex-direction:column",
"gap:12px",
"background:#ffffff",
"border:1px solid #ddd",
"border-radius:10px",
"padding:12px",
"min-height:0",
].join(";");
const header = document.createElement("div");
header.style.cssText =
"display:flex; gap:8px; align-items:center; justify-content:space-between;";
const title = document.createElement("div");
title.textContent = "Input (apply to #prompt-textarea on Done)";
title.style.cssText =
"color:#000; font:600 14px/1.2 system-ui, -apple-system, Segoe UI, sans-serif;";
const buttons = document.createElement("div");
buttons.style.cssText = "display:flex; gap:8px;";
const btnCancel = document.createElement("button");
btnCancel.textContent = "Cancel";
btnCancel.style.cssText = [
"padding:8px 12px",
"border-radius:8px",
"border:1px solid #bbb",
"background:#f3f3f3",
"color:#000",
"cursor:pointer",
"font:13px system-ui",
].join(";");
const btnDone = document.createElement("button");
btnDone.textContent = "Done";
btnDone.style.cssText = [
"padding:8px 12px",
"border-radius:8px",
"border:1px solid #2a7",
"background:#2a7",
"color:#fff",
"cursor:pointer",
"font:13px system-ui",
].join(";");
const textarea = document.createElement("textarea");
textarea.placeholder =
"Enter multi-line text here (blank lines are preserved).";
textarea.style.cssText = [
"flex:1",
"width:100%",
"resize:none",
"border-radius:8px",
"border:1px solid #bbb",
"background:#ffffff",
"color:#000",
"padding:10px",
"font:13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
"outline:none",
"min-height:0",
].join(";");
textarea.value = Array.from(
document.getElementById("prompt-textarea").childNodes
)
.map((value, index) => {
return value.textContent;
})
.join("\n");
const hint = document.createElement("div");
hint.style.cssText =
"color:#333; font:12px/1.4 system-ui; opacity:0.9;";
hint.textContent = "Press Esc to close / Ctrl+Enter to apply";
buttons.appendChild(btnCancel);
buttons.appendChild(btnDone);
header.appendChild(title);
header.appendChild(buttons);
panel.appendChild(header);
panel.appendChild(textarea);
panel.appendChild(hint);
overlay.appendChild(panel);
document.body.appendChild(overlay);
const close = () => {
document.removeEventListener("keydown", onKeyDown, true);
overlay.remove();
};
const onKeyDown = (e) => {
if (e.key === "Escape") {
e.preventDefault();
close();
return;
}
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
btnDone.click();
return;
}
};
document.addEventListener("keydown", onKeyDown, true);
btnCancel.addEventListener("click", close);
btnDone.addEventListener("click", async () => {
const promptTextarea = document.querySelector("#prompt-textarea");
if (!promptTextarea) {
alert(
"#prompt-textarea was not found. This page may not contain the target element."
);
return;
}
// Convert input to an array of lines (including blank lines)
// Also supports Windows newlines
const promptInputList = textarea.value.replace(/\r\n/g, "\n").split("\n");
// --- Process according to the provided logic ---
const childNodesSaved = Array.from(promptTextarea.childNodes);
for (const promptLine of promptInputList) {
const p = document.createElement("p");
if (promptLine === "") {
p.appendChild(document.createElement("br"));
} else {
p.textContent = promptLine;
}
promptTextarea.appendChild(p);
}
for (const childNodeSaved of childNodesSaved) {
promptTextarea.removeChild(childNodeSaved);
// promptTextarea.appendChild(childNodeSaved);
}
await sleep(0);
// Guard: lastChild may not exist
if (promptTextarea.lastChild && promptTextarea.lastChild.scrollIntoView) {
promptTextarea.lastChild.scrollIntoView();
}
close();
});
// Initial focus
textarea.focus();
})();
Top comments (0)