stephenou / fruitionsite

Build your website with Notion for free
https://fruitionsite.com
MIT License
1.59k stars 218 forks source link

Issue at Step 2 on Fruition #137

Open fra-campo opened 2 years ago

fra-campo commented 2 years ago

I've been setting up my notion site with help from Fruition but I've reached an error message at a crucial moment..!

Step 2: Customise and generate the script comes up with an error message saying 'the webpage at (...) may be temporarily down or it may have moved permanently to a new web address.'

So I'm stuck!! I tried watching the linked Youtube tutorial video, but it seems that's a crucial step that I can't access - in order to generate the code that I need.

If anyone could help me I'd be super grateful - I'm quite a (read: total) novice with this stuff.

Also wondering whether Fruition still works as it did, since Notion has dropped their own easy urls..? Or if I'm better off setting up just on Notion..? Already bought my domain on namecheap though!!

Thanks so much!

maxhoheiser commented 2 years ago

I am having the same issue with a notion workspace that was created after the switch to the new url schema (from www.notion.so/xxx to name.notion.site/xxx)

I tried replacing the notion base URL acording to my base url site, but that throws a worker error any idea on how to fix this?

Changes:

url.hostname = 'www.notion.so'; -> url.hostname = 'xxx.notion.site'; where xxx is my notion domain

I also changed it here response = new Response(body.replace(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/g, MY_DOMAIN), response);

Here is my code

/* CONFIGURATION STARTS HERE */
const MY_DOMAIN = 'mydomain.com';
const SLUG_TO_PAGE = {
'': 'xxx',
};
const PAGE_TITLE = "Text";
const PAGE_DESCRIPTION = 'Personal Blog about Book Notes, Thoughts, Ideas, Best Practices and things I have learned and want to share';

const GOOGLE_FONT = 'Open Sans';

/*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 = 'xxx.notion.site';
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(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/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 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 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)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 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
    });
    }
    element.append(`<style>
    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(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
}
}

class BodyRewriter {
constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
}
element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    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()];
    if (slug != null) {
        history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    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}`, {
    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);
}
olimpa commented 2 years ago

I am having the same issue with a notion workspace that was created after the switch to the new url schema (from www.notion.so/xxx to name.notion.site/xxx)

I tried replacing the notion base URL acording to my base url site, but that throws a worker error any idea on how to fix this?

Changes:

url.hostname = 'www.notion.so'; -> url.hostname = 'xxx.notion.site'; where xxx is my notion domain

I also changed it here response = new Response(body.replace(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/g, MY_DOMAIN), response);

Here is my code

/* CONFIGURATION STARTS HERE */
const MY_DOMAIN = 'mydomain.com';
const SLUG_TO_PAGE = {
'': 'xxx',
};
const PAGE_TITLE = "Text";
const PAGE_DESCRIPTION = 'Personal Blog about Book Notes, Thoughts, Ideas, Best Practices and things I have learned and want to share';

const GOOGLE_FONT = 'Open Sans';

