Greasy Fork 还支持 简体中文。

Linux do Level Enhanced

Enhanced script to track progress towards next trust level on linux.do with added search functionality, adjusted posts read limit, and a breathing icon animation.

目前為 2024-05-28 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Linux do Level Enhanced
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.7
  5. // @description Enhanced script to track progress towards next trust level on linux.do with added search functionality, adjusted posts read limit, and a breathing icon animation.
  6. // @author Hua, Reno, NullUser
  7. // @match https://linux.do/*
  8. // @icon https://www.google.com/s2/favicons?domain=linux.do
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const StyleManager = {
  17. styles: `
  18. @keyframes breathAnimation {
  19. 0%, 100% { transform: scale(1); box-shadow: 0 0 10px rgba(0,0,0,0.15); }
  20. 50% { transform: scale(1.1); box-shadow: 0 0 20px rgba(0,0,0,0.3); }
  21. }
  22. .breath-animation {
  23. animation: breathAnimation 3s ease-in-out infinite;
  24. }
  25. .minimized {
  26. border-radius: 50%;
  27. cursor: pointer;
  28. transition: transform 0.3s ease, box-shadow 0.3s ease;
  29. width: 50px;
  30. height: 50px;
  31. display: flex;
  32. justify-content: center;
  33. align-items: center;
  34. background: var(--minimized-bg);
  35. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  36. }
  37. .minimized:hover {
  38. transform: scale(1.1);
  39. box-shadow: 0 0 15px rgba(0,0,0,0.3);
  40. }
  41. .linuxDoLevelPopup {
  42. position: fixed;
  43. width: 250px;
  44. height: auto;
  45. background: var(--popup-bg);
  46. box-shadow: 0 8px 30px rgba(0,0,0,0.1);
  47. padding: 15px;
  48. z-index: 10000;
  49. font-size: 14px;
  50. border-radius: 15px;
  51. cursor: move;
  52. transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
  53. }
  54. .linuxDoLevelPopup.hidden {
  55. opacity: 0;
  56. visibility: hidden;
  57. }
  58. .linuxDoLevelPopup:hover {
  59. box-shadow: 0 12px 40px rgba(0,0,0,0.2);
  60. }
  61. .linuxDoLevelPopup input,
  62. .linuxDoLevelPopup button {
  63. width: 100%;
  64. background: transparent;
  65. margin-top: 8px;
  66. padding: 10px;
  67. border-radius: 6px;
  68. border: 1px solid var(--input-border);
  69. box-sizing: border-box;
  70. font-size: 14px;
  71. transition: border-color 0.3s ease, box-shadow 0.3s ease;
  72. }
  73. .linuxDoLevelPopup input:focus,
  74. .linuxDoLevelPopup button:focus {
  75. outline: none;
  76. border-color: #007BFF;
  77. box-shadow: 0 0 5px rgba(0,123,255,0.5);
  78. }
  79. .linuxDoLevelPopup button {
  80. background-color: var(--button-bg);
  81. color: var(--button-color);
  82. border: none;
  83. cursor: pointer;
  84. transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
  85. }
  86. .linuxDoLevelPopup button:hover {
  87. background-color: var(--button-hover-bg);
  88. transform: translateY(-2px);
  89. box-shadow: 0 6px 15px rgba(0,0,0,0.1);
  90. }
  91. .minimizeButton {
  92. position: absolute;
  93. top: 5px;
  94. right: 5px;
  95. background: transparent;
  96. border: none;
  97. cursor: pointer;
  98. width: 25px;
  99. height: 25px;
  100. font-size: 16px;
  101. color: var(--minimize-btn-color);
  102. transition: color 0.3s ease;
  103. }
  104. .minimizeButton:hover {
  105. color: var(--minimize-btn-hover-color);
  106. }
  107. .summary-table {
  108. width: 100%;
  109. border-collapse: collapse;
  110. animation: fadeIn 0.5s ease-in-out;
  111. font-size: 14px;
  112. }
  113. .summary-table td {
  114. padding: 4px;
  115. text-align: left;
  116. border-bottom: none;
  117. white-space: nowrap;
  118. }
  119. .progress-bar {
  120. position: relative;
  121. height: 10px;
  122. background-color: var(--progress-bg);
  123. border-radius: 5px;
  124. overflow: hidden;
  125. width: 60%;
  126. display: inline-block;
  127. vertical-align: middle;
  128. margin-right: 10px;
  129. }
  130. .progress-bar-fill {
  131. height: 100%;
  132. background-color: #28a745;
  133. text-align: right;
  134. line-height: 10px;
  135. color: white;
  136. transition: width 0.4s ease-in-out;
  137. padding-right: 5px;
  138. border-radius: 5px 0 0 5px;
  139. }
  140. .progress-bar-fill::after {
  141. content: '';
  142. position: absolute;
  143. top: 0;
  144. left: 0;
  145. right: 0;
  146. bottom: 0;
  147. background-image: linear-gradient(90deg, transparent 10%, rgba(0,0,0,0.2) 10%, rgba(0,0,0,0.2) 15%, transparent 15%);
  148. background-size: 30px 10px;
  149. z-index: 1;
  150. }
  151. .progress-text {
  152. display: inline-block;
  153. vertical-align: middle;
  154. font-size: 13px;
  155. visibility: hidden;
  156. position: absolute;
  157. top: -25px; /* Adjust position */
  158. left: 0;
  159. background-color: #f39c12;
  160. color: #fff;
  161. border: 1px solid #e67e22;
  162. padding: 2px 5px;
  163. border-radius: 4px;
  164. box-shadow: 0px 0px 5px rgba(0,0,0,0.1);
  165. z-index: 1000;
  166. }
  167. .summary-row {
  168. display: flex;
  169. align-items: center;
  170. justify-content: space-between;
  171. margin-bottom: 5px;
  172. position: relative;
  173. }
  174. .summary-row:hover .progress-text {
  175. visibility: visible;
  176. }
  177. @media (prefers-color-scheme: dark) {
  178. :root {
  179. --minimized-bg: #2c2c2c;
  180. --popup-bg: #333;
  181. --input-border: #555;
  182. --button-bg: #444;
  183. --button-color: #f0f0f0;
  184. --button-hover-bg: #555;
  185. --minimize-btn-color: #888;
  186. --minimize-btn-hover-color: #fff;
  187. --progress-bg: #3d3d3d;
  188. }
  189. }
  190. @media (prefers-color-scheme: light) {
  191. :root {
  192. --minimized-bg: #f0f0f0;
  193. --popup-bg: #fff;
  194. --input-border: #ddd;
  195. --button-bg: #e0e0e0;
  196. --button-color: #333;
  197. --button-hover-bg: #d5d5d5;
  198. --minimize-btn-color: #888;
  199. --minimize-btn-hover-color: #333;
  200. --progress-bg: #f3f3f3;
  201. }
  202. }
  203. `,
  204.  
  205. injectStyles: function() {
  206. const styleSheet = document.createElement('style');
  207. styleSheet.type = 'text/css';
  208. styleSheet.innerText = this.styles;
  209. document.head.appendChild(styleSheet);
  210. }
  211. };
  212.  
  213. const DataManager = {
  214. Config: {
  215. BASE_URL: 'https://linux.do',
  216. PATHS: {
  217. ABOUT: '/about.json',
  218. USER_SUMMARY: '/u/{username}/summary.json',
  219. USER_DETAIL: '/u/{username}.json',
  220. },
  221. },
  222.  
  223. levelRequirements: {
  224. 0: { 'topics_entered': 5, 'posts_read_count': 30, 'time_read': 600 },
  225. 1: { 'days_visited': 15, 'likes_given': 1, 'likes_received': 1, 'post_count': 3, 'topics_entered': 20, 'posts_read_count': 100, 'time_read': 3600 },
  226. 2: { 'days_visited': 50, 'likes_given': 30, 'likes_received': 20, 'post_count': 10 },
  227. },
  228.  
  229. levelDescriptions: {
  230. 0: "新用户 🌱",
  231. 1: "基本用户 ⭐ ",
  232. 2: "成员 ⭐⭐",
  233. 3: "活跃用户 ⭐⭐⭐",
  234. 4: "领导者 🏆"
  235. },
  236.  
  237. fetch: async function(url, options = {}) {
  238. try {
  239. const response = await fetch(url, {
  240. ...options,
  241. headers: { "Accept": "application/json", "User-Agent": "Mozilla/5.0" },
  242. method: options.method || "GET",
  243. });
  244. if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
  245. return await response.json();
  246. } catch (error) {
  247. console.error(`Error fetching data from ${url}:`, error);
  248. throw error;
  249. }
  250. },
  251.  
  252. fetchAboutData: function() {
  253. const url = this.buildUrl(this.Config.PATHS.ABOUT);
  254. return this.fetch(url);
  255. },
  256.  
  257. fetchSummaryData: function(username) {
  258. const url = this.buildUrl(this.Config.PATHS.USER_SUMMARY, { username });
  259. return this.fetch(url);
  260. },
  261.  
  262. fetchUserData: function(username) {
  263. const url = this.buildUrl(this.Config.PATHS.USER_DETAIL, { username });
  264. return this.fetch(url);
  265. },
  266.  
  267. buildUrl: function(path, params = {}) {
  268. let url = this.Config.BASE_URL + path;
  269. Object.keys(params).forEach(key => {
  270. url = url.replace(`{${key}}`, encodeURIComponent(params[key]));
  271. });
  272. return url;
  273. },
  274. };
  275.  
  276. const UIManager = {
  277. initPopup: function() {
  278. this.popup = this.createElement('div', { id: 'linuxDoLevelPopup', class: 'linuxDoLevelPopup' });
  279. this.content = this.createElement('div', { id: 'linuxDoLevelPopupContent' }, '欢迎使用 Linux do 等级增强插件');
  280. this.searchBox = this.createElement('input', { placeholder: '请输入用户名...', type: 'text', class: 'searchBox' });
  281. this.searchButton = this.createElement('button', { class: 'searchButton' }, '搜索');
  282. this.minimizeButton = this.createElement('button', { }, '隐藏');
  283. this.popup.style.bottom = '20px'; // 示例:距离顶部20px
  284. this.popup.style.right = '20px'; // 示例:距离左侧20px
  285. this.popup.style.width = '250px'; // 初始化宽度
  286. this.popup.style.height = 'auto'; // 高度自适应内容
  287. this.searchButton.classList.add('btn', 'btn-icon-text', 'btn-default')
  288. this.minimizeButton.classList.add('btn', 'btn-icon-text', 'btn-default')
  289.  
  290. this.popup.append(this.content, this.searchBox, this.searchButton, this.minimizeButton);
  291. document.body.appendChild(this.popup);
  292.  
  293. this.minimizeButton.addEventListener('click', () => this.togglePopupSize());
  294. this.searchButton.addEventListener('click', () => EventHandler.handleSearch());
  295. // 添加输入框的回车键事件监听器
  296. this.searchBox.addEventListener('keypress', (event) => {
  297. // 检查是否按下了回车键并且弹窗不处于最小化状态
  298. if (event.key === 'Enter' && !this.popup.classList.contains('minimized')) {
  299. EventHandler.handleSearch();
  300. }
  301. });
  302.  
  303. var checkInterval = setInterval(function() {
  304. // 查找id为current-user的li元素
  305. var currentUserLi = document.querySelector('#current-user');
  306.  
  307. // 如果找到了元素
  308. if(currentUserLi) {
  309. // 查找该元素下的button
  310. var button = currentUserLi.querySelector('button');
  311.  
  312. // 如果找到了button元素
  313. if(button) {
  314. // 获取button的href属性值
  315. var href = button.getAttribute('href');
  316. UIManager.searchBox.value = href.replace('/u/', '');
  317. clearInterval(checkInterval); // 停止检查
  318. // 这里你可以根据需要对href进行进一步操作
  319. }
  320. }
  321. }, 1000); // 每隔1秒检查一次
  322. },
  323.  
  324. createElement: function(tag, attributes, text) {
  325. const element = document.createElement(tag);
  326. for (const attr in attributes) {
  327. if (attr === 'class') {
  328. element.classList.add(attributes[attr]);
  329. } else {
  330. element.setAttribute(attr, attributes[attr]);
  331. }
  332. }
  333. if (text) element.textContent = text;
  334. return element;
  335. },
  336.  
  337. updatePopupContent: function(userSummary, user, userDetail, status) {
  338. if (!userSummary || !user || !userDetail) return;
  339.  
  340. // 初始化内容字符串,并添加用户信任等级
  341. let content = `<strong>信任等级🏅:</strong>${DataManager.levelDescriptions[user.trust_level]}<br>`;
  342.  
  343. // 获取用户的信任等级要求
  344. const requirements = DataManager.levelRequirements[user.trust_level] || {};
  345.  
  346. // 添加用户的 gamification_score
  347. if (userDetail.gamification_score) {
  348. content += `<strong>你的点数🪙:</strong><span style="color: green;">${userDetail.gamification_score}</span><br>`;
  349. }
  350.  
  351. // 添加用户的最近活跃时间
  352. content += `<strong>最近活跃🕒:</strong>${formatTimestamp(userDetail.last_seen_at)}<br>`;
  353.  
  354. // 处理2级以下用户,调用 summaryRequired 功能
  355. if (user.trust_level <= 2) {
  356. if (user.trust_level === 2) {
  357. requirements['posts_read_count'] = Math.min(parseInt(parseInt(status.posts_30_days) / 4), 20000);
  358. requirements['topics_entered'] = Math.min(parseInt(parseInt(status.topics_30_days) / 4), 500);
  359. }
  360. let summary = summaryRequired(requirements, userSummary, this.translateStat.bind(this));
  361. content += summary;
  362. } else {
  363. // 处理2级以上用户,调用 analyzeAbility 功能
  364. if (userSummary.top_categories) {
  365. content += analyzeAbility(userSummary.top_categories);
  366. }
  367. }
  368.  
  369. // 更新弹窗内容
  370. this.content.innerHTML = content;
  371. },
  372.  
  373. togglePopupSize: function() {
  374. if (this.popup.classList.contains('minimized')) {
  375. this.popup.classList.remove('minimized');
  376. this.popup.style.width = '250px';
  377. this.popup.style.height = 'auto';
  378. this.content.style.display = 'block';
  379. this.searchBox.style.display = 'block';
  380. this.searchButton.style.display = 'block';
  381. this.minimizeButton.textContent = '隐藏';
  382. this.minimizeButton.style.color = 'black';
  383. this.popup.classList.remove('breath-animation');
  384. } else {
  385. this.popup.classList.add('minimized');
  386. this.popup.style.width = '50px';
  387. this.popup.style.height = '50px';
  388. this.content.style.display = 'none';
  389. this.searchBox.style.display = 'none';
  390. this.searchButton.style.display = 'none';
  391. this.popup.classList.add('breath-animation');
  392.  
  393. // 调用 updatePercentage 函数并更新按钮文本
  394. updatePercentage().then(percentage => {
  395. let color;
  396. // 根据百分比设置颜色
  397. if (percentage > 50) {
  398. color = 'purple';
  399. } else if (percentage > 30) {
  400. color = 'red';
  401. } else {
  402. color = 'green';
  403. }
  404.  
  405. // 更新按钮的文本和文本颜色
  406. this.minimizeButton.textContent = `${percentage.toFixed(2)}%`;
  407. this.minimizeButton.style.color = color; // 设置文本颜色
  408. }).catch(error => {
  409. console.error('Error calculating percentage:', error);
  410. // 出错时保持原有文本
  411. this.minimizeButton.textContent = '展开';
  412. this.minimizeButton.style.color = 'black';
  413. });
  414. }
  415.  
  416. // 自动校正窗口位置
  417. addDraggableFeature(this.popup);
  418. const windowWidth = window.innerWidth;
  419. const windowHeight = window.innerHeight;
  420. const popupWidth = this.popup.offsetWidth;
  421. const popupHeight = this.popup.offsetHeight;
  422. const popupTop = parseInt(this.popup.style.top);
  423. const popupLeft = parseInt(this.popup.style.left);
  424.  
  425. // 初始化新的位置
  426. let newTop = popupTop;
  427. let newLeft = popupLeft;
  428.  
  429. // 上下边界同时检查
  430. newTop = Math.min(Math.max(70, popupTop), windowHeight - popupHeight);
  431.  
  432. // 左右边界同时检查
  433. newLeft = Math.min(Math.max(5, popupLeft), windowWidth - popupWidth - 20);
  434.  
  435. this.popup.style.top = newTop + 'px';
  436. this.popup.style.left = newLeft + 'px';
  437. },
  438.  
  439. displayError: function(message) {
  440. this.content.innerHTML = `<strong>错误:</strong>${message}`;
  441. },
  442.  
  443. translateStat: function(stat) {
  444. const translations = {
  445. 'days_visited': '访问天数',
  446. 'likes_given': '给出的赞',
  447. 'likes_received': '收到的赞',
  448. 'post_count': '帖子数量',
  449. 'posts_read_count': '已读帖子',
  450. 'topics_entered': '已读主题',
  451. 'time_read': '阅读时间(秒)'
  452. };
  453. return translations[stat] || stat;
  454. }
  455. };
  456.  
  457. const EventHandler = {
  458. handleSearch: async function() {
  459. const username = UIManager.searchBox.value.trim();
  460. if (!username) return;
  461.  
  462. try {
  463. const [aboutData, summaryData, userData] = await Promise.all([
  464. DataManager.fetchAboutData(),
  465. DataManager.fetchSummaryData(username),
  466. DataManager.fetchUserData(username)
  467. ]);
  468. if (summaryData && userData && aboutData) {
  469. UIManager.updatePopupContent(summaryData.user_summary, summaryData.users ? summaryData.users[0] : { 'trust_level': 0 }, userData.user, aboutData.about.stats);
  470. }
  471. } catch (error) {
  472. console.error(error);
  473. }
  474. },
  475. // 更新拖动状态
  476. handleDragEnd: function() {
  477. UIManager.updateDragStatus(true);
  478. }
  479. };
  480.  
  481. // 添加技能分析
  482. function analyzeAbility(topCategories) {
  483. let resultStr = "<strong>技能分析🎯:</strong><br>";
  484. const icons = {
  485. "常规话题": "🌐",
  486. "wiki": "📚",
  487. "快问快答": "❓",
  488. "人工智能": "🤖",
  489. "周周热点": "🔥",
  490. "精华神贴": "✨",
  491. "高阶秘辛": "🔮",
  492. "读书成诗": "📖",
  493. "配置调优": "⚙️",
  494. "网络安全": "🔒",
  495. "软件分享": "💾",
  496. "软件开发": "💻",
  497. "嵌入式": "🔌",
  498. "机器学习": "🧠",
  499. "代码审查": "👀",
  500. "new-api": "🆕",
  501. "一机难求": "📱",
  502. "速来拼车": "🚗",
  503. "网络记忆": "💭",
  504. "非我莫属": "🏆",
  505. "赏金猎人": "💰",
  506. "搞七捻三": "🎲",
  507. "碎碎碎念": "🗨️",
  508. "金融经济": "💹",
  509. "新闻": "📰",
  510. "旅行": "✈️",
  511. "美食": "🍽️",
  512. "健身": "🏋️",
  513. "音乐": "🎵",
  514. "游戏": "🎮",
  515. "羊毛": "🐑",
  516. "树洞": "🌳",
  517. "病友": "🤒",
  518. "职场": "💼",
  519. "断舍离": "♻️",
  520. "二次元": "🎎",
  521. "运营反馈": "🔄",
  522. "老干部疗养院": "🛌",
  523. "活动": "🎉",
  524. };
  525. const totalScore = topCategories.reduce((sum, category) => sum + (category.topic_count * 2) + (category.post_count * 1), 0);
  526. topCategories.sort((a, b) => a.name.length - b.name.length);
  527. topCategories.forEach((category, index) => {
  528. const score = (category.topic_count * 2) + (category.post_count * 1);
  529. const percentage = ((score / totalScore) * 100).toFixed(1) + "%";
  530. let numStars;
  531. if (score >= 999) {
  532. numStars = 7; // 满分7颗红星
  533. } else {
  534. numStars = Math.round((score / 999) * 7); // 其他按比例显示
  535. }
  536. const stars = "❤️".repeat(numStars) + "🤍".repeat(7 - numStars); // 显示红星和空星
  537. let icon = icons[category.name] || "❓"; // 如果没有找到图标,显示默认图标
  538. resultStr += `
  539. <div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; opacity: 0; animation: fadeIn 0.5s forwards; animation-delay: ${index * 0.1}s; font-size: 13px;'>
  540. <div style='flex: 0 0 20px; text-align: center;'>${icon}</div>
  541. <div style='flex: 2; text-align: left;'>${category.name}</div>
  542. <div style='flex: 4; text-align: left;'>${stars}</div>
  543. <div style='flex: 1; text-align: right;'>${percentage}</div>
  544. </div>`;
  545. });
  546.  
  547. resultStr += `
  548. <style>
  549. @keyframes fadeIn {
  550. to { opacity: 1; }
  551. }
  552. </style>
  553. `;
  554.  
  555. return resultStr;
  556. }
  557.  
  558. // 添加含水率
  559. function updatePercentage() {
  560. return new Promise((resolve, reject) => {
  561. let badIds = [11, 16, 34, 17, 18, 19, 29, 36, 35, 22, 26, 25];
  562. const badScore = [];
  563. const goodScore = [];
  564. const urls = [
  565. 'https://linux.do/latest.json?order=created',
  566. 'https://linux.do/new.json',
  567. 'https://linux.do/top.json?period=daily'
  568. ];
  569.  
  570. Promise.all(urls.map(url => fetch(url).then(resp => resp.json())))
  571. .then(data => {
  572. data.forEach(({ topic_list: { topics } }) => {
  573. topics.forEach(topic => {
  574. const score = topic.posts_count + topic.like_count + topic.reply_count;
  575. (badIds.includes(topic.category_id) ? badScore : goodScore).push(score);
  576. });
  577. });
  578.  
  579. const badTotal = badScore.reduce((acc, curr) => acc + curr, 0);
  580. const goodTotal = goodScore.reduce((acc, curr) => acc + curr, 0);
  581. const percentage = (badTotal / (badTotal + goodTotal)) * 100;
  582.  
  583. resolve(percentage);
  584. })
  585. .catch(reject);
  586. });
  587. };
  588.  
  589. // 添加时间格式化
  590. function formatTimestamp(lastSeenAt) {
  591. // 解析时间戳并去除毫秒
  592. let timestamp = new Date(lastSeenAt);
  593.  
  594. // 使用Intl.DateTimeFormat格式化时间为上海时区
  595. let formatter = new Intl.DateTimeFormat('zh-CN', {
  596. timeZone: 'Asia/Shanghai',
  597. year: 'numeric',
  598. month: 'numeric',
  599. day: 'numeric',
  600. hour: 'numeric',
  601. minute: 'numeric',
  602. second: 'numeric',
  603. });
  604.  
  605. // 获取格式化后的字符串
  606. let formattedTimestamp = formatter.format(timestamp);
  607.  
  608. return formattedTimestamp;
  609. }
  610.  
  611. // 添加升级进度功能
  612. function summaryRequired(required, current, translateStat) {
  613. let summary = '<strong>升级进度🌟:</strong><br>';
  614.  
  615. summary += '<div class="summary-table">';
  616.  
  617. for (const stat in required) {
  618. if (required.hasOwnProperty(stat) && current.hasOwnProperty(stat)) {
  619. const reqValue = required[stat];
  620. const curValue = current[stat] || 0; // 使用 || 0 确保未定义的情况下使用0
  621. const percentage = Math.min((curValue / reqValue) * 100, 100); // 计算百分比
  622. let color = curValue >= reqValue ? '#28a745' : '#dc3545'; // 使用绿色或红色
  623.  
  624. summary += `
  625. <div class="summary-row">
  626. <div>${translateStat(stat)}</div>
  627. <div class="progress-bar" title="${curValue}/${reqValue}">
  628. <div class="progress-bar-fill" style="width: ${percentage}%; background-color: ${color};"></div>
  629. </div>
  630. <div class="progress-text">${curValue}/${reqValue}</div>
  631. </div>`;
  632. }
  633. }
  634.  
  635. summary += '</div>';
  636. return summary;
  637. }
  638.  
  639. // 添加拖动功能
  640. function addDraggableFeature(element) {
  641. let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  642. let isDragging = false;
  643.  
  644. const dragMouseDown = function(e) {
  645. // 检查事件的目标是否是输入框,按钮或其他可以忽略拖动逻辑的元素
  646. if (e.target.tagName.toUpperCase() === 'INPUT' || e.target.tagName.toUpperCase() === 'TEXTAREA' || e.target.tagName.toUpperCase() === 'BUTTON') {
  647. return; // 如果是,则不执行拖动逻辑
  648. }
  649.  
  650. e = e || window.event;
  651. e.preventDefault();
  652. pos3 = e.clientX;
  653. pos4 = e.clientY;
  654. document.onmouseup = closeDragElement;
  655. document.onmousemove = elementDrag;
  656. isDragging = true;
  657. };
  658.  
  659. const elementDrag = function(e) {
  660. if (!isDragging) return;
  661. e = e || window.event;
  662. e.preventDefault();
  663. pos1 = pos3 - e.clientX;
  664. pos2 = pos4 - e.clientY;
  665. pos3 = e.clientX;
  666. pos4 = e.clientY;
  667.  
  668. // 使用requestAnimationFrame优化拖动
  669. requestAnimationFrame(() => {
  670. element.style.top = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, element.offsetTop - pos2)) + "px";
  671. element.style.left = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, element.offsetLeft - pos1)) + "px";
  672. // 为了避免与拖动冲突,在此移除bottom和right样式
  673. element.style.bottom = '';
  674. element.style.right = '';
  675. });
  676. };
  677.  
  678. const closeDragElement = function() {
  679. document.onmouseup = null;
  680. document.onmousemove = null;
  681. isDragging = false;
  682. // 在拖动结束时更新拖动状态
  683. EventHandler.handleDragEnd();
  684. };
  685.  
  686. element.onmousedown = dragMouseDown;
  687. }
  688.  
  689. const init = () => {
  690. StyleManager.injectStyles();
  691. UIManager.initPopup();
  692. addDraggableFeature(document.getElementById('linuxDoLevelPopup')); // 确保已设置该ID
  693. UIManager.togglePopupSize(); // 初始最小化
  694. };
  695.  
  696. init();
  697.  
  698. })();