├── 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-description)
3 |
4 | # Elsevier Tracker Chrome Plugins
5 |
6 | [](#)
7 | [](#)
8 | [](#)
9 | [](#)
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 | 
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 | 
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
--------------------------------------------------------------------------------