"Ship It" GIF button for Github Review

Adds a button to Github to add "Let's ship it!" GIFs when reviewing PRs

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         "Ship It" GIF button for Github Review
// @namespace    happyviking
// @version      1.7.0
// @grant        none
// @license      MIT
// @description  Adds a button to Github to add "Let's ship it!" GIFs when reviewing PRs
// @author       HappyViking
// @grant        none
// @match        https://github.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/tsparticles.confetti.bundle.min.js
// @require      https://unpkg.com/[email protected]/index.js
// ==/UserScript==

const delay = (t) => new Promise((r) => setTimeout(r, t))

const randomInRange = (min, max) => {
  return Math.random() * (max - min) + min;
}

const main = () => {
  attemptButtonSetup()
}

const attemptGetPRReviewSection = () => {
  const feedbackModal = document.getElementById("review-changes-modal")
  if (!feedbackModal) return null;
  const buttonPanelQuery = feedbackModal.getElementsByClassName("Overlay-footer Overlay-footer--alignEnd")
  if (buttonPanelQuery.length != 2) return null;
  return buttonPanel = buttonPanelQuery[1]
}

const attemptGetNewCommentSection = () => {
  const commentFormSection = document.getElementById("partial-new-comment-form-actions")
  if (!commentFormSection) return null;
  const sampleButton = commentFormSection.querySelector("button")
  if (!sampleButton) return null;
  return sampleButton.parentElement.parentElement
}

const attemptButtonSetup = () => {

  onClickTarget = ""
  buttonID = ""
  buttonParent = null

  if (!document.getElementById("shipitbuttonpr")) {
    buttonParent = attemptGetPRReviewSection()
    if (buttonParent) {
      onClickTarget = "#pull_request_review_body"
      buttonID = "shipitbuttonpr"
    }
  }

  if (!buttonParent && !document.getElementById("shipitbuttonissue")) {
    buttonParent = attemptGetNewCommentSection()
    if (buttonParent) {
      onClickTarget = "#new_comment_field"
      buttonID = "shipitbuttonissue"
    }
  }

  if (!buttonParent) return

  //Have to make it a div cuz some forms in Github have all buttons perform automatic logic, which I don't want
  const newButton = document.createElement("div")
  newButton.id = buttonID
  buttonParent.prepend(newButton)
  //Copying from the existing "submit" button
  //But if you want you can also look into more styles from:
  //https://github.githubassets.com/assets/primer-8f43f7721dc7.css
  //though I think the suffix to "primer" might change by the time you read this
  newButton.classList = "Button--primary Button--small Button float-left mr-1"
  const buttonContentHolder = document.createElement("span")
  buttonContentHolder.className = "Button-content"
  newButton.append(buttonContentHolder)
  const buttonLabel = document.createElement("span")
  buttonLabel.className = "Button-label"
  buttonContentHolder.append(buttonLabel)
  buttonLabel.innerHTML = "Ship that shit"

  const theme = window.getComputedStyle(newButton).getPropertyValue("color-scheme"); //Cant just access via "style" because it's passed down to the button; it's not inline
  if (theme == "light") {
    newButton.style.backgroundImage = "linear-gradient(319deg, rgba(255,126,1,1) 8%, rgba(229,110,21,1) 40%, rgba(179,52,4,1) 81%)"
  } else {
    newButton.style.backgroundImage = "linear-gradient(0deg, rgba(212,74,38,1) 0%, rgba(254,128,13,1) 100%)"
  }

  const template = document.createElement('template'); //<template /> is specifically meant for string->html logic
  //Taken fron https://tabler-icons.io/i/sailboat and slightly modified (no color information so github will take care of that)
  template.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
  <path d="M2 20a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1"></path>
  <path d="M4 18l-1 -3h18l-1 3"></path>
  <path d="M11 12h7l-7 -9v9"></path>
  <path d="M8 7l-2 5"></path>
  </svg>`

  const buttonIcon = template.content.firstChild;
  buttonIcon.className = "Button--visual"
  newButton.append(buttonIcon)

  newButton.addEventListener("click", (event) => {
    const textarea = document.querySelector(onClickTarget)
    textarea.value += `\n\n<img src="https://i.shipit.today" height=100/>\n<sup>Let's ship it! <a href="https://shipit.today/">Img source.<a/></sup>`
    confetti({
      angle: randomInRange(55, 125),
      spread: randomInRange(50, 70),
      particleCount: randomInRange(50, 100),
      position: { x: (event.clientX / window.innerWidth) * 100, y: (event.clientY / window.innerHeight) * 100},
      shapes: ["circle", "square", "line", "spiral", "star"],
    });
  })
}

attemptButtonSetup()
document.addEventListener("soft-nav:end", attemptButtonSetup); 
document.addEventListener("navigation:end", attemptButtonSetup);