Add hf-clone-with-local-dir.user.js
This commit is contained in:
commit
a5c8dfd47b
1 changed files with 125 additions and 0 deletions
125
hf-clone-with-local-dir.user.js
Normal file
125
hf-clone-with-local-dir.user.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// ==UserScript==
|
||||
// @name HF Clone Modal - Add hf download with --local-dir
|
||||
// @namespace https://kyu.sh/
|
||||
// @version 1.0
|
||||
// @description Hugging Face clone repository 모달에 --local-dir 포함 hf download 명령어 + 복사 버튼 추가
|
||||
// @match https://huggingface.co/*
|
||||
// @grant none
|
||||
// @run-at document-idle
|
||||
// ==/UserScript==
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const PROCESSED = 'data-hf-localdir-added';
|
||||
|
||||
function makeLocalDir(repoId) {
|
||||
// lcw99/wikipedia-korean-20240501 -> lcw99-wikipedia-korean-20240501
|
||||
return repoId.replace(/\//g, '-');
|
||||
}
|
||||
|
||||
function buildCopyButton(getText) {
|
||||
// 기존 복사 버튼 마크업을 모방
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className =
|
||||
'z-1 ml-4 mt-4 sm:mr-6 sm:absolute sm:top-0 sm:right-0 focus:outline-hidden inline-flex cursor-pointer items-center text-sm bg-white shadow-xs rounded-md border px-2 py-1 text-gray-600';
|
||||
btn.title = 'Copy snippet to clipboard';
|
||||
btn.innerHTML =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z"></path><rect fill="none" width="32" height="32"></rect></svg>';
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
const text = getText();
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
const orig = btn.style.color;
|
||||
btn.style.color = '#16a34a';
|
||||
setTimeout(() => (btn.style.color = orig), 1000);
|
||||
});
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
function processModal(modal) {
|
||||
const pres = modal.querySelectorAll('pre');
|
||||
for (const pre of pres) {
|
||||
const text = pre.textContent || '';
|
||||
const m = text.match(
|
||||
/hf\s+download\s+(\S+)(?:\s+--repo-type=(\S+))?/
|
||||
);
|
||||
if (!m) continue;
|
||||
|
||||
// 이미 --local-dir 이 들어있으면 skip
|
||||
if (text.includes('--local-dir')) continue;
|
||||
|
||||
const repoId = m[1];
|
||||
const repoType = m[2]; // undefined 가능
|
||||
const localDir = makeLocalDir(repoId);
|
||||
const repoTypeFlag = repoType ? ` --repo-type=${repoType}` : '';
|
||||
const newCmd =
|
||||
`hf download ${repoId}${repoTypeFlag} --local-dir="${localDir}"`;
|
||||
|
||||
const container = pre.closest('.relative.border-t') || pre.parentElement;
|
||||
if (!container || container.hasAttribute(PROCESSED)) continue;
|
||||
container.setAttribute(PROCESSED, '1');
|
||||
|
||||
const block = document.createElement('div');
|
||||
block.className = 'relative border-t border-gray-100';
|
||||
block.setAttribute(PROCESSED, '1');
|
||||
|
||||
const copyBtn = buildCopyButton(() => newCmd);
|
||||
|
||||
const inner = document.createElement('div');
|
||||
inner.className = 'overflow-x-auto pl-4 pb-6 sm:pl-6';
|
||||
|
||||
const codeWrap = document.createElement('div');
|
||||
codeWrap.setAttribute('translate', 'no');
|
||||
codeWrap.className =
|
||||
'inline-block text-sm text-gray-700 pt-5 mr-4 sm:mr-6';
|
||||
|
||||
const comment = document.createElement('pre');
|
||||
comment.className = 'text-gray-400';
|
||||
comment.textContent = '# Download into a named local directory';
|
||||
|
||||
const cmdPre = document.createElement('pre');
|
||||
cmdPre.textContent = newCmd;
|
||||
|
||||
codeWrap.appendChild(comment);
|
||||
codeWrap.appendChild(cmdPre);
|
||||
inner.appendChild(codeWrap);
|
||||
block.appendChild(copyBtn);
|
||||
block.appendChild(inner);
|
||||
|
||||
container.parentNode.insertBefore(block, container.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
function scan() {
|
||||
document.querySelectorAll('dialog[open]').forEach((dialog) => {
|
||||
const h3 = dialog.querySelector('h3');
|
||||
if (h3 && /clone this/i.test(h3.textContent)) {
|
||||
processModal(dialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 모달은 동적으로 열리므로 MutationObserver 로 감시
|
||||
const observer = new MutationObserver(() => {
|
||||
// 약간의 디바운스
|
||||
clearTimeout(observer._t);
|
||||
observer._t = setTimeout(scan, 50);
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
scan();
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue