Repository: rhysd/monolith-of-web
Branch: master
Commit: 9a69d2153ad9
Files: 24
Total size: 27.5 KB
Directory structure:
gitextract_vft5ts7_/
├── .eslintrc.json
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── .prettierrc.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── background.html
├── background.ts
├── bootstrap.ts
├── content.ts
├── lib.d.ts
├── manifest.json
├── package.json
├── popup.css
├── popup.html
├── popup.ts
├── resources/
│ └── icon.xcf
├── storage.ts
├── tsconfig.json
├── tsconfig.webpack.json
└── webpack.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"ignorePatterns": [
"webpack.config.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"@typescript-eslint",
"prettier"
],
"env": {
"es6": true,
"browser": true,
"node": true
},
"globals": {
"chrome": "readonly"
},
"rules": {
"prefer-spread": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"eqeqeq": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/prefer-optional-chain": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/prefer-readonly": "error"
}
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on: [push, pull_request]
jobs:
check:
name: Try build and apply eslint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- uses: actions/setup-node@v1
- run: npm ci
- run: npm run build
- run: npm run lint
================================================
FILE: .gitignore
================================================
/node_modules
/dist
================================================
FILE: .gitmodules
================================================
[submodule "monolith"]
path = monolith
url = https://github.com/rhysd/monolith.git
================================================
FILE: .prettierrc.json
================================================
{
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120
}
================================================
FILE: CHANGELOG.md
================================================
<a name="v0.1.2"></a>
# [v0.1.2](https://github.com/rhysd/monolith-of-web/releases/tag/v0.1.2) - 12 Jan 2020
- **Fix:** Handle CORS more properly
- **Improve:** Update upstream monolith repo
[Changes][v0.1.2]
<a name="v0.1.1"></a>
# [v0.1.1](https://github.com/rhysd/monolith-of-web/releases/tag/v0.1.1) - 03 Jan 2020
- **Improve:** Insecure permissions are no longer necessary. They were removed from permissions list and now only `activeTab` is required
[Changes][v0.1.1]
<a name="v0.1.0"></a>
# [v0.1.0](https://github.com/rhysd/monolith-of-web/releases/tag/v0.1.0) - 02 Jan 2020
First release :tada:
[Changes][v0.1.0]
[v0.1.2]: https://github.com/rhysd/monolith-of-web/compare/v0.1.1...v0.1.2
[v0.1.1]: https://github.com/rhysd/monolith-of-web/compare/v0.1.0...v0.1.1
[v0.1.0]: https://github.com/rhysd/monolith-of-web/tree/v0.1.0
<!-- Generated by changelog-from-release -->
================================================
FILE: CONTRIBUTING.md
================================================
Thank you for contributing this repository!
Before contributing, please read 'Contributing' section of [README.md](./README.md).
https://github.com/rhysd/monolith-of-web/blob/master/README.md#contributing
================================================
FILE: LICENSE
================================================
Copyright (c) 2019 rhysd
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
'Monolith of Web': Chrome Extension Port of [Monolith][1]
=========================================================
['Monolith of Web'][6] is a Chrome extension ported from CLI tool [Monolith][1]. Monolith is a CLI tool to
download a web page as static single HTML file. 'Monolith of Web' provides the same functionality as
a browser extension by compiling Monolith (written in Rust) into WebAssembly.

## Installation
- Install from [Chrome Web Store][7]
- Download `.crx` file from [releases page][5] and install it manually
## Usage

