diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..01ee8a5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+# System files
+# ------------
+
+Icon
+.DS_Store
+.dropbox
+.git
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6f51fa9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Andy Braren
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2716ec8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# GitHub PR Image Inserter
+
+A bookmarklet that inserts a pull request's images below the Markdown-formatted lines that refer to them.
+
+Visit this site to install the bookmarklet.
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..d55c6c9
--- /dev/null
+++ b/script.js
@@ -0,0 +1,129 @@
+// GitHub PR Review Image Inserter
+// v1.0
+//
+// Terribly-written but functional JS that inserts images directly below
+// the relevant lines within the Markdown file of the "Conversation" and "Files changed" page.
+//
+// Made with <3 for all the designers out there who have to struggle through
+// GitHub's code-oriented UI while reviewing image-oriented design work.
+
+if (isFilesChangedPage()) {
+ insertFilesChangedImages();
+} else {
+ insertConversationImages();
+}
+
+function isFilesChangedPage() {
+ var currentPath = window.location.pathname; // Get the current page URL
+ if (currentPath.split('/').pop() == "files") { // If the URL ends in "files"
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function insertFilesChangedImages() {
+
+ // Loop through every "View file" link
+ var aTags = document.getElementsByTagName("a");
+ var searchText = "View file";
+ for (var i = 0; i < aTags.length; i++) {
+ if (aTags[i].textContent.includes(searchText)) {
+
+ // Get the "View file" link itself
+ var link = aTags[i].href;
+
+ // Get the filename from that link
+ var filename = link.substr(link.lastIndexOf('/') + 1);
+
+ // Get the folder path from that link, like "org/project-directory"
+ var folderPath = link.match(/.com\/(.*)\/blob/)[1];
+
+ // Get everything after the blob part of that link (commit ID, subdirectory and image name)
+ var lastPart = link.split('blob/').pop();
+
+ // Loop through every line of the Markdown file on the page searching for
+ // the same filename with a ")" at the end (Markdown-formatted image)
+ var spanTags = document.getElementsByClassName("blob-code-inner");
+ var spanSearchText = filename + ")";
+ for (var g = 0; g < spanTags.length; g++) {
+ if (spanTags[g].innerHTML.includes(spanSearchText)) {
+
+ // Create the image URL and HTML
+ var newImageURL = "/" + folderPath + "/raw/" + lastPart;
+ var newImage = document.createElement('div');
+ newImage.setAttribute("class", "inserted-image");
+ newImage.innerHTML = '';
+
+ // Append that image HTML right after the appropriate span
+ if (spanTags[g].nextSibling) {
+ if (spanTags[g].nextSibling.classList) {
+ if (spanTags[g].nextSibling.classList.contains("inserted-image") === -1) {
+ spanTags[g].parentNode.insertBefore(newImage, spanTags[g].nextSibling);
+ }
+ } else {
+ spanTags[g].parentNode.insertBefore(newImage, spanTags[g].nextSibling);
+ }
+ } else {
+ spanTags[g].parentNode.insertBefore(newImage, spanTags[g].nextSibling);
+ }
+ }
+ }
+ }
+ }
+ return null;
+}
+
+function insertConversationImages() {
+
+ // Get the path of the repository
+ var repoPath = window.location.pathname.match(/(.*)pull/)[1];
+
+ // Get every file comment block and loop through each one
+ var fileComments = document.getElementsByClassName("has-inline-notes");
+ console.log(fileComments);
+ Array.prototype.forEach.call(fileComments, function(el, i) {
+
+ // Get the file info section
+ var fileInfo = el.getElementsByClassName("file-info")[0];
+ //console.log(fileInfo);
+
+ // Get the commit ID and folder path if the file is a Markdown file
+ if (fileInfo.innerText.indexOf(".md") !== -1) {
+ var folderPath = fileInfo.innerText.match(/(.*)\//)[1]; // web-console/knikubevirt/snapshots
+ var commitID = fileInfo.href.match(/files\/(.*)#/)[1];
+ }
+
+ // Loop through every Markdown line
+ var fileLines = el.getElementsByClassName("blob-code-inner");
+ Array.prototype.forEach.call(fileLines, function(el, i) {
+
+ // If the line has a Markdown-formatted image, append the image to the line
+ if (el.innerHTML.match(/(?:!\[.*?\]\((.*?)\))/)) {
+
+ // Get the image path specified in the Markdown line
+ var imagePath = el.innerHTML.match(/(?:!\[.*?\]\((.*?)\))/)[1];
+
+ // Create the image HTML
+ imageURL = repoPath + 'raw/' + commitID + '/' + folderPath + '/' + imagePath;
+ var newImage = document.createElement('div');
+ newImage.setAttribute("class", "inserted-image");
+ newImage.innerHTML = '';
+
+ // Append that image HTML right after the line if it doesn't already exist
+ if (el.nextSibling) {
+ if (el.nextSibling.classList) {
+ if (el.nextSibling.classList.contains("inserted-image") === -1) {
+ el.parentNode.insertBefore(newImage, el.nextSibling);
+ }
+ } else {
+ el.parentNode.insertBefore(newImage, el.nextSibling);
+ }
+ } else {
+ el.parentNode.insertBefore(newImage, el.nextSibling);
+ }
+ }
+ });
+ });
+ return null;
+}