国会図書館個人送信のPDFダウンロード支援Chrome拡張
国立国会図書館(NDL)デジタルコレクションの個人送信サービスにおいて、複数ページの「印刷用ファイル(PDF)」を効率よく保存するためのGoogle Chromeなどの拡張機能(プラグイン)のソースコードです。
※本ツールは個人利用の利便性のために作成されたものであり、国会図書館のシステム変更等により動作しなくなる可能性があります。利用によるいかなる損害も開発者は責任を負いません
初心者の方に向けて、このコードが「どのような仕組みで動くのか(仕様)」と「どのように使うのか(使い方)」を分かりやすく解説します。
1. このコードの「仕様」(なにができるの?)
NDLデジタルコレクションでは、著作権保護などの理由から一度に印刷(PDF化)できるページ数に上限(通常は最大100コマなど)があります。そのため、数百ページある本を保存するには「1〜100ページを選択してPDF化」「101〜200ページを選択してPDF化」という手動の繰り返し作業が必要です。
このプログラムは、その面倒な繰り返し操作をセミオート(半分自動)にしてくれるツールです。
主な機能と特徴
入力の自動化: 印刷ボタンを押し、指定したページ範囲(例:1–100、101–200)を自動で入力して、PDF生成ボタンを押すところまでを自動で行います。
賢い一時停止: PDFが作られると処理を一度ストップし、あなたがPDFをパソコンに保存(ダウンロード)するのを待ってくれます。保存が終わって「次へ進む」を押すと、次の100ページ分を自動で入力します。
ファイル名の自動作成: 開いている本の「著者名」「タイトル」「出版社」「出版年」をページ内から自動で読み取り、Windowsのファイル名として使える形(禁止文字を消した状態)にしてクリップボードに自動コピーしてくれます。ダウンロードしたPDFに名前を付けるのが劇的に楽になります。
画面上に専用パネルが出現: NDLのページを開くと、画面の右上に操作用の近未来的なデザインのコントロールパネル(操作画面)が表示されます。
2. 各ファイルの役割
提供されたコードは3つの部品で構成されています。これらが合体して1つの拡張機能として動きます。
manifest.json(設定書 )
content.js(プログラムの本体)
実際にNDLの画面を操作したり、本のタイトルを読み取ったり、操作パネルを作ったりする、一番重要な頭脳部分です。
overlay.css(デザインシート)
画面右上に表示される操作パネルの「色(紺色ベース)」「文字の大きさ」「ボタンの見た目」などを整えるためのデザインファイルです。
popup.html(説明画面)
ブラウザの右上にある拡張機能アイコンをクリックしたときに、ちいさな説明ウィンドウを表示するためのファイルです。
3. 「使い方」(どうやって使うの?)
このプログラムを実際に自分のパソコン(Google Chromeなど)に導入して使う手順です。
ステップ 1:準備(フォルダとファイルを作る)
パソコンのデスクトップなどに、新しく「ndl-helper」という名前のフォルダを作ります。
メモ帳などのテキストエディタを開き、提示された各コードをそれぞれ別名で、作ったフォルダの中に保存します。
manifest.json
content.js
overlay.css
popup.html
ステップ 2:ブラウザ(Chrome)に読み込ませる
Google Chromeを開き、アドレスバーに chrome://extensions/ と入力してEnterキーを押します(拡張機能の設定画面が開きます)。
画面右上にある「デベロッパー モード」のスイッチをオンにします。
画面左上に出現する「パッケージ化されていない拡張機能を読み込む」というボタンをクリックします。
先ほど作った「ndl-helper」フォルダを選択して読み込ませます。これで準備完了です!
ステップ 3:実際にNDLで使う
NDLデジタルコレクションにログインし、保存したい資料(本)のページを開きます。
画面の右上に、紺色の「📚 NDL 印刷支援」というパネルが自動的に表示されます。
パネルの入力欄に以下を入れます。
総コマ数: その本の全ページ数(例: 350)
バッチサイズ: 一回で処理するページ数(通常は上限の 100 のままでOK)
「▶ 開始」ボタンをクリックします。
ステップ 4:セミオート作業の流れ
プログラムが自動でNDLの印刷画面を開き、「1-100」と入力してPDF生成を実行します。
別タブでPDFが開く(または作成される)と同時に、パネルが「一時停止」状態になります。
この時点で、クリップボードには「著者『タイトル』,出版社,年」というファイル名が自動コピーされています。
PDFを保存する際、ファイル名入力欄で Ctrl + V(貼り付け)を押すだけで、綺麗なファイル名で保存できます。
保存が終わったら、パネルの「▶ 次へ進む」をクリックします。
自動的に次の範囲(101〜200コマ)の処理が始まります。全ページ終わるまでこれを繰り返します。
💡 ポイント: もし途中で休憩したいときや、ページがズレて手動で直したいときは、「■ 停止」を押したり、「開始コマ」の数値を直接書き換えて途中から再開することも可能です。
code:content.js
// NDL 個人送信 印刷支援 - Content Script
// セレクタはスクリーンショットのHTMLに基づいて確定済み
(function () {
"use strict";
// --- State ---
let state = {
active: false,
totalPages: 0,
batchSize: 100,
currentStart: 1,
paused: false, // 「印刷用ファイルを開く」クリック後の一時停止中
pendingNext: null, // 次バッチの開始コマ番号
};
// ============================================================
// NDL 固有セレクタ(スクリーンショットHTMLより確定)
// ============================================================
const SEL_RADIO_SPECIFIC = '#printing-modal-range-specific';
const SEL_RANGE_INPUT = '#range-specific-input';
const SEL_PRINT_BTN = '#printing-modal-printing-button';
const SEL_CANCEL_BTN = '#printing-modal-cancel-button';
// ============================================================
// 書誌情報の取得・サニタイズ
// ============================================================
/**
* ページのタイトル・メタデータから書誌情報を取得し
* 「著者『タイトル』,出版社,年」形式にまとめる
*
* NDL ページの転記例:
* 松平千秋 著『ホメロスとヘロドトス : ギリシア文学論考』,筑摩書房,1985.9.
* → ファイル名用: "松平千秋 著『ホメロスとヘロドトス ギリシア文学論考』,筑摩書房,1985.9"
*/
function getBibliographyText() {
// ── メタタグから取得(NDL の実際の HTML 構造に基づく)──
//
// <meta property="og:title" content="ホメロスとヘロドトス : ギリシア文学論考 - 国立国会図書館デジタルコレクション">
// <meta name="description" content="著者:松平千秋 著, 出版者:筑摩書房, 出版年月日:1985.9">
function getMeta(attr, value) {
const el = document.querySelector(meta[${attr}="${value}"]);
return el ? el.getAttribute("content") || "" : "";
}
// タイトル: og:title から「- 国立国会図書館…」以降を除去
const ogTitle = getMeta("property", "og:title")
.replace(/\s*-−-\s*国立国会図書館.*$/u, "") .trim();
// description: "著者:松平千秋 著, 出版者:筑摩書房, 出版年月日:1985.9"
const desc = getMeta("name", "description");
// description をパース
let creator = "";
let publisher = "";
let date = "";
if (desc) {
const creatorMatch = desc.match(/著者::\s*(.+?)(?:,|$)/); const publisherMatch = desc.match(/出版者::\s*(.+?)(?:,|$)/); const dateMatch = desc.match(/出版年月日::\s*(.+?)(?:,|$)/); creator = creatorMatch ? creatorMatch1.trim() : ""; publisher = publisherMatch ? publisherMatch1.trim() : ""; date = dateMatch ? dateMatch1.trim() : ""; }
// 転記形式で組み立て:「著者 著『タイトル』,出版社,年」
// 転記例: 松平千秋 著『ホメロスとヘロドトス : ギリシア文学論考』,筑摩書房,1985.9
// description の著者フィールドは "松平千秋 著" のように「著」が含まれている
let bib = "";
if (creator) bib += creator; // "松平千秋 著"
if (ogTitle) bib += (bib ? "『" + ogTitle + "』" : ogTitle); // 『タイトル』
if (publisher) bib += (bib ? "," : "") + publisher;
if (date) bib += (bib ? "," : "") + date;
// フォールバック
if (!bib) {
bib = document.title.replace(/\s*-−-\s*国立国会図書館.*$/u, "").trim(); }
return sanitizeForWindowsFilename(bib);
}
/**
* Windows ファイル名禁止文字を除去・置換
* 禁止: \ / : * ? " < > |
* 全角コロン「:」なども半角に変換してから除去
*/
function sanitizeForWindowsFilename(str) {
return str
// 全角→半角の変換(コロン等)
.replace(/:/g, "") // 全角コロンは削除(タイトル中の区切りになることが多い)
.replace(/?/g, "")
.replace(/*/g, "")
.replace(/</g, "")
.replace(/>/g, "")
.replace(/|/g, " ")
.replace(/¥/g, "")
.replace(///g, " ")
// 半角禁止文字
// 連続スペース・末尾スペース整理
.replace(/\s{2,}/g, " ")
.trim();
}
// ============================================================
// Overlay UI
// ============================================================
function createOverlay() {
if (document.getElementById("ndl-helper-overlay")) return;
const overlay = document.createElement("div");
overlay.id = "ndl-helper-overlay";
overlay.innerHTML = `
<div id="ndl-helper-header">
<span id="ndl-helper-title">📚 NDL 印刷支援</span>
<button id="ndl-helper-toggle" title="最小化">−</button>
</div>
<div id="ndl-helper-body">
<div class="ndl-row">
<label>総コマ数</label>
<input type="number" id="ndl-total-pages" min="1" placeholder="例: 350">
</div>
<div class="ndl-row">
<label>バッチサイズ</label>
<input type="number" id="ndl-batch-size" min="1" max="100" value="100">
</div>
<div id="ndl-progress-area" style="display:none">
<div id="ndl-progress-label"></div>
<div id="ndl-progress-bar-wrap"><div id="ndl-progress-bar"></div></div>
</div>
<div id="ndl-status-msg"></div>
<!-- 通常操作ボタン -->
<div class="ndl-btn-row" id="ndl-btn-row-main">
<button id="ndl-start-btn">▶ 開始</button>
<button id="ndl-stop-btn" style="display:none">■ 停止</button>
</div>
<!-- 一時停止中パネル -->
<div id="ndl-pause-panel" style="display:none">
<div id="ndl-bib-area">
<div class="ndl-pause-label">📋 ファイル名用テキスト(コピー済み)</div>
<div id="ndl-bib-text"></div>
<button id="ndl-copy-btn">📋 再コピー</button>
</div>
<div class="ndl-pause-label" style="margin-top:10px">▶ 次のバッチ範囲</div>
<div id="ndl-next-range-display"></div>
<div class="ndl-row" style="margin-top:6px">
<label>開始コマ</label>
<input type="number" id="ndl-resume-start" min="1">
</div>
<div class="ndl-btn-row">
<button id="ndl-next-btn">▶ 次へ進む</button>
<button id="ndl-abort-btn">■ 終了</button>
</div>
</div>
<div id="ndl-log"></div>
</div>
`;
document.body.appendChild(overlay);
bindOverlayEvents();
}
function bindOverlayEvents() {
document.getElementById("ndl-helper-toggle").addEventListener("click", () => {
const body = document.getElementById("ndl-helper-body");
const btn = document.getElementById("ndl-helper-toggle");
if (body.style.display === "none") {
body.style.display = "";
btn.textContent = "−";
} else {
body.style.display = "none";
btn.textContent = "+";
}
});
document.getElementById("ndl-start-btn").addEventListener("click", startAutomation);
document.getElementById("ndl-stop-btn").addEventListener("click", stopAutomation);
document.getElementById("ndl-next-btn").addEventListener("click", resumeFromPause);
document.getElementById("ndl-abort-btn").addEventListener("click", stopAutomation);
document.getElementById("ndl-copy-btn").addEventListener("click", () => {
const bib = document.getElementById("ndl-bib-text").textContent;
copyToClipboard(bib);
const btn = document.getElementById("ndl-copy-btn");
btn.textContent = "✅ コピーしました";
setTimeout(() => { btn.textContent = "📋 再コピー"; }, 2000);
});
// ドラッグ移動
const header = document.getElementById("ndl-helper-header");
let dragging = false, ox = 0, oy = 0;
header.addEventListener("mousedown", (e) => {
if (e.target.tagName === "BUTTON") return;
dragging = true;
const ov = document.getElementById("ndl-helper-overlay");
ox = e.clientX - ov.offsetLeft;
oy = e.clientY - ov.offsetTop;
});
document.addEventListener("mousemove", (e) => {
if (!dragging) return;
const ov = document.getElementById("ndl-helper-overlay");
ov.style.left = (e.clientX - ox) + "px";
ov.style.top = (e.clientY - oy) + "px";
ov.style.right = "auto";
ov.style.bottom = "auto";
});
document.addEventListener("mouseup", () => { dragging = false; });
}
function setStatus(msg, type = "info") {
const el = document.getElementById("ndl-status-msg");
if (!el) return;
el.textContent = msg;
el.className = "ndl-status-" + type;
}
function addLog(msg) {
const log = document.getElementById("ndl-log");
if (!log) return;
const line = document.createElement("div");
line.textContent = [${new Date().toLocaleTimeString()}] ${msg};
log.insertBefore(line, log.firstChild);
while (log.children.length > 30) log.removeChild(log.lastChild);
}
function updateProgress() {
const area = document.getElementById("ndl-progress-area");
const label = document.getElementById("ndl-progress-label");
const bar = document.getElementById("ndl-progress-bar");
if (!area) return;
if (!state.active && !state.paused) { area.style.display = "none"; return; }
area.style.display = "";
const end = Math.min(state.currentStart + state.batchSize - 1, state.totalPages);
const done = state.currentStart - 1;
const pct = state.totalPages > 0 ? Math.round((done / state.totalPages) * 100) : 0;
label.textContent = コマ ${state.currentStart}–${end} / ${state.totalPages}(${pct}%完了);
bar.style.width = pct + "%";
}
// ============================================================
// 一時停止パネルの表示
// ============================================================
function showPausePanel(bibText, nextStart) {
state.paused = true;
state.pendingNext = nextStart;
document.getElementById("ndl-btn-row-main").style.display = "none";
const panel = document.getElementById("ndl-pause-panel");
panel.style.display = "";
// 書誌情報を表示
document.getElementById("ndl-bib-text").textContent = bibText;
// 次の範囲を表示・編集可能に
const nextEnd = Math.min(nextStart + state.batchSize - 1, state.totalPages);
const resumeInput = document.getElementById("ndl-resume-start");
resumeInput.value = nextStart;
if (nextStart > state.totalPages) {
document.getElementById("ndl-next-range-display").textContent =
"これが最終バッチでした。「終了」を押してください。";
document.getElementById("ndl-next-btn").style.display = "none";
} else {
document.getElementById("ndl-next-range-display").textContent =
次: コマ ${nextStart}–${nextEnd}(変更可);
document.getElementById("ndl-next-btn").style.display = "";
}
// クリップボードへコピー
copyToClipboard(bibText);
setStatus("⏸ PDF作業後、「次へ進む」を押してください", "waiting");
}
function hidePausePanel() {
state.paused = false;
document.getElementById("ndl-pause-panel").style.display = "none";
document.getElementById("ndl-btn-row-main").style.display = "";
document.getElementById("ndl-stop-btn").style.display = "";
document.getElementById("ndl-start-btn").style.display = "none";
}
function resumeFromPause() {
const resumeStart = parseInt(document.getElementById("ndl-resume-start").value);
if (!resumeStart || resumeStart < 1) {
setStatus("開始コマ番号を確認してください", "error");
return;
}
if (resumeStart > state.totalPages) {
setStatus("✅ 全バッチ完了!", "success");
stopAutomation();
return;
}
state.currentStart = resumeStart;
hidePausePanel();
addLog(再開: コマ ${resumeStart} から);
runBatch();
}
// ============================================================
// Core automation
// ============================================================
async function runBatch() {
if (!state.active) return;
const end = Math.min(state.currentStart + state.batchSize - 1, state.totalPages);
const rangeStr = state.currentStart === end
? String(state.currentStart)
: ${state.currentStart}-${end};
addLog(バッチ開始: コマ ${rangeStr});
setStatus(コマ ${rangeStr} を処理中..., "info");
updateProgress();
// --- Step 1: 印刷モーダルを開く ---
if (!isModalOpen()) {
let openBtn = document.querySelector(SEL_OPEN_PRINT_MODAL);
if (!openBtn) {
openBtn = Array.from(document.querySelectorAll("button"))
.find(b => b.textContent.trim() === "印刷" || b.getAttribute("aria-label") === "印刷");
}
if (!openBtn) {
setStatus("印刷ボタンが見つかりません。手動で「印刷」ボタンを押してください", "error");
addLog("⚠ 印刷ボタン未検出 - 手動操作を待機中");
const opened = await waitFor(() => isModalOpen(), 30000);
if (!opened) { stopAutomation(); return; }
} else {
openBtn.click();
addLog("印刷モーダルを開く");
const opened = await waitFor(() => isModalOpen(), 8000);
if (!opened) {
setStatus("モーダルが開きませんでした", "error");
stopAutomation();
return;
}
}
await sleep(600);
}
// --- Step 2: 「範囲を指定」ラジオを選択 ---
const radioSpecific = document.querySelector(SEL_RADIO_SPECIFIC);
if (!radioSpecific) {
setStatus("「範囲を指定」ラジオボタンが見つかりません", "error");
addLog("⚠ ラジオボタン未検出");
stopAutomation();
return;
}
if (!radioSpecific.checked) {
if (lbl) lbl.click(); else radioSpecific.click();
addLog("「範囲を指定」を選択");
await sleep(400);
}
// --- Step 3: コマ番号入力欄が表示されるまで待つ ---
await waitFor(() => {
const inp = document.querySelector(SEL_RANGE_INPUT);
return inp && inp.offsetParent !== null;
}, 3000);
const rangeInput = document.querySelector(SEL_RANGE_INPUT);
if (!rangeInput) {
setStatus("コマ番号入力欄が表示されません", "error");
addLog("⚠ 入力欄未表示");
stopAutomation();
return;
}
rangeInput.focus();
await sleep(100);
setNativeValue(rangeInput, rangeStr);
addLog(入力: "${rangeStr}");
await sleep(400);
// --- Step 4: 「印刷用ファイルを開く」クリック ---
const printBtn = document.querySelector(SEL_PRINT_BTN);
if (!printBtn) {
setStatus("「印刷用ファイルを開く」ボタンが見つかりません", "error");
addLog("⚠ 印刷実行ボタン未検出");
stopAutomation();
return;
}
if (printBtn.disabled) {
setStatus("印刷ボタンが無効です。コマ番号を確認してください", "error");
addLog(⚠ ボタン disabled。入力値: "${rangeInput.value}");
stopAutomation();
return;
}
printBtn.click();
addLog(「印刷用ファイルを開く」クリック → コマ ${rangeStr});
// --- Step 5: 即座に一時停止して書誌情報をコピー ---
// PDF は新しいタブで自動的に開く。
// ユーザーが PDF読み込み → 印刷 → DL → ファイル名貼り付けを行い
// 「次へ」を押すまで待機する。
const bibText = getBibliographyText();
const nextStart = end + 1;
addLog(書誌情報取得: "${bibText}");
showPausePanel(bibText, nextStart);
// ここで処理を返す。再開は resumeFromPause() が行う。
}
// ============================================================
// Helpers
// ============================================================
function isModalOpen() {
const modal = document.querySelector(SEL_MODAL);
return !!(modal && modal.offsetHeight > 0);
}
function waitFor(condition, timeout = 5000) {
return new Promise((resolve) => {
const start = Date.now();
const check = () => {
if (condition()) { resolve(true); return; }
if (Date.now() - start > timeout) { resolve(false); return; }
setTimeout(check, 200);
};
check();
});
}
function setNativeValue(input, value) {
// Vue v-model 対応
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
setter.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
input.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true }));
}
function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
} else {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
const ta = document.createElement("textarea");
ta.value = text;
ta.style.cssText = "position:fixed;top:-9999px;left:-9999px";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
// ============================================================
// Start / Stop
// ============================================================
function startAutomation() {
const total = parseInt(document.getElementById("ndl-total-pages").value);
const batch = parseInt(document.getElementById("ndl-batch-size").value);
if (!total || total < 1) { setStatus("総コマ数を入力してください", "error"); return; }
if (!batch || batch < 1 || batch > 100) { setStatus("バッチサイズは1〜100の範囲で入力してください", "error"); return; }
state.active = true;
state.paused = false;
state.totalPages = total;
state.batchSize = batch;
state.currentStart = 1;
document.getElementById("ndl-start-btn").style.display = "none";
document.getElementById("ndl-stop-btn").style.display = "";
document.getElementById("ndl-log").innerHTML = "";
addLog(開始: 総${total}コマ、バッチ${batch}コマ);
runBatch();
}
function stopAutomation() {
state.active = false;
state.paused = false;
document.getElementById("ndl-pause-panel").style.display = "none";
document.getElementById("ndl-btn-row-main").style.display = "";
document.getElementById("ndl-start-btn").style.display = "";
document.getElementById("ndl-stop-btn").style.display = "none";
setStatus("停止しました", "info");
addLog("停止");
updateProgress();
}
// ============================================================
// Init
// ============================================================
function init() {
createOverlay();
updateProgress();
setStatus("総コマ数を入力して「開始」を押してください", "info");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
code:manifest.json
{
"manifest_version": 3,
"name": "NDL 個人送信 印刷支援",
"version": "1.0.0",
"description": "国立国会図書館デジタルコレクション個人送信サービスの印刷用ファイル生成を自動化します",
"host_permissions": [
],
"content_scripts": [
{
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
code:overlay.css
/* NDL 印刷支援 オーバーレイ */
position: fixed;
top: 20px;
right: 20px;
z-index: 2147483647;
width: 290px;
border-radius: 10px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
font-family: 'Hiragino Sans', 'Yu Gothic', Meiryo, sans-serif;
font-size: 13px;
user-select: none;
}
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-radius: 10px 10px 0 0;
cursor: move;
}
font-weight: 700;
font-size: 13px;
letter-spacing: 0.5px;
}
background: none;
width: 22px;
height: 22px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background 0.15s;
}
/* 入力行 */
.ndl-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.ndl-row label {
font-size: 12px;
white-space: nowrap;
}
.ndl-row input {
width: 120px;
padding: 5px 8px;
border-radius: 5px;
font-size: 13px;
font-family: inherit;
outline: none;
transition: border-color 0.15s;
}
.ndl-row input:focus { border-color: #7a7acf; } /* プログレス */
border-radius: 4px;
height: 6px;
overflow: hidden;
}
height: 100%;
width: 0%;
border-radius: 4px;
transition: width 0.4s ease;
}
/* ステータス */
font-size: 11px;
margin-bottom: 10px;
min-height: 16px;
line-height: 1.5;
}
/* ボタン行 */
.ndl-btn-row {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.ndl-btn-row button {
flex: 1;
padding: 8px 0;
border-radius: 6px;
border: none;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: opacity 0.15s, transform 0.1s;
font-family: inherit;
letter-spacing: 0.3px;
}
.ndl-btn-row button:active { transform: scale(0.97); }
}
}
}
.ndl-btn-row button:hover { opacity: 0.88; }
/* ==============================
一時停止パネル
============================== */
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.ndl-pause-label {
font-size: 10px;
margin-bottom: 5px;
letter-spacing: 0.3px;
}
border-radius: 5px;
padding: 8px;
margin-bottom: 4px;
}
font-size: 11px;
line-height: 1.6;
word-break: break-all;
user-select: text;
cursor: text;
margin-bottom: 6px;
/* テキスト選択できるよう user-select を許可 */
}
display: block;
width: 100%;
padding: 5px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
font-family: inherit;
transition: background 0.15s;
}
font-size: 11px;
margin-bottom: 6px;
line-height: 1.5;
}
/* ログ */
max-height: 110px;
overflow-y: auto;
font-size: 10px;
line-height: 1.6;
padding-top: 8px;
}
code:popup.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NDL 印刷支援</title>
<style>
body {
width: 240px;
padding: 16px;
font-family: 'Hiragino Sans', 'Yu Gothic', Meiryo, sans-serif;
font-size: 13px;
margin: 0;
}
h2 {
margin: 0 0 10px;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
}
p {
margin: 0 0 10px;
font-size: 12px;
line-height: 1.6;
}
.tip {
border-radius: 6px;
padding: 10px;
font-size: 11px;
line-height: 1.7;
}
.tip strong {
}
.status {
margin-top: 10px;
padding: 8px;
border-radius: 5px;
font-size: 11px;
}
</style>
</head>
<body>
<h2>📚 NDL 印刷支援</h2>
<p>国立国会図書館デジタルコレクション(個人送信)の印刷用ファイル生成を自動化します。</p>
<div class="tip">
<strong>使い方</strong><br>
① NDL の資料ページを開く<br>
② 右上に浮かぶパネルで<br>
総コマ数・バッチサイズを入力<br>
③「▶ 開始」を押す<br>
④ 自動でバッチ生成が進みます
</div>
<div class="status">✅ dl.ndl.go.jp で有効</div>
</body>
</html>