├── README.md ├── background.js ├── leetcode-pdf.js ├── logo.png ├── manifest.json ├── popup.html ├── popup.js ├── privacy-policy.html └── vendors ├── buttons.js └── jquery-3.3.1.min.js /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 |

Leet Book

9 | 10 |

11 | 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | 22 | 23 | ## LeetCode Submission Saver 24 | [Github](https://github.com/TheShubham99/leetcode-to-pdf) 25 | 26 | LeetCode Submission Saver is a tool that allows users to save all their accepted submissions from LeetCode to a PDF file for easy reference and offline access. 27 | 28 | ## How to Use 29 | 30 | Click to open following youtube video 31 | [![IMAGE ALT TEXT HERE](https://github.com/TheShubham99/leetcode-to-pdf/assets/20538904/3d16c982-270a-4341-bf64-1896a0520047)](https://www.youtube.com/watch?v=lLwm5kSVyFk) 32 | 33 | (https://www.youtube.com/watch?v=lLwm5kSVyFk) // Video Link 34 | 35 | 1. **Install the extension**: Install the Leet Book Extension on chrome. [Link To Extension](https://chromewebstore.google.com/detail/emdfjmejkmhehmpaggklnmhgloijgnam?hl=en-GB&utm_source=ext_sidebar.) 36 | 37 | 2. **Log in to LeetCode**: Visit the LeetCode website (https://leetcode.com) and log in to your account. 38 | 39 | 3. **Download PDF**: Click on download PDF to save the PDF to your device. 40 | 41 | 4. **Observe the Console**: After pasting the code, wait for a few moments and observe the console for any errors or messages. 42 | 43 | 5. **View the Output**: Once the process is complete, the generated PDF will be available for download. 44 | 45 | ## Credits 46 | 47 | Inspired by [Leetcode Downloader for Submissions](https://github.com/world177/Leetcode-Downloader-for-Submissions) by world177. 48 | 49 | ## Contributing 50 | 51 | Contributions are welcome! If you have any ideas for improvements or feature requests, feel free to open an issue or submit a pull request. 52 | 53 | ## License 54 | 55 | This project is licensed under the [MIT License](LICENSE). 56 | 57 | --- 58 | 59 | **Disclaimer**: This project is not affiliated with LeetCode. Use it responsibly and in compliance with LeetCode's terms of service. 60 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | let process_status = ""; 2 | let button = `Download PDF `; 3 | let processing = false; 4 | 5 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 6 | if (message.type === "getUpdates") { 7 | if (!processing) { 8 | process_status = ""; 9 | button = `Download PDF `; 10 | } 11 | // Process the message and prepare the data to send back 12 | const responseData = { button, process_status, processing }; 13 | 14 | // Send the response back to the content script or popup script 15 | sendResponse(responseData); 16 | } 17 | 18 | if (message.type === "setUpdates") { 19 | if (message.button) { 20 | button = message.button; 21 | } 22 | 23 | if (message.process_status) { 24 | process_status = message.process_status; 25 | } 26 | 27 | if (message.processing == false) { 28 | processing = false; 29 | } else { 30 | processing = true; 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /leetcode-pdf.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | // Credits : 4 | Inspired from - https://github.com/world177/Leetcode-Downloader-for-Submissions 5 | User - https://github.com/world177 6 | */ 7 | 8 | LeetCodeSubmissionDownloader.LEETCODE_SUBMISSIONS_API = 9 | "https://leetcode.com/api/submissions"; 10 | LeetCodeSubmissionDownloader.BASE_PROBLEM_ADDRESS = 11 | "https://leetcode.com/problems/"; 12 | 13 | LeetCodeSubmissionDownloader.INCREASE_LAST_BY = 20; 14 | LeetCodeSubmissionDownloader.WAIT_BETWEEN_REQUESTS = 2500; // milliseconds 15 | LeetCodeSubmissionDownloader.INCREASE_WAIT_BY_ON_ERROR = 2; 16 | 17 | LeetCodeSubmissionDownloader.questionMap = {}; 18 | LeetCodeSubmissionDownloader.last = 0; 19 | LeetCodeSubmissionDownloader.processed = 0; 20 | LeetCodeSubmissionDownloader.waitUsedBetweenRequests = 21 | LeetCodeSubmissionDownloader.WAIT_BETWEEN_REQUESTS; 22 | LeetCodeSubmissionDownloader.MAX_ATTEMPT = 5; 23 | 24 | function sleep(ms) { 25 | return new Promise((resolve) => setTimeout(resolve, ms)); 26 | } 27 | // Function to send a message to the popup script 28 | function sendMessageToPopup(message) { 29 | chrome.runtime.sendMessage({ type: "setUpdates", ...message }); 30 | } 31 | 32 | function fetchUsername() { 33 | return new Promise((resolve, reject) => { 34 | const query = ` 35 | query globalData { 36 | userStatus { 37 | username 38 | } 39 | } 40 | `; 41 | 42 | const body = JSON.stringify({ 43 | query: query, 44 | variables: {}, 45 | operationName: "globalData", 46 | }); 47 | 48 | const xhr = new XMLHttpRequest(); 49 | xhr.open("POST", "https://leetcode.com/graphql/"); 50 | xhr.setRequestHeader("Content-Type", "application/json"); 51 | xhr.onload = function () { 52 | if (xhr.status >= 200 && xhr.status < 300) { 53 | const data = JSON.parse(xhr.responseText); 54 | if (data && data.data && data.data.userStatus) { 55 | const username = data.data.userStatus.username; 56 | resolve(username); 57 | } else { 58 | console.error("Failed to fetch username"); 59 | reject("Failed to fetch username"); 60 | } 61 | } else { 62 | console.error("Failed to fetch username"); 63 | reject("Failed to fetch username"); 64 | } 65 | }; 66 | xhr.onerror = function () { 67 | console.error("Request failed"); 68 | reject("Request failed"); 69 | }; 70 | xhr.send(body); 71 | }); 72 | } 73 | 74 | function fetchQuestionContent(titleSlug) { 75 | return new Promise((resolve, reject) => { 76 | setTimeout(() => { 77 | const query = ` 78 | query questionContent($titleSlug: String!) { 79 | question(titleSlug: $titleSlug) { 80 | content 81 | } 82 | } 83 | `; 84 | 85 | const variables = { 86 | titleSlug: titleSlug, 87 | }; 88 | 89 | const body = JSON.stringify({ 90 | query: query, 91 | variables: variables, 92 | operationName: "questionContent", 93 | }); 94 | 95 | const xhr = new XMLHttpRequest(); 96 | xhr.open("POST", "https://leetcode.com/graphql/"); 97 | xhr.setRequestHeader("Content-Type", "application/json"); 98 | xhr.onload = function () { 99 | if (xhr.status >= 200 && xhr.status < 300) { 100 | const data = JSON.parse(xhr.responseText); 101 | resolve(data.data.question.content); 102 | } else { 103 | console.error("Failed to fetch question content"); 104 | reject("Failed to fetch question content"); 105 | } 106 | }; 107 | xhr.onerror = function () { 108 | console.error("Request failed"); 109 | reject("Request failed"); 110 | }; 111 | xhr.send(body); 112 | }, 50); // 50 milliseconds delay to avoid rate limitting 113 | }); 114 | } 115 | 116 | function highlightCode(language, code) { 117 | let highlightedCode = ""; 118 | const lines = code.split("\n"); 119 | 120 | switch (language) { 121 | case "python": 122 | case "python3": 123 | for (const line of lines) { 124 | const isComment = line.trim().startsWith("#"); 125 | if (isComment) { 126 | highlightedCode += `${line}\n`; 127 | } else { 128 | let highlightedLine = line.replace( 129 | /\b(if|else|elif|while|for|in|def|class|pass|return)\b/g, 130 | "$1" 131 | ); 132 | highlightedCode += `${highlightedLine}\n`; 133 | } 134 | } 135 | break; 136 | case "cpp": 137 | for (const line of lines) { 138 | // Define patterns to highlight C++ keywords and comments 139 | const isComment = 140 | line.trim().startsWith("//") || 141 | line.trim().startsWith("/*") || 142 | line.trim().endsWith("*/"); 143 | 144 | if (isComment) { 145 | highlightedCode += `${line}\n`; 146 | } else { 147 | let highlightedLine = line.replace( 148 | /\b(if|else|while|for|return|break|continue|class|int|double|string|void|float|long|bool|vector|string|char|map|set)\b/g, 149 | "$1" 150 | ); 151 | highlightedCode += `${highlightedLine}\n`; 152 | } 153 | } 154 | break; 155 | default: 156 | // If language is not recognized, simply return the original code 157 | highlightedCode = code; 158 | break; 159 | } 160 | 161 | return highlightedCode; 162 | } 163 | 164 | function generateHTMLContent(username = "") { 165 | let htmlContent = ` 166 | 167 | 168 | 169 | 170 | 171 | LeetCode Submissions 172 | 173 | 253 | 254 | 255 |
256 |

