├── LICENSE ├── README.md ├── WxPay.jpg ├── content.js ├── extension_icon128.png ├── extension_icon16.png ├── extension_icon32.png ├── extension_icon48.png ├── manifest.json └── previews.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 WL666 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![English](https://img.shields.io/badge/Language-English-blue)](#english-description) 3 | 4 | # Elsevier Tracker Chrome Plugins 5 | 6 | [![Elsevier](https://img.shields.io/badge/Elsevier-Supported-brightgreen)](#) 7 | [![IEEE](https://img.shields.io/badge/IEEE-Coming%20Soon-blue)](#) 8 | [![Springer](https://img.shields.io/badge/Springer-Coming%20Soon-blue)](#) 9 | [![Wiley](https://img.shields.io/badge/Wiley-Coming%20Soon-blue)](#) 10 | 11 | **Version: 1.0** 12 | **Description:** Helps users track the latest status and details of their submissions. 13 | 14 | --- 15 | 16 | ## 🚀 Overview 17 | 18 | ### Description {#description} 19 | Elsevier Tracker is a simple yet powerful tool designed to help authors track the latest status and updates of their manuscript submissions to Elsevier journals. This tool fetches real-time review progress and displays key submission details in a user-friendly interface. 20 | [Back to Top](#elsevier-tracker-chrome-plugins) 21 | 22 | --- 23 | 24 | ## 🎯 Features 25 | 26 | - **📌 Real-time Tracking:** 27 | Retrieve and display the latest status of submitted manuscripts. 28 | 29 | - **📜 Detailed Review Events:** 30 | View submission history, including revisions, updates, and review comments. 31 | 32 | - **🎨 User-friendly UI:** 33 | Clean and responsive interface with a toggleable display panel. 34 | 35 | - **🔍 Timestamp Formatting:** 36 | Converts Unix timestamps into a readable date-time format. 37 | 38 | --- 39 | 40 | ## 📦 Installation 41 | 42 | **Installation:** 43 | Download the plugin and install it to your Chrome browser. 44 | 45 | --- 46 | 47 | ## 🛠 Usage 48 | 49 | **Usage:** 50 | Simply open the tracking page with the UUID and it will be displayed automatically. 51 | Like this URL: https://track.authorhub.elsevier.com/?uuid=yourUUID 52 | --- 53 | 54 | ## 🎨 UI Preview 55 | 56 | **UI Preview:** 57 | A floating review status panel will be displayed on the page, allowing users to monitor their submission progress easily. 58 | 59 | ![Additional UI Screenshot](https://github.com/WL661/Elsevier-Tracker/blob/main/previews.jpg) 60 | 61 | --- 62 | 63 | 💖 Support My Project 64 | If you find this plugin useful, you can support my work by making a donation. Your support helps maintain and improve this tool. 65 | 66 | 🎁 Ways to Support: 67 | WeChat Pay: Scan the QR code below to make a donation. 68 | Sharing & Feedback: Share this plugin with others and provide feedback to help improve it. 69 | WeChat Pay QR Code: 70 | ![Additional UI Screenshot](https://github.com/WL661/Elsevier-Tracker/blob/main/WxPay.jpg) 71 | 72 | Every bit of support is greatly appreciated! Thank you! 😊 73 | 74 | 75 | --- 76 | 77 | ## 📜 License 78 | 79 | **License:** 80 | This project is licensed under the MIT License. 81 | -------------------------------------------------------------------------------- /WxPay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/WxPay.jpg -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | 2 | // 1. 获取 URL 参数中的 uuid 3 | const urlParams = new URLSearchParams(window.location.search); 4 | const uuid = urlParams.get('uuid'); 5 | console.log("当前页面的uuid:", uuid); 6 | 7 | // 2. 请求后端接口 8 | const url = `https://tnlkuelk67.execute-api.us-east-1.amazonaws.com/tracker/${uuid}`; 9 | fetch(url) 10 | .then(response => { 11 | if (!response.ok) { 12 | throw new Error('网络请求错误'); 13 | } 14 | return response.json(); 15 | }) 16 | .then(data => { 17 | console.log("当前页面的json:", data); 18 | displayReviewStatus(data); 19 | }) 20 | .catch(error => { 21 | console.error('获取JSON数据失败:', error); 22 | }); 23 | 24 | /** 25 | * 核心函数:生成并显示审稿状态面板 26 | */ 27 | function displayReviewStatus(data) { 28 | // 创建容器 29 | const container = document.createElement('div'); 30 | container.id = 'review-status-container'; 31 | container.style.cssText = ` 32 | position: fixed; 33 | top: 60px; 34 | right: 10px; 35 | width: 35vw; 36 | min-width: 480px; 37 | background-color: #f8f9fa; 38 | border: 1px solid #ccc; 39 | padding: 15px; 40 | z-index: 9999; 41 | max-height: 80vh; 42 | overflow-y: auto; 43 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 44 | border-radius: 4px; 45 | font-family: Arial, sans-serif; 46 | font-size: 14px; 47 | color: #333; 48 | display: block; 49 | `; 50 | 51 | // 标题 52 | const header = document.createElement('h2'); 53 | header.textContent = 'Elsevier 审稿状态'; 54 | header.style.cssText = ` 55 | font-size: 18px; 56 | margin: 0 0 10px; 57 | text-align: center; 58 | border-bottom: 1px solid #ccc; 59 | padding-bottom: 8px; 60 | `; 61 | container.appendChild(header); 62 | 63 | // 顶部信息(稿件标题、期刊名、状态等) 64 | const infoList = document.createElement('ul'); 65 | infoList.style.cssText = ` 66 | margin: 0; 67 | padding: 0; 68 | list-style: none; 69 | margin-bottom: 10px; 70 | `; 71 | const fields = [ 72 | 'ManuscriptTitle', 73 | 'JournalName', 74 | 'Status', 75 | 'SubmissionDate', 76 | 'LatestRevisionNumber' 77 | ]; 78 | fields.forEach(field => { 79 | if (data[field] !== undefined) { 80 | const li = document.createElement('li'); 81 | li.style.marginBottom = '5px'; 82 | let value = data[field]; 83 | // 如果是时间戳字段,转成对应时区 84 | if (field === 'SubmissionDate' || field === 'LastUpdated') { 85 | const date = toUTC8DateFromUTC3(value); 86 | value = formatDateTime(date); 87 | } 88 | li.textContent = `${field}: ${value}`; 89 | infoList.appendChild(li); 90 | } 91 | }); 92 | container.appendChild(infoList); 93 | 94 | // 拿到 ReviewEvents 并按 Revision 分组 95 | const revisionMap = {}; 96 | if (Array.isArray(data.ReviewEvents)) { 97 | data.ReviewEvents.forEach(evt => { 98 | const rev = evt.Revision; 99 | if (!revisionMap[rev]) { 100 | revisionMap[rev] = []; 101 | } 102 | revisionMap[rev].push(evt); 103 | }); 104 | } 105 | 106 | // 取出所有 revision,并按升序排列 107 | const allRevisions = Object.keys(revisionMap) 108 | .map(x => Number(x)) 109 | .sort((a, b) => a - b); 110 | 111 | // 最新修订号 112 | const latestRevision = data.LatestRevisionNumber; 113 | 114 | // 遍历每个修订版本,生成折叠菜单 115 | allRevisions.forEach(rev => { 116 | // 每个版本的外层容器(含标题和内容区) 117 | const revisionContainer = document.createElement('div'); 118 | revisionContainer.style.marginBottom = '10px'; 119 | 120 | // 审稿事件 121 | const events = revisionMap[rev] || []; 122 | 123 | // 统计该修订下的各种事件次数(Completed / Accepted / Invited) 124 | const completedCount = events.filter(e => e.Event === 'REVIEWER_COMPLETED').length; 125 | const acceptedCount = events.filter(e => e.Event === 'REVIEWER_ACCEPTED').length; 126 | const invitedCount = events.filter(e => e.Event === 'REVIEWER_INVITED').length; 127 | 128 | // 标题(点击可展开/折叠),带上颜色和统计信息 129 | const revHeader = document.createElement('div'); 130 | revHeader.innerHTML = `Revision ${rev} ( 131 | Completed: ${completedCount}, 132 | Accepted: ${acceptedCount}, 133 | Invited: ${invitedCount} 134 | )`; 135 | revHeader.style.cssText = ` 136 | font-size: 16px; 137 | font-weight: bold; 138 | margin: 10px 0; 139 | padding: 8px; 140 | border: 1px solid #ddd; 141 | background-color: #f1f1f1; 142 | cursor: pointer; 143 | `; 144 | revisionContainer.appendChild(revHeader); 145 | 146 | // 内容区:默认只展开最新修订 147 | const contentDiv = document.createElement('div'); 148 | contentDiv.style.cssText = ` 149 | border: 1px solid #ddd; 150 | border-top: none; 151 | padding: 10px; 152 | `; 153 | if (rev === latestRevision) { 154 | contentDiv.style.display = 'block'; 155 | } else { 156 | contentDiv.style.display = 'none'; 157 | } 158 | 159 | // 点击标题切换展开/折叠 160 | revHeader.addEventListener('click', () => { 161 | contentDiv.style.display = 162 | (contentDiv.style.display === 'none') ? 'block' : 'none'; 163 | }); 164 | 165 | // 如果该修订没有审稿事件 166 | if (!events.length) { 167 | const noData = document.createElement('p'); 168 | noData.textContent = '该修订阶段暂无审稿事件。'; 169 | contentDiv.appendChild(noData); 170 | } else { 171 | // 按 Reviewer 分析事件 172 | const reviewersMap = {}; 173 | events.forEach(event => { 174 | const rId = event.Id; 175 | if (!reviewersMap[rId]) { 176 | reviewersMap[rId] = { 177 | invitedDate: null, 178 | acceptedDate: null, 179 | completedDate: null 180 | }; 181 | } 182 | // 把原本UTC+3 的时间戳,再加5小时变为 UTC+8 183 | const eventDate = toUTC8DateFromUTC3(event.Date); 184 | 185 | if (event.Event === 'REVIEWER_INVITED') { 186 | reviewersMap[rId].invitedDate ||= eventDate; 187 | } else if (event.Event === 'REVIEWER_ACCEPTED') { 188 | reviewersMap[rId].acceptedDate ||= eventDate; 189 | } else if (event.Event === 'REVIEWER_COMPLETED') { 190 | reviewersMap[rId].completedDate ||= eventDate; 191 | } 192 | }); 193 | 194 | // 排序 reviewer Id 195 | const sortedReviewerIds = Object.keys(reviewersMap) 196 | .sort((a, b) => Number(a) - Number(b)); 197 | 198 | // 逐个 reviewer 生成卡片 199 | sortedReviewerIds.forEach((rId, idx) => { 200 | const { invitedDate, acceptedDate, completedDate } = reviewersMap[rId]; 201 | // 当前时间也做相同处理(UTC+3转到UTC+8) 202 | const now = new Date(new Date().getTime() + 5 * 3600 * 1000); 203 | 204 | // 计算 responseTime, reviewTime 205 | const responseTimeDays = invitedDate ? diffInDays(invitedDate, acceptedDate || now) : ''; 206 | let reviewTimeDays = ''; 207 | if (acceptedDate) { 208 | reviewTimeDays = diffInDays(acceptedDate, completedDate || now); 209 | } 210 | 211 | // 计算状态 212 | let status = 'Invited'; 213 | if (acceptedDate && !completedDate) { 214 | status = 'In Review'; 215 | } else if (completedDate) { 216 | status = 'Completed'; 217 | } 218 | 219 | // 卡片DOM 220 | const card = document.createElement('div'); 221 | card.style.cssText = ` 222 | background-color: #fff; 223 | border: 1px solid #eee; 224 | border-radius: 6px; 225 | padding: 16px; 226 | margin-bottom: 12px; 227 | box-shadow: 0 1px 2px rgba(0,0,0,0.05); 228 | display: flex; 229 | align-items: center; 230 | justify-content: space-between; 231 | `; 232 | 233 | // 左侧:Reviewer及邀请/接受/完成时间 234 | const leftCol = document.createElement('div'); 235 | leftCol.style.cssText = ` 236 | display: flex; 237 | flex-direction: column; 238 | justify-content: center; 239 | min-width: 120px; 240 | `; 241 | const reviewerTitle = document.createElement('div'); 242 | reviewerTitle.textContent = `Reviewer #${idx + 1}`; 243 | reviewerTitle.style.cssText = ` 244 | font-size: 15px; 245 | font-weight: bold; 246 | margin-bottom: 8px; 247 | `; 248 | leftCol.appendChild(reviewerTitle); 249 | 250 | const invitedLine = document.createElement('div'); 251 | invitedLine.textContent = `Invited: ${formatDateTime(invitedDate)}`; 252 | invitedLine.style.marginBottom = '4px'; 253 | leftCol.appendChild(invitedLine); 254 | 255 | const acceptedLine = document.createElement('div'); 256 | acceptedLine.textContent = `Accepted: ${formatDateTime(acceptedDate)}`; 257 | acceptedLine.style.marginBottom = '4px'; 258 | leftCol.appendChild(acceptedLine); 259 | 260 | const completedLine = document.createElement('div'); 261 | completedLine.textContent = `Completed: ${formatDateTime(completedDate)}`; 262 | leftCol.appendChild(completedLine); 263 | 264 | card.appendChild(leftCol); 265 | 266 | // 中列1:Response Time 267 | const middleCol1 = document.createElement('div'); 268 | middleCol1.style.cssText = ` 269 | text-align: center; 270 | flex: 1; 271 | `; 272 | const responseLabel = document.createElement('div'); 273 | responseLabel.textContent = 'Response Time'; 274 | responseLabel.style.cssText = 'color: #6c757d; margin-bottom: 6px; font-size: 13px;'; 275 | middleCol1.appendChild(responseLabel); 276 | 277 | const responseValue = document.createElement('div'); 278 | responseValue.textContent = (responseTimeDays !== '') ? `${responseTimeDays} days` : '--'; 279 | responseValue.style.cssText = ` 280 | font-weight: bold; 281 | font-size: 16px; 282 | `; 283 | middleCol1.appendChild(responseValue); 284 | card.appendChild(middleCol1); 285 | 286 | // 中列2:Review Time 287 | const middleCol2 = document.createElement('div'); 288 | middleCol2.style.cssText = ` 289 | text-align: center; 290 | flex: 1; 291 | `; 292 | const reviewLabel = document.createElement('div'); 293 | reviewLabel.textContent = 'Review Time'; 294 | reviewLabel.style.cssText = 'color: #6c757d; margin-bottom: 6px; font-size: 13px;'; 295 | middleCol2.appendChild(reviewLabel); 296 | 297 | const reviewValue = document.createElement('div'); 298 | if (!acceptedDate) { 299 | reviewValue.textContent = '--'; 300 | } else { 301 | reviewValue.textContent = (reviewTimeDays !== '') ? `${reviewTimeDays} days` : 'In Progress'; 302 | } 303 | reviewValue.style.cssText = ` 304 | font-weight: bold; 305 | font-size: 16px; 306 | `; 307 | middleCol2.appendChild(reviewValue); 308 | card.appendChild(middleCol2); 309 | 310 | // 右侧:Status 311 | const rightCol = document.createElement('div'); 312 | rightCol.style.cssText = ` 313 | text-align: right; 314 | min-width: 70px; 315 | `; 316 | const statusLabel = document.createElement('div'); 317 | statusLabel.textContent = 'Status'; 318 | statusLabel.style.cssText = 'color: #6c757d; margin-bottom: 6px; font-size: 13px;'; 319 | rightCol.appendChild(statusLabel); 320 | 321 | const statusValue = document.createElement('div'); 322 | statusValue.textContent = status; 323 | if (status === 'Completed') { 324 | statusValue.style.color = '#28a745'; 325 | } else if (status === 'In Review') { 326 | statusValue.style.color = '#0d6efd'; 327 | } else { 328 | statusValue.style.color = '#6c757d'; 329 | } 330 | statusValue.style.cssText += ` 331 | font-weight: bold; 332 | font-size: 16px; 333 | `; 334 | rightCol.appendChild(statusValue); 335 | card.appendChild(rightCol); 336 | 337 | // 卡片加入 contentDiv 338 | contentDiv.appendChild(card); 339 | }); 340 | } 341 | 342 | // 内容区加入修订容器,再放到总容器 343 | revisionContainer.appendChild(contentDiv); 344 | container.appendChild(revisionContainer); 345 | }); 346 | 347 | // 将审稿状态容器加入页面 348 | document.body.appendChild(container); 349 | 350 | // 创建“显示/隐藏审稿状态”按钮 351 | createToggleButton(container); 352 | } 353 | 354 | /** 355 | * 将原本 UTC+3 的时间戳(秒)转换为 UTC+8 的 Date 对象 356 | */ 357 | function toUTC8DateFromUTC3(timestampInSeconds) { 358 | if (!timestampInSeconds) return null; 359 | let d = new Date(timestampInSeconds * 1000); 360 | d = new Date(d.getTime() + 5 * 3600 * 1000); 361 | return d; 362 | } 363 | 364 | /** 365 | * 工具函数:格式化日期到秒(yyyy/mm/dd HH:MM:SS) 366 | */ 367 | function formatDateTime(dateObj) { 368 | if (!dateObj || isNaN(dateObj)) return ''; 369 | const y = dateObj.getFullYear(); 370 | const m = String(dateObj.getMonth() + 1).padStart(2, '0'); 371 | const d = String(dateObj.getDate()).padStart(2, '0'); 372 | const hh = String(dateObj.getHours()).padStart(2, '0'); 373 | const mm = String(dateObj.getMinutes()).padStart(2, '0'); 374 | const ss = String(dateObj.getSeconds()).padStart(2, '0'); 375 | return `${y}/${m}/${d} ${hh}:${mm}:${ss}`; 376 | } 377 | 378 | /** 379 | * 计算两个日期之间的天数,四舍五入 380 | */ 381 | function diffInDays(d1, d2) { 382 | if (!d1 || !d2) return ''; 383 | const msInOneDay = 24 * 60 * 60 * 1000; 384 | return Math.round((d2 - d1) / msInOneDay); 385 | } 386 | 387 | /** 388 | * 创建“显示/隐藏审稿状态”按钮 389 | */ 390 | function createToggleButton(container) { 391 | const toggleButton = document.createElement('button'); 392 | toggleButton.id = 'toggle-button'; 393 | toggleButton.textContent = '显示/隐藏审稿状态'; 394 | toggleButton.style.cssText = ` 395 | position: fixed; 396 | top: 10px; 397 | right: 10px; 398 | z-index: 10000; 399 | padding: 8px 12px; 400 | border: none; 401 | background-color: #007BFF; 402 | color: #fff; 403 | border-radius: 4px; 404 | font-size: 14px; 405 | cursor: pointer; 406 | box-shadow: 0 2px 4px rgba(0,0,0,0.2); 407 | transition: background-color 0.3s; 408 | `; 409 | toggleButton.addEventListener('mouseenter', () => { 410 | toggleButton.style.backgroundColor = '#0056b3'; 411 | }); 412 | toggleButton.addEventListener('mouseleave', () => { 413 | toggleButton.style.backgroundColor = '#007BFF'; 414 | }); 415 | document.body.appendChild(toggleButton); 416 | 417 | toggleButton.addEventListener('click', () => { 418 | container.style.display = (container.style.display === 'none') ? 'block' : 'none'; 419 | }); 420 | } 421 | -------------------------------------------------------------------------------- /extension_icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/extension_icon128.png -------------------------------------------------------------------------------- /extension_icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/extension_icon16.png -------------------------------------------------------------------------------- /extension_icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/extension_icon32.png -------------------------------------------------------------------------------- /extension_icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/extension_icon48.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Elsevier tracker", 4 | "version": "1.0", 5 | "description": "Helps users track the latest status and details of their submissions.", 6 | "host_permissions": [ 7 | "https://tnlkuelk67.execute-api.us-east-1.amazonaws.com/*", 8 | "*://track.authorhub.elsevier.com/*" 9 | ], 10 | "content_scripts": [ 11 | { 12 | "matches": ["https://track.authorhub.elsevier.com/*"], 13 | "js": ["content.js"], 14 | "run_at": "document_end" 15 | } 16 | ], 17 | "icons": { 18 | "16": "extension_icon16.png", 19 | "32": "extension_icon32.png", 20 | "48": "extension_icon48.png", 21 | "128": "extension_icon128.png" 22 | } 23 | } -------------------------------------------------------------------------------- /previews.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WL661/Elsevier-Tracker/d97580e7922f2047574c47a2f07d1197966ec444/previews.jpg --------------------------------------------------------------------------------