FFXIV Guide: Toggle language columns

Add language toggle buttons that work across multiple tables correctly + description toggle

  1. // ==UserScript==
  2. // @name FFXIV Guide: Toggle language columns
  3. // @name:ko FFXIV Guide: 언어별 세로단 토글
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.1.1
  6. // @description Add language toggle buttons that work across multiple tables correctly + description toggle
  7. // @description:ko 언어별로 세로단을 가리는 버튼을 추가하는 스크립트 입니다
  8. // @match https://ffxivguide.akurosia.org/translate/
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. const languageMap = {
  17. en: { label: '🇺🇸 EN', headers: ['Singular_en', 'Name_en', 'Description_en', 'Text_en'] },
  18. de: { label: '🇩🇪 DE', headers: ['Singular_de', 'Name_de', 'Description_de', 'Text_de'] },
  19. fr: { label: '🇫🇷 FR', headers: ['Singular_fr', 'Name_fr', 'Description_fr', 'Text_fr'] },
  20. ja: { label: '🇯🇵 JA', headers: ['Singular_ja', 'Name_ja', 'Description_ja', 'Text_ja'] },
  21. cn: { label: '🇨🇳 CN', headers: ['Singular_cn', 'Name_cn', 'Description_cn', 'Text_cn'] },
  22. ko: { label: '🇰🇷 KO', headers: ['Singular_ko', 'Name_ko', 'Description_ko', 'Text_ko'] },
  23. };
  24.  
  25. const hiddenLangs = new Set();
  26. const buttonRefs = {};
  27. let descriptionHidden = false;
  28.  
  29. function toggleLanguage(langKey) {
  30. const { headers } = languageMap[langKey];
  31. const shouldHide = !hiddenLangs.has(langKey);
  32.  
  33. const tables = document.querySelectorAll('table');
  34.  
  35. tables.forEach(table => {
  36. const ths = table.querySelectorAll('thead tr th');
  37. const indexes = [];
  38.  
  39. ths.forEach((th, i) => {
  40. const text = th.textContent.trim();
  41. if (headers.includes(text)) {
  42. if (descriptionHidden && text.startsWith('Description_')) return;
  43. indexes.push(i + 1);
  44. }
  45. });
  46.  
  47. indexes.forEach(index => {
  48. const cells = table.querySelectorAll(`th:nth-child(${index}), td:nth-child(${index})`);
  49. cells.forEach(cell => {
  50. cell.style.display = shouldHide ? 'none' : '';
  51. });
  52. });
  53. });
  54.  
  55. if (shouldHide) hiddenLangs.add(langKey);
  56. else hiddenLangs.delete(langKey);
  57.  
  58. updateButtonStyle(langKey);
  59. }
  60.  
  61. function updateButtonStyle(langKey) {
  62. const btn = buttonRefs[langKey];
  63. if (!btn) return;
  64. const isHidden = hiddenLangs.has(langKey);
  65. btn.style.opacity = isHidden ? '0.4' : '1';
  66. btn.style.backgroundColor = isHidden ? '#222' : '#444';
  67. }
  68.  
  69. function toggleDescriptionColumns() {
  70. descriptionHidden = !descriptionHidden;
  71.  
  72. const tables = document.querySelectorAll('table');
  73. tables.forEach(table => {
  74. const ths = table.querySelectorAll('thead tr th');
  75. const indexes = [];
  76.  
  77. ths.forEach((th, i) => {
  78. const text = th.textContent.trim();
  79. const match = text.match(/^Description_(\w{2})$/);
  80. if (match) {
  81. const langKey = match[1];
  82. if (!hiddenLangs.has(langKey)) {
  83. indexes.push(i + 1);
  84. }
  85. }
  86. });
  87.  
  88. indexes.forEach(index => {
  89. const cells = table.querySelectorAll(`th:nth-child(${index}), td:nth-child(${index})`);
  90. cells.forEach(cell => {
  91. cell.style.display = descriptionHidden ? 'none' : '';
  92. });
  93. });
  94. });
  95.  
  96. const btn = buttonRefs['description'];
  97. if (btn) {
  98. btn.style.opacity = descriptionHidden ? '0.4' : '1';
  99. btn.style.backgroundColor = descriptionHidden ? '#222' : '#444';
  100. }
  101. }
  102.  
  103. function insertToggleButtons() {
  104. const searchButton = document.getElementById('submit');
  105. if (!searchButton || !searchButton.parentElement) {
  106. setTimeout(insertToggleButtons, 300);
  107. return;
  108. }
  109.  
  110. const wrapper = document.createElement('div');
  111. wrapper.style.margin = '10px 0';
  112.  
  113. // Language buttons
  114. Object.entries(languageMap).forEach(([key, { label }]) => {
  115. const btn = document.createElement('button');
  116. btn.textContent = label;
  117. styleButton(btn);
  118. btn.addEventListener('click', () => toggleLanguage(key));
  119. wrapper.appendChild(btn);
  120. buttonRefs[key] = btn;
  121. });
  122.  
  123. // Description-only toggle
  124. const descBtn = document.createElement('button');
  125. descBtn.textContent = '📝 아이템 설명';
  126. styleButton(descBtn);
  127. descBtn.addEventListener('click', toggleDescriptionColumns);
  128. wrapper.appendChild(descBtn);
  129. buttonRefs['description'] = descBtn;
  130.  
  131. searchButton.parentElement.insertAdjacentElement('afterend', wrapper);
  132. }
  133.  
  134. function styleButton(btn) {
  135. btn.style.marginRight = '5px';
  136. btn.style.padding = '4px 8px';
  137. btn.style.fontSize = '12px';
  138. btn.style.cursor = 'pointer';
  139. btn.style.background = '#444';
  140. btn.style.color = '#fff';
  141. btn.style.border = '1px solid #888';
  142. btn.style.borderRadius = '4px';
  143. btn.style.transition = 'all 0.2s ease';
  144. }
  145.  
  146. insertToggleButtons();
  147. })();