diff --git a/script.user.js b/script.user.js index ab226bc..2f1d6a4 100644 --- a/script.user.js +++ b/script.user.js @@ -14,30 +14,23 @@ var mirrorIcon = ''; -if(!GM) { +if (!GM) { alert("This script requires GreaseMonkey!"); return; } -var instance = "https://git.incest.world"; +var config = {} async function api(url, data) { - var token = await GM.getValue("token"); - - if(!token) { - var input = prompt("Enter your git.incest.world access token\nSee: https://git.incest.world/user/settings/applications"); - GM.setValue("token", input); - } - return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "POST", - url: `${instance}/api/v1/${url}`, + url: `${config.instance}/api/v1/${url}`, data: JSON.stringify(data), headers: { "Accept": "application/json", "Content-Type": "application/json", - "Authorization": `token ${token}`, + "Authorization": `token ${config.token}`, }, onload: (response) => { resolve(response); @@ -46,61 +39,210 @@ async function api(url, data) { }); } +function createFooterElement() { + // Create footer + const footer = document.createElement('footer'); + footer.className = 'SelectMenu-footer px-2'; + + // Create button + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'user-lists-menu-action btn-invisible btn btn-block text-left text-normal rounded-1 px-2'; + + // Create SVG + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('aria-hidden', 'true'); + svg.setAttribute('height', '16'); + svg.setAttribute('viewBox', '0 0 16 16'); + svg.setAttribute('version', '1.1'); + svg.setAttribute('width', '16'); + svg.setAttribute('data-view-component', 'true'); + svg.setAttribute('class', 'octicon octicon-plus mr-1'); + + // Create path for SVG + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', 'M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z'); + + // Create text node + const textNode = document.createTextNode('Mirror repository'); + + // Assemble the elements + svg.appendChild(path); + button.appendChild(svg); + button.appendChild(textNode); + footer.appendChild(button); + + return footer; +} + +function createCheckbox(labelText, value) { + const div = document.createElement('div'); + div.className = 'form-checkbox mt-1 mb-0 p-1'; + div.setAttribute('role', 'listitem'); + + const label = document.createElement('label'); + label.className = 'd-flex'; + + const input = document.createElement('input'); + input.type = 'checkbox'; + input.checked = value; + input.className = 'mx-0 js-user-list-menu-item'; + + const outerSpan = document.createElement('span'); + outerSpan.setAttribute('data-view-component', 'true'); + outerSpan.className = 'Truncate ml-2 text-normal f5'; + + const innerSpan = document.createElement('span'); + innerSpan.setAttribute('data-view-component', 'true'); + innerSpan.className = 'Truncate-text'; + innerSpan.textContent = labelText; + + outerSpan.appendChild(innerSpan); + label.appendChild(input); + label.appendChild(outerSpan); + div.appendChild(label); + + return div; +} + +function createInput(placeholder, value) { + const input = document.createElement('input'); + input.className = 'FormControl-input'; + input.placeholder = placeholder ?? ''; + input.value = value ?? ''; + + return input; +} + +function createSettingElement(key, [label, value, editable]) { + if (typeof value === 'boolean') { + const checkbox = createCheckbox(label, value); + checkbox.querySelector('input').disabled = !editable; + checkbox.querySelector('input').name = key; + return checkbox; + } else { + const div = document.createElement('div'); + div.className = "mx-0 mt-2"; + + const labelElement = document.createElement('label'); + labelElement.textContent = label; + div.appendChild(labelElement); + + const input = createInput(label, value); + input.disabled = !editable; + input.name = key; + div.appendChild(input); + + return div; + } +} + (async () => { + config.instance = await GM.getValue("instance"); + if (!config.instance) { + var input = prompt("Enter your Gitea/Forgejo instance (include protocol, no trailing slash)"); + GM.setValue("instance", input); + config.instance = input; + } + + config.token = await GM.getValue("token"); + if (!config.token) { + var input = prompt(`Enter your git access token\nSee: ${config.instance}/user/settings/applications`); + GM.setValue("token", input); + config.token = input; + } + function main() { var container = document.querySelector(".pagehead-actions"); - if(!container) + if (!container) return; var mirrorButton = container.querySelector("#mirror"); - if(mirrorButton != null) + if (mirrorButton != null) return; mirrorButton = container.children[container.children.length - 1].cloneNode(true); mirrorButton.id = "mirror"; var starCount = mirrorButton.querySelector("#repo-stars-counter-star"); - if(starCount) + if (starCount) starCount.parentNode.removeChild(starCount); var dropDown = mirrorButton.querySelector("details"); - dropDown.parentNode.removeChild(dropDown); + dropDown.querySelector("details-menu").removeAttribute("src"); - var btn = mirrorButton.querySelector(".unstarred button"); + var title = dropDown.querySelector(".SelectMenu-title"); + title.textContent = "Mirror Settings"; - var newButton = document.createElement("button"); - newButton.className = btn.className; - newButton.innerHTML = btn.innerHTML; - newButton.querySelector("svg").innerHTML = mirrorIcon; - newButton.querySelector("span").innerText = "Mirror"; - newButton.onclick = async () => { - newButton.disabled = true; + var data = {} - var repo = location.pathname.split("/")[2]; + var address = document.querySelector("meta[name=go-import]").content.split(" ").slice(-1)[0]; + var repo = address.split("/")[4].slice(0, -4); - var data = await api("repos/migrate", { - "clone_addr": document.querySelector("meta[name=go-import]").content.split(" ").slice(-1)[0], + var settings = { + "repo_owner": ["Owner", "mirrors", true], + "repo_name": ["Repository Name", repo, true], + "description": ["Description", "Mirror of " + address, true], + "mirror": ["Mirror", true, true], + "private": ["Private", false, true] + } + + var list = dropDown.querySelector(".SelectMenu-list"); + list.className += " flex-1 overflow-y-auto px-3 py-2 mb-0"; + list.innerHTML = ""; + + Object.entries(settings).forEach(([key, value]) => { + const settingElement = list.appendChild(createSettingElement(key, value)); + settingElement.querySelector("input").addEventListener("input", (ev) => { + var input = ev.target; + data[input.name] = input.type == "checkbox" ? input.checked : input.value; + }); + }); + + var callback = async () => { + mirrorButton.querySelector("button").disabled = true; + footer.querySelector("button").disabled = true; + + var _data = { + "clone_addr": address, "repo_name": repo, "mirror": true, "repo_owner": "mirrors", - "description": "Mirror of " + location.href, + "description": "Mirror of " + address, "private": false, "issues": false, - "pull_requests": false - }); - - var resp = JSON.parse(data.response); - if(resp.message) { - if(resp.message == "The repository with the same name already exists.") { - window.open(`${instance}/mirrors/${repo}`); + "pull_requests": false, + ...data + } + + var res = await api("repos/migrate", _data); + var resp = JSON.parse(res); + + if (resp.message) { + if (resp.message == "The repository with the same name already exists.") { + window.open(`${config.instance}/mirrors/${repo}`); } else alert(resp.message); } else { - window.open(resp.html_url); + window.open(resp.html_url); } }; + const footer = createFooterElement(repo); + list.parentNode.appendChild(footer); + list.parentNode.querySelector("footer button").onclick = callback; + + var btn = mirrorButton.querySelector(".unstarred button"); + + var newButton = btn.cloneNode(true); + newButton.querySelector("svg").innerHTML = mirrorIcon; + newButton.querySelector("span").innerText = "Mirror"; + newButton.onclick = footer.querySelector("button").onclick; + + btn.parentNode.removeChild(btn); mirrorButton.innerHTML = ""; mirrorButton.appendChild(newButton); + mirrorButton.appendChild(dropDown); + container.prepend(mirrorButton); }