Fruitionで独自ドメイン化したNotionページにおける不具合を修正する

Notion のページを手軽に独自ドメイン化することができるスクリプト生成ツール「Fruition」。近頃はメンテナンスされておらず、Notion 側の仕様変更により正しく動作しなくなってしまった部分があるため、期待通りに使えるようにするには少し手を加える必要があります。

不具合その1

  • ブラウザの戻るボタンを押すと Notion のサインアップ画面に遷移してしまう。
  • ライト/ダークモードの切替ボタン(トグルスイッチ)が機能しない。

ライト/ダークモードの切替ボタンを追加するための addDarkModeButton 関数に問題があるようです。

Cloudflare のダッシュボードから worker.js を編集し、コードの一部を以下の通りに修正することで解決できます。

修正前

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();
}

修正後

function enableConsoleEffectAndSetMode(mode){
  if (__console && !__console.isEnabled) {
    __console.enable();
    window.location.reload();
  } else {
    __console.environment.ThemeStore.setState({ mode: mode });
   localStorage.setItem('newTheme', JSON.stringify({ mode: mode }));
  }
}
function onDark() {
  el.innerHTML = '<div title="Change to Light Mode" style="margin-left: 14px; 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');
  enableConsoleEffectAndSetMode('dark')
}
function onLight() {
  el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: 14px; 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');
  enableConsoleEffectAndSetMode('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)
  const timeout = device === 'web' ? 0 : 500
  setTimeout(() => {
    nav.appendChild(el)
  }, timeout)
  const currentTheme = JSON.parse(localStorage.getItem('newTheme'))?.mode
  if (currentTheme) {
    if (currentTheme === 'dark') {
      onDark()
    } else {
      onLight()
    }
  } else {
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      onDark()
    } else {
      onLight()
    }
  }
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
    toggle()
  })
}

追記(2024/08/13)

2024年7月以降、上記の通りにコードを修正した状態でも、戻るボタンの不具合が発生するようになってしまいました。

追加で以下の修正を行ったところ、再度解決しました。

修正前

history.replaceState(history.state, 'bypass', '/' + page);

修正後

window.location.href = '/' + page; return;

依然としてブラウザバックするたびにサインアップ画面が表示されてしまいますが、すぐに正しいページへリダイレクトされるようになりました。以前のような完璧な挙動とまではいかないものの、一応これでちゃんと機能するようにはなったので良しとします。

不具合その2

  • 特定の条件下において、iOS のブラウザからのページ閲覧時に以下のエラーが表示され、操作ができなくなる。
    こんにちは。お客様のiOSアプリに問題が発生したようです。このアプリを削除して、App Storeから再インストールしてください。
    Image in a image block

その条件は不明ですが、少なくとも私の環境では X(Twitter)の埋め込みが挿入されているページを開いた際にこのエラーが発生することを確認しています。

MetaRewriter クラスに問題があるようなので、以下のコードを丸々コメントアウトまたは削除することで解決できます。

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();
		}
	}
}
.on('title', new MetaRewriter())
.on('meta', new MetaRewriter())
.on('head', new HeadRewriter())

参考