Linux.do Base64 script

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

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

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