/*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 = 'xxx.notion.site';
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(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/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 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 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)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 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
    });
    }
    element.append(`<style>
    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(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
}
}

class BodyRewriter {
constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
}
element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    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()];
    if (slug != null) {
        history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    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}`, {
    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);
}

I've been setting up my notion site with help from Fruition but I've reached an error message at a crucial moment..!

Step 2: Customise and generate the script comes up with an error message saying 'the webpage at (...) may be temporarily down or it may have moved permanently to a new web address.'

So I'm stuck!! I tried watching the linked Youtube tutorial video, but it seems that's a crucial step that I can't access - in order to generate the code that I need.

If anyone could help me I'd be super grateful - I'm quite a (read: total) novice with this stuff.

Also wondering whether Fruition still works as it did, since Notion has dropped their own easy urls..? Or if I'm better off setting up just on Notion..? Already bought my domain on namecheap though!!

Thanks so much!

Hello , I will share my script you fix it on your site.

Remember if you make modifications aya from the original everything must go according to the original because it will be seen with the error dev or baseurl.

Greetings to you! And it is not too much to remind them to generate new script with instructions and after they do it and the web is available they make modifications.

olimpa commented 2 years ago

[](Code/scrip: https://gist.github.com/isjair/ba625d70afbb5290e1f2e46fee21e1bd)

Now remember this: the code works with the current notion changes so when configuring the scrip you should pay attention to this.

I recommend you to use the official page to generate the notion page code transformed to scrip and generate the pages and their IDs for you to just copy and paste.

Greetings and enjoy yourselves

I am having the same issue with a notion workspace that was created after the switch to the new url schema (from www.notion.so/xxx to name.notion.site/xxx)

I tried replacing the notion base URL acording to my base url site, but that throws a worker error any idea on how to fix this?

Changes:

url.hostname = 'www.notion.so'; -> url.hostname = 'xxx.notion.site'; where xxx is my notion domain

I also changed it here response = new Response(body.replace(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/g, MY_DOMAIN), response);

Here is my code

/* CONFIGURATION STARTS HERE */
const MY_DOMAIN = 'mydomain.com';
const SLUG_TO_PAGE = {
'': 'xxx',
};
const PAGE_TITLE = "Text";
const PAGE_DESCRIPTION = 'Personal Blog about Book Notes, Thoughts, Ideas, Best Practices and things I have learned and want to share';

const GOOGLE_FONT = 'Open Sans';

/*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 = 'xxx.notion.site';
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(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/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 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 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)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 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
    });
    }
    element.append(`<style>
    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(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
}
}

class BodyRewriter {
constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
}
element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    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()];
    if (slug != null) {
        history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    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}`, {
    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);
}

I am having the same issue with a notion workspace that was created after the switch to the new url schema (from www.notion.so/xxx to name.notion.site/xxx) I tried replacing the notion base URL acording to my base url site, but that throws a worker error any idea on how to fix this? Changes: url.hostname = 'www.notion.so'; -> url.hostname = 'xxx.notion.site'; where xxx is my notion domain I also changed it here response = new Response(body.replace(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/g, MY_DOMAIN), response); Here is my code

/* CONFIGURATION STARTS HERE */
const MY_DOMAIN = 'mydomain.com';
const SLUG_TO_PAGE = {
'': 'xxx',
};
const PAGE_TITLE = "Text";
const PAGE_DESCRIPTION = 'Personal Blog about Book Notes, Thoughts, Ideas, Best Practices and things I have learned and want to share';

const GOOGLE_FONT = 'Open Sans';

/*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 = 'xxx.notion.site';
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(/xxx.notion.site/g, MY_DOMAIN).replace(/xxx.notion.site/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 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 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)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 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
    });
    }
    element.append(`<style>
    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(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
}
}

class BodyRewriter {
constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
}
element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    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()];
    if (slug != null) {
        history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    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}`, {
    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);
}

I've been setting up my notion site with help from Fruition but I've reached an error message at a crucial moment..! Step 2: Customise and generate the script comes up with an error message saying 'the webpage at (...) may be temporarily down or it may have moved permanently to a new web address.' So I'm stuck!! I tried watching the linked Youtube tutorial video, but it seems that's a crucial step that I can't access - in order to generate the code that I need. If anyone could help me I'd be super grateful - I'm quite a (read: total) novice with this stuff. Also wondering whether Fruition still works as it did, since Notion has dropped their own easy urls..? Or if I'm better off setting up just on Notion..? Already bought my domain on namecheap though!! Thanks so much!

Hello , I will share my script you fix it on your site.

Remember if you make modifications aya from the original everything must go according to the original because it will be seen with the error dev or baseurl.

Greetings to you! And it is not too much to remind them to generate new script with instructions and after they do it and the web is available they make modifications.