- When using SingleFile as:
- an extension, enable the hidden option
userScriptEnabledin the options editor. Open the options page and click the icon next to the title to open theOptions editor. If two icons are displayed, the editor is the second one. Replace"userScriptEnabled": falsewith"userScriptEnabled": true, then clickSave. You will also need to install an extension to store and run user scripts (search "user script" on stores). - a CLI tool, use the option
--browser-scriptto pass script path(s) to SingleFile.
- Dispatch the custom event
single-file-user-script-initin the user script.
dispatchEvent(new CustomEvent("single-file-user-script-init"));
- Listen to the custom event
single-file-on-before-capture-requestin the user script. The listener function is called just before the page is saved.
addEventListener("single-file-on-before-capture-request", () => {
console.log("The page will be saved by SingleFile");
});
If the listener function has to be async, call event.preventDefault() and dispatch the custom event single-file-on-before-capture-response once the asynchronous tasks are terminated.
addEventListener("single-file-on-before-capture-request", async event => {
event.preventDefault();
try {
await ...
} finally {
dispatchEvent(new CustomEvent("single-file-on-before-capture-response"));
}
});
You can also get and set the options via the detail property of the single-file-on-before-capture-request and single-file-on-before-capture-response events. If the detail value is tainted (e.g. in Firefox), you should dispatch the single-file-user-script-init event with { detail: "jsonDetail" } as option. Then, you have to handle the detail value as a JSON string (instead of an object).
- Listen to the custom event
single-file-on-after-capture-requestin the user script to execute a script after the page is processed by SingleFile.
addEventListener("single-file-on-after-capture-request", () => {
console.log("The page has been processed by SingleFile");
});
Like single-file-on-before-capture-request, you can call event.preventDefault() and dispatch the custom event single-file-on-after-capture-response if the listener is an async function.
- Good to know
-
Hidden elements are removed from the saved page when the option "HTML content > remove hidden elements" is enabled (the default). Two kinds of hidden elements are kept anyway:
- the content of a
<details>element, even when it is closed, - a
popoverelement or a<dialog>element with anid, when a button of the page refers to it with apopovertargetorcommandforattribute.
If your script builds content that must stay hidden until the user opens it (e.g. a dialog or a menu), use one of these elements. Buttons with
popovertargetorcommandforopen and close the element without JavaScript, so it also works in the saved page. - the content of a
-
When the option "Stylesheets > remove unused styles" is enabled (the default), a CSS rule depending on the state of an element is kept if the selector matches without its pseudo-classes. For example,
#toggle:checked ~ .panel { display: block; }is kept if#toggle ~ .panelmatches an element. A checkbox and a<label for="toggle">can therefore show and hide content in the saved page, even if scripts are removed. -
With the CLI, the option
--block-scriptsis enabled by default. It removes the scripts from the saved page, but the scripts of the page still run while the page is loaded, so a user script can click on a button of the page before it is saved. Pass--block-scripts=falseif the saved page must keep its scripts.
- Examples
- This script removes images from the page just before saving it and restores them after the page is processed.
// ==UserScript==
// @name Remove images
// @namespace https://github.com/gildas-lormeau/SingleFile
// @version 1.0
// @description [SingleFile] Remove all the images
// @author Gildas Lormeau
// @match *://*/*
// @grant none
// ==/UserScript==
(() => {
const elements = new Map();
const removedElementsSelector = "img";
dispatchEvent(new CustomEvent("single-file-user-script-init"));
addEventListener("single-file-on-before-capture-request", () => {
document.querySelectorAll(removedElementsSelector).forEach(element => {
const placeHolderElement = document.createElement(element.tagName);
elements.set(placeHolderElement, element);
element.parentElement.replaceChild(placeHolderElement, element);
});
});
addEventListener("single-file-on-after-capture-request", () => {
Array.from(elements).forEach(([placeHolderElement, element]) => {
placeHolderElement.parentElement.replaceChild(element, placeHolderElement);
});
elements.clear();
});
})();
- This script converts the title to kebab-case and restores it after the page is processed.
// ==UserScript==
// @name Convert title to kebab-case
// @namespace https://github.com/gildas-lormeau/SingleFile
// @version 1.0
// @description [SingleFile] Convert title to kebab-case
// @author Gildas Lormeau
// @match *://*/*
// @noframes
// @grant none
// ==/UserScript==
(() => {
let title;
dispatchEvent(new CustomEvent("single-file-user-script-init"));
addEventListener("single-file-on-before-capture-request", () => {
title = document.title;
document.title = document.title
.replace(/[^a-z0-9-\s]/gi, "")
.replace(/[-\s]+/g, "-")
.toLowerCase();
});
addEventListener("single-file-on-after-capture-request", () => {
document.title = title;
});
})();
-
This script displays the page in reader mode (with readability.js) just before saving it: https://gist.github.com/gildas-lormeau/963f363bce2a7bdb53b46ec74e11bf5c
-
With the CLI, the autoconsent library can be passed as a user script to dismiss cookie banners: How to remove cookie banners with the CLI.
-
These scripts save AI chat conversations entirely: How to save AI chat conversations. One expands the scrollable areas of the page, for sites like ChatGPT or Perplexity, which remove the messages that are scrolled out of view. Another one expands the long prompts folded by Google Gemini.