Add hf-clone-with-local-dir.user.js

This commit is contained in:
Kyush 2026-06-17 02:25:21 +00:00
commit a5c8dfd47b

View 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();
})();