|
1 | 1 | // ==UserScript==
|
2 | 2 | // @name UnityForumFixer
|
3 | 3 | // @namespace https://unitycoder.com/
|
4 |
| -// @version 0.72 (28.11.2024) |
| 4 | +// @version 0.73 (16.12.2024) |
5 | 5 | // @description Fixes For Unity Forums - https://github.com/unitycoder/UnityForumFixer
|
6 | 6 | // @author unitycoder.com
|
7 | 7 | // @match https://discussions.unity.com/latest
|
@@ -179,72 +179,69 @@ function NavBar()
|
179 | 179 |
|
180 | 180 | // FORUM VIEW
|
181 | 181 |
|
182 |
| -function TopicsViewShowOriginalPosterInfo() |
183 |
| -{ |
184 |
| - // Select all topic rows |
| 182 | +function TopicsViewShowOriginalPosterInfo() { |
185 | 183 | const topicRows = document.querySelectorAll('tr.topic-list-item');
|
186 | 184 |
|
187 | 185 | topicRows.forEach(row => {
|
188 |
| - // Find the first 'a' element inside the 'posters topic-list-data' cell that does not have the 'latest' class (Original Poster) |
189 |
| - let firstPosterLink = row.querySelector('td.posters.topic-list-data a:not(.latest)'); |
190 |
| - |
191 |
| - // If there is no such element, it might be a single poster with 'latest single' class |
192 |
| - if (!firstPosterLink) { |
193 |
| - firstPosterLink = row.querySelector('td.posters.topic-list-data a.latest.single'); |
194 |
| - } |
| 186 | + // Always take the first username in the posters column as the original poster |
| 187 | + const postersColumn = row.querySelector('td.posters.topic-list-data'); |
| 188 | + const firstPosterAvatar = postersColumn ? postersColumn.querySelector('a[data-user-card]') : null; |
195 | 189 |
|
196 |
| - if (firstPosterLink) { |
197 |
| - // Extract the username from the 'data-user-card' attribute for the original poster |
198 |
| - const originalPosterUsername = firstPosterLink.getAttribute('data-user-card'); |
| 190 | + if (firstPosterAvatar) { |
| 191 | + const originalPosterUsername = firstPosterAvatar.getAttribute('data-user-card'); |
199 | 192 |
|
200 |
| - // Find the topic creation date from the title attribute in the activity column |
| 193 | + // Extract creation date from the activity cell |
201 | 194 | const activityCell = row.querySelector('td.activity');
|
202 | 195 | const titleText = activityCell ? activityCell.getAttribute('title') : '';
|
203 | 196 | const creationDateMatch = titleText.match(/Created: (.+?)(?:\n|$)/);
|
204 | 197 |
|
205 |
| - let creationDateFormatted = 'Unknown'; // Default to "Unknown" if no date is found |
| 198 | + let creationDateFormatted = 'Unknown'; |
206 | 199 | if (creationDateMatch) {
|
207 | 200 | const creationDateStr = creationDateMatch[1];
|
208 | 201 | const creationDate = new Date(creationDateStr);
|
209 | 202 | creationDateFormatted = formatDateString(creationDate);
|
210 | 203 | }
|
211 | 204 |
|
212 |
| - // Find the 'link-bottom-line' element to insert the original poster's name and creation date before it |
| 205 | + // Insert the original poster info into the main-link cell |
213 | 206 | const linkBottomLine = row.querySelector('td.main-link .link-bottom-line');
|
214 | 207 | if (linkBottomLine && !row.querySelector('.original-poster-span')) {
|
215 |
| - // Create a new span element for the original poster's username and creation date |
216 | 208 | const originalPosterSpan = document.createElement('span');
|
217 |
| - originalPosterSpan.textContent = originalPosterUsername+","+creationDateFormatted; |
218 |
| - originalPosterSpan.className = 'original-poster-span'; // Adding a class to prevent duplication |
219 |
| - originalPosterSpan.style.display = 'block'; // Ensure it's placed as a block element |
| 209 | + originalPosterSpan.className = 'original-poster-span'; |
| 210 | + originalPosterSpan.style.display = 'block'; |
| 211 | + originalPosterSpan.textContent = `${originalPosterUsername}, ${creationDateFormatted}`; |
220 | 212 |
|
221 |
| - // Insert the original poster span before the link-bottom-line |
| 213 | + // Insert the span before the link-bottom-line |
222 | 214 | linkBottomLine.parentNode.insertBefore(originalPosterSpan, linkBottomLine);
|
223 | 215 | }
|
224 | 216 | }
|
225 | 217 |
|
226 |
| - // Find the most recent poster (always marked with 'latest') |
| 218 | + // Handle the latest poster's info |
227 | 219 | const latestPosterLink = row.querySelector('td.posters.topic-list-data a.latest');
|
228 | 220 | if (latestPosterLink) {
|
229 |
| - // Extract the username from the 'data-user-card' attribute |
230 | 221 | const latestPosterUsername = latestPosterLink.getAttribute('data-user-card');
|
231 |
| - |
232 |
| - // Find the 'post-activity' element |
233 | 222 | const postActivity = row.querySelector('td.activity .post-activity');
|
| 223 | + |
234 | 224 | if (postActivity && !row.querySelector('.latest-poster-span')) {
|
235 |
| - // Create a new span element for the latest poster's username |
236 | 225 | const latestPosterSpan = document.createElement('span');
|
| 226 | + latestPosterSpan.className = 'latest-poster-span'; |
| 227 | + latestPosterSpan.style.display = 'block'; |
237 | 228 | latestPosterSpan.textContent = latestPosterUsername;
|
238 |
| - latestPosterSpan.className = 'latest-poster-span'; // Adding a class to prevent duplication |
239 |
| - latestPosterSpan.style.display = 'block'; // Ensure it's placed as a block element |
240 | 229 |
|
241 |
| - // Insert the latest poster span before the <a> tag, placing it outside the link |
| 230 | + // Insert the latest poster span before the activity link |
242 | 231 | postActivity.parentNode.insertBefore(latestPosterSpan, postActivity);
|
243 | 232 | }
|
244 | 233 | }
|
245 | 234 | });
|
246 | 235 | }
|
247 | 236 |
|
| 237 | +// Utility function to format dates |
| 238 | +function formatDateString(date) { |
| 239 | + const options = { year: 'numeric', month: 'short', day: 'numeric' }; |
| 240 | + return date.toLocaleDateString('en-GB', options); |
| 241 | +} |
| 242 | + |
| 243 | + |
| 244 | + |
248 | 245 | function TopicsViewCombineViewAndReplyCounts()
|
249 | 246 | {
|
250 | 247 | // Select all rows in the topic list
|
@@ -290,8 +287,9 @@ function TopicsViewCombineViewAndReplyCounts()
|
290 | 287 | viewsHeader.remove(); // Remove the "Views" header
|
291 | 288 | }
|
292 | 289 | }
|
293 |
| - |
294 |
| -function FixPostActivityTime() { |
| 290 | + |
| 291 | + function FixPostActivityTime() |
| 292 | + { |
295 | 293 | document.querySelectorAll('.relative-date').forEach(function (el) {
|
296 | 294 | const dataTime = parseInt(el.getAttribute('data-time'), 10);
|
297 | 295 | if (!dataTime) return;
|
@@ -324,86 +322,105 @@ function FixPostActivityTime() {
|
324 | 322 | });
|
325 | 323 | }
|
326 | 324 |
|
327 |
| - let prevTopicId = ''; // Global variable to store the previously fetched topicId |
328 |
| - let currentTooltip = null; // Global variable to store the currently visible tooltip |
| 325 | +let prevTopicId = ''; // Global variable to store the previously fetched topicId |
| 326 | +let currentTooltip = null; // Global variable to store the currently visible tooltip |
| 327 | +let tooltipTarget = null; // Track the current element triggering the tooltip |
| 328 | +let hoverTimeout = null; // Timeout for delaying tooltip display |
| 329 | + |
| 330 | +// Initialize the mouseover event handler |
| 331 | +function OnMouseOverPostPreview() { |
| 332 | + document.querySelectorAll('a.title.raw-link.raw-topic-link[data-topic-id]').forEach(function (element) { |
| 333 | + const topicId = element.getAttribute('data-topic-id'); |
| 334 | + |
| 335 | + // Add mouseover event listener to the <a> elements only |
| 336 | + element.addEventListener('mouseover', function (event) { |
| 337 | + tooltipTarget = element; // Set the current tooltip target |
| 338 | + hoverTimeout = setTimeout(() => { |
| 339 | + if (tooltipTarget === element && topicId !== prevTopicId) { // Ensure the mouse is still over the same element |
| 340 | + fetchPostDataAndShowTooltip(event, topicId, element); |
| 341 | + } |
| 342 | + }, 250); // Delay tooltip display by 250ms |
| 343 | + }); |
329 | 344 |
|
| 345 | + // Add mouseout event listener to clear timeout and hide tooltip |
| 346 | + element.addEventListener('mouseout', function () { |
| 347 | + tooltipTarget = null; // Clear the current tooltip target |
| 348 | + clearTimeout(hoverTimeout); // Cancel the tooltip display timeout |
| 349 | + hideTooltip(); |
| 350 | + }); |
| 351 | + }); |
330 | 352 |
|
331 |
| - // Initialize the mouseover event handler |
332 |
| - function OnMouseOverPostPreview() { |
333 |
| - document.querySelectorAll('a.title.raw-link.raw-topic-link[data-topic-id]').forEach(function (element) { |
334 |
| - const topicId = element.getAttribute('data-topic-id'); |
| 353 | + // Add a global mousemove listener to hide the tooltip if the mouse moves outside |
| 354 | + document.addEventListener('mousemove', function (event) { |
| 355 | + if (currentTooltip && (!tooltipTarget || !tooltipTarget.contains(event.target))) { |
| 356 | + hideTooltip(); |
| 357 | + } |
| 358 | + }); |
| 359 | +} |
335 | 360 |
|
336 |
| - // Add mouseover event listener to the <a> elements only |
337 |
| - element.addEventListener('mouseover', function (event) { |
338 |
| - if (topicId !== prevTopicId) { // Check if the post data was already fetched |
339 |
| - fetchPostDataAndShowTooltip(event, topicId, element); |
340 |
| - } |
341 |
| - }); |
| 361 | +// Function to fetch data and display tooltip |
| 362 | +function fetchPostDataAndShowTooltip(event, topicId, element) { |
| 363 | + const url = `https://discussions.unity.com/t/${topicId}.json`; |
342 | 364 |
|
343 |
| - // Add mouseout event listener to hide tooltip |
344 |
| - element.addEventListener('mouseout', function () {hideTooltip();}); |
| 365 | + fetch(url) |
| 366 | + .then(response => response.json()) |
| 367 | + .then(data => { |
| 368 | + // Extract necessary data from JSON (limit to 250 characters) |
| 369 | + const rawPostContent = data['post_stream']['posts'][0]['cooked']; |
| 370 | + const postContent = rawPostContent.length > 250 ? rawPostContent.substring(0, 250) + "..." : rawPostContent; |
| 371 | + const plainText = stripHtmlTags(postContent); |
| 372 | + |
| 373 | + // Update the global variable to store the fetched topicId |
| 374 | + prevTopicId = topicId; |
| 375 | + |
| 376 | + // Create and position the tooltip based on the element's position |
| 377 | + showTooltip(element, plainText); |
| 378 | + }) |
| 379 | + .catch(error => { |
| 380 | + console.error('Error fetching post data:', error); |
345 | 381 | });
|
346 |
| - } |
| 382 | +} |
347 | 383 |
|
348 |
| - // Function to fetch data and display tooltip |
349 |
| - function fetchPostDataAndShowTooltip(event, topicId, element) |
350 |
| - { |
351 |
| - const url = `https://discussions.unity.com/t/${topicId}.json`; |
352 |
| - |
353 |
| - fetch(url) |
354 |
| - .then(response => response.json()) |
355 |
| - .then(data => { |
356 |
| - // Extract necessary data from JSON (limit to 250 characters) |
357 |
| - const rawPostContent = data['post_stream']['posts'][0]['cooked']; |
358 |
| - const postContent = rawPostContent.length > 250 ? rawPostContent.substring(0, 250) + "..." : rawPostContent; |
359 |
| - const plainText = stripHtmlTags(postContent); |
360 |
| - |
361 |
| - // Update the global variable to store the fetched topicId |
362 |
| - prevTopicId = topicId; |
363 |
| - |
364 |
| - // Create and position the tooltip based on the element's position |
365 |
| - showTooltip(element, plainText); |
366 |
| - }) |
367 |
| - .catch(error => { |
368 |
| - console.error('Error fetching post data:', error); |
369 |
| - }); |
370 |
| - } |
| 384 | +// Function to create and show the tooltip |
| 385 | +function showTooltip(element, content) { |
| 386 | + hideTooltip(); // Ensure any existing tooltip is removed first |
371 | 387 |
|
372 |
| - // Function to create and show the tooltip |
373 |
| - function showTooltip(element, content) { |
374 |
| - hideTooltip(); // Ensure any existing tooltip is removed first |
| 388 | + // Create a new tooltip element |
| 389 | + currentTooltip = createTooltip(content); |
375 | 390 |
|
376 |
| - // Create a new tooltip element |
377 |
| - currentTooltip = createTooltip(content); |
| 391 | + // Get the bounding rectangle of the <a> element |
| 392 | + const rect = element.getBoundingClientRect(); |
378 | 393 |
|
379 |
| - // Get the bounding rectangle of the <a> element |
380 |
| - const rect = element.getBoundingClientRect(); |
| 394 | + // Position the tooltip relative to the <a> element |
| 395 | + currentTooltip.style.top = `${window.scrollY + rect.top - currentTooltip.offsetHeight - 10}px`; // 10px above the element |
| 396 | + currentTooltip.style.left = `${window.scrollX + rect.left}px`; |
| 397 | +} |
381 | 398 |
|
382 |
| - // Position the tooltip relative to the <a> element |
383 |
| - currentTooltip.style.top = `${window.scrollY + rect.top - currentTooltip.offsetHeight - 10}px`; // 10px above the element |
384 |
| - currentTooltip.style.left = `${window.scrollX + rect.left}px`; |
385 |
| - |
386 |
| - currentTooltip.addEventListener('mouseover', function () { |
387 |
| - hideTooltip(); |
388 |
| - }); |
| 399 | +// Function to hide the tooltip |
| 400 | +function hideTooltip() { |
| 401 | + if (currentTooltip) { |
| 402 | + currentTooltip.remove(); |
| 403 | + currentTooltip = null; |
389 | 404 | }
|
| 405 | +} |
| 406 | + |
| 407 | +// Function to create a tooltip element |
| 408 | +function createTooltip(content) { |
| 409 | + const tooltip = document.createElement('div'); |
| 410 | + tooltip.className = 'custom-post-preview'; // Assign the CSS class |
| 411 | + tooltip.textContent = content; |
| 412 | + document.body.appendChild(tooltip); |
| 413 | + return tooltip; |
| 414 | +} |
| 415 | + |
| 416 | +// Function to strip HTML tags from a string |
| 417 | +function stripHtmlTags(html) { |
| 418 | + const tempDiv = document.createElement("div"); // Create a temporary <div> element |
| 419 | + tempDiv.innerHTML = html; // Set its inner HTML to the input HTML string |
| 420 | + return tempDiv.textContent || tempDiv.innerText || ""; // Return the text content without HTML tags |
| 421 | +} |
390 | 422 |
|
391 |
| - // Function to hide the tooltip |
392 |
| - function hideTooltip() { |
393 |
| - if (currentTooltip) { |
394 |
| - currentTooltip.remove(); |
395 |
| - currentTooltip = null; |
396 |
| - } |
397 |
| - } |
398 | 423 |
|
399 |
| - // Function to create a tooltip element |
400 |
| - function createTooltip(content) { |
401 |
| - const tooltip = document.createElement('div'); |
402 |
| - tooltip.className = 'custom-post-preview'; // Assign the CSS class |
403 |
| - tooltip.textContent = content; |
404 |
| - document.body.appendChild(tooltip); |
405 |
| - return tooltip; |
406 |
| - } |
407 | 424 |
|
408 | 425 | function stripHtmlTags(html)
|
409 | 426 | {
|
|
0 commit comments