My Zipline Uploader

Upload files to your Zipline instance with URL history, timestamps, minimize support, middle-click to open in new tab without shifting focus.

  1. // ==UserScript==
  2. // @name My Zipline Uploader
  3. // @namespace *://*/*
  4. // @version 1.0
  5. // @description Upload files to your Zipline instance with URL history, timestamps, minimize support, middle-click to open in new tab without shifting focus.
  6. // @author heapsofjoy
  7. // @match *://*/*
  8. // @grant GM_addStyle
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Create necessary DOM elements
  17. const uploadButton = document.createElement('div');
  18. uploadButton.id = 'uploadButton';
  19. uploadButton.innerHTML = '⬆';
  20. document.body.appendChild(uploadButton);
  21.  
  22. const fileInput = document.createElement('input');
  23. fileInput.type = 'file';
  24. fileInput.style.display = 'none';
  25. document.body.appendChild(fileInput);
  26.  
  27. const urlTextBox = document.createElement('input');
  28. urlTextBox.type = 'text';
  29. urlTextBox.id = 'fileUrl';
  30. urlTextBox.placeholder = 'URL will appear here';
  31. urlTextBox.readOnly = true;
  32. urlTextBox.style.display = 'none';
  33. document.body.appendChild(urlTextBox);
  34.  
  35. const copyButton = document.createElement('div');
  36. copyButton.id = 'copyButton';
  37. copyButton.innerHTML = '📋';
  38. copyButton.style.display = 'none';
  39. document.body.appendChild(copyButton);
  40.  
  41. const dropZone = document.createElement('div');
  42. dropZone.id = 'dropZone';
  43. dropZone.innerText = 'Drag & Drop File Here';
  44. dropZone.style.display = 'none';
  45. document.body.appendChild(dropZone);
  46.  
  47. const minimizeButton = document.createElement('div');
  48. minimizeButton.id = 'minimizeButton';
  49. minimizeButton.innerHTML = '—';
  50. minimizeButton.style.display = 'none';
  51. document.body.appendChild(minimizeButton);
  52.  
  53. const historyButton = document.createElement('div');
  54. historyButton.id = 'historyButton';
  55. historyButton.innerHTML = '📜';
  56. historyButton.style.display = 'none';
  57. document.body.appendChild(historyButton);
  58.  
  59. const clearHistoryButton = document.createElement('div');
  60. clearHistoryButton.id = 'clearHistoryButton';
  61. clearHistoryButton.innerHTML = '🗑️';
  62. clearHistoryButton.style.display = 'none';
  63. document.body.appendChild(clearHistoryButton);
  64.  
  65. const historyList = document.createElement('div');
  66. historyList.id = 'historyList';
  67. historyList.style.display = 'none';
  68. document.body.appendChild(historyList);
  69.  
  70. GM_addStyle(`
  71. #uploadButton, #historyButton, #clearHistoryButton, #minimizeButton {
  72. position: fixed;
  73. bottom: 0;
  74. width: 50px;
  75. height: 50px;
  76. background-color: #333;
  77. color: white;
  78. border: none;
  79. border-radius: 50%;
  80. cursor: pointer;
  81. font-family: Arial, sans-serif;
  82. font-size: 24px;
  83. text-align: center;
  84. line-height: 50px;
  85. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  86. z-index: 10000;
  87. }
  88. #uploadButton { left: 20px; transition: bottom 0.4s ease; }
  89. #uploadButton.minimized {
  90. bottom: 0;
  91. width: 50px;
  92. height: 10px;
  93. border-radius: 50px 50px 0 0;
  94. font-size: 10px;
  95. line-height: 10px;
  96. }
  97. #minimizeButton, #historyButton, #clearHistoryButton {
  98. width: 40px;
  99. height: 40px;
  100. font-size: 20px;
  101. line-height: 40px;
  102. }
  103. #minimizeButton { left: 80px; }
  104. #historyButton { left: 140px; }
  105. #clearHistoryButton { left: 200px; }
  106.  
  107. #fileUrl, #historyList {
  108. position: fixed;
  109. bottom: 70px;
  110. left: 20px;
  111. width: 270px;
  112. background-color: #333;
  113. color: white;
  114. padding: 10px;
  115. border: none;
  116. border-radius: 5px;
  117. font-size: 14px;
  118. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  119. z-index: 10000;
  120. overflow-y: auto;
  121. }
  122. #fileUrl { display: block; }
  123. #historyList {
  124. height: 200px;
  125. display: none;
  126. }
  127. #historyList div {
  128. padding: 5px;
  129. border-bottom: 1px solid #555;
  130. cursor: pointer;
  131. color: #66ccff;
  132. }
  133. #historyList div span.timestamp {
  134. display: block;
  135. color: #aaa;
  136. font-size: 12px;
  137. margin-top: 2px;
  138. }
  139. #copyButton {
  140. position: fixed;
  141. bottom: 70px;
  142. left: 300px;
  143. width: 30px;
  144. height: 30px;
  145. background-color: #333;
  146. color: white;
  147. border: none;
  148. border-radius: 5px;
  149. cursor: pointer;
  150. font-family: Arial, sans-serif;
  151. font-size: 16px;
  152. text-align: center;
  153. line-height: 30px;
  154. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  155. z-index: 10000;
  156. }
  157. #dropZone {
  158. position: fixed;
  159. bottom: 120px;
  160. left: 20px;
  161. width: 300px;
  162. height: 150px;
  163. border: 2px dashed #aaa;
  164. background-color: #444;
  165. color: white;
  166. text-align: center;
  167. line-height: 150px;
  168. font-family: Arial, sans-serif;
  169. font-size: 14px;
  170. border-radius: 5px;
  171. z-index: 10000;
  172. }
  173. #dropZone.dragover {
  174. border-color: #fff;
  175. background-color: #555;
  176. }
  177. `);
  178.  
  179. let isMinimized = true;
  180. uploadButton.classList.add('minimized');
  181.  
  182. uploadButton.addEventListener('click', () => {
  183. if (isMinimized) {
  184. uploadButton.classList.remove('minimized');
  185. isMinimized = false;
  186. minimizeButton.style.display = 'block';
  187. historyButton.style.display = 'block';
  188. clearHistoryButton.style.display = 'block';
  189. } else {
  190. fileInput.click();
  191. }
  192. });
  193.  
  194. minimizeButton.addEventListener('click', () => {
  195. uploadButton.classList.add('minimized');
  196. isMinimized = true;
  197. minimizeButton.style.display = 'none';
  198. urlTextBox.style.display = 'none';
  199. copyButton.style.display = 'none';
  200. historyList.style.display = 'none';
  201. historyButton.style.display = 'none';
  202. clearHistoryButton.style.display = 'none';
  203. dropZone.style.display = 'none';
  204. });
  205.  
  206. fileInput.addEventListener('change', () => {
  207. const file = fileInput.files[0];
  208. if (file) uploadFile(file);
  209. });
  210.  
  211. const uploadedUrlsKey = 'globalUploadedUrls';
  212. const urlLimit = 10; // Limit the number of stored URLs
  213.  
  214. // Load the saved URLs from GM storage
  215. let savedUrls = GM_getValue(uploadedUrlsKey, []);
  216.  
  217. function updateHistoryList() {
  218. historyList.innerHTML = '';
  219. const sortedUrls = [...savedUrls].reverse(); // Reverse order for recent-first
  220.  
  221. sortedUrls.forEach(entry => {
  222. const urlElement = document.createElement('div');
  223. urlElement.innerHTML = `${entry.url} <span class="timestamp">${entry.timestamp}</span>`;
  224.  
  225. // Left-click to open in a new tab (focus)
  226. urlElement.addEventListener('click', () => {
  227. window.open(entry.url, '_blank');
  228. });
  229.  
  230. // Middle-click to open in a background tab
  231. urlElement.addEventListener('auxclick', (e) => {
  232. if (e.button === 1) {
  233. window.open(entry.url, '_blank', 'noopener,noreferrer');
  234. }
  235. });
  236. historyList.appendChild(urlElement);
  237. });
  238. }
  239.  
  240. historyButton.addEventListener('click', () => {
  241. historyList.style.display = historyList.style.display === 'none' ? 'block' : 'none';
  242. });
  243.  
  244. clearHistoryButton.addEventListener('click', () => {
  245. GM_setValue(uploadedUrlsKey, []); // Clear GM storage
  246. savedUrls = []; // Reset local array
  247. historyList.innerHTML = ''; // Clear the displayed list
  248. alert('History cleared!');
  249. });
  250.  
  251. let dragCounter = 0;
  252.  
  253. document.addEventListener('dragover', (e) => {
  254. e.preventDefault();
  255. if (!isMinimized) {
  256. dragCounter++;
  257. dropZone.style.display = 'block';
  258. dropZone.classList.add('dragover');
  259. }
  260. });
  261.  
  262. document.addEventListener('dragleave', (e) => {
  263. e.preventDefault();
  264. if (!isMinimized) {
  265. dragCounter--;
  266. if (dragCounter === 0) {
  267. dropZone.classList.remove('dragover');
  268. dropZone.style.display = 'none';
  269. }
  270. }
  271. });
  272.  
  273. dropZone.addEventListener('drop', (e) => {
  274. if (!isMinimized) {
  275. e.preventDefault();
  276. dragCounter = 0;
  277. dropZone.classList.remove('dragover');
  278. dropZone.style.display = 'none';
  279. const file = e.dataTransfer.files[0];
  280. if (file) uploadFile(file);
  281. }
  282. });
  283.  
  284. function uploadFile(file) {
  285. const formData = new FormData();
  286. formData.append('file', file); // Use "file" as the form key per your ShareX config
  287.  
  288. fetch('https://YOURZIPLINEINSTANCE.COM/api/upload', {
  289. method: 'POST',
  290. headers: {
  291. 'Authorization': 'TOKEN' // Replace with your actual token
  292. },
  293. body: formData
  294. })
  295. .then(response => response.json()) // Adjust to parse JSON response
  296. .then(data => {
  297. if (data && data.files && data.files[0]) {
  298. const url = data.files[0];
  299. const timestamp = new Date().toLocaleString();
  300. savedUrls.push({ url, timestamp });
  301. // Check for limit and remove oldest if needed
  302. if (savedUrls.length > urlLimit) {
  303. savedUrls.shift(); // Remove the oldest entry
  304. }
  305. GM_setValue(uploadedUrlsKey, savedUrls); // Save to global storage
  306. urlTextBox.style.display = 'block';
  307. urlTextBox.value = url;
  308. copyButton.style.display = 'block';
  309. updateHistoryList();
  310. } else {
  311. throw new Error('Invalid response format');
  312. }
  313. })
  314. .catch(error => {
  315. urlTextBox.value = 'Upload failed!';
  316. console.error('Error:', error);
  317. });
  318. }
  319.  
  320.  
  321. copyButton.addEventListener('click', () => {
  322. navigator.clipboard.writeText(urlTextBox.value).then(() => {
  323. copyButton.innerHTML = '✔';
  324. setTimeout(() => copyButton.innerHTML = '📋', 1000);
  325. }).catch(error => console.error('Copy failed:', error));
  326. });
  327.  
  328. updateHistoryList(); // Initial call to populate the history on load
  329. })();