My LeetCode Submissions ${ 257 | username ? `- @${username}` : "" 258 | }

259 |
260 | Download PDF 261 | Follow @TheShubham99 on GitHub 262 | Star on GitHub 263 | View Source Code 264 |
265 |
266 | `; 267 | 268 | // Iterate over each question in the questionMap and add it to the HTML content 269 | for (const [ 270 | title, 271 | { heading, language, description, solution }, 272 | ] of Object.entries(LeetCodeSubmissionDownloader.questionMap)) { 273 | let highlightedSolution = highlightCode( 274 | language, 275 | solution 276 | .replace(/&/g, "&") 277 | .replace(//g, ">") 279 | .replace(/"/g, """) 280 | .replace(/'/g, "'") 281 | ); 282 | 283 | htmlContent += ` 284 |
285 |
286 | ${heading} (link) 287 |
288 |
289 |

Description

290 | ${description} 291 |
292 |
(scroll down for solution)
293 |
294 |
295 |

Solution

296 |
Language: ${language}
297 |
Status: Accepted
298 |
${highlightedSolution}
299 |
300 |
301 | `; 302 | } 303 | 304 | htmlContent += ` 305 | 306 | 307 | `; 308 | 309 | return htmlContent; 310 | } 311 | 312 | // Function to open print dialog for specific HTML content 313 | function printHTML(htmlContent) { 314 | sendMessageToPopup({ 315 | button: `Download PDF `, 316 | processing: false, 317 | }); 318 | // Open the HTML content in a new window 319 | const newWindow = window.open(); 320 | newWindow.document.write(htmlContent); 321 | // Close the document to complete the loading process 322 | newWindow.document.close(); 323 | } 324 | 325 | LeetCodeSubmissionDownloader.pullFromLeetCodeAPI = function () { 326 | if (LeetCodeSubmissionDownloader.MAX_ATTEMPT <= 0) { 327 | sendMessageToPopup({ 328 | process_status: "Error: Something Went Wrong", 329 | button: "Please wait...", 330 | processing: false, 331 | }); 332 | return; 333 | } 334 | try { 335 | let address = new URL( 336 | LeetCodeSubmissionDownloader.LEETCODE_SUBMISSIONS_API + 337 | "?offset=" + 338 | LeetCodeSubmissionDownloader.last + 339 | "&limit=" + 340 | LeetCodeSubmissionDownloader.INCREASE_LAST_BY 341 | ); 342 | 343 | let xmlHttp = new XMLHttpRequest(); 344 | 345 | xmlHttp.onreadystatechange = function () { 346 | if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { 347 | LeetCodeSubmissionDownloader.processAPIResults(xmlHttp.responseText); 348 | } else if (xmlHttp.readyState == 4) { 349 | LeetCodeSubmissionDownloader.handleError(); 350 | } 351 | }; 352 | 353 | xmlHttp.addEventListener("error", function () { 354 | LeetCodeSubmissionDownloader.handleError(); 355 | }); 356 | 357 | xmlHttp.open("GET", address, true); 358 | xmlHttp.send(null); 359 | } catch (e) { 360 | LeetCodeSubmissionDownloader.handleError(); 361 | } 362 | }; 363 | 364 | LeetCodeSubmissionDownloader.handleError = async function () { 365 | LeetCodeSubmissionDownloader.waitUsedBetweenRequests *= 366 | LeetCodeSubmissionDownloader.INCREASE_WAIT_BY_ON_ERROR; 367 | 368 | sendMessageToPopup({ 369 | process_status: "Error: Failed to pull API data", 370 | processing: true, 371 | }); 372 | console.log( 373 | "Error: Failed to pull API data from " + 374 | LeetCodeSubmissionDownloader.LEETCODE_SUBMISSIONS_API + 375 | ". Doubling the wait time between requests to " + 376 | LeetCodeSubmissionDownloader.waitUsedBetweenRequests + 377 | " milliseconds." 378 | ); 379 | 380 | LeetCodeSubmissionDownloader.MAX_ATTEMPT -= 1; 381 | await sleep(LeetCodeSubmissionDownloader.waitUsedBetweenRequests); 382 | 383 | LeetCodeSubmissionDownloader.pullFromLeetCodeAPI(); 384 | }; 385 | 386 | // example question in questionMap 387 | // const question = { 388 | // title: title, 389 | // heading:titleHeading, 390 | // language: submissionsFound[i].lang, 391 | // description: questionContent, 392 | // solution: String(submissionsFound[i].code), 393 | // question_id: submissionsFound[i].question_id, 394 | // }; 395 | 396 | LeetCodeSubmissionDownloader.processAPIResults = async function ( 397 | lastWebResult 398 | ) { 399 | let data = JSON.parse(lastWebResult); 400 | 401 | let submissionsFound = data.submissions_dump; 402 | 403 | LeetCodeSubmissionDownloader.last += submissionsFound.length; 404 | LeetCodeSubmissionDownloader.processed += 0; 405 | 406 | for (let i = 0; i < submissionsFound.length; i++) { 407 | const title = submissionsFound[i].title_slug; 408 | const titleHeading = 409 | submissionsFound[i].question_id + " " + submissionsFound[i].title; 410 | 411 | if ( 412 | title in LeetCodeSubmissionDownloader.questionMap || 413 | submissionsFound[i]?.status_display?.toLowerCase() !== "accepted" 414 | ) { 415 | // if question is already in map 416 | // or 417 | // status is not accepted then continue 418 | continue; 419 | } 420 | 421 | LeetCodeSubmissionDownloader.processed += 1; 422 | 423 | await fetchQuestionContent(title) 424 | .then((questionContent) => { 425 | // Log question content 426 | const question = { 427 | title: title, 428 | heading: titleHeading, 429 | language: submissionsFound[i].lang, 430 | description: questionContent, 431 | solution: String(submissionsFound[i].code), 432 | question_id: submissionsFound[i].question_id, 433 | }; 434 | 435 | LeetCodeSubmissionDownloader.questionMap[title] = question; 436 | }) 437 | .catch((error) => { 438 | console.error("Failed to fetch question content:", error); 439 | }); 440 | } 441 | 442 | if (!data.has_next) { 443 | sendMessageToPopup({ 444 | process_status: 445 | ` Total submissions processed: ` + 446 | LeetCodeSubmissionDownloader.processed + 447 | "", 448 | processing: false, 449 | }); 450 | console.log( 451 | "Total Questions processed: " + LeetCodeSubmissionDownloader.processed 452 | ); 453 | fetchUsername() 454 | .then((username) => { 455 | const htmlContent = generateHTMLContent(username); 456 | printHTML(htmlContent); 457 | }) 458 | .catch((error) => { 459 | console.error("Failed to get username Error:", error); 460 | const htmlContent = generateHTMLContent(""); 461 | printHTML(htmlContent); 462 | }); 463 | } else { 464 | // Send messages to the popup script 465 | sendMessageToPopup({ button: "Loading...", processing: true }); 466 | sendMessageToPopup({ 467 | process_status: 468 | ` 469 |
470 |
471 |
472 | Please wait.. 473 |
Questions saved so far: ` + 474 | LeetCodeSubmissionDownloader.processed + 475 | `
`, 476 | processing: true, 477 | }); 478 | console.log( 479 | "Questions saved so far: " + LeetCodeSubmissionDownloader.processed 480 | ); 481 | console.log("Batch Complete! Hold on, next batch in progress."); 482 | 483 | await sleep(LeetCodeSubmissionDownloader.waitUsedBetweenRequests); 484 | 485 | LeetCodeSubmissionDownloader.pullFromLeetCodeAPI(); 486 | } 487 | }; 488 | 489 | function LeetCodeSubmissionDownloader() { 490 | return this; 491 | } 492 | 493 | LeetCodeSubmissionDownloader.pullFromLeetCodeAPI(); 494 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheShubham99/leetcode-to-pdf/edad806919f0f9b26a1e28d09bcd538a7ad0b168/logo.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "LeetCode PDF Saver", 4 | "author": "Prathamesh Sahasrabhojane", 5 | "version": "1.1", 6 | "description": "Save all accepted submissions from LeetCode to a PDF.", 7 | "permissions": ["scripting"], 8 | "host_permissions": [ 9 | "https://leetcode.com/*" 10 | ], 11 | "action": { 12 | "default_popup": "popup.html", 13 | "default_icon": { 14 | "16": "logo.png", 15 | "48": "logo.png", 16 | "128": "logo.png" 17 | } 18 | }, 19 | "background": { 20 | "service_worker": "background.js" 21 | }, 22 | "content_security_policy": { 23 | "extension_pages": "script-src 'self'; object-src 'self';" 24 | } 25 | } -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LeetBook 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |

LeetBook

23 | 24 |
1. Make sure you are logged in on LeetCode.com
25 |
2. Click on the following button to view and download your accepted 26 | submissions.
27 |
28 |
Do not close the tab.
29 |
Downloading may take upto 10 minutes.
30 |
31 |
33 | Star 36 | on GitHub 37 |
38 |
39 | 42 |
43 |
44 |
45 | 47 | 49 | 50 |
51 | Report a Bug 52 |
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | let button; 2 | let processStatus; 3 | 4 | async function getUpdates(button, processStatus) { 5 | await chrome.runtime.sendMessage({ type: "getUpdates" }, (response) => { 6 | if (!response.processing) { 7 | button.innerHTML = `Download PDF `; 8 | processStatus.innerHTML = ""; 9 | button.disabled = false; 10 | return; 11 | } 12 | if (response?.process_status) { 13 | processStatus.innerHTML = response.process_status; 14 | } 15 | if (response?.button) { 16 | button.innerHTML = response.button; 17 | if (String(response?.button)?.toLowerCase()?.includes("loading")) { 18 | button.disabled = true; 19 | } else { 20 | button.disabled = false; 21 | } 22 | } 23 | }); 24 | } 25 | 26 | document.addEventListener("DOMContentLoaded", async () => { 27 | button = document.getElementById("download-submissions"); 28 | processStatus = document.getElementById("process-status"); 29 | 30 | await getUpdates(button, processStatus); 31 | 32 | if (button) { 33 | button.addEventListener("click", async () => { 34 | const int = setInterval(async () => { 35 | await getUpdates(button, processStatus); 36 | }, 4000); 37 | processStatus.innerText = "Please wait..."; 38 | button.innerHTML = "Loading..."; 39 | button.disabled = true; 40 | const [tab] = await chrome.tabs.query({ 41 | active: true, 42 | currentWindow: true, 43 | }); 44 | 45 | button.disabled = true; 46 | originalContent = button.innerHTML; 47 | button.innerHTML = "Loading..."; 48 | 49 | if (tab.url.includes("leetcode.com")) { 50 | chrome.scripting.executeScript({ 51 | target: { tabId: tab.id }, 52 | files: ["leetcode-pdf.js"], 53 | }); 54 | } else { 55 | button.disabled = false; 56 | button.innerHTML = originalContent; 57 | processStatus.innerHTML = "This extension works only on leetcode.com"; 58 | alert("This extension works only on leetcode.com"); 59 | } 60 | return int; 61 | }); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /privacy-policy.html: -------------------------------------------------------------------------------- 1 | Privacy Policy for LeetCode Submission Saver 2 | Introduction 3 | Your privacy is important to us. This Privacy Policy explains how "LeetCode Submission Saver" (the "Extension") 4 | collects, uses, and protects your information when you use our service. 5 | 6 | Information We Collect 7 | The Extension collects the following information: 8 | 9 | User Submissions: When you use the Extension to download your accepted LeetCode submissions, we temporarily access your 10 | LeetCode account data to fetch the submissions. This data includes your username and accepted solutions. 11 | How We Use the Information 12 | The information we collect is used solely for the purpose of generating a PDF file containing your accepted LeetCode 13 | submissions. Specifically: 14 | 15 | Generate PDF: The data is used to compile your accepted submissions into a PDF file for download. 16 | User Authentication: The Extension verifies that you are logged in to LeetCode to access your submissions. 17 | Data Storage and Security 18 | We prioritize your data security: 19 | 20 | No Data Storage: We do not store any of your data. All processing is done locally on your device, and no data is sent to 21 | external servers. 22 | Secure Access: The Extension accesses LeetCode data through secure API requests directly from your browser. 23 | Permissions 24 | The Extension requires certain permissions to function: 25 | 26 | Active Tab Permission: To interact with the active LeetCode tab where the user is logged in and downloading submissions. 27 | Host Permission: To fetch data from https://leetcode.com/*. 28 | Scripting Permission: To execute scripts necessary for fetching and processing your submissions. 29 | Third-Party Services 30 | The Extension does not use third-party services to process or store your data. 31 | 32 | Changes to This Privacy Policy 33 | We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy 34 | on this page. You are advised to review this Privacy Policy periodically for any changes. 35 | 36 | Contact Us 37 | If you have any questions or concerns about this Privacy Policy, please contact us at: 38 | 39 | Email: ssahasrabhojane@gmail.com 40 | 41 | Consent 42 | By using the Extension, you consent to this Privacy Policy. -------------------------------------------------------------------------------- /vendors/buttons.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * github-buttons v2.28.0 3 | * (c) 2024 なつき 4 | * @license BSD-2-Clause 5 | */ 6 | !(function () { 7 | "use strict"; 8 | var e = window.document, 9 | o = e.location, 10 | t = window.Math, 11 | r = window.HTMLElement, 12 | a = window.XMLHttpRequest, 13 | n = "github-button", 14 | i = "https://buttons.github.io/buttons.html", 15 | c = "github.com", 16 | l = "https://api." + c, 17 | d = a && "prototype" in a && "withCredentials" in a.prototype, 18 | s = 19 | d && 20 | r && 21 | "attachShadow" in r.prototype && 22 | !("prototype" in r.prototype.attachShadow), 23 | u = function (e, o) { 24 | for (var t = 0, r = e.length; t < r; t++) o(e[t]); 25 | }, 26 | h = function (e) { 27 | return function (o, t, r) { 28 | var a = e.createElement(o); 29 | if (null != t) 30 | for (var n in t) { 31 | var i = t[n]; 32 | null != i && (null != a[n] ? (a[n] = i) : a.setAttribute(n, i)); 33 | } 34 | return ( 35 | null != r && 36 | u(r, function (o) { 37 | a.appendChild("string" == typeof o ? e.createTextNode(o) : o); 38 | }), 39 | a 40 | ); 41 | }; 42 | }, 43 | f = h(e), 44 | g = function (e) { 45 | var o; 46 | return function () { 47 | o || ((o = 1), e.apply(this, arguments)); 48 | }; 49 | }, 50 | p = function (e, o) { 51 | return {}.hasOwnProperty.call(e, o); 52 | }, 53 | b = function (e) { 54 | return ("" + e).toLowerCase(); 55 | }, 56 | v = function (e, o, t, r) { 57 | null == o && (o = "&"), 58 | null == t && (t = "="), 59 | null == r && (r = window.decodeURIComponent); 60 | var a = {}; 61 | return ( 62 | u(e.split(o), function (e) { 63 | if ("" !== e) { 64 | var o = e.split(t); 65 | a[r(o[0])] = null != o[1] ? r(o.slice(1).join(t)) : void 0; 66 | } 67 | }), 68 | a 69 | ); 70 | }, 71 | m = function (e, o, t) { 72 | e.addEventListener 73 | ? e.addEventListener(o, t, !1) 74 | : e.attachEvent("on" + o, t); 75 | }, 76 | w = function (e, o, t) { 77 | e.removeEventListener 78 | ? e.removeEventListener(o, t, !1) 79 | : e.detachEvent("on" + o, t); 80 | }, 81 | k = function (e, o, t) { 82 | var r = function () { 83 | return w(e, o, r), t.apply(this, arguments); 84 | }; 85 | m(e, o, r); 86 | }, 87 | x = function (e, o, t) { 88 | if (null != e.readyState) { 89 | var r = "readystatechange", 90 | a = function () { 91 | if (o.test(e.readyState)) 92 | return w(e, r, a), t.apply(this, arguments); 93 | }; 94 | m(e, r, a); 95 | } 96 | }, 97 | y = { 98 | light: 99 | ".btn:focus-visible,.social-count:focus-visible{outline:2px solid #0969da;outline-offset:-2px}.btn{color:#24292f;background-color:#ebf0f4;border-color:#d0d7de;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23f6f8fa'/%3e%3cstop offset='90%25' stop-color='%23ebf0f4'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #f6f8fa, #ebf0f4 90%);background-image:linear-gradient(180deg, #f6f8fa, #ebf0f4 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFF6F8FA', endColorstr='#FFEAEFF3')}:root .btn{filter:none}.btn:hover,.btn:focus{background-color:#e4e9ed;background-position:0 -0.5em;border-color:#d0d7de;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23eef1f4'/%3e%3cstop offset='90%25' stop-color='%23e4e9ed'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #eef1f4, #e4e9ed 90%);background-image:linear-gradient(180deg, #eef1f4, #e4e9ed 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFEEF1F4', endColorstr='#FFE3E8EC')}:root .btn:hover,:root .btn:focus{filter:none}.btn:active{background-color:#e7ebef;border-color:#d0d7de;background-image:none;filter:none}.social-count{color:#24292f;background-color:#fff;border-color:#d0d7de}.social-count:hover,.social-count:focus{color:#0969da}.octicon-heart{color:#bf3989}", 100 | light_high_contrast: 101 | ".btn:focus-visible,.social-count:focus-visible{outline:2px solid #0349b4;outline-offset:-2px}.btn{color:#0e1116;background-color:#e7ecf0;border-color:#20252c;background-image:none;filter:none}.btn:hover,.btn:focus{background-color:#d1d9df;background-position:0 -0.5em;border-color:#20252c;background-image:none;filter:none}.btn:active{background-color:#cfd6dd;border-color:#20252c}.social-count{color:#0e1116;background-color:#fff;border-color:#20252c}.social-count:hover,.social-count:focus{color:#023b95}.octicon-heart{color:#971368}", 102 | dark: ".btn:focus-visible,.social-count:focus-visible{outline:2px solid #1f6feb;outline-offset:-2px}.btn{color:#c9d1d9;background-color:#1a1e23;border-color:#30363d;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%2321262d'/%3e%3cstop offset='90%25' stop-color='%231a1e23'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #21262d, #1a1e23 90%);background-image:linear-gradient(180deg, #21262d, #1a1e23 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF21262D', endColorstr='#FF191D22')}:root .btn{filter:none}.btn:hover,.btn:focus{background-color:#22262c;background-position:0 -0.5em;border-color:#30363d;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23292e36'/%3e%3cstop offset='90%25' stop-color='%2322262c'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #292e36, #22262c 90%);background-image:linear-gradient(180deg, #292e36, #22262c 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF292E36', endColorstr='#FF21252B')}:root .btn:hover,:root .btn:focus{filter:none}.btn:active{background-color:#31363e;border-color:#30363d;background-image:none;filter:none}.social-count{color:#c9d1d9;background-color:#0d1117;border-color:#30363d}.social-count:hover,.social-count:focus{color:#388bfd}.octicon-heart{color:#db61a2}", 103 | dark_dimmed: 104 | ".btn:focus-visible,.social-count:focus-visible{outline:2px solid #316dca;outline-offset:-2px}.btn{color:#adbac7;background-color:#30363d;border-color:#444c56;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23373e47'/%3e%3cstop offset='90%25' stop-color='%2330363d'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #373e47, #30363d 90%);background-image:linear-gradient(180deg, #373e47, #30363d 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF373E47', endColorstr='#FF2F353C')}:root .btn{filter:none}.btn:hover,.btn:focus{background-color:#363c44;background-position:0 -0.5em;border-color:#444c56;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%233d444e'/%3e%3cstop offset='90%25' stop-color='%23363c44'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");background-image:-moz-linear-gradient(top, #3d444e, #363c44 90%);background-image:linear-gradient(180deg, #3d444e, #363c44 90%);filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF3D444E', endColorstr='#FF353B43')}:root .btn:hover,:root .btn:focus{filter:none}.btn:active{background-color:#434a54;border-color:#444c56;background-image:none;filter:none}.social-count{color:#adbac7;background-color:#22272e;border-color:#444c56}.social-count:hover,.social-count:focus{color:#4184e4}.octicon-heart{color:#c96198}", 105 | dark_high_contrast: 106 | ".btn:focus-visible,.social-count:focus-visible{outline:2px solid #409eff;outline-offset:-2px}.btn{color:#f0f3f6;background-color:#3d424d;border-color:#7a828e;background-image:none;filter:none}.btn:hover,.btn:focus{background-color:#414751;background-position:0 -0.5em;border-color:#7a828e;background-image:none;filter:none}.btn:active{background-color:#555c67;border-color:#7a828e}.social-count{color:#f0f3f6;background-color:#0a0c10;border-color:#7a828e}.social-count:hover,.social-count:focus{color:#5cacff}.octicon-heart{color:#f87cbd}", 107 | }, 108 | C = function (e, o) { 109 | return ( 110 | "@media(prefers-color-scheme:" + e + "){" + y[p(y, o) ? o : e] + "}" 111 | ); 112 | }, 113 | M = { 114 | "comment-discussion": { 115 | heights: { 116 | 16: { 117 | width: 16, 118 | path: '', 119 | }, 120 | }, 121 | }, 122 | download: { 123 | heights: { 124 | 16: { 125 | width: 16, 126 | path: '', 127 | }, 128 | }, 129 | }, 130 | eye: { 131 | heights: { 132 | 16: { 133 | width: 16, 134 | path: '', 135 | }, 136 | }, 137 | }, 138 | heart: { 139 | heights: { 140 | 16: { 141 | width: 16, 142 | path: '', 143 | }, 144 | }, 145 | }, 146 | "issue-opened": { 147 | heights: { 148 | 16: { 149 | width: 16, 150 | path: '', 151 | }, 152 | }, 153 | }, 154 | "mark-github": { 155 | heights: { 156 | 16: { 157 | width: 16, 158 | path: '', 159 | }, 160 | }, 161 | }, 162 | package: { 163 | heights: { 164 | 16: { 165 | width: 16, 166 | path: '', 167 | }, 168 | }, 169 | }, 170 | play: { 171 | heights: { 172 | 16: { 173 | width: 16, 174 | path: '', 175 | }, 176 | }, 177 | }, 178 | "repo-forked": { 179 | heights: { 180 | 16: { 181 | width: 16, 182 | path: '', 183 | }, 184 | }, 185 | }, 186 | "repo-template": { 187 | heights: { 188 | 16: { 189 | width: 16, 190 | path: '', 191 | }, 192 | }, 193 | }, 194 | star: { 195 | heights: { 196 | 16: { 197 | width: 16, 198 | path: '', 199 | }, 200 | }, 201 | }, 202 | }, 203 | Z = function (e, o) { 204 | (e = b(e).replace(/^octicon-/, "")), p(M, e) || (e = "mark-github"); 205 | var t = o >= 24 && 24 in M[e].heights ? 24 : 16, 206 | r = M[e].heights[t]; 207 | return ( 208 | '" 221 | ); 222 | }, 223 | A = {}, 224 | F = function (e, o) { 225 | var t = A[e] || (A[e] = []); 226 | if (!(t.push(o) > 1)) { 227 | var r = g(function () { 228 | for (delete A[e]; (o = t.shift()); ) o.apply(null, arguments); 229 | }); 230 | if (d) { 231 | var n = new a(); 232 | m(n, "abort", r), 233 | m(n, "error", r), 234 | m(n, "load", function () { 235 | var e; 236 | try { 237 | e = JSON.parse(this.responseText); 238 | } catch (e) { 239 | return void r(e); 240 | } 241 | r(200 !== this.status, e); 242 | }), 243 | n.open("GET", e), 244 | n.send(); 245 | } else { 246 | var i = this || window; 247 | i._ = function (e) { 248 | (i._ = null), r(200 !== e.meta.status, e.data); 249 | }; 250 | var c = h(i.document)("script", { 251 | async: !0, 252 | src: e + (-1 !== e.indexOf("?") ? "&" : "?") + "callback=_", 253 | }), 254 | l = function () { 255 | i._ && i._({ meta: {} }); 256 | }; 257 | m(c, "load", l), 258 | m(c, "error", l), 259 | x(c, /de|m/, l), 260 | i.document.getElementsByTagName("head")[0].appendChild(c); 261 | } 262 | } 263 | }, 264 | E = function (e, o, t) { 265 | var r = h(e.ownerDocument), 266 | a = e.appendChild(r("style", { type: "text/css" })), 267 | n = 268 | "body{margin:0}a{text-decoration:none;outline:0}.widget{display:inline-block;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;font-size:0;line-height:0;white-space:nowrap}.btn,.social-count{position:relative;display:inline-block;display:inline-flex;height:14px;padding:2px 5px;font-size:11px;font-weight:600;line-height:14px;vertical-align:bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-repeat:repeat-x;background-position:-1px -1px;background-size:110% 110%;border:1px solid}.btn{border-radius:.25em}.btn:not(:last-child){border-radius:.25em 0 0 .25em}.social-count{border-left:0;border-radius:0 .25em .25em 0}.widget-lg .btn,.widget-lg .social-count{height:16px;padding:5px 10px;font-size:12px;line-height:16px}.octicon{display:inline-block;vertical-align:text-top;fill:currentColor;overflow:visible}" + 269 | (function (e) { 270 | if (null == e) return y.light; 271 | if (p(y, e)) return y[e]; 272 | var o = v(e, ";", ":", function (e) { 273 | return e.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g, ""); 274 | }); 275 | return ( 276 | y[p(y, o["no-preference"]) ? o["no-preference"] : "light"] + 277 | C("light", o.light) + 278 | C("dark", o.dark) 279 | ); 280 | })(o["data-color-scheme"]); 281 | a.styleSheet 282 | ? (a.styleSheet.cssText = n) 283 | : a.appendChild(e.ownerDocument.createTextNode(n)); 284 | var i = "large" === b(o["data-size"]), 285 | d = r( 286 | "a", 287 | { 288 | className: "btn", 289 | href: o.href, 290 | rel: "noopener", 291 | target: "_blank", 292 | title: o.title || void 0, 293 | "aria-label": o["aria-label"] || void 0, 294 | innerHTML: Z(o["data-icon"], i ? 16 : 14) + " ", 295 | }, 296 | [r("span", {}, [o["data-text"] || ""])] 297 | ), 298 | s = e.appendChild( 299 | r("div", { className: "widget" + (i ? " widget-lg" : "") }, [d]) 300 | ), 301 | u = d.hostname.replace(/\.$/, ""); 302 | if (("." + u).substring(u.length - 10) !== "." + c) 303 | return d.removeAttribute("href"), void t(s); 304 | var f = (" /" + d.pathname).split(/\/+/); 305 | if ( 306 | ((((u === c || u === "gist." + c) && "archive" === f[3]) || 307 | (u === c && 308 | "releases" === f[3] && 309 | ("download" === f[4] || 310 | ("latest" === f[4] && "download" === f[5]))) || 311 | u === "codeload." + c) && 312 | (d.target = "_top"), 313 | "true" === b(o["data-show-count"]) && 314 | u === c && 315 | "marketplace" !== f[1] && 316 | "sponsors" !== f[1] && 317 | "orgs" !== f[1] && 318 | "users" !== f[1] && 319 | "-" !== f[1]) 320 | ) { 321 | var g, m; 322 | if (!f[2] && f[1]) (m = "followers"), (g = "?tab=followers"); 323 | else if (!f[3] && f[2]) (m = "stargazers_count"), (g = "/stargazers"); 324 | else if (f[4] || "subscription" !== f[3]) 325 | if (f[4] || "fork" !== f[3]) { 326 | if ("issues" !== f[3]) return void t(s); 327 | (m = "open_issues_count"), (g = "/issues"); 328 | } else (m = "forks_count"), (g = "/forks"); 329 | else (m = "subscribers_count"), (g = "/watchers"); 330 | var w = f[2] ? "/repos/" + f[1] + "/" + f[2] : "/users/" + f[1]; 331 | F.call(this, l + w, function (e, o) { 332 | if (!e) { 333 | var a = o[m]; 334 | s.appendChild( 335 | r( 336 | "a", 337 | { 338 | className: "social-count", 339 | href: o.html_url + g, 340 | rel: "noopener", 341 | target: "_blank", 342 | "aria-label": 343 | a + 344 | " " + 345 | m 346 | .replace(/_count$/, "") 347 | .replace("_", " ") 348 | .slice(0, a < 2 ? -1 : void 0) + 349 | " on GitHub", 350 | }, 351 | [("" + a).replace(/\B(?=(\d{3})+(?!\d))/g, ",")] 352 | ) 353 | ); 354 | } 355 | t(s); 356 | }); 357 | } else t(s); 358 | }, 359 | L = window.devicePixelRatio || 1, 360 | _ = function (e) { 361 | return (L > 1 ? t.ceil((t.round(e * L) / L) * 2) / 2 : t.ceil(e)) || 0; 362 | }, 363 | G = function (e, o) { 364 | (e.style.width = o[0] + "px"), (e.style.height = o[1] + "px"); 365 | }, 366 | T = function (o, r) { 367 | if (null != o && null != r) 368 | if ( 369 | (o.getAttribute && 370 | (o = (function (e) { 371 | var o = { 372 | href: e.href, 373 | title: e.title, 374 | "aria-label": e.getAttribute("aria-label"), 375 | }; 376 | return ( 377 | u( 378 | ["icon", "color-scheme", "text", "size", "show-count"], 379 | function (t) { 380 | var r = "data-" + t; 381 | o[r] = e.getAttribute(r); 382 | } 383 | ), 384 | null == o["data-text"] && 385 | (o["data-text"] = e.textContent || e.innerText), 386 | o 387 | ); 388 | })(o)), 389 | s) 390 | ) { 391 | var a = f("span"); 392 | E(a.attachShadow({ mode: "closed" }), o, function () { 393 | r(a); 394 | }); 395 | } else { 396 | var n = f("iframe", { 397 | src: "javascript:0", 398 | title: o.title || void 0, 399 | allowtransparency: !0, 400 | scrolling: "no", 401 | frameBorder: 0, 402 | }); 403 | G(n, [0, 0]), (n.style.border = "none"); 404 | var c = function () { 405 | var a, 406 | l = n.contentWindow; 407 | try { 408 | a = l.document.body; 409 | } catch (o) { 410 | return void e.body.appendChild(n.parentNode.removeChild(n)); 411 | } 412 | w(n, "load", c), 413 | E.call(l, a, o, function (e) { 414 | var a = (function (e) { 415 | var o = e.offsetWidth, 416 | r = e.offsetHeight; 417 | if (e.getBoundingClientRect) { 418 | var a = e.getBoundingClientRect(); 419 | (o = t.max(o, _(a.width))), (r = t.max(r, _(a.height))); 420 | } 421 | return [o, r]; 422 | })(e); 423 | n.parentNode.removeChild(n), 424 | k(n, "load", function () { 425 | G(n, a); 426 | }), 427 | (n.src = 428 | i + 429 | "#" + 430 | (n.name = (function (e, o, t, r) { 431 | null == o && (o = "&"), 432 | null == t && (t = "="), 433 | null == r && (r = window.encodeURIComponent); 434 | var a = []; 435 | for (var n in e) { 436 | var i = e[n]; 437 | null != i && a.push(r(n) + t + r(i)); 438 | } 439 | return a.join(o); 440 | })(o))), 441 | r(n); 442 | }); 443 | }; 444 | m(n, "load", c), e.body.appendChild(n); 445 | } 446 | }; 447 | o.protocol + "//" + o.host + o.pathname === i 448 | ? E(e.body, v(window.name || o.hash.replace(/^#/, "")), function () {}) 449 | : (function (o) { 450 | if ( 451 | "complete" === e.readyState || 452 | ("loading" !== e.readyState && !e.documentElement.doScroll) 453 | ) 454 | setTimeout(o); 455 | else if (e.addEventListener) { 456 | var t = g(o); 457 | k(e, "DOMContentLoaded", t), k(window, "load", t); 458 | } else x(e, /m/, o); 459 | })(function () { 460 | var o, 461 | t = e.querySelectorAll 462 | ? e.querySelectorAll("a." + n) 463 | : ((o = []), 464 | u(e.getElementsByTagName("a"), function (e) { 465 | -1 !== 466 | (" " + e.className + " ") 467 | .replace(/[ \t\n\f\r]+/g, " ") 468 | .indexOf(" " + n + " ") && o.push(e); 469 | }), 470 | o); 471 | u(t, function (e) { 472 | T(e, function (o) { 473 | e.parentNode.replaceChild(o, e); 474 | }); 475 | }); 476 | }); 477 | })(); 478 | --------------------------------------------------------------------------------