Linux.do Base64 script

Base64编解码工具,支持位置记忆、夜间模式和拖动功能

当前为 2025-04-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Linux.do Base64 script
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Base64编解码工具,支持位置记忆、夜间模式和拖动功能
  6. // @author Xavier
  7. // @match https://linux.do/*
  8. // @grant GM_setClipboard
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // 创建主容器
  17. const container = document.createElement('div');
  18. container.id = 'b64Container';
  19. Object.assign(container.style, {
  20. position: 'fixed',
  21. zIndex: 9999,
  22. borderRadius: '8px',
  23. fontFamily: 'inherit',
  24. cursor: 'move'
  25. });
  26.  
  27. // 初始化位置
  28. const savedPosition = GM_getValue('b64Position', null);
  29. if (savedPosition) {
  30. container.style.left = `${savedPosition.x}px`;
  31. container.style.top = `${savedPosition.y}px`;
  32. container.style.bottom = 'auto';
  33. container.style.right = 'auto';
  34. } else {
  35. container.style.bottom = '20px';
  36. container.style.right = '20px';
  37. container.style.left = 'auto';
  38. container.style.top = 'auto';
  39. }
  40.  
  41. // 主触发器
  42. const trigger = document.createElement('div');
  43. trigger.innerHTML = 'Base64';
  44. Object.assign(trigger.style, {
  45. padding: '8px 16px',
  46. borderRadius: '6px',
  47. fontSize: '14px',
  48. cursor: 'pointer',
  49. transition: 'all 0.2s',
  50. backgroundColor: 'var(--primary)',
  51. color: 'var(--secondary)',
  52. boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
  53. });
  54.  
  55. // 拖动功能
  56. let isDragging = false;
  57. let offsetX = 0;
  58. let offsetY = 0;
  59.  
  60. container.addEventListener('mousedown', (e) => {
  61. isDragging = true;
  62. const rect = container.getBoundingClientRect();
  63. offsetX = e.clientX - rect.left;
  64. offsetY = e.clientY - rect.top;
  65. });
  66.  
  67. document.addEventListener('mousemove', (e) => {
  68. if (isDragging) {
  69. const x = e.clientX - offsetX;
  70. const y = e.clientY - offsetY;
  71. container.style.left = `${x}px`;
  72. container.style.top = `${y}px`;
  73. container.style.right = 'auto';
  74. container.style.bottom = 'auto';
  75. }
  76. });
  77.  
  78. document.addEventListener('mouseup', () => {
  79. if (isDragging) {
  80. const rect = container.getBoundingClientRect();
  81. GM_setValue('b64Position', {
  82. x: rect.left,
  83. y: rect.top
  84. });
  85. }
  86. isDragging = false;
  87. });
  88.  
  89. // 下拉菜单容器
  90. const menu = document.createElement('div');
  91. menu.style.cssText = `
  92. position: absolute;
  93. bottom: 100%;
  94. right: 0;
  95. width: 140px;
  96. background: var(--secondary);
  97. border-radius: 6px;
  98. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  99. margin-bottom: 8px;
  100. opacity: 0;
  101. transform: translateY(10px);
  102. transition: all 0.25s ease;
  103. pointer-events: none;
  104. `;
  105.  
  106. // 创建菜单项
  107. const createMenuItem = (text) => {
  108. const item = document.createElement('div');
  109. item.textContent = text;
  110. item.style.cssText = `
  111. padding: 10px 16px;
  112. font-size: 13px;
  113. cursor: pointer;
  114. color: var(--primary);
  115. transition: background 0.2s;
  116. text-align: left;
  117. line-height: 1.4;
  118. `;
  119. item.onmouseenter = () => item.style.background = 'rgba(0,0,0,0.08)';
  120. item.onmouseleave = () => item.style.background = '';
  121. return item;
  122. };
  123.  
  124. const decodeItem = createMenuItem('解析本页 Base64');
  125. const encodeItem = createMenuItem('文本转 Base64');
  126.  
  127. menu.append(decodeItem, encodeItem);
  128. container.append(trigger, menu);
  129.  
  130. // 菜单切换逻辑
  131. const toggleMenu = (show) => {
  132. menu.style.opacity = show ? 1 : 0;
  133. menu.style.transform = show ? 'translateY(0)' : 'translateY(10px)';
  134. menu.style.pointerEvents = show ? 'all' : 'none';
  135. };
  136.  
  137. // 主题适配
  138. const updateTheme = () => {
  139. const isDark = document.body.getAttribute('data-theme') === 'dark';
  140. trigger.style.backgroundColor = isDark ? 'var(--primary)' : 'var(--secondary)';
  141. trigger.style.color = isDark ? 'var(--secondary)' : 'var(--primary)';
  142. };
  143.  
  144. // 复制提示样式
  145. const notificationStyle = document.createElement('style');
  146. notificationStyle.textContent = `
  147. .b64-copy-notification {
  148. position: fixed;
  149. top: 20px;
  150. left: 50%;
  151. transform: translateX(-50%);
  152. background: rgba(0, 200, 0, 0.9);
  153. color: white;
  154. padding: 10px 20px;
  155. border-radius: 4px;
  156. font-size: 14px;
  157. z-index: 10000;
  158. animation: fadeOut 2s forwards;
  159. animation-delay: 0.5s;
  160. }
  161.  
  162. @keyframes fadeOut {
  163. from { opacity: 1; }
  164. to { opacity: 0; }
  165. }
  166. `;
  167. document.head.appendChild(notificationStyle);
  168.  
  169. // 显示复制提示
  170. function showCopyNotification() {
  171. const notification = document.createElement('div');
  172. notification.className = 'b64-copy-notification';
  173. notification.textContent = '已复制到剪贴板';
  174. document.body.appendChild(notification);
  175.  
  176. setTimeout(() => {
  177. notification.remove();
  178. }, 2500);
  179. }
  180.  
  181. // 解码功能
  182. let isParsed = false;
  183. let originalTexts = {};
  184.  
  185. const decodeBase64 = () => {
  186. const base64Regex = /([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?/g;
  187.  
  188. if (isParsed) {
  189. // 恢复原始文本
  190. Object.entries(originalTexts).forEach(([wrapperId, text]) => {
  191. const wrapper = document.getElementById(wrapperId);
  192. if (wrapper) {
  193. const textNode = document.createTextNode(text);
  194. wrapper.replaceWith(textNode);
  195. }
  196. });
  197. originalTexts = {};
  198. isParsed = false;
  199. } else {
  200. document.querySelectorAll('div.post, div.cooked').forEach(container => {
  201. const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
  202.  
  203. while (walker.nextNode()) {
  204. const node = walker.currentNode;
  205. if (node.textContent.length > 20) {
  206. const decodedContent = node.textContent.replace(base64Regex, match => {
  207. try {
  208. const decoded = decodeURIComponent(escape(atob(match)));
  209. return decoded !== match ?
  210. `<span class="b64-decoded" style="
  211. color: #FF7F28;
  212. background: rgba(255, 127, 40, 0.1);
  213. cursor: pointer;
  214. transition: all 0.2s;
  215. padding: 1px 2px;
  216. border-radius: 3px;
  217. ">${decoded}</span>` :
  218. match;
  219. } catch {
  220. return match;
  221. }
  222. });
  223.  
  224. if (decodedContent !== node.textContent) {
  225. const wrapper = document.createElement('span');
  226. const wrapperId = `b64-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  227. wrapper.id = wrapperId;
  228. wrapper.innerHTML = decodedContent;
  229.  
  230. wrapper.querySelectorAll('.b64-decoded').forEach(span => {
  231. span.onclick = (e) => {
  232. GM_setClipboard(span.textContent);
  233. showCopyNotification();
  234. e.target.style.opacity = '0.7';
  235. setTimeout(() => e.target.style.opacity = '', 500);
  236. };
  237. });
  238.  
  239. originalTexts[wrapperId] = node.textContent;
  240. node.replaceWith(wrapper);
  241. }
  242. }
  243. }
  244. });
  245. isParsed = true;
  246. }
  247. };
  248.  
  249. // 编码功能
  250. const encodeBase64 = () => {
  251. const text = prompt('请输入要编码的文本:');
  252. if (text) {
  253. const encoded = btoa(unescape(encodeURIComponent(text)));
  254. GM_setClipboard(encoded);
  255. alert('已复制到剪贴板: ' + encoded);
  256. }
  257. };
  258.  
  259. // 事件绑定
  260. trigger.addEventListener('click', (e) => {
  261. e.stopPropagation();
  262. toggleMenu(menu.style.opacity === '0');
  263. });
  264.  
  265. decodeItem.addEventListener('click', () => {
  266. decodeBase64();
  267. toggleMenu(false);
  268. });
  269.  
  270. encodeItem.addEventListener('click', () => {
  271. encodeBase64();
  272. toggleMenu(false);
  273. });
  274.  
  275. // 初始化
  276. document.body.appendChild(container);
  277. updateTheme();
  278.  
  279. // 主题变化监听
  280. new MutationObserver(updateTheme).observe(document.body, {
  281. attributes: true,
  282. attributeFilter: ['data-theme']
  283. });
  284.  
  285. // 点击外部关闭
  286. document.addEventListener('click', (e) => {
  287. if (!container.contains(e.target)) toggleMenu(false);
  288. });
  289.  
  290. // 全局样式
  291. const style = document.createElement('style');
  292. style.textContent = `
  293. .b64-decoded:hover {
  294. background: rgba(255, 127, 40, 0.2) !important;
  295. }
  296. .b64-decoded:active {
  297. background: rgba(255, 127, 40, 0.3) !important;
  298. }
  299. `;
  300. document.head.appendChild(style);
  301. })();