PDF Viewer や PDFプレビュープラグイン を使えば済むことだが
- ブラウザの拡張機能は、端末ごとにインストールする必要があり
そもそも社内のセキュリティ規定のせいで使えない会社もある。 - プラグインはアプリごとに設定しないといけない。
ポータルとスペースには対応できない。
kintone全体のカスタマイズをすれば、そうした問題をクリアできる。
(() => {
'use strict';
const events = [
'portal.show',
'space.portal.show',
'app.record.index.show',
'app.report.show',
'app.record.detail.show',
'app.record.print.show'
];
kintone.events.on(events, async (event) => {
// PDFダウンロードリンクを処理する関数
const processLinks = () => {
// ファイルダウンロードリンクを全て取得
const links = document.querySelectorAll('a[href*="/download.do"]');
// 各リンクを個別に処理
links.forEach((link) => {
// 既に処理済のリンクはスキップ
if (link.dataset.pdfPreviewBound) return;
// ファイルがPDFかどうかを判定
const textIsPdf = (link.textContent || '').trim().toLowerCase().endsWith('.pdf');
const hrefIsPdf = (link.getAttribute('href') || '').toLowerCase().includes('.pdf');
// PDFでない場合はスキップ
if (!textIsPdf && !hrefIsPdf) return;
// 処理済フラグの付与
link.dataset.pdfPreviewBound = 'true';
// リンクがクリックされたときの処理
link.addEventListener('click', async (e) => {
e.preventDefault(); // デフォルトのクリックイベントを抑止
e.stopPropagation(); // イベント伝播を停止
// ローディング表示用の要素を作成
const loadingDiv = document.createElement('div');
// ローディング表示のスタイル設定
loadingDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 5px;
z-index: 9999;
font-family: sans-serif;
`;
loadingDiv.textContent = 'PDFを読み込み中...';
document.body.appendChild(loadingDiv);
// PDFをBlobとして取得
const resp = await fetch(link.href, { credentials: 'include' });
const blob = await resp.blob();
// PDFのファイル名を取得
const linkText = (link.textContent || '').trim();
const fileName = linkText.endsWith('.pdf') ?
linkText : (linkText || 'document.pdf');
// BlobからオブジェクトURLを作成
const blobUrl = URL.createObjectURL(blob);
// ローディング表示を削除
document.body.removeChild(loadingDiv);
/* PDFビューア画面を作成開始 */
// 画面全体を覆うオーバーレイの要素を作成
const overlay = document.createElement('div');
overlay.id = 'pdf-viewer-overlay';
// フルスクリーン表示のためのスタイル設定
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.9);
z-index: 10000;
display: flex;
flex-direction: column;
`;
// PDFを表示するためのiframe要素を作成
const iframe = document.createElement('iframe');
iframe.src = blobUrl;
// ヘッダー以外の残り全体を使うようにiframeのスタイルを設定
iframe.style.cssText = `
flex: 1;
border: none;
background: white;
`;
// オーバーレイに要素を追加
overlay.appendChild(iframe);
// DOMにオーバーレイを追加して画面に表示
document.body.appendChild(overlay);
/* PDFビューア画面の作成終了 */
// ブラウザバック対応のため現在のURLにハッシュを追加して履歴を作成
history.pushState(null, '', window.location.href + '#pdf-viewer');
// PDFビューアを閉じる関数
const closePdfViewer = (fromPopstate = false) => {
// オーバーレイがDOMに存在する場合のみ削除処理を実行
if (document.body.contains(overlay)) {
document.body.removeChild(overlay);
URL.revokeObjectURL(blobUrl);
}
// 全てのイベントリスナーを削除してメモリリークを防止
document.removeEventListener('keydown', escapeHandler);
window.removeEventListener('popstate', popstateHandler);
// ブラウザバックから呼ばれた場合は追加の履歴操作は不要
if (!fromPopstate) {
// 手動で閉じた場合は履歴を1つ戻す
history.back();
}
};
// PDFをダウンロードする処理
const downloadHandler = () => {
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// ブラウザバック時にPDFビューアを閉じる処理
const popstateHandler = () => {
if (document.body.contains(overlay)) {
closePdfViewer(true);
}
};
// Escキーが押されたときにPDFビューアを閉じる処理
const escapeHandler = (e) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
closePdfViewer();
}
};
document.addEventListener('keydown', escapeHandler);
window.addEventListener('popstate', popstateHandler);
// キーボードイベントを確実に受け取るためオーバーレイにフォーカスを設定
overlay.setAttribute('tabindex', '-1');
overlay.focus();
});
});
};
// ページ読み込み時の初回リンク処理を実行
processLinks();
// DOM変更を監視してリンクの動的追加に対応
if (!window.__pdfPreviewObserver__) {
const observer = new MutationObserver(processLinks);
observer.observe(document.body, { childList: true, subtree: true });
window.__pdfPreviewObserver__ = observer;
}
return event;
});
})();