1. Go to a web page you want to store
2. Click 'Monolith of Web' icon in a browser bar (above popup window will open)
3. Click 'Get Monolith' button
4. Wait for the process completing
5. The generated single static HTML file is stored in your downloads folder
By toggling icons at bottom of the popup window, you can determine to or not to include followings
in the generated HTML file.
- JavaScript
- CSS
- `<iframe/>`
- Images
The button at right-bottom toggles if allow CORS request or not. Please read following 'Permissions'
section and 'CORS Requests in Background Page' section for more details.
## Permissions
- **Required permissions**
- `activeTab`: This extension gets an HTML text and a page title from the active tab to generate a monolith
- `storage`: This extension remembers the last state of toggle buttons at bottom in the popup window.
- **Optional permissions**
- `http://*/*` and `https://*/*`: Allow any cross-origin requests in background page. This is runtime
permission so this extension does not require by default. **Only when you see a broken HTML file is
generated due to CORS error in background page, please enable this option.** The reason of these
permissions are explained in next 'CORS Requests in Background Page' section.
## CORS Requests in Background Page
This extension generates a single HTML file in background page of Chrome extension. Since CSP in a
content script is not applied in a background page, some resources in content's HTML cannot be fetched
in background page.
By default, this extension ignores CORS errors in background page. It is usually not a problem since
resources protected by CSP are usually scripts which don't affect main content. But a broken single HTML
page may be generated due to CORS errors.
When you see a broken page due to the CORS error in background page, please enable 'allow CORS requests'
button at right-bottom in the popup window. Permission dialog will appear to require permissions for
sending CORS requests in background page. After accepting it, CORS request error is disabled and all
resources should be fetched with no error.
After generating a single HTML file with the runtime permissions, this extension will remove the permissions
as soon as possible for security.
## Development
WebAssembly port of Monolith is developed in [the forked repository][4]. Currently it has some differences
and duplicates against the original repository. reqwest did not support Wasm before 0.10.0 so my Wasm
port does not use it and uses `fetch()` directly via `js_sys` and `web_sys` crate.
This repository adds the forked Monolith repository as a Git submodule and uses it by bundling sources
with Webpack.
## Contributing
### Creating an issue
Before reporting an issue, please try the same URL with [CLI version][1]. If it is reproducible with
CLI version, please report it to the CLI repository at first.
If it is not reproducible with CLI version (it means the issue only occurs with this extension), please
report it from [issues page][8].
### Improve Wasm part
This repository only includes TypeScript part of extension. Wasm part is developed in
[forked monolith repository][4]. If your improvement can be applied to [upstream][1], please make a
pull request in the upstream at first. After the pull request is merged, please make an issue to
request to merge upstream at this repository or the forked repository.
## License
Distributed under [the MIT license](LICENSE).
[1]: https://github.com/Y2Z/monolith
[3]: https://chrome.google.com/webstore/detail/koalogomkahjlabefiglodpnhhkokekg
[4]: https://github.com/rhysd/monolith
[5]: https://github.com/rhysd/monolith-of-web/releases
[6]: https://github.com/rhysd/monolith-of-web
[7]: https://chrome.google.com/webstore/detail/monolith/koalogomkahjlabefiglodpnhhkokekg
[8]: https://github.com/rhysd/monolith-of-web/issues
================================================
FILE: background.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Background page for Monolith</title>
</head>
<body>
<script src="./bootstrap.js"></script>
</body>
</html>
================================================
FILE: background.ts
================================================
import { monolithOfHtml, MonolithOptions } from 'monolith';
import sanitizeFileName from 'sanitize-filename';
declare global {
interface Window {
wasmLoadedInBackground?: boolean;
}
}
const ANY_ORIGIN_PERMISSIONS = { permissions: [], origins: ['http://*/*', 'https://*/*'] };
function downloadURL(fileName: string, url: string) {
const a = document.createElement('a');
a.download = fileName;
a.href = url;
a.click();
}
function requestAnyOriginAccess() {
return new Promise<boolean>(resolve => {
chrome.permissions.request(ANY_ORIGIN_PERMISSIONS, resolve);
});
}
function revokeAnyOriginAccess() {
return new Promise<boolean>(resolve => {
chrome.permissions.remove(ANY_ORIGIN_PERMISSIONS, resolve);
});
}
async function download(msg: MessageCreateMonolith) {
const granted = msg.cors && (await requestAnyOriginAccess());
console.log('Permissions for CORS request granted:', granted);
const c = msg.config;
console.log('Start monolith for', msg.url, 'with', c);
const opts = MonolithOptions.new();
if (c.noJs) {
opts.noJs(true);
}
if (c.noCss) {
opts.noCss(true);
}
if (c.noIFrames) {
opts.noFrames(true);
}
if (c.noImages) {
opts.noImages(true);
}
const html = await monolithOfHtml(msg.html, msg.url, opts);
const data = new Blob([html], { type: 'text/html' });
const obj = URL.createObjectURL(data);
try {
const file = `${sanitizeFileName(msg.title) || 'index'}.html`;
downloadURL(file, obj);
} finally {
URL.revokeObjectURL(obj);
if (granted) {
const revoked = await revokeAnyOriginAccess();
console.log('Permissions for CORS request revoked:', revoked);
}
}
}
chrome.runtime.onMessage.addListener(async (msg: Message) => {
switch (msg.type) {
case 'bg:start':
try {
await download(msg);
chrome.runtime.sendMessage({ type: 'popup:complete' });
} catch (err) {
chrome.runtime.sendMessage({
type: 'popup:error',
name: err.name || 'Error',
message: err.message,
});
}
break;
default:
break;
}
});
window.wasmLoadedInBackground = true;
================================================
FILE: bootstrap.ts
================================================
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./background').catch(e => console.error('Error importing `background.js`:', e));
================================================
FILE: content.ts
================================================
const html = '<!DOCTYPE html>' + document.documentElement.outerHTML;
const url = location.href;
const title = document.title;
const msg: MessageToPopup = {
type: 'popup:content',
html,
title,
url,
};
chrome.runtime.sendMessage(msg);
================================================
FILE: lib.d.ts
================================================
interface Config {
noJs: boolean;
noCss: boolean;
noIFrames: boolean;
noImages: boolean;
}
type MessageMonolithContent = {
type: 'popup:content';
html: string;
title: string;
url: string;
};
type MessageDownloadComplete = {
type: 'popup:complete';
};
type MessageDownloadError = {
type: 'popup:error';
name: string;
message: string;
};
type MessageToPopup = MessageMonolithContent | MessageDownloadComplete | MessageDownloadError;
type MessageCreateMonolith = {
type: 'bg:start';
html: string;
title: string;
url: string;
cors: boolean;
config: Config;
};
type MessageToBackground = MessageCreateMonolith;
type Message = MessageToPopup | MessageToBackground;
================================================
FILE: manifest.json
================================================
{
"name": "Monolith",
"version": "0.1.3",
"description": "Get a monolith (single static HTML file) of the web page",
"manifest_version": 2,
"permissions": [
"activeTab",
"storage"
],
"optional_permissions": [
"http://*/*",
"https://*/*"
],
"icons": {
"16": "icon/icon-16.png",
"32": "icon/icon-32.png",
"48": "icon/icon-48.png",
"96": "icon/icon-96.png",
"128": "icon/icon-128.png",
"256": "icon/icon-256.png"
},
"browser_action": {
"default_icon": {
"16": "icon/icon-16.png",
"32": "icon/icon-32.png"
},
"default_title": "Monolith",
"default_popup": "popup.html"
},
"background": {
"page": "background.html",
"persistent": false
},
"content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'"
}
================================================
FILE: package.json
================================================
{
"name": "monolith-of-web",
"private": true,
"version": "0.1.3",
"description": "",
"main": "",
"scripts": {
"build": "TS_NODE_PROJECT=tsconfig.webpack.json webpack",
"build:release": "TS_NODE_PROJECT=tsconfig.webpack.json NODE_ENV=production webpack --mode production",
"clean": "rm -rf ./dist",
"release": "npm-run-all clean build:release",
"lint": "eslint '*.ts'",
"start": "TS_NODE_PROJECT=tsconfig.webpack.json webpack-dev-server"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rhysd/monolith-of-web.git"
},
"keywords": [],
"author": "rhysd <https://rhysd.github.io>",
"license": "MIT",
"bugs": {
"url": "https://github.com/rhysd/monolith-of-web/issues"
},
"homepage": "https://github.com/rhysd/monolith-of-web#readme",
"devDependencies": {
"@types/chrome": "0.0.91",
"@types/copy-webpack-plugin": "^5.0.0",
"@types/sanitize-filename": "^1.6.3",
"@types/webpack": "^4.41.2",
"@types/webpack-dev-server": "^3.9.0",
"@typescript-eslint/eslint-plugin": "^2.16.0",
"@typescript-eslint/parser": "^2.16.0",
"copy-webpack-plugin": "^5.1.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-prettier": "^3.1.2",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"tsconfig-paths": "^3.9.0",
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"@mdi/font": "^4.8.95",
"balloon-css": "^1.0.4",
"bulma": "^0.8.0",
"monolith": "file:./monolith",
"sanitize-filename": "^1.6.3"
}
}
================================================
FILE: popup.css
================================================
main {
display: flex;
flex-direction: column;
margin: 32px;
}
#error-message {
margin-top: 32px;
display: none;
}
#get-monolith-msg {
margin-left: 0.3em;
}
.config-panel {
width: 100%;
display: flex;
justify-content: space-around;
}
.config-btn {
cursor: pointer;
}
.tooltip-bg-normal {
--balloon-color: hsl(204, 86%, 53%);
}
.tooltip-bg-danger {
--balloon-color: rgb(205, 0, 0);
}
================================================
FILE: popup.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="bulma.min.css" />
<link rel="stylesheet" href="materialdesignicons.min.css" />
<link rel="stylesheet" href="balloon.min.css" />
<link rel="stylesheet" href="popup.css" />
<title>Monolith of Web</title>
</head>
<body>
<main>
<button id="get-monolith-btn" class="button is-dark">
<span class="icon">
<i class="mdi mdi-arrow-down-bold-box"></i>
<span id="get-monolith-msg">Get Monolith</span>
</span>
</button>
<article id="error-message" class="message is-danger">
<div class="message-header">
<p id="error-title"></p>
<button id="error-close" class="delete" aria-label="delete"></button>
</div>
<div id="error-body" class="message-body"></div>
</article>
</main>
<div class="config-panel">
<span
id="config-js"
class="config-btn icon is-medium tooltip-bg-normal"
aria-label="Toggle including javascript"
data-balloon-pos="up-left"
>
<i class="mdi mdi-language-javascript mdi-24px"></i>
</span>
<span
id="config-css"
class="config-btn icon is-medium tooltip-bg-normal"
aria-label="Toggle including CSS"
data-balloon-pos="up"
>
<i class="mdi mdi-language-css3 mdi-24px"></i>
</span>
<span
id="config-iframes"
class="config-btn icon is-medium tooltip-bg-normal"
aria-label="Toggle including <iframe/>"
data-balloon-pos="up"
>
<i class="mdi mdi-iframe-outline mdi-24px"></i>
</span>
<span
id="config-images"
class="config-btn icon is-medium tooltip-bg-normal"
aria-label="Toggle including images"
data-balloon-pos="up"
>
<i class="mdi mdi-file-image mdi-24px"></i>
</span>
<span
id="config-allow-cors"
class="config-btn icon is-medium tooltip-bg-danger"
aria-label="Toggle allowing CORS requests"
data-balloon-pos="up-right"
>
<i class="mdi mdi-shield-check mdi-24px"/></i>
</span>
</div>
<script src="./popup.js"></script>
</body>
</html>
================================================
FILE: popup.ts
================================================
import { loadFromStorage, storeToStorage, Storage, DEFAULT_STORAGE } from './storage';
type GetButtonState = 'normal' | 'loading' | 'success';
class GetButton {
private state: GetButtonState;
constructor(private elem: HTMLButtonElement) {
this.elem = elem;
this.state = 'normal';
}
clear() {
this.elem.classList.remove('is-loading', 'is-success');
this.elem.classList.add('is-dark');
this.setText('Get Monolith', 'arrow-down-bold-box');
this.state = 'normal';
}
startLoading() {
if (this.state !== 'normal') {
this.clear();
}
this.elem.classList.add('is-loading');
this.state = 'loading';
}
success() {
this.elem.classList.remove('is-dark', 'is-loading');
this.elem.classList.add('is-success');
this.setText('Success!', 'check-bold');
this.state = 'success';
setTimeout(() => {
if (this.state === 'success') {
this.clear();
}
}, 2000);
}
onClick(cb: () => void) {
this.elem.addEventListener('click', cb, { passive: true });
}
private setText(label: string, iconName: string) {
this.elem.innerHTML = '';
const icon = document.createElement('span');
icon.className = 'icon';
const check = document.createElement('i');
check.className = `mdi mdi-${iconName}`;
icon.appendChild(check);
this.elem.appendChild(icon);
const text = document.createElement('span');
text.innerText = label;
this.elem.appendChild(text);
}
}
class ErrorMessage {
constructor(
private readonly container: HTMLElement,
private readonly title: HTMLElement,
private readonly body: HTMLElement,
closeBtn: HTMLButtonElement,
) {
this.close = this.close.bind(this);
closeBtn.addEventListener('click', this.close, { passive: true });
}
show(title: string, message: string) {
this.title.innerText = title;
this.body.innerText = message;
this.container.style.display = 'block';
}
close() {
this.container.style.display = '';
}
}
const COLOR_DISABLED = 'has-text-grey-light';
class ConfigButton {
constructor(private readonly elem: HTMLElement) {
elem.addEventListener('click', this.toggle.bind(this), { passive: true });
}
toggle() {
this.set(!this.enabled());
}
set(enabled: boolean) {
console.log('set!', enabled);
if (enabled) {
this.elem.classList.remove(COLOR_DISABLED);
} else {
this.elem.classList.add(COLOR_DISABLED);
}
}
enabled() {
return !this.elem.classList.contains(COLOR_DISABLED);
}
}
const errorMessage = new ErrorMessage(
document.getElementById('error-message') as HTMLElement,
document.getElementById('error-title') as HTMLElement,
document.getElementById('error-body') as HTMLElement,
document.getElementById('error-close') as HTMLButtonElement,
);
const getButton = new GetButton(document.getElementById('get-monolith-btn') as HTMLButtonElement);
const configButtons = {
noJs: new ConfigButton(document.getElementById('config-js') as HTMLElement),
noCss: new ConfigButton(document.getElementById('config-css') as HTMLElement),
noIFrames: new ConfigButton(document.getElementById('config-iframes') as HTMLElement),
noImages: new ConfigButton(document.getElementById('config-images') as HTMLElement),
allowCors: new ConfigButton(document.getElementById('config-allow-cors') as HTMLElement),
};
getButton.onClick(() => {
getButton.startLoading();
chrome.tabs.executeScript({ file: 'content.js' });
});
type BackgroundWindow = Window & {
wasmLoadedInBackground?: boolean;
};
function pollBackgroundWindowLoaded() {
return new Promise<boolean>(resolve => {
chrome.runtime.getBackgroundPage(w => {
resolve(!!(w as BackgroundWindow).wasmLoadedInBackground);
});
});
}
function sleep(ms: number) {
return new Promise<void>(resolve => setTimeout(resolve, ms));
}
async function waitForBackgroundPageLoaded() {
// Retry for 12 * 250 = 3 seconds
const retries = 12;
const interval = 250;
for (let c = 0; c < retries; ++c) {
if (await pollBackgroundWindowLoaded()) {
return;
}
await sleep(interval);
}
throw new Error(
`No background page is open nor no background script was loaded successfully after ${retries / 4} seconds`,
);
}
async function startMonolith(msg: MessageMonolithContent) {
const config = {
noJs: !configButtons.noJs.enabled(),
noCss: !configButtons.noCss.enabled(),
noIFrames: !configButtons.noIFrames.enabled(),
noImages: !configButtons.noImages.enabled(),
};
const cors = configButtons.allowCors.enabled();
const startMsg: MessageToBackground = {
...msg,
type: 'bg:start',
config,
cors,
};
// Note: Retry is necessary since background page might not be fully opened yet.
// In the case, popup page must wait for the background page being loaded.
// When loading the background page, background.js loads Wasm file asynchronously.
// We need to wait for the page being fully loaded. Otherwise, the callback to
// receive bg:start is not set yet.
await waitForBackgroundPageLoaded();
// Note: Getting the background window object by chrome.runtime.getBackgroundPage()
// and call its method does not work. While executing JavaScript in background from
// popup window, chrome.permissions.request() does not work. It just fires its callback
// without requesting any permissions.
chrome.runtime.sendMessage(startMsg);
await storeToStorage(config, cors);
}
chrome.runtime.onMessage.addListener(async (msg: Message) => {
if (!msg.type.startsWith('popup:')) {
return;
}
switch (msg.type) {
case 'popup:content':
await startMonolith(msg);
break;
case 'popup:complete':
getButton.success();
break;
case 'popup:error':
getButton.clear();
errorMessage.show(msg.name || 'ERROR', msg.message);
break;
default:
console.error('Unexpected message:', msg);
break;
}
});
async function setupConfigButtons() {
let storage: Storage;
try {
storage = await loadFromStorage();
} catch (err) {
storage = DEFAULT_STORAGE;
}
const { config, cors } = storage;
configButtons.noJs.set(!config.noJs);
configButtons.noCss.set(!config.noCss);
configButtons.noIFrames.set(!config.noIFrames);
configButtons.noImages.set(!config.noImages);
configButtons.allowCors.set(cors);
}
setupConfigButtons().catch(err => console.error('Could not set config buttons:', err));
================================================
FILE: storage.ts
================================================
export interface Storage {
config: Config;
cors: boolean;
}
const DEFAULT_CONFIG: Config = {
noJs: false,
noCss: false,
noIFrames: false,
noImages: false,
};
const DEFAULT_CORS = false;
export const DEFAULT_STORAGE: Storage = {
config: DEFAULT_CONFIG,
cors: DEFAULT_CORS,
};
export async function loadFromStorage() {
return new Promise<Storage>(resolve => {
chrome.storage.local.get(['config', 'cors'], items => {
console.log('load!', items);
resolve({
...DEFAULT_STORAGE,
...items,
});
});
});
}
export async function storeToStorage(config: Config, cors: boolean) {
console.log('store!', config, cors);
return new Promise<void>(resolve => {
const s: Storage = { config, cors };
chrome.storage.local.set(s, resolve);
});
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "./dist/",
"module": "esNext",
"moduleResolution": "node",
"preserveConstEnums": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"target": "es2019",
"sourceMap": true,
"esModuleInterop": true
},
"files": [
"popup.ts",
"bootstrap.ts",
"background.ts",
"content.ts",
"storage.ts",
"lib.d.ts"
]
}
================================================
FILE: tsconfig.webpack.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
}
================================================
FILE: webpack.config.ts
================================================
import * as webpack from 'webpack';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import * as path from 'path';
const config: webpack.Configuration = {
mode: 'development',
entry: {
popup: './popup.ts',
bootstrap: './bootstrap.ts',
content: './content.ts',
},
devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js', '.wasm'],
},
plugins: [
new CopyWebpackPlugin([
'background.html',
'node_modules/bulma/css/bulma.min.css',
'node_modules/@mdi/font/css/materialdesignicons.min.css',
{
from: 'node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2',
to: 'fonts/',
},
'node_modules/balloon-css/balloon.min.css',
{ from: 'icon', to: 'icon' },
'manifest.json',
'popup.html',
'popup.css',
]),
],
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
disableHostCheck: true,
writeToDisk: true, // Useful for Chrome extension
},
};
export default config;
gitextract_vft5ts7_/ ├── .eslintrc.json ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .prettierrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── background.html ├── background.ts ├── bootstrap.ts ├── content.ts ├── lib.d.ts ├── manifest.json ├── package.json ├── popup.css ├── popup.html ├── popup.ts ├── resources/ │ └── icon.xcf ├── storage.ts ├── tsconfig.json ├── tsconfig.webpack.json └── webpack.config.ts
SYMBOL INDEX (44 symbols across 4 files)
FILE: background.ts
type Window (line 5) | interface Window {
constant ANY_ORIGIN_PERMISSIONS (line 10) | const ANY_ORIGIN_PERMISSIONS = { permissions: [], origins: ['http://*/*'...
function downloadURL (line 12) | function downloadURL(fileName: string, url: string) {
function requestAnyOriginAccess (line 19) | function requestAnyOriginAccess() {
function revokeAnyOriginAccess (line 25) | function revokeAnyOriginAccess() {
function download (line 31) | async function download(msg: MessageCreateMonolith) {
FILE: lib.d.ts
type Config (line 1) | interface Config {
type MessageMonolithContent (line 8) | type MessageMonolithContent = {
type MessageDownloadComplete (line 14) | type MessageDownloadComplete = {
type MessageDownloadError (line 17) | type MessageDownloadError = {
type MessageToPopup (line 22) | type MessageToPopup = MessageMonolithContent | MessageDownloadComplete |...
type MessageCreateMonolith (line 24) | type MessageCreateMonolith = {
type MessageToBackground (line 32) | type MessageToBackground = MessageCreateMonolith;
type Message (line 34) | type Message = MessageToPopup | MessageToBackground;
FILE: popup.ts
type GetButtonState (line 3) | type GetButtonState = 'normal' | 'loading' | 'success';
class GetButton (line 4) | class GetButton {
method constructor (line 7) | constructor(private elem: HTMLButtonElement) {
method clear (line 12) | clear() {
method startLoading (line 19) | startLoading() {
method success (line 27) | success() {
method onClick (line 40) | onClick(cb: () => void) {
method setText (line 44) | private setText(label: string, iconName: string) {
class ErrorMessage (line 60) | class ErrorMessage {
method constructor (line 61) | constructor(
method show (line 71) | show(title: string, message: string) {
method close (line 77) | close() {
constant COLOR_DISABLED (line 82) | const COLOR_DISABLED = 'has-text-grey-light';
class ConfigButton (line 83) | class ConfigButton {
method constructor (line 84) | constructor(private readonly elem: HTMLElement) {
method toggle (line 88) | toggle() {
method set (line 92) | set(enabled: boolean) {
method enabled (line 101) | enabled() {
type BackgroundWindow (line 126) | type BackgroundWindow = Window & {
function pollBackgroundWindowLoaded (line 130) | function pollBackgroundWindowLoaded() {
function sleep (line 138) | function sleep(ms: number) {
function waitForBackgroundPageLoaded (line 142) | async function waitForBackgroundPageLoaded() {
function startMonolith (line 157) | async function startMonolith(msg: MessageMonolithContent) {
function setupConfigButtons (line 211) | async function setupConfigButtons() {
FILE: storage.ts
type Storage (line 1) | interface Storage {
constant DEFAULT_CONFIG (line 6) | const DEFAULT_CONFIG: Config = {
constant DEFAULT_CORS (line 12) | const DEFAULT_CORS = false;
constant DEFAULT_STORAGE (line 13) | const DEFAULT_STORAGE: Storage = {
function loadFromStorage (line 18) | async function loadFromStorage() {
function storeToStorage (line 30) | async function storeToStorage(config: Config, cors: boolean) {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": ".eslintrc.json",
"chars": 1588,
"preview": "{\n \"extends\": [\n \"eslint:recommended\",\n \"plugin:@typescript-eslint/recommended\",\n \"prettier\",\n "
},
{
"path": ".github/workflows/ci.yml",
"chars": 592,
"preview": "name: CI\non: [push, pull_request]\n\njobs:\n check:\n name: Try build and apply eslint\n runs-on: ubuntu-latest\n st"
},
{
"path": ".gitignore",
"chars": 20,
"preview": "/node_modules\n/dist\n"
},
{
"path": ".gitmodules",
"chars": 85,
"preview": "[submodule \"monolith\"]\n\tpath = monolith\n\turl = https://github.com/rhysd/monolith.git\n"
},
{
"path": ".prettierrc.json",
"chars": 106,
"preview": "{\n \"tabWidth\": 4,\n \"semi\": true,\n \"singleQuote\": true,\n \"trailingComma\": \"all\",\n \"printWidth\": 120\n}\n"
},
{
"path": "CHANGELOG.md",
"chars": 894,
"preview": "<a name=\"v0.1.2\"></a>\n# [v0.1.2](https://github.com/rhysd/monolith-of-web/releases/tag/v0.1.2) - 12 Jan 2020\n\n- **Fix:**"
},
{
"path": "CONTRIBUTING.md",
"chars": 207,
"preview": "Thank you for contributing this repository!\n\nBefore contributing, please read 'Contributing' section of [README.md](./RE"
},
{
"path": "LICENSE",
"chars": 1049,
"preview": "Copyright (c) 2019 rhysd\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software "
},
{
"path": "README.md",
"chars": 4520,
"preview": "'Monolith of Web': Chrome Extension Port of [Monolith][1]\n=========================================================\n\n['M"
},
{
"path": "background.html",
"chars": 187,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>Background page for Monolith</title>\n </head>\n <"
},
{
"path": "background.ts",
"chars": 2390,
"preview": "import { monolithOfHtml, MonolithOptions } from 'monolith';\nimport sanitizeFileName from 'sanitize-filename';\n\ndeclare g"
},
{
"path": "bootstrap.ts",
"chars": 283,
"preview": "// A dependency graph that contains any wasm must all be imported\n// asynchronously. This `bootstrap.js` file does the s"
},
{
"path": "content.ts",
"chars": 249,
"preview": "const html = '<!DOCTYPE html>' + document.documentElement.outerHTML;\nconst url = location.href;\nconst title = document.t"
},
{
"path": "lib.d.ts",
"chars": 734,
"preview": "interface Config {\n noJs: boolean;\n noCss: boolean;\n noIFrames: boolean;\n noImages: boolean;\n}\n\ntype Message"
},
{
"path": "manifest.json",
"chars": 819,
"preview": "{\n \"name\": \"Monolith\",\n \"version\": \"0.1.3\",\n \"description\": \"Get a monolith (single static HTML file) of the web page"
},
{
"path": "package.json",
"chars": 1702,
"preview": "{\n \"name\": \"monolith-of-web\",\n \"private\": true,\n \"version\": \"0.1.3\",\n \"description\": \"\",\n \"main\": \"\",\n \"scripts\": "
},
{
"path": "popup.css",
"chars": 430,
"preview": "main {\n display: flex;\n flex-direction: column;\n margin: 32px;\n}\n#error-message {\n margin-top: 32px;\n dis"
},
{
"path": "popup.html",
"chars": 2263,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <link rel=\"stylesheet\" href=\"bulma.min.css\" />\n <link "
},
{
"path": "popup.ts",
"chars": 7006,
"preview": "import { loadFromStorage, storeToStorage, Storage, DEFAULT_STORAGE } from './storage';\n\ntype GetButtonState = 'normal' |"
},
{
"path": "storage.ts",
"chars": 878,
"preview": "export interface Storage {\n config: Config;\n cors: boolean;\n}\n\nconst DEFAULT_CONFIG: Config = {\n noJs: false,\n "
},
{
"path": "tsconfig.json",
"chars": 578,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./dist/\",\n \"module\": \"esNext\",\n \"moduleResolution\": \"node\",\n \"preserveC"
},
{
"path": "tsconfig.webpack.json",
"chars": 106,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es5\",\n \"esModuleInterop\": true\n }\n}\n"
},
{
"path": "webpack.config.ts",
"chars": 1451,
"preview": "import * as webpack from 'webpack';\nimport CopyWebpackPlugin from 'copy-webpack-plugin';\nimport * as path from 'path';\n\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the rhysd/monolith-of-web GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (27.5 KB), approximately 7.4k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.