🎨

【Arc Browser】GitHubで、PRがdraftかつレビュワー指定時に警告を出す

2024/12/02に公開

tl;dr

ArcのBoost機能を使って、下のコードを実行するとこうなります。

// 定数の定義
const MESSAGE = 'Ready for Reviewを押してください!';
const POPUP_STYLES = {
  position: 'absolute',
  backgroundColor: '#fff3b0',
  color: '#333',
  border: '1px solid #e0c027',
  borderRadius: '8px',
  padding: '12px 16px',
  maxWidth: '400px',
  zIndex: '1000',
  cursor: 'pointer',
  boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
  display: 'flex',
  alignItems: 'center'
};
const POSITION_OFFSET = -30;

// セレクタの定義
const SELECTORS = {
  reviewersSelectMenu: '#reviewers-select-menu',
  draftStateElement: '.new-discussion-timeline #partial-discussion-header .gh-header-meta [reviewable_state="draft"]',
  targetElement: '.Layout-sidebar #partial-discussion-sidebar .discussion-sidebar-item:has(.js-large-teams-check-warning-container)',
  readyForReviewButton: '.timeline-comment--caret .js-details-container .branch-action-item [data-disable-with=""]'
};

// SVGアイコン
const WARNING_ICON = `
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" width="16" style="vertical-align: text-bottom; fill: #e0c027; margin-right: 12px;">
  <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575zm1.763.707a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368zm.53 3.996v2.5a.75.75 0 01-1.5 0v-2.5a.75.75 0 011.5 0zM9 11a1 1 0 11-2 0 1 1 0 012 0z"></path>
</svg>
`;

// スタイル適用関数
function applyStyles(element, styles) {
  Object.assign(element.style, styles);
}

// 通知メッセージの生成
function createNotificationContent(message) {
  return `<div style="display: flex; align-items: center;">${WARNING_ICON}<span style="font-size: 14px;">${message}</span></div>`;
}

// ポップアップ表示
function showCustomNotification(message) {
  let notification = document.querySelector('.custom-notification');
  if (notification) return notification; 

  notification = document.createElement('div');
  notification.classList.add('custom-notification');
  applyStyles(notification, POPUP_STYLES);
  notification.innerHTML = createNotificationContent(message);
  
  notification.addEventListener('click', () => {
    const button = document.querySelector(SELECTORS.readyForReviewButton);
    if (button) button.scrollIntoView({ behavior: 'smooth', block: 'center' });
  });

  document.body.appendChild(notification);
  return notification;
}

// ポップアップの位置を調整
function positionNotification(notification) {
  const targetElement = document.querySelector(SELECTORS.targetElement);
  if (targetElement) {
    const rect = targetElement.getBoundingClientRect();
    notification.style.top = `${rect.top + window.scrollY + POSITION_OFFSET}px`;
    notification.style.left = `${rect.left + window.scrollX}px`;
  }
}

// ポップアップの非表示
function hideCustomNotification() {
  const notification = document.querySelector('.custom-notification');
  if (notification) notification.remove();
}

// ドラフト状態の変更を監視
function observeDraftState() {
  const observer = new MutationObserver(checkReadyForReviewState);
  observer.observe(document.body, { childList: true, subtree: true });
}

// PRレビュー依頼ボタンの監視
function observeReviewersSelectMenu() {
  const reviewersSelectMenu = document.querySelector(SELECTORS.reviewersSelectMenu);
  if (reviewersSelectMenu && !reviewersSelectMenu.dataset.readyForReviewChecked) {
    reviewersSelectMenu.dataset.readyForReviewChecked = 'true';

    const observer = new MutationObserver(() => checkReadyForReviewState());
    observer.observe(reviewersSelectMenu, { attributes: true });
  }
}

// Ready for Review状態の確認
function checkReadyForReviewState() {
  const isDraft = document.querySelector(SELECTORS.draftStateElement);
  const reviewersSelectMenu = document.querySelector(SELECTORS.reviewersSelectMenu);

  if (reviewersSelectMenu?.hasAttribute('open') && isDraft) {
    const notification = showCustomNotification(MESSAGE);
    positionNotification(notification);
  } else {
    hideCustomNotification();
  }
}

// 監視の開始
function monitorChanges() {
  observeDraftState();
  observeReviewersSelectMenu();
  checkReadyForReviewState();
}

// ページロード後に監視開始
window.addEventListener('load', monitorChanges);

Boost機能とは

Arcの機能で、Webサイトに独自のスタイルを当てたり、読み込み後に実行するユーザースクリプトを指定したりできる機能です。
https://resources.arc.net/hc/en-us/articles/19212718608151-Boosts-Customize-Any-Website

ChromiumにおけるTampermonkeyに近いですね。

使い方

  1. Arcを開いて、画面左上のURL欄内の一番のアイコンをクリック
  2. 出現したポップアップの中にある左から2番目のブラシボタンをクリック
  3. 画面右側に表示されたウィンドウの「Code {}」というボタンをクリック
  4. JSタブを選択してスクリプトを編集し、右下の「Refresh to Run」ボタンを押して実行

    これでユーザースクリプトが利用できます。

便利な機能「Pick Selector」

特定の要素のスタイルを変更したい場合に便利です。先のウィンドウの左下のスポイトボタンを押すと利用できます。

クリックすると、画像のようにカーソルでホバーした要素のセレクタが表示されコピーすることができます。tl;drで紹介したスクリプトもこの機能を活用しております。

まとめ

  • Arc BrowserのBoostを使うとユーザースクリプトが埋めこめる
  • 操作したいDOM要素のCSSセレクタをコピーできる「Pick Selector」という機能がある

Discussion