Skip to content

Instantly share code, notes, and snippets.

@xavetar
Created March 14, 2025 15:20
Show Gist options
  • Select an option

  • Save xavetar/79a379d4955a1c6d5609d6e1f1dae94e to your computer and use it in GitHub Desktop.

Select an option

Save xavetar/79a379d4955a1c6d5609d6e1f1dae94e to your computer and use it in GitHub Desktop.
Replicate Web-Page
(async function() {
'use strict';
// Функция для инициализации слушателя клавиш (зависим от раскладки)
function initializeKeyListener() {
console.log('Initializing key listener...');
document.addEventListener('keydown', async function(event) {
console.log(`Key pressed: ${event.key}, Ctrl: ${event.ctrlKey}, Meta: ${event.metaKey}, Alt: ${event.altKey}`);
// Фиксированное сочетание: Ctrl + Command (Meta) + S
const isSaveShortcut = event.ctrlKey && event.metaKey && event.key.toLowerCase() === 's';
// Альтернативное сочетание (закомментировано): Command + Option + S
// const isSaveShortcut = event.metaKey && event.altKey && event.key.toLowerCase() === 's';
// Если хотите оба варианта, используйте:
// const isSaveShortcut = (event.ctrlKey && event.metaKey && event.key.toLowerCase() === 's') ||
// (event.metaKey && event.altKey && event.key.toLowerCase() === 's');
if (isSaveShortcut) {
event.preventDefault();
console.log('Save shortcut detected (Ctrl+Command+S). Starting download...');
try {
await downloadCompleteHTML();
console.log('Page saved successfully');
} catch (err) {
console.error('Failed to save page:', err);
alert(`Error saving page: ${err.message}`);
}
} else {
console.log('Not a save shortcut');
}
}, { passive: false, capture: true });
}
// Основная функция сохранения страницы
async function downloadCompleteHTML() {
console.log('Starting downloadCompleteHTML...');
async function fetchResource(url, isBinary = false) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
signal: controller.signal,
cache: 'no-store'
});
clearTimeout(timeout);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(`Fetched resource: ${url}`);
if (isBinary) {
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
return await response.text();
} catch (error) {
console.warn(`Failed to fetch ${url}: ${error.message}`);
return isBinary ? '' : `/* Failed to load: ${url} */`;
}
}
async function inlineCSS(linkElement) {
const href = linkElement.href;
console.log(`Inlining CSS: ${href}`);
const cssContent = await fetchResource(href);
const resolvedCSS = cssContent.replace(
/url\((?!['"]?(?:data|https?|ftp):)['"]?([^'")]+)['"]?\)/g,
(match, relativeUrl) => {
try {
const absoluteUrl = new URL(relativeUrl.trim(), href).href;
return `url("${absoluteUrl}")`;
} catch (err) {
console.warn(`Failed to resolve URL: ${relativeUrl}`);
return match;
}
}
);
const styleElement = document.createElement('style');
styleElement.textContent = resolvedCSS;
return styleElement;
}
async function inlineMedia(element) {
const mediaElements = element.querySelectorAll('img, video, audio, source');
console.log(`Found ${mediaElements.length} media elements to inline`);
for (const media of mediaElements) {
const srcAttr = media.src ? 'src' : 'data-src';
if (media[srcAttr]?.startsWith('http')) {
const dataUri = await fetchResource(media[srcAttr], true);
if (dataUri) media[srcAttr] = dataUri;
}
}
}
console.log('Cloning document...');
const parser = new DOMParser();
const doc = parser.parseFromString(document.documentElement.outerHTML, 'text/html');
const linkElements = [...doc.querySelectorAll('link[rel="stylesheet"]')];
console.log(`Processing ${linkElements.length} CSS links...`);
await Promise.all(linkElements.map(async link => {
const styleElement = await inlineCSS(link);
link.replaceWith(styleElement);
}));
await inlineMedia(doc);
const scripts = doc.querySelectorAll('script[src]');
console.log(`Processing ${scripts.length} scripts...`);
await Promise.all([...scripts].map(async script => {
if (script.src.startsWith('http')) {
const scriptContent = await fetchResource(script.src);
script.textContent = scriptContent;
script.removeAttribute('src');
}
}));
const finalHTML = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
console.log('HTML prepared for download');
function downloadHTML(content, fileName) {
const blob = new Blob([content], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName || `${document.title || 'page'}_${new Date().toISOString().split('T')[0]}.html`;
document.body.appendChild(a);
console.log(`Triggering download: ${a.download}`);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
downloadHTML(finalHTML);
}
// Немедленный запуск
console.log('Script loaded. Running initial save...');
try {
await downloadCompleteHTML();
console.log('Initial save completed');
} catch (err) {
console.error('Initial save failed:', err);
}
// Активируем слушатель клавиш
initializeKeyListener();
console.log('Key listener activated. Press Ctrl+Command+S to save');
})();
(async function() {
'use strict';
// Функция для инициализации слушателя клавиш
function initializeKeyListener() {
console.log('Initializing key listener...');
document.addEventListener('keydown', async function(event) {
console.log(`Key pressed: ${event.key}, Ctrl: ${event.ctrlKey}, Shift: ${event.shiftKey}, Alt: ${event.altKey}`);
// Универсальное сочетание: Ctrl + Shift + S (работает на Linux, Windows и macOS)
const isSaveShortcut = event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 's';
if (isSaveShortcut) {
event.preventDefault();
console.log('Save shortcut detected (Ctrl+Shift+S). Starting download...');
try {
await downloadCompleteHTML();
console.log('Page saved successfully');
} catch (err) {
console.error('Failed to save page:', err);
alert(`Error saving page: ${err.message}`);
}
} else {
console.log('Not a save shortcut');
}
}, { passive: false, capture: true });
}
// Основная функция сохранения страницы
async function downloadCompleteHTML() {
console.log('Starting downloadCompleteHTML...');
async function fetchResource(url, isBinary = false) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
signal: controller.signal,
cache: 'no-store'
});
clearTimeout(timeout);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(`Fetched resource: ${url}`);
if (isBinary) {
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
return await response.text();
} catch (error) {
console.warn(`Failed to fetch ${url}: ${error.message}`);
return isBinary ? '' : `/* Failed to load: ${url} */`;
}
}
async function inlineCSS(linkElement) {
const href = linkElement.href;
console.log(`Inlining CSS: ${href}`);
const cssContent = await fetchResource(href);
const resolvedCSS = cssContent.replace(
/url\((?!['"]?(?:data|https?|ftp):)['"]?([^'")]+)['"]?\)/g,
(match, relativeUrl) => {
try {
const absoluteUrl = new URL(relativeUrl.trim(), href).href;
return `url("${absoluteUrl}")`;
} catch (err) {
console.warn(`Failed to resolve URL: ${relativeUrl}`);
return match;
}
}
);
const styleElement = document.createElement('style');
styleElement.textContent = resolvedCSS;
return styleElement;
}
async function inlineMedia(element) {
const mediaElements = element.querySelectorAll('img, video, audio, source');
console.log(`Found ${mediaElements.length} media elements to inline`);
for (const media of mediaElements) {
const srcAttr = media.src ? 'src' : 'data-src';
if (media[srcAttr]?.startsWith('http')) {
const dataUri = await fetchResource(media[srcAttr], true);
if (dataUri) media[srcAttr] = dataUri;
}
}
}
console.log('Cloning document...');
const parser = new DOMParser();
const doc = parser.parseFromString(document.documentElement.outerHTML, 'text/html');
const linkElements = [...doc.querySelectorAll('link[rel="stylesheet"]')];
console.log(`Processing ${linkElements.length} CSS links...`);
await Promise.all(linkElements.map(async link => {
const styleElement = await inlineCSS(link);
link.replaceWith(styleElement);
}));
await inlineMedia(doc);
const scripts = doc.querySelectorAll('script[src]');
console.log(`Processing ${scripts.length} scripts...`);
await Promise.all([...scripts].map(async script => {
if (script.src.startsWith('http')) {
const scriptContent = await fetchResource(script.src);
script.textContent = scriptContent;
script.removeAttribute('src');
}
}));
const finalHTML = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
console.log('HTML prepared for download');
function downloadHTML(content, fileName) {
const blob = new Blob([content], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName || `${document.title || 'page'}_${new Date().toISOString().split('T')[0]}.html`;
document.body.appendChild(a);
console.log(`Triggering download: ${a.download}`);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
downloadHTML(finalHTML);
}
// Немедленный запуск
console.log('Script loaded. Running initial save...');
try {
await downloadCompleteHTML();
console.log('Initial save completed');
} catch (err) {
console.error('Initial save failed:', err);
}
// Активируем слушатель клавиш
initializeKeyListener();
console.log('Key listener activated. Press Ctrl+Shift+S to save');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment