Last active
November 5, 2024 16:28
-
-
Save hitripod/3574f26447641061cd2bcdc3ea59e8b2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* CONFIGURATION STARTS HERE */ | |
/* Step 1: enter your domain name like fruitionsite.com */ | |
const MY_DOMAIN = 'kordan.me'; | |
/* | |
* Step 2: enter your URL slug to page ID mapping | |
* The key on the left is the slug (without the slash) | |
* The value on the right is the Notion page ID | |
*/ | |
const SLUG_TO_PAGE = { | |
'': 'b2de72854a8b42609622ce925e328d41', | |
}; | |
/* Step 3: enter your page title and description for SEO purposes */ | |
const PAGE_TITLE = ''; | |
const PAGE_DESCRIPTION = ''; | |
/* Step 4: enter a Google Font name, you can choose from https://fonts.google.com */ | |
const GOOGLE_FONT = ''; | |
/* Step 5: enter any custom scripts you'd like */ | |
const CUSTOM_SCRIPT = ``; | |
/* CONFIGURATION ENDS HERE */ | |
const PAGE_TO_SLUG = {}; | |
const slugs = []; | |
const pages = []; | |
Object.keys(SLUG_TO_PAGE).forEach(slug => { | |
const page = SLUG_TO_PAGE[slug]; | |
slugs.push(slug); | |
pages.push(page); | |
PAGE_TO_SLUG[page] = slug; | |
}); | |
addEventListener('fetch', event => { | |
event.respondWith(fetchAndApply(event.request)); | |
}); | |
function generateSitemap() { | |
let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'; | |
slugs.forEach( | |
(slug) => | |
(sitemap += | |
'<url><loc>https://' + MY_DOMAIN + '/' + slug + '</loc></url>') | |
); | |
sitemap += '</urlset>'; | |
return sitemap; | |
} | |
const corsHeaders = { | |
'Access-Control-Allow-Origin': '*', | |
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS', | |
'Access-Control-Allow-Headers': 'Content-Type', | |
}; | |
function handleOptions(request) { | |
if (request.headers.get('Origin') !== null && | |
request.headers.get('Access-Control-Request-Method') !== null && | |
request.headers.get('Access-Control-Request-Headers') !== null) { | |
// Handle CORS pre-flight request. | |
return new Response(null, { | |
headers: corsHeaders | |
}); | |
} else { | |
// Handle standard OPTIONS request. | |
return new Response(null, { | |
headers: { | |
'Allow': 'GET, HEAD, POST, PUT, OPTIONS', | |
} | |
}); | |
} | |
} | |
async function fetchAndApply(request) { | |
if (request.method === 'OPTIONS') { | |
return handleOptions(request); | |
} | |
let url = new URL(request.url); | |
url.hostname = 'www.notion.so'; | |
if (url.pathname === '/robots.txt') { | |
return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml'); | |
} | |
if (url.pathname === '/sitemap.xml') { | |
let response = new Response(generateSitemap()); | |
response.headers.set('content-type', 'application/xml'); | |
return response; | |
} | |
let response; | |
if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) { | |
response = await fetch(url.toString()); | |
let body = await response.text(); | |
response = new Response(body.replace(/www.notion.so/g, MY_DOMAIN).replace(/notion.so/g, MY_DOMAIN), response); | |
response.headers.set('Content-Type', 'application/x-javascript'); | |
return response; | |
} else if (url.pathname.startsWith('/api')) { | |
// Forward API | |
response = await fetch(url.toString(), { | |
body: url.pathname.startsWith('/api/v3/getPublicPageData') ? null : request.body, | |
headers: { | |
'content-type': 'application/json;charset=UTF-8', | |
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' | |
}, | |
method: 'POST', | |
}); | |
response = new Response(response.body, response); | |
response.headers.set('Access-Control-Allow-Origin', '*'); | |
return response; | |
} else if (slugs.indexOf(url.pathname.slice(1)) > -1) { | |
const pageId = SLUG_TO_PAGE[url.pathname.slice(1)]; | |
const redirectUrl = 'https://' + MY_DOMAIN + '/' + pageId | |
return Response.redirect(redirectUrl, 301); | |
} else { | |
response = await fetch(url.toString(), { | |
body: request.body, | |
headers: request.headers, | |
method: request.method, | |
}); | |
response = new Response(response.body, response); | |
response.headers.delete('Content-Security-Policy'); | |
response.headers.delete('X-Content-Security-Policy'); | |
} | |
return appendJavascript(response, SLUG_TO_PAGE); | |
} | |
class MetaRewriter { | |
element(element) { | |
if (PAGE_TITLE !== '') { | |
if (element.getAttribute('property') === 'og:title' | |
|| element.getAttribute('name') === 'twitter:title') { | |
element.setAttribute('content', PAGE_TITLE); | |
} | |
if (element.tagName === 'title') { | |
element.setInnerContent(PAGE_TITLE); | |
} | |
} | |
if (PAGE_DESCRIPTION !== '') { | |
if (element.getAttribute('name') === 'description' | |
|| element.getAttribute('property') === 'og:description' | |
|| element.getAttribute('name') === 'twitter:description') { | |
element.setAttribute('content', PAGE_DESCRIPTION); | |
} | |
} | |
if (element.getAttribute('property') === 'og:url' | |
|| element.getAttribute('name') === 'twitter:url') { | |
element.setAttribute('content', MY_DOMAIN); | |
} | |
if (element.getAttribute('name') === 'apple-itunes-app') { | |
element.remove(); | |
} | |
} | |
} | |
class HeadRewriter { | |
element(element) { | |
/* | |
if (GOOGLE_FONT !== '') { | |
element.append(`<link href="https://fonts.googleapis.com/css?family=${GOOGLE_FONT.replace(' ', '+')}:Regular,Bold,Italic&display=swap" rel="stylesheet"> | |
<style>* { font-family: "${GOOGLE_FONT}" !important; }</style>`, { | |
html: true | |
}); | |
} | |
*/ | |
const el = `<style> | |
div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; } | |
div.notion-topbar > div > div:nth-child(3) { display: none !important; } | |
div.notion-topbar > div > div:nth-child(4) { display: none !important; } | |
div.notion-topbar > div > div:nth-child(5) { display: none !important; } | |
div.notion-topbar > div > div:nth-child(6) { display: none !important; } | |
div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; } | |
div.notion-topbar-mobile > div:nth-child(3) { display: none !important; } | |
div.notion-topbar-mobile > div:nth-child(4) { display: none !important; } | |
div.notion-topbar-mobile > div:nth-child(5) { display: none !important; } | |
</style>`.replace(/\r?\n/g, ''); | |
element.append( | |
el, | |
{ | |
html: true | |
} | |
); | |
} | |
} | |
class BodyRewriter { | |
constructor(SLUG_TO_PAGE) { | |
this.SLUG_TO_PAGE = SLUG_TO_PAGE; | |
} | |
element(element) { | |
const el = `<script> | |
window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}'; | |
localStorage.__console = true; | |
const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE).replace(/"/g, '\'')}; | |
const PAGE_TO_SLUG = {}; | |
const slugs = []; | |
const pages = []; | |
const el = document.createElement('div'); | |
let redirected = false; | |
Object.keys(SLUG_TO_PAGE).forEach(slug => { | |
const page = SLUG_TO_PAGE[slug]; | |
slugs.push(slug); | |
pages.push(page); | |
PAGE_TO_SLUG[page] = slug; | |
}); | |
function getPage() { | |
return location.pathname.slice(-32); | |
}; | |
function getSlug() { | |
return location.pathname.slice(1); | |
}; | |
function updateSlug() { | |
const slug = PAGE_TO_SLUG[getPage()]; | |
}; | |
function onDark() { | |
}; | |
function onLight() { | |
}; | |
function toggle() { | |
if (document.body.classList.contains('dark')) { | |
onLight(); | |
} else { | |
onDark(); | |
}; | |
}; | |
function addDarkModeButton(device) { | |
const nav = device === 'web' ? document.querySelector('.notion-topbar').firstChild : document.querySelector('.notion-topbar-mobile'); | |
el.className = 'toggle-mode'; | |
el.addEventListener('click', toggle); | |
nav.appendChild(el); | |
onLight(); | |
}; | |
const observer = new MutationObserver(function() { | |
if (redirected) return; | |
const nav = document.querySelector('.notion-topbar'); | |
const mobileNav = document.querySelector('.notion-topbar-mobile'); | |
if (nav && nav.firstChild && nav.firstChild.firstChild | |
|| mobileNav && mobileNav.firstChild) { | |
redirected = true; | |
updateSlug(); | |
addDarkModeButton(nav ? 'web' : 'mobile'); | |
const onpopstate = window.onpopstate; | |
window.onpopstate = function() { | |
if (slugs.includes(getSlug())) { | |
const page = SLUG_TO_PAGE[getSlug()]; | |
if (page) { | |
history.replaceState(history.state, 'bypass', '/' + page); | |
}; | |
}; | |
onpopstate.apply(this, [].slice.call(arguments)); | |
updateSlug(); | |
}; | |
}; | |
}); | |
observer.observe(document.querySelector('#notion-app'), { | |
childList: true, | |
subtree: true, | |
}); | |
const replaceState = window.history.replaceState; | |
window.history.replaceState = function(state) { | |
if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return; | |
return replaceState.apply(window.history, arguments); | |
}; | |
const pushState = window.history.pushState; | |
window.history.pushState = function(state) { | |
const dest = new URL(location.protocol + location.host + arguments[2]); | |
const id = dest.pathname.slice(-32); | |
if (pages.includes(id)) { | |
arguments[2] = '/' + PAGE_TO_SLUG[id]; | |
}; | |
return pushState.apply(window.history, arguments); | |
}; | |
const open = window.XMLHttpRequest.prototype.open; | |
window.XMLHttpRequest.prototype.open = function() { | |
arguments[1] = arguments[1].replace('${MY_DOMAIN}', 'www.notion.so'); | |
return open.apply(this, [].slice.call(arguments)); | |
}; | |
</script>${CUSTOM_SCRIPT}`.replace(/\r?\n/g, ''); | |
element.append( | |
el, | |
{html: true} | |
); | |
} | |
} | |
async function appendJavascript(res, SLUG_TO_PAGE) { | |
return new HTMLRewriter() | |
.on('title', new MetaRewriter()) | |
.on('meta', new MetaRewriter()) | |
.on('head', new HeadRewriter()) | |
.on('body', new BodyRewriter(SLUG_TO_PAGE)) | |
.transform(res); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment