mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2025-12-05 20:40:18 -08:00
Sync theme/ with master
This commit is contained in:
90
theme/ai.js
90
theme/ai.js
@@ -5,17 +5,33 @@
|
||||
|
||||
(() => {
|
||||
const KEY = 'htSummerDiscountsDismissed';
|
||||
const IMG = '/images/discount.jpeg';
|
||||
const IMG = '/ima * HackTricks AI Chat Widget v1.17 – enhanced resizable sidebar
|
||||
* ---------------------------------------------------
|
||||
* ❶ Markdown rendering + sanitised (same as before)
|
||||
* ❷ ENHANCED: improved drag‑to‑resize panel with better UXdiscount.jpeg';
|
||||
const TXT = 'Click here for HT Summer Discounts, Last Days!';
|
||||
const URL = 'https://training.hacktricks.xyz';
|
||||
|
||||
# Stop if user already dismissed
|
||||
// Stop if user already dismissed
|
||||
if (localStorage.getItem(KEY) === 'true') return;
|
||||
|
||||
# Quick helper
|
||||
const $ = (tag, css = '') => Object.assign(document.createElement(tag), { style: css });
|
||||
// Quick helper
|
||||
const $ = (tag, css = '') => Object.assign(document.cr p.innerHTML = `
|
||||
<div id="ht-ai-header">
|
||||
<strong>HackTricks AI Chat</strong>
|
||||
<span style="font-size:11px;opacity:0.6;margin-left:8px;">↔ Drag edge to resize</span>
|
||||
<div class="ht-actions">
|
||||
<button id="ht-ai-reset" title="Reset">↺</button>
|
||||
<span id="ht-ai-close" title="Close">✖</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ht-ai-chat"></div>
|
||||
<div id="ht-ai-input">
|
||||
<textarea id="ht-ai-question" placeholder="Type your question…"></textarea>
|
||||
<button id="ht-ai-send">Send</button>
|
||||
</div>`;tag), { style: css });
|
||||
|
||||
# --- Overlay (blur + dim) ---
|
||||
// --- Overlay (blur + dim) ---
|
||||
const overlay = $('div', `
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,.4);
|
||||
@@ -24,7 +40,7 @@
|
||||
z-index: 10000;
|
||||
`);
|
||||
|
||||
# --- Modal ---
|
||||
// --- Modal ---
|
||||
const modal = $('div', `
|
||||
max-width: 90vw; width: 480px;
|
||||
background: #fff; border-radius: 12px; overflow: hidden;
|
||||
@@ -33,10 +49,10 @@
|
||||
display: flex; flex-direction: column; align-items: stretch;
|
||||
`);
|
||||
|
||||
# --- Title bar (link + close) ---
|
||||
// --- Title bar (link + close) ---
|
||||
const titleBar = $('div', `
|
||||
position: relative;
|
||||
padding: 1rem 2.5rem 1rem 1rem; # room for the close button
|
||||
padding: 1rem 2.5rem 1rem 1rem; // room for the close button
|
||||
text-align: center;
|
||||
background: #222; color: #fff;
|
||||
font-size: 1.3rem; font-weight: 700;
|
||||
@@ -53,7 +69,7 @@
|
||||
link.textContent = TXT;
|
||||
titleBar.appendChild(link);
|
||||
|
||||
# Close "X" (no persistence)
|
||||
// Close "X" (no persistence)
|
||||
const closeBtn = $('button', `
|
||||
position: absolute; top: .25rem; right: .5rem;
|
||||
background: transparent; border: none;
|
||||
@@ -65,11 +81,11 @@
|
||||
closeBtn.onclick = () => overlay.remove();
|
||||
titleBar.appendChild(closeBtn);
|
||||
|
||||
# --- Image ---
|
||||
// --- Image ---
|
||||
const img = $('img');
|
||||
img.src = IMG; img.alt = TXT; img.style.width = '100%';
|
||||
|
||||
# --- Checkbox row ---
|
||||
// --- Checkbox row ---
|
||||
const label = $('label', `
|
||||
display: flex; align-items: center; justify-content: center; gap: .6rem;
|
||||
padding: 1rem; font-size: 1rem; color: #222; cursor: pointer;
|
||||
@@ -83,7 +99,7 @@
|
||||
};
|
||||
label.append(cb, document.createTextNode("Don't show again"));
|
||||
|
||||
# --- Assemble & inject ---
|
||||
// --- Assemble & inject ---
|
||||
modal.append(titleBar, img, label);
|
||||
overlay.appendChild(modal);
|
||||
|
||||
@@ -93,28 +109,28 @@
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
})();
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* HackTricks AI Chat Widget v1.16 – resizable sidebar
|
||||
* ---------------------------------------------------
|
||||
* ❶ Markdown rendering + sanitised (same as before)
|
||||
* ❷ NEW: drag‑to‑resize panel, width persists via localStorage
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function () {
|
||||
const LOG = "[HackTricks‑AI]";
|
||||
const LOG = "[HackTricks-AI]";
|
||||
/* ---------------- User‑tunable constants ---------------- */
|
||||
const MAX_CONTEXT = 3000; // highlighted‑text char limit
|
||||
const MAX_QUESTION = 500; // question char limit
|
||||
const MIN_W = 250; // ← resize limits →
|
||||
const MAX_W = 600;
|
||||
const MAX_W = 800;
|
||||
const DEF_W = 350; // default width (if nothing saved)
|
||||
const TOOLTIP_TEXT =
|
||||
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
|
||||
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
|
||||
|
||||
const API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
|
||||
const BRAND_RED = "#b31328";
|
||||
@@ -345,8 +361,9 @@
|
||||
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
|
||||
#ht-ai-panel.open{transform:translateX(0)}
|
||||
@media(max-width:768px){#ht-ai-panel{display:none}}
|
||||
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
|
||||
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
|
||||
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333;flex-wrap:wrap}
|
||||
#ht-ai-header strong{flex-shrink:0}
|
||||
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center;margin-left:auto}
|
||||
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
|
||||
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
|
||||
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
|
||||
@@ -367,8 +384,10 @@
|
||||
::selection{background:#ffeb3b;color:#000}
|
||||
::-moz-selection{background:#ffeb3b;color:#000}
|
||||
/* NEW: resizer handle */
|
||||
#ht-ai-resizer{position:absolute;left:0;top:0;width:6px;height:100%;cursor:ew-resize;background:transparent}
|
||||
#ht-ai-resizer:hover{background:rgba(255,255,255,.05)}`;
|
||||
#ht-ai-resizer{position:absolute;left:0;top:0;width:8px;height:100%;cursor:ew-resize;background:rgba(255,255,255,.08);border-right:1px solid rgba(255,255,255,.15);transition:background .2s ease}
|
||||
#ht-ai-resizer:hover{background:rgba(255,255,255,.15);border-right:1px solid rgba(255,255,255,.3)}
|
||||
#ht-ai-resizer:active{background:rgba(255,255,255,.25)}
|
||||
#ht-ai-resizer::before{content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:2px;height:20px;background:rgba(255,255,255,.4);border-radius:1px}`;
|
||||
const s = document.createElement("style");
|
||||
s.id = "ht-ai-style";
|
||||
s.textContent = css;
|
||||
@@ -432,24 +451,43 @@
|
||||
|
||||
const onMove = (e) => {
|
||||
if (!dragging) return;
|
||||
const dx = startX - e.clientX; // dragging leftwards ⇒ +dx
|
||||
e.preventDefault();
|
||||
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
|
||||
const dx = startX - clientX; // dragging leftwards ⇒ +dx
|
||||
let newW = startW + dx;
|
||||
newW = Math.min(Math.max(newW, MIN_W), MAX_W);
|
||||
panel.style.width = newW + "px";
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
handle.style.background = "";
|
||||
document.body.style.userSelect = "";
|
||||
document.body.style.cursor = "";
|
||||
localStorage.setItem("htAiWidth", parseInt(panel.style.width, 10));
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
document.removeEventListener("touchmove", onMove);
|
||||
document.removeEventListener("touchend", onUp);
|
||||
};
|
||||
handle.addEventListener("mousedown", (e) => {
|
||||
|
||||
const onStart = (e) => {
|
||||
e.preventDefault();
|
||||
dragging = true;
|
||||
startX = e.clientX;
|
||||
startX = e.clientX || (e.touches && e.touches[0].clientX);
|
||||
startW = parseInt(window.getComputedStyle(panel).width, 10);
|
||||
handle.style.background = "rgba(255,255,255,.25)";
|
||||
document.body.style.userSelect = "none";
|
||||
document.body.style.cursor = "ew-resize";
|
||||
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
});
|
||||
document.addEventListener("touchmove", onMove, { passive: false });
|
||||
document.addEventListener("touchend", onUp);
|
||||
};
|
||||
|
||||
handle.addEventListener("mousedown", onStart);
|
||||
handle.addEventListener("touchstart", onStart, { passive: false });
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -21,37 +21,112 @@
|
||||
try { importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); }
|
||||
catch { importScripts(abs('/elasticlunr.min.js')); }
|
||||
|
||||
/* 2 — load a single index (remote → local) */
|
||||
async function loadIndex(remote, local, isCloud=false){
|
||||
let rawLoaded = false;
|
||||
/* 2 — XOR decryption function */
|
||||
function xorDecrypt(encryptedData, key){
|
||||
const keyBytes = new TextEncoder().encode(key);
|
||||
const decrypted = new Uint8Array(encryptedData.length);
|
||||
for(let i = 0; i < encryptedData.length; i++){
|
||||
decrypted[i] = encryptedData[i] ^ keyBytes[i % keyBytes.length];
|
||||
}
|
||||
return decrypted.buffer;
|
||||
}
|
||||
|
||||
/* 3 — decompress gzip data */
|
||||
async function decompressGzip(arrayBuffer){
|
||||
if(typeof DecompressionStream !== 'undefined'){
|
||||
/* Modern browsers: use native DecompressionStream */
|
||||
const stream = new Response(arrayBuffer).body.pipeThrough(new DecompressionStream('gzip'));
|
||||
const decompressed = await new Response(stream).arrayBuffer();
|
||||
return new TextDecoder().decode(decompressed);
|
||||
} else {
|
||||
/* Fallback: use pako library */
|
||||
if(typeof pako === 'undefined'){
|
||||
try { importScripts('https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js'); }
|
||||
catch(e){ throw new Error('pako library required for decompression: '+e); }
|
||||
}
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const decompressed = pako.ungzip(uint8Array, {to: 'string'});
|
||||
return decompressed;
|
||||
}
|
||||
}
|
||||
|
||||
/* 4 — load a single index (remote → local) */
|
||||
async function loadIndex(remote, local, isCloud=false){
|
||||
const XOR_KEY = "Prevent_Online_AVs_From_Flagging_HackTricks_Search_Gzip_As_Malicious_394h7gt8rf9u3rf9g";
|
||||
let rawLoaded = false;
|
||||
if(remote){
|
||||
/* Try ONLY compressed version from GitHub (remote already includes .js.gz) */
|
||||
try {
|
||||
const r = await fetch(remote,{mode:'cors'});
|
||||
if (!r.ok) throw new Error('HTTP '+r.status);
|
||||
importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'})));
|
||||
rawLoaded = true;
|
||||
} catch(e){ console.warn('remote',remote,'failed →',e); }
|
||||
if(!rawLoaded){
|
||||
try { importScripts(abs(local)); rawLoaded = true; }
|
||||
catch(e){ console.error('local',local,'failed →',e); }
|
||||
}
|
||||
if(!rawLoaded) return null; /* give up on this index */
|
||||
const data = { json:self.search.index, urls:self.search.doc_urls, cloud:isCloud };
|
||||
delete self.search.index; delete self.search.doc_urls;
|
||||
return data;
|
||||
if (r.ok) {
|
||||
const encryptedCompressed = await r.arrayBuffer();
|
||||
/* Decrypt first */
|
||||
const compressed = xorDecrypt(new Uint8Array(encryptedCompressed), XOR_KEY);
|
||||
/* Then decompress */
|
||||
const text = await decompressGzip(compressed);
|
||||
importScripts(URL.createObjectURL(new Blob([text],{type:'application/javascript'})));
|
||||
rawLoaded = true;
|
||||
console.log('Loaded encrypted+compressed from GitHub:',remote);
|
||||
}
|
||||
} catch(e){ console.warn('encrypted+compressed GitHub',remote,'failed →',e); }
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const MAIN_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js';
|
||||
const CLOUD_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/refs/heads/master/searchindex.js';
|
||||
|
||||
/* If remote (GitHub) failed, fall back to local uncompressed file */
|
||||
if(!rawLoaded && local){
|
||||
try {
|
||||
importScripts(abs(local));
|
||||
rawLoaded = true;
|
||||
console.log('Loaded local fallback:',local);
|
||||
}
|
||||
catch(e){ console.error('local',local,'failed →',e); }
|
||||
}
|
||||
if(!rawLoaded) return null; /* give up on this index */
|
||||
const data = { json:self.search.index, urls:self.search.doc_urls, cloud:isCloud };
|
||||
delete self.search.index; delete self.search.doc_urls;
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loadWithFallback(remotes, local, isCloud=false){
|
||||
if(remotes.length){
|
||||
const [primary, ...secondary] = remotes;
|
||||
const primaryData = await loadIndex(primary, null, isCloud);
|
||||
if(primaryData) return primaryData;
|
||||
|
||||
if(local){
|
||||
const localData = await loadIndex(null, local, isCloud);
|
||||
if(localData) return localData;
|
||||
}
|
||||
|
||||
for (const remote of secondary){
|
||||
const data = await loadIndex(remote, null, isCloud);
|
||||
if(data) return data;
|
||||
}
|
||||
}
|
||||
|
||||
return local ? loadIndex(null, local, isCloud) : null;
|
||||
}
|
||||
|
||||
let built = [];
|
||||
const MAX = 30, opts = {bool:'AND', expand:true};
|
||||
|
||||
self.onmessage = async ({data}) => {
|
||||
if(data.type === 'init'){
|
||||
const lang = data.lang || 'en';
|
||||
const searchindexBase = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-searchindex/master';
|
||||
|
||||
/* Remote sources are .js.gz (compressed), local fallback is .js (uncompressed) */
|
||||
const mainFilenames = Array.from(new Set(['searchindex-' + lang + '.js.gz', 'searchindex-en.js.gz']));
|
||||
const cloudFilenames = Array.from(new Set(['searchindex-cloud-' + lang + '.js.gz', 'searchindex-cloud-en.js.gz']));
|
||||
|
||||
const MAIN_REMOTE_SOURCES = mainFilenames.map(function(filename) { return searchindexBase + '/' + filename; });
|
||||
const CLOUD_REMOTE_SOURCES = cloudFilenames.map(function(filename) { return searchindexBase + '/' + filename; });
|
||||
|
||||
const indices = [];
|
||||
const main = await loadIndex(MAIN_RAW , '/searchindex-book.js', false); if(main) indices.push(main);
|
||||
const cloud= await loadIndex(CLOUD_RAW, '/searchindex.js', true ); if(cloud) indices.push(cloud);
|
||||
|
||||
const main = await loadWithFallback(MAIN_REMOTE_SOURCES , '/searchindex-book.js', false); if(main) indices.push(main);
|
||||
const cloud= await loadWithFallback(CLOUD_REMOTE_SOURCES, '/searchindex.js', true ); if(cloud) indices.push(cloud);
|
||||
if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; }
|
||||
|
||||
/* build index objects */
|
||||
const built = indices.map(d => ({
|
||||
built = indices.map(d => ({
|
||||
idx : elasticlunr.Index.load(d.json),
|
||||
urls: d.urls,
|
||||
cloud: d.cloud,
|
||||
@@ -59,10 +134,11 @@
|
||||
}));
|
||||
|
||||
postMessage({ready:true});
|
||||
const MAX = 30, opts = {bool:'AND', expand:true};
|
||||
|
||||
self.onmessage = ({data:q}) => {
|
||||
if(!q){ postMessage([]); return; }
|
||||
return;
|
||||
}
|
||||
|
||||
const q = data.query || data;
|
||||
if(!q){ postMessage([]); return; }
|
||||
|
||||
const all = [];
|
||||
for(const s of built){
|
||||
@@ -83,12 +159,16 @@
|
||||
}
|
||||
all.sort((a,b)=>b.norm-a.norm);
|
||||
postMessage(all.slice(0,MAX));
|
||||
};
|
||||
})();
|
||||
};
|
||||
`;
|
||||
|
||||
/* ───────────── 2. spawn worker ───────────── */
|
||||
const worker = new Worker(URL.createObjectURL(new Blob([workerCode],{type:'application/javascript'})));
|
||||
|
||||
/* ───────────── 2.1. initialize worker with language ───────────── */
|
||||
const htmlLang = (document.documentElement.lang || 'en').toLowerCase();
|
||||
const lang = htmlLang.split('-')[0];
|
||||
worker.postMessage({type: 'init', lang: lang});
|
||||
|
||||
/* ───────────── 3. DOM refs ─────────────── */
|
||||
const wrap = document.getElementById('search-wrapper');
|
||||
@@ -97,11 +177,32 @@
|
||||
const listOut = document.getElementById('searchresults-outer');
|
||||
const header = document.getElementById('searchresults-header');
|
||||
const icon = document.getElementById('search-toggle');
|
||||
|
||||
const READY_ICON = icon.innerHTML;
|
||||
|
||||
if(!wrap || !bar || !list || !listOut || !header || !icon) {
|
||||
console.error('[HT Search] Missing DOM elements:', {wrap:!!wrap, bar:!!bar, list:!!list, listOut:!!listOut, header:!!header, icon:!!icon});
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear icon content and use emoji states directly */
|
||||
icon.textContent = '⏳';
|
||||
icon.setAttribute('aria-label','Loading search …');
|
||||
icon.setAttribute('title','Search is loading, please wait...');
|
||||
icon.setAttribute('title','Search is loading, please wait...');
|
||||
|
||||
const setIconState = state => {
|
||||
if(state === 'ready'){
|
||||
icon.textContent = '🔍';
|
||||
icon.setAttribute('aria-label','Open search (S)');
|
||||
icon.removeAttribute('title');
|
||||
} else if(state === 'error'){
|
||||
icon.textContent = '❌';
|
||||
icon.setAttribute('aria-label','Search unavailable');
|
||||
icon.setAttribute('title','Search is unavailable');
|
||||
} else {
|
||||
icon.textContent = '⏳';
|
||||
icon.setAttribute('aria-label','Loading search …');
|
||||
icon.setAttribute('title','Search is loading, please wait...');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const HOT=83, ESC=27, DOWN=40, UP=38, ENTER=13;
|
||||
@@ -155,13 +256,12 @@
|
||||
else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); if(e.keyCode===DOWN){const nxt=cur.nextElementSibling; if(nxt){cur.classList.remove('focus'); nxt.classList.add('focus');}} else if(e.keyCode===UP){const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){prv.classList.add('focus');} else {bar.focus();}} else {const a=cur.querySelector('a'); if(a) window.location.assign(a.href);}}
|
||||
});
|
||||
|
||||
bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage(e.target.value.trim()),120); });
|
||||
bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage({query: e.target.value.trim()}),120); });
|
||||
|
||||
/* ───────────── worker messages ───────────── */
|
||||
worker.onmessage = ({data}) => {
|
||||
if(data && data.ready!==undefined){
|
||||
if(data.ready){ icon.innerHTML=READY_ICON; icon.setAttribute('aria-label','Open search (S)'); }
|
||||
else { icon.textContent='❌'; icon.setAttribute('aria-label','Search unavailable'); }
|
||||
setIconState(data.ready ? 'ready' : 'error');
|
||||
return;
|
||||
}
|
||||
const docs=data, q=bar.value.trim(), terms=q.split(/\s+/).filter(Boolean);
|
||||
|
||||
@@ -255,33 +255,33 @@
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
{{#if previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{previous.path}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
{{/if}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
{{#if next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{next.path}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
{{/if}}
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i><span style="font-size: medium; align-self: center; margin-left: 10px;">{{title}}</span>
|
||||
{{#if previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{previous.path}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i><span style="font-size: medium; align-self: center; margin-left: 10px;">{{previous.name}}</span>
|
||||
</a>
|
||||
{{/previous}}
|
||||
{{/if}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<span style="font-size: medium; align-self: center; margin-right: 10px;">{{title}}</span><i class="fa fa-angle-right"></i>
|
||||
{{#if next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{next.path}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<span style="font-size: medium; align-self: center; margin-right: 10px;">{{next.name}}</span><i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
{{/if}}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
var mobilesponsorCTA = mobilesponsorSide.querySelector(".mobilesponsor-cta")
|
||||
|
||||
async function getSponsor() {
|
||||
const url = "https://cloud.hacktricks.wiki/sponsor"
|
||||
const currentUrl = encodeURIComponent(window.location.href);
|
||||
const url = `https://cloud.hacktricks.wiki/sponsor?current_url=${currentUrl}`;
|
||||
try {
|
||||
const response = await fetch(url, { method: "GET" })
|
||||
if (!response.ok) {
|
||||
|
||||
Reference in New Issue
Block a user