Linux.do Base64工具

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

当前为 2025-03-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Linux.do Base64工具
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  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. } else {
  33. container.style.bottom = '20px';
  34. container.style.right = '20px';
  35. }
  36.  
  37. // 主触发器
  38. const trigger = document.createElement('div');
  39. trigger.innerHTML = 'Base64';
  40. Object.assign(trigger.style, {
  41. padding: '8px 16px',
  42. borderRadius: '6px',
  43. fontSize: '14px',
  44. cursor: 'pointer',
  45. transition: 'all 0.2s',
  46. backgroundColor: 'var(--primary)',
  47. color: 'var(--secondary)',
  48. boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
  49. });
  50.  
  51. // 拖动功能
  52. let isDragging = false;
  53. let offsetX = 0;
  54. let offsetY = 0;
  55.  
  56. container.addEventListener('mousedown', (e) => {
  57. isDragging = true;
  58. const rect = container.getBoundingClientRect();
  59. offsetX = e.clientX - rect.left;
  60. offsetY = e.clientY - rect.top;
  61. });
  62.  
  63. document.addEventListener('mousemove', (e) => {
  64. if (isDragging) {
  65. const x = e.clientX - offsetX;
  66. const y = e.clientY - offsetY;
  67. container.style.left = `${x}px`;
  68. container.style.top = `${y}px`;
  69. container.style.right = 'auto';
  70. container.style.bottom = 'auto';
  71. }
  72. });
  73.  
  74. document.addEventListener('mouseup', () => {
  75. if (isDragging) {
  76. const rect = container.getBoundingClientRect();
  77. GM_setValue('b64Position', {
  78. x: rect.left,
  79. y: rect.top
  80. });
  81. }
  82. isDragging = false;
  83. });
  84.  
  85. // 下拉菜单容器
  86. const menu = document.createElement('div');
  87. menu.style.cssText = `
  88. position: absolute;
  89. bottom: 100%;
  90. right: 0;
  91. width: 160px;
  92. background: var(--secondary);
  93. border-radius: 6px;
  94. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  95. margin-bottom: 8px;
  96. opacity: 0;
  97. transform: translateY(10px);
  98. transition: all 0.25s ease;
  99. pointer-events: none;
  100. `;
  101.  
  102. // 创建菜单项
  103. const createMenuItem = (text) => {
  104. const item = document.createElement('div');
  105. item.textContent = text;
  106. item.style.cssText = `
  107. padding: 10px 16px;
  108. font-size: 13px;
  109. cursor: pointer;
  110. color: var(--primary);
  111. transition: background 0.2s;
  112. text-align: left;
  113. line-height: 1.4;
  114. `;
  115. item.onmouseenter = () => item.style.background = 'rgba(0,0,0,0.08)';
  116. item.onmouseleave = () => item.style.background = '';
  117. return item;
  118. };
  119.  
  120. const decodeItem = createMenuItem('解析本页base64文本');
  121. const encodeItem = createMenuItem('文本转base64');
  122.  
  123. menu.append(decodeItem, encodeItem);
  124. container.append(trigger, menu);
  125.  
  126. // 菜单切换逻辑
  127. const toggleMenu = (show) => {
  128. menu.style.opacity = show ? 1 : 0;
  129. menu.style.transform = show ? 'translateY(0)' : 'translateY(10px)';
  130. menu.style.pointerEvents = show ? 'all' : 'none';
  131. };
  132.  
  133. // 主题适配
  134. const updateTheme = () => {
  135. const isDark = document.body.getAttribute('data-theme') === 'dark';
  136. trigger.style.backgroundColor = isDark ? 'var(--primary)' : 'var(--secondary)';
  137. trigger.style.color = isDark ? 'var(--secondary)' : 'var(--primary)';
  138. };
  139.  
  140. // 解码功能
  141. const decodeBase64 = () => {
  142. const base64Regex = /([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?/g;
  143.  
  144. document.querySelectorAll('div.post, div.cooked').forEach(container => {
  145. const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
  146.  
  147. while (walker.nextNode()) {
  148. const node = walker.currentNode;
  149. if (node.textContent.length > 20) {
  150. const decodedContent = node.textContent.replace(base64Regex, match => {
  151. try {
  152. const decoded = decodeURIComponent(escape(atob(match)));
  153. return decoded !== match ?
  154. `<span class="b64-decoded" style="
  155. color: var(--highlight);
  156. cursor: pointer;
  157. transition: all 0.2s;
  158. border-bottom: 1px dotted currentColor;
  159. ">${decoded}</span>` :
  160. match;
  161. } catch {
  162. return match;
  163. }
  164. });
  165.  
  166. if (decodedContent !== node.textContent) {
  167. const wrapper = document.createElement('span');
  168. wrapper.innerHTML = decodedContent;
  169.  
  170. wrapper.querySelectorAll('.b64-decoded').forEach(span => {
  171. span.onclick = (e) => {
  172. GM_setClipboard(span.textContent);
  173. e.target.style.opacity = '0.5';
  174. setTimeout(() => e.target.style.opacity = '', 500);
  175. };
  176. });
  177.  
  178. node.replaceWith(wrapper);
  179. }
  180. }
  181. }
  182. });
  183. };
  184.  
  185. // 编码功能
  186. const encodeBase64 = () => {
  187. const text = prompt('请输入要编码的文本:');
  188. if (text) {
  189. const encoded = btoa(unescape(encodeURIComponent(text)));
  190. GM_setClipboard(encoded);
  191. alert('已复制到剪贴板: ' + encoded);
  192. }
  193. };
  194.  
  195. // 事件绑定
  196. trigger.addEventListener('click', (e) => {
  197. e.stopPropagation();
  198. toggleMenu(menu.style.opacity === '0');
  199. });
  200.  
  201. decodeItem.addEventListener('click', () => {
  202. decodeBase64();
  203. toggleMenu(false);
  204. });
  205.  
  206. encodeItem.addEventListener('click', () => {
  207. encodeBase64();
  208. toggleMenu(false);
  209. });
  210.  
  211. // 初始化
  212. document.body.appendChild(container);
  213. updateTheme();
  214.  
  215. // 主题变化监听
  216. new MutationObserver(updateTheme).observe(document.body, {
  217. attributes: true,
  218. attributeFilter: ['data-theme']
  219. });
  220.  
  221. // 点击外部关闭
  222. document.addEventListener('click', (e) => {
  223. if (!container.contains(e.target)) toggleMenu(false);
  224. });
  225.  
  226. // 全局样式
  227. const style = document.createElement('style');
  228. style.textContent = `
  229. .b64-decoded:hover {
  230. opacity: 0.8 !important;
  231. }
  232. `;
  233. document.head.appendChild(style);
  234. })();