BookmeterReviewSaver

Save reviews on Bookmeter(読書メーター) in real time

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         BookmeterReviewSaver
// @namespace    https://github.com/mosaicer
// @version      1.0.0
// @description  Save reviews on Bookmeter(読書メーター) in real time
// @author       mosaicer
// @match        https://bookmeter.com/*
// run-at        document-idle
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
  'use strict';

  const readModal = document.getElementById('modals').children[2];
  const reviewArea = readModal.getElementsByTagName('textarea')[0]
  const hiddenTextArea = reviewArea.previousSibling;

  let targetBookId = null;
  let working = false;

  const hiddenTextAreaObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // テキストが変更されたとき、キーを本のID・値を変更後のテキストで保存する
      if (mutation.addedNodes.length > 0 && targetBookId && working) {
        const text = mutation.addedNodes[0].textContent.trim();
        GM_setValue(targetBookId, text);
      }
    });
  });
  const hiddenTextAreaObserverConfig = { childList: true };

  const readModalObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // ダイアログ(モーダル)がアクティブ(前面)になったときに、監視を始める
      if (mutation.target.classList.contains('modal--active')) {
        hiddenTextAreaObserver.observe(hiddenTextArea, hiddenTextAreaObserverConfig);
      }
      // ダイアログ(モーダル)が閉じたときに、値をリセットし、動作を停止する
      else {
        working = false;
        targetBookId = null;

        hiddenTextAreaObserver.disconnect();
      }
    });
  });
  const readModalObserverConfig = { attributes: true };

  // 読んだ本として登録するダイアログ(モーダル)の監視を開始する
  readModalObserver.observe(readModal, readModalObserverConfig);

  // クリックイベントリスナのセットアップ
  document.addEventListener('click', (e) => {
    const targetNode = e.target;

    // 下記クラスを持たないノードは無視
    if (targetNode.className !== 'js-modal-button modal-button') {
      return;
    }

    // 読んだ本として登録するアクション以外は無視
    const actionText = targetNode.textContent;
    if (actionText !== '読んだ本に登録' && actionText !== '再読本に登録') {
      return;
    }

    const bookData = JSON.parse(targetNode.getAttribute('data-modal'));
    // 本情報を持っていなければ無視
    if (!bookData.hasOwnProperty('book')) {
      return;
    }

    targetBookId = bookData.book.id;

    const draft = GM_getValue(targetBookId);
    if (draft) {
      reviewArea.value = draft;

      // フォーカス&ブラーで入力チェッカーのイベントを発火させる
      reviewArea.focus();
      reviewArea.blur();
    }

    // 登録ボタン押下を動作開始の契機とする
    working = true;
  }, false);
})();