generated at
PDFをGyazoにあげてScrapboxに取り込むUserScript
2022-12-01 16:26:41 deprecated
今はPDFをGyazoにuploadするUserScriptにあるコードを使っている

hr

2021-01-22 17:16:19 後ほど、要素要素に分解するtakker
moduleに分解して、使いやすくする
done隠し <input> を作ってfileを取得する
Promiseで包むと使いやすそう
pdf.jsにfileを渡して、画像を生成する
done画像をGyazoに上げる
認証なしと認証ありとの2パターンを使いたい
認証なしで取得したURLを fetch すれば永続化できるので、認証ありは必要ない
個人的には、PDF1ページにScrapbox1ページを分けたい
doingprogress areaはShadow DOMの練習で作ってあるやつを使う
進捗状況を出せるように特化させてもいいかも
promise-parallel-throttleは多分必要ない
自前で一度にfetchする数を制限すればいいだけ
18:27:16 実装した

2021-01-24 18:43:47 /villagepump/Scrapboxを用いたオンラインノートの学習記録と学習成果の分析をuploadしてみたが、英文フォントが消えてしまった
なんでだろう?
ScanSnapの結果なら問題なさそう
ただの画像だし

script.js
/* MIT License Copyright (c) 2020 ci7lus */ import {installCDN} from '/api/code/takker/scrapbox-install-CDN/script.js'; import {insertText} from '/api/code/takker/scrapbox-insert-text/script.js'; import '/api/code/takker/PDFをGyazoにあげてScrapboxに取り込むUserScript/progressBar.js'; import {getLocalFiles} from '/api/code/takker/簡単にfileをbrowserに取り込むscript/script.js'; (async () => { await installCDN({ id: 'pdfMinJs-for-scrapbox', src: '//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.min.js'}); await installCDN({ id: 'pdfWorkerMinJs-for-scrapbox', src: '//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.worker.min.js'}); scrapbox.PageMenu.addMenu({ title: 'upload-pdf', image: 'https://i.gyazo.com/7057219f5b20ca8afd122945b72453d3.png', onClick: async () => { const file = await getLocalFiles({accept: 'application/pdf,.pdf'}); if (!file) return; const filename = file.name; console.log(file); // 進捗表示エリア const progressArea = document.createElement('progress-area'); document.body.appendChild(progressArea); try { // cors制限のためcmapsはstorage.googleapis.comに上げたものを用いる progressArea.message = 'Loading pdf file...'; const pdf = await pdfjsLib.getDocument({ data: await toArrayBuffer(file),
ここ本当は自分で上げたcmapsを使わないとだめそう
他人のを勝手に使うのはよくない
scrapbox.ioにuploadしたやつなら使えるのかな?
directoryでuploadする必要があるから無理そう
cmapsは必要っぽい?
storage.google.apiを使用しないでcmapsを使う方法は、これがヒントになりそう
同じドメインにdirectoryをuploadしているだけ
script.js
cMapUrl: 'https://storage.googleapis.com/chrono-lexica/ci7lus-assets/pdfjs/cmaps/', cMapPacked: true, }).promise; const metadata = await pdf.getMetadata(); console.log(metadata); const isBigImage = window.pdfscrap_big ?? true; progressArea.message = 'pdf file loaded. Uploading to gyazo...'; let progress = 0; const updateProgressText = () => { progressArea.message = `Uploading... (${progress++}/${ pdf.numPages })`; }; const uploadPage = async (page) => { const dataURI = await pdf2imageDataURL(await pdf.getPage(page)); const {get_image_url} = await uploadToGyazoEasily({ dataURI, title: file.name, clientId: 'fd84cef882b9b51d8de3365cd28c86bc2cc8a1646ef05dbc641ca49278f9d0d6',});
前ページの画像を取得するまで待つ
これ必要なのかな?
script.js
while (true) { // 登録順序を保証する if (page - 1 <= progress) break; await sleep(100); }
画像URLを永続化する
script.js
const getImage = await fetch(get_image_url, { mode: 'cors', credentials: 'include', }); updateProgressText(); console.log(`page${page} -> ${getImage.url}`); return getImage.url; }; const pages = await throttle( [...Array(pdf.numPages).keys()] .map(i => i + 1) .map(page => uploadPage(page)), {maxInProgress: 5}, ); progressArea.message = 'done'; const urls = pages.map((url) => isBigImage ? `[[${url}]]` : `[${url}]`); urls.unshift(file.name); insertText({text: urls.join('\n') + '\n'}); } catch (e) { console.log('failed', file, e); alert(`failed to load: ${filename}`); } finally { document.body.removeChild(progressArea); } }, }); })();

BlobArrayBufferに変換する
Blob.arrayBuffer()もあるが、Firefox Androidで使用できないのでpolyfillを作っておく
script.js
const toArrayBuffer = (blob) => blob.arrayBuffer?.() ?? new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = () => { resolve(reader.result); }; reader.readAsArrayBuffer(blob); });

PDFの1ページを画像に変換する
script.js
async function pdf2imageDataURL(pdfPage, {type = 'image/png'} = {}) { const viewport = pdfPage.getViewport({ scale: window.devicePixelRatio ?? 1.5, }); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const renderContext = {canvasContext: ctx, viewport: viewport}; canvas.height = viewport.height; canvas.width = viewport.width; await pdfPage.render(renderContext).promise; return canvas.toDataURL(type); }

待つ
script.js
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));

upload.gyazo.com/api/upload/easy_authを使って画像をuploadする
この後なるべく早く get_image_url をfetchする必要がある
script.js
async function uploadToGyazoEasily({dataURI, clientId, title}) { const formData = new FormData(); formData.append('image_url', dataURI); formData.append('client_id', clientId); formData.append('referer_url', location.href); formData.append('title', title); const easyAuth = await fetch( 'https://upload.gyazo.com/api/upload/easy_auth', { method: 'POST', mode: 'cors', credentials: 'include', body: formData, }, ); return await easyAuth.json(); }

一定数だけ同時にPromiseを実行する
script.js
async function throttle(promises, {maxInProgress = undefined} = {}) { if (!maxInProgress || maxInProgress < 0) return await Promise.all(promises); let progressList = promises.map(_ => false); const promiseList = promises.map((promise, i) => {return {index: i, promise};}); const result = []; await Promise.all([...Array(maxInProgress).keys()] .map(async index => { do { progressList[index] = true; result.push(await promises[index]); index = progressList.findIndex(state => !state); } while(index !== -1) })); return result; }

Progress bar
Progress barというより、ただのmessage windowだな
progressBar.js
customElements.define('progress-area', class extends HTMLElement { connectedCallback() { if (this.rendered) return; this.render(); this.rendered = true; } render() { const shadow = this.shadowRoot ?? this.attachShadow({mode: 'open'}); shadow.innerHTML = ` <style> div { position: fixed; top: 0; right: 0; margin: 1rem; padding: 1rem; background: #FFF; color: 000; z-index: 999999; } </style> <div> ${this.getAttribute('message')} </div>`; } get message() { this.getAttribute('message'); } set message(message) { this.setAttribute('message', message); } static get observedAttributes() { return ['message']; } attributeChangedCallback(name, oldValue, newValue) { this.render(); } });

#2022-12-01 16:27:02
#2022-02-02 10:10:13
#2021-02-05 22:24:10
#2021-01-24 00:55:51
#2021-01-22 17:16:31