通用论坛屏蔽插件

通用的论坛贴子/用户屏蔽工具

目前為 2025-01-12 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 通用论坛屏蔽插件
  3. // @name:en Universal Forum Block
  4. // @namespace https://github.com/Heavrnl/UniversalForumBlock
  5. // @version 1.1.1
  6. // @description 通用的论坛贴子/用户屏蔽工具
  7. // @description:en Universal forum post/user blocking tool
  8. // @author Heavrnl
  9. // @match *://*/*
  10. // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogICAgPCEtLSBGaWx0ZXIgTGluZXMgLS0+DQogICAgPGcgc3Ryb2tlPSIjMjE5NmYzIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+DQogICAgICAgIDxsaW5lIHgxPSI4IiB5MT0iMTAiIHgyPSIyNCIgeTI9IjEwIiBvcGFjaXR5PSIwLjE1Ii8+DQogICAgICAgIDxsaW5lIHgxPSI2IiB5MT0iMTYiIHgyPSIyNiIgeTI9IjE2IiBvcGFjaXR5PSIwLjE1Ii8+DQogICAgICAgIDxsaW5lIHgxPSI4IiB5MT0iMjIiIHgyPSIyNCIgeTI9IjIyIiBvcGFjaXR5PSIwLjE1Ii8+DQogICAgPC9nPg0KICAgIA0KICAgIDwhLS0gQmxvY2sgU3ltYm9sIC0tPg0KICAgIDxnIHN0cm9rZT0iIzIxOTZmMyIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiPg0KICAgICAgICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjIyIiB5Mj0iMTYiIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIvPg0KICAgICAgICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjIyIiB5Mj0iMTYiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiLz4NCiAgICA8L2c+DQo8L3N2Zz4g
  11. // @grant GM_addStyle
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_registerMenuCommand
  16. // @connect *
  17. // @license MIT
  18. // ==/UserScript==
  19. (function() {
  20. 'use strict';
  21. if (window.top !== window.self) {
  22. return;
  23. }
  24. let panelVisible = true;
  25. GM_registerMenuCommand("显示/隐藏面板", function() {
  26. panelVisible = !panelVisible;
  27. const panel = document.getElementById('forum-filter-panel');
  28. if (panel) {
  29. panel.style.display = panelVisible ? 'block' : 'none';
  30. }
  31. GM_setValue('panelVisible', panelVisible);
  32. });
  33. panelVisible = GM_getValue('panelVisible', true);
  34. GM_addStyle(` #forum-filter-panel { position: fixed; bottom: 0; z-index: 9999; background: #fff; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 0 10px rgba(0,0,0,0.1); font-family: Arial, sans-serif; transition: all 0.3s ease; width: 400px; user-select: none; transform: translateY(calc(100% - 25px)); } #forum-filter-panel:not(.click-mode):hover, #forum-filter-panel:not(.click-mode):focus-within { transform: translateY(0); width: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 290 : 400; max-width: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? Math.min(290, window.innerWidth * 0.9) : undefined; } #forum-filter-panel.click-mode.expanded { transform: translateY(0) !important; width: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 290 : 400; max-width: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? Math.min(290, window.innerWidth * 0.9) : undefined; } #forum-filter-panel.click-mode:not(.expanded), #forum-filter-panel:not(.click-mode):not(:hover):not(:focus-within) { width: 50px; max-width: 200px; min-width: 30px; } #forum-filter-panel:not(.click-mode):hover .panel-content, #forum-filter-panel:not(.click-mode):focus-within .panel-content, #forum-filter-panel.click-mode.expanded .panel-content { opacity: 1; visibility: visible; pointer-events: auto; } #forum-filter-panel:not(.click-mode):not(:hover):not(:focus-within) .panel-content, #forum-filter-panel.click-mode:not(.expanded) .panel-content { opacity: 0; visibility: hidden; pointer-events: none; } .panel-settings-btn { display: flex !important; align-items: center !important; justify-content: center !important; padding: 6px 10px !important; background: #2196F3 !important; color: white !important; border: none !important; border-radius: 4px !important; cursor: pointer !important; font-size: 13px !important; font-weight: 500 !important; transition: all 0.2s ease !important; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1) !important; text-decoration: none !important; user-select: none !important; position: absolute !important; top: 10px !important; right: 10px !important; } .panel-settings-btn:hover { background: #1976D2 !important; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15) !important; transform: translateY(-1px) !important; } .panel-settings-btn:active { background: #1565C0 !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; transform: translateY(0) !important; } .panel-settings-btn::before { margin-right: 6px !important; font-size: 16px !important; } .panel-content { padding: 10px 15px; padding-top: 30px !important; max-height: 600px; overflow-y: auto; background: #fff; border-radius: 4px; transition: opacity 0.2s ease, visibility 0.2s ease; position: relative; } .array-editor-toggle { width: 100%; padding: 6px; background: white; border: none; border-radius: 3px; text-align: left; cursor: pointer; display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; color: #000; font-weight: 600; font-size: 14px; font:bold 14px Arial, sans-serif !important; } .domain-enabled-label { font-size: 14px !important; color: #333 !important; display: flex !important; align-items: center !important; } .domain-enabled-label #domain-enabled { font-size: 14px !important; color: #333 !important; margin-right: 6px !important; } .array-editor-toggle:hover { background: #eee; } #forum-filter-panel .config-section-toggle.collapsed { margin: 3px; padding: 4px 8px; border: none; background: #f9f9f9; cursor: pointer; width: calc(100% - 6px); text-align: left; } #forum-filter-panel .config-section-toggle.collapsed:hover { background: #e5e5e5; } .panel-tab { box-sizing: content-box !important; padding: 3px; background: #f5f5f5; border-bottom: 1px solid #ddd; cursor: pointer; display: flex; justify-content: center; align-items: center; font-size: 14px; height: 14px; white-space: nowrap; overflow: hidden; } .panel-tab:hover { background: #e8e8e8; } #forum-filter-panel label { display: block; margin: 5px 0; } #forum-filter-settings { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.2); z-index: 10000; display: none; min-width: 300px; max-width: 90%; max-height: 90vh; overflow-y: auto; } #forum-filter-settings.visible { display: block; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } #forum-filter-settings h3 { margin: 0 0 20px 0; padding-bottom: 15px; border-bottom: 2px solid #f0f0f0; color: #333; font-size: 18px; font-weight: 600; text-align: center; } #forum-filter-settings .setting-group { margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 6px; transition: all 0.2s ease; } #forum-filter-settings .setting-group:hover { background: #f0f2f5; } #forum-filter-settings .setting-group label { display: block; margin-bottom: 8px; color: #444; font-weight: 600; font-size: 14px; } #forum-filter-settings select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; color: #333; background: #fff; cursor: pointer; transition: all 0.2s ease; } #forum-filter-settings select:hover { border-color: #2196F3; } #forum-filter-settings select:focus { border-color: #2196F3; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1); outline: none; } #forum-filter-settings input[type="range"] { -webkit-appearance: none; width: 100%; height: 4px; background: #ddd; border-radius: 2px; outline: none; margin: 15px 0; padding: 0; position: relative; } #forum-filter-settings .position-value, #forum-filter-settings .collapsed-width-value, #forum-filter-settings .expanded-width-value { text-align: center; font-size: 13px; color: #666; margin-top: 5px; } #forum-filter-settings .buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 25px; padding-top: 15px; border-top: 1px solid #eee; } #forum-filter-settings .buttons button { padding: 8px 20px; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } #forum-filter-settings .buttons button#settings-cancel { background: #f5f5f5; color: #666; } #forum-filter-settings .buttons button#settings-cancel:hover { background: #e0e0e0; color: #333; } #forum-filter-settings .buttons button#settings-save { background: #2196F3; color: white; } #forum-filter-settings .buttons button#settings-save:hover { background: #1976D2; } #settings-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 9999; display: none; animation: fadeOverlay 0.3s ease; } @keyframes fadeOverlay { from { opacity: 0; } to { opacity: 1; } } #forum-filter-settings input[type="range"] { -webkit-appearance: none; width: 100%; height: 4px; background: #ddd; border-radius: 2px; outline: none; margin: 15px 0; } #forum-filter-settings input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; background: #2196F3; border-radius: 50%; cursor: pointer; transition: all 0.2s ease; margin-top: -7px; } #forum-filter-settings input[type="range"]::-webkit-slider-thumb:hover { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; background: #2196F3; border: none; border-radius: 50%; cursor: pointer; transition: all 0.2s ease; } #forum-filter-settings input[type="range"]::-moz-range-thumb:hover { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]::-ms-thumb { width: 18px; height: 18px; background: #2196F3; border: none; border-radius: 50%; cursor: pointer; transition: all 0.2s ease; margin-top: 0; } #forum-filter-settings input[type="range"]::-ms-thumb:hover { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]::-webkit-slider-runnable-track { width: 100%; height: 4px; background: #ddd; border-radius: 2px; cursor: pointer; border: none; } #forum-filter-settings input[type="range"]::-moz-range-track { width: 100%; height: 4px; background: #ddd; border-radius: 2px; cursor: pointer; border: none; } #forum-filter-settings input[type="range"]::-ms-track { width: 100%; height: 4px; background: transparent; border-color: transparent; color: transparent; cursor: pointer; } #forum-filter-settings input[type="range"]::-ms-fill-lower { background: #2196F3; border-radius: 2px; border: none; } #forum-filter-settings input[type="range"]:hover::-webkit-slider-thumb { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); } #forum-filter-settings input[type="range"]:hover::-moz-range-thumb { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]:active::-moz-range-thumb { transform: scale(1.2); } #forum-filter-settings input[type="range"]:hover::-ms-thumb { background: #1976D2; transform: scale(1.1); } #forum-filter-settings input[type="range"]:active::-ms-thumb { transform: scale(1.2); } #forum-filter-settings input[type="range"]::-ms-fill-upper { background: #ddd; border-radius: 2px; border: none; } .domain-info { margin-bottom: 15px !important; padding: 12px !important; border-bottom: 1px solid #eee !important; background: #f8f9fa !important; border-radius: 4px !important; position: relative !important; text-align: left !important; } .domain-info h4 { margin: 0 0 8px 0 !important; color: #333 !important; font-size: 14px !important; font-weight: 600 !important; } .domain-info .page-type { margin-bottom: 10px !important; font-size: 13px !important; color: #666 !important; } .domain-info #page-type-value { font-weight: 600 !important; color: #2196F3 !important; } .domain-enable-row { display: flex !important; align-items: center !important; gap: 8px !important; padding: 8px 12px !important; background: #f8f9fa !important; border-radius: 4px !important; margin: 10px 0 !important; border: 1px solid #e0e0e0 !important; transition: all 0.2s ease !important; } .domain-enable-row:hover { background: #f0f2f5 !important; border-color: #2196F3 !important; } #domain-enabled { position: relative !important; width: 16px !important; height: 16px !important; margin: 0 !important; padding: 0 !important; cursor: pointer !important; -webkit-appearance: none !important; -moz-appearance: none !important; appearance: none !important; border: 2px solid #ccc !important; border-radius: 3px !important; background: white !important; transition: all 0.2s ease-in-out !important; vertical-align: middle !important; } #domain-enabled:checked { background: #2196F3 !important; border-color: #2196F3 !important; } #domain-enabled:checked::after { content: '' !important; position: absolute !important; left: 4px !important; top: 1px !important; width: 4px !important; height: 8px !important; border: solid white !important; border-width: 0 2px 2px 0 !important; transform: rotate(45deg) !important; } #domain-enabled:hover { border-color: #2196F3 !important; } #domain-enabled:focus { outline: none !important; box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.2) !important; } #domain-enabled + label { margin: 0 !important; padding: 0 !important; cursor: pointer !important; user-select: none !important; font-size: 14px !important; color: #333 !important; line-height: 16px !important; display: inline-flex !important; align-items: center !important; } .domain-info .domain-enable-row label { flex: 1 !important; display: flex !important; align-items: center !important; margin: 0 !important; font-size: 13px !important; color: #333 !important; cursor: pointer !important; user-select: none !important; position: relative !important; padding-left: 32px !important; min-height: 24px !important; line-height: 24px !important; font-weight: 600 !important; } .domain-info .domain-enable-row input[type="checkbox"] { position: absolute !important; opacity: 0 !important; cursor: pointer !important; height: 0 !important; width: 0 !important; } .domain-info .domain-enable-row label:before { content: '' !important; position: absolute !important; left: 0 !important; top: 50% !important; transform: translateY(-50%) !important; width: 22px !important; height: 22px !important; border: 2px solid #ccc !important; border-radius: 4px !important; background-color: #fff !important; transition: all 0.2s ease-in-out !important; box-sizing: border-box !important; } .domain-info .domain-enable-row label:after { content: '' !important; position: absolute !important; left: 7px !important; top: 50% !important; transform: translateY(-65%) rotate(45deg) !important; width: 8px !important; height: 12px !important; border: solid white !important; border-width: 0 2px 2px 0 !important; opacity: 0 !important; transition: all 0.2s ease-in-out !important; } .domain-info .domain-enable-row input[type="checkbox"]:checked + label:before { background-color: #4CAF50 !important; border-color: #4CAF50 !important; } .domain-info .domain-enable-row input[type="checkbox"]:checked + label:after { opacity: 1 !important; } .domain-info .domain-enable-row input[type="checkbox"]:focus + label:before { box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.2) !important; } .domain-info .domain-enable-row label:hover:before { border-color: #4CAF50 !important; } .config-section,.config-section-toggle.collapsed { margin: 8px 0; padding: 6px; background: #f9f9f9; border-radius: 4px; border: 1px solid #eee; overflow: hidden !important; } .config-section > * { max-width: 100% !important; box-sizing: border-box !important; overflow-x: hidden !important; } .array-editor > * { max-width: 100% !important; box-sizing: border-box !important; overflow-x: hidden !important; } .button-group > * { max-width: 100% !important; box-sizing: border-box !important; overflow-x: hidden !important; } .config-section[data-section="global"] { border-left: 4px solid #2196F3; } .config-section[data-section="keywords"] { border-left: 4px solid #4CAF50; } .config-section[data-section="usernames"] { border-left: 4px solid #FF9800; } .config-section[data-section="url"] { border-left: 4px solid #9C27B0; } .config-section[data-section="xpath"] { border-left: 4px solid #E91E63; } .config-section[data-section="sync"] { border-left: 4px solid #00BCD4; } .config-section[data-section="global"] .array-item { border-left-color: #2196F3 !important; } .config-section[data-section="keywords"] .array-item { border-left-color: #4CAF50 !important; } .config-section[data-section="usernames"] .array-item { border-left-color: #FF9800 !important; } .config-section[data-section="url"] .array-item { border-left-color: #9C27B0 !important; } .config-section[data-section="global"] .array-editor-toggle { color: #1565C0; } .config-section[data-section="keywords"] .array-editor-toggle { color: #2E7D32; } .config-section[data-section="usernames"] .array-editor-toggle { color: #E65100; } .config-section[data-section="url"] .array-editor-toggle { color: #6A1B9A; } .config-section[data-section="xpath"] .array-editor-toggle { color: #C2185B; } .config-section[data-section="global"] .config-section-toggle { background: #E3F2FD; color: #1565C0; } .config-section[data-section="keywords"] .config-section-toggle { background: #E8F5E9; color: #2E7D32; } .config-section[data-section="usernames"] .config-section-toggle { background: #FFF3E0; color: #E65100; } .config-section[data-section="url"] .config-section-toggle { background: #F3E5F5; color: #6A1B9A; } .config-section[data-section="xpath"] .config-section-toggle { background: #FCE4EC; color: #C2185B; } .config-section[data-section="sync"] .config-section-toggle { background: #E0F7FA; color: #0097A7; } .config-section-content { max-width: 100% !important; transition: max-height 0.3s ease, opacity 0.3s ease; max-height: 300px; opacity: 1; overflow-y: auto; padding: 8px; margin-top: 5px; background: #fff; border-radius: 4px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .checkbox-row { display: flex !important; justify-content: space-between !important; margin-bottom: 8px !important; gap: 10px !important; } .checkbox-row label { flex: 1 !important; display: flex !important; align-items: center !important; margin: 0 !important; font-size: 13px !important; color: #333 !important; cursor: pointer !important; user-select: none !important; position: relative !important; padding-left: 28px !important; min-height: 20px !important; line-height: 20px !important; } .checkbox-row label,.domain-enabled-label{ font-weight: unset !important; } .domain-info input[type="checkbox"]{ visibility: visible !important; } .domain-info input[type="checkbox"]:checked::after{ content: '' !important; position: absolute !important; left: 5px !important; top: 1px !important; width: 4px !important; height: 8px !important; border: solid white !important; border-width: 0 2px 2px 0 !important; transform: rotate(45deg) !important; background-color: transparent !important; } .domain-info input[type="checkbox"]::after{ border: solid transparent !important; } .checkbox-row input[type="checkbox"] { padding: 0 !important; position: absolute !important; left: 0 !important; top: 50% !important; transform: translateY(-50%) !important; margin: 0 !important; width: 18px !important; height: 18px !important; cursor: pointer !important; opacity: 1 !important; z-index: 1 !important; border: 2px solid #ccc !important; border-radius: 3px !important; background-color: #fff !important; transition: all 0.2s ease-in-out !important; visibility: visible !important; } .checkbox-row input[type="checkbox"]::after { content: '' !important; border: solid transparent !important; } .checkbox-row input[type="checkbox"]:checked::after{ background-color: transparent !important; } .checkbox-row input[type="checkbox"]:hover { border-color: #2196F3 !important; } .checkbox-row input[type="checkbox"]:focus { box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2) !important; outline: none !important; } .checkbox-row label:before { content: '' !important; position: absolute !important; left: 0 !important; top: 50% !important; transform: translateY(-50%) !important; width: 18px !important; height: 18px !important; border: 2px solid #ccc !important; border-radius: 3px !important; background-color: #fff !important; transition: all 0.2s ease-in-out !important; box-sizing: border-box !important; } .checkbox-row label:after { content: '' !important; position: absolute !important; left: 6px !important; top: 50% !important; transform: translateY(-50%) rotate(45deg) !important; width: 6px !important; height: 10px !important; border: solid white !important; border-width: 0 2px 2px 0 !important; opacity: 0 !important; transition: all 0.2s ease-in-out !important; } .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #2196F3 !important; border-color: #2196F3 !important; } .checkbox-row input[type="checkbox"]:checked + label:after { opacity: 1 !important; } .checkbox-row input[type="checkbox"]:focus + label:before { box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2) !important; } .checkbox-row label:hover:before { border-color: #2196F3 !important; } .config-section[data-section="global"] .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #2196F3 !important; border-color: #2196F3 !important; } .config-section[data-section="keywords"] .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #4CAF50 !important; border-color: #4CAF50 !important; } .config-section[data-section="usernames"] .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #FF9800 !important; border-color: #FF9800 !important; } .config-section[data-section="url"] .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #9C27B0 !important; border-color: #9C27B0 !important; } .config-section[data-section="xpath"] .checkbox-row input[type="checkbox"]:checked + label:before { background-color: #E91E63 !important; border-color: #E91E63 !important; } .button-group { margin-top: 15px !important; text-align: center !important; padding: 8px 0 !important; border-top: 1px solid #eee !important; display: flex !important; flex-direction: column !important; gap: 8px !important; width: 100% !important; } .button-group button { width: 100% !important; align-items: center !important; text-align: center !important; justify-content: center !important; padding: 8px 15px !important; font-size: 13px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important; background: #f5f5f5 !important; transition: background 0.2s !important; } .button-group button:hover { background: #e0e0e0 !important; } .button-group button#save-domain-config { background: #4CAF50 !important; color: white !important; } .button-group button#save-domain-config:hover { background: #45a049 !important; } .button-group button#delete-domain-config { background: #ff4444 !important; color: white !important; } .button-group button#delete-domain-config:hover { background: #ff3333 !important; } .button-group button#export-config { background: #2196F3 !important; color: white !important; } .button-group button#export-config:hover { background: #1e88e5 !important; } .button-group button#import-config, .button-group button#import-domain-config { background: #FF9800 !important; color: white !important; } .button-group button#import-config:hover, .button-group button#import-domain-config:hover { background: #f57c00 !important; } .panel-content::-webkit-scrollbar { width: 8px; } .panel-content::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; } .panel-content::-webkit-scrollbar-thumb:hover { background: #aaa; } .array-editor { margin: 4px 0; border: 1px solid #eee; border-radius: 4px; padding: 6px; background: #fff; } .config-section[data-section="global"] .array-editor { border-width: 2px; border-color: #2196F3; } .config-section[data-section="keywords"] .array-editor { border-width: 2px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .config-section[data-section="usernames"] .array-editor { border-width: 2px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .config-section[data-section="url"] .array-editor { border-width: 2px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .config-section[data-section="xpath"] .array-editor { border-width: 2px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } #add-global-url, #apply-global-apply { margin-top: 3px !important; height: 33px !important; background: white; color: #333; border: 1px solid #ddd; border-bottom-width: 3px; padding: 6px 12px; transition: all 0.2s ease; border-radius: 6px; font-size: 13px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; outline: none; border-bottom-color: #2196F3; line-height: 1.4 !important; font: 13px/1.4 Arial, sans-serif !important; } #add-global-url:hover, #apply-global-apply:hover { background: #f5f5f5; transform: translateY(-1px); } #add-global-url:active, #apply-global-apply:active { transform: translateY(1px); border-bottom-width: 2px; } .array-editor-header { display: flex !important; gap: 4px !important; margin-bottom: 6px !important; align-items: center !important; flex-wrap: wrap !important; } .array-editor-header input[type="text"] { min-width: 10px !important; padding: 6px 8px !important; border: 1px solid #ddd !important; border-radius: 3px !important; font-size: 13px !important; height: 28px !important; box-sizing: border-box !important; } .array-editor-search-input { width: 100% !important; margin: 4px 0 !important; background: #f5f5f5 !important; } .array-editor .button-group-inline button { background: white; color: #333; border: 1px solid #ddd; border-bottom-width: 3px; padding: 6px 12px; transition: all 0.2s ease; border-radius: 6px; font-size: 13px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; outline: none; white-space: normal; word-break: break-word; line-height: 1.4 !important; font: 13px/1.4 Arial, sans-serif !important; } .config-section[data-section="global"] .array-editor .button-group-inline button { border-bottom-color: #42A5F5; } .config-section[data-section="global"] .array-editor .button-group-inline button:hover { border-bottom-color: #1E88E5; } .config-section[data-section="keywords"] .array-editor .button-group-inline button { border-bottom-color: #66BB6A; } .config-section[data-section="keywords"] .array-editor .button-group-inline button:hover { border-bottom-color: #43A047; } .config-section[data-section="usernames"] .array-editor .button-group-inline button { border-bottom-color: #FB8C00; } .config-section[data-section="usernames"] .array-editor .button-group-inline button:hover { border-bottom-color: #F57C00; } .config-section[data-section="url"] .array-editor .button-group-inline button { border-bottom-color: #AB47BC; } .config-section[data-section="url"] .array-editor .button-group-inline button:hover { border-bottom-color: #8E24AA; } .config-section[data-section="xpath"] .array-editor .button-group-inline button { border-bottom-color: #EC407A; } .config-section[data-section="xpath"] .array-editor .button-group-inline button:hover { border-bottom-color: #D81B60; } .array-editor .button-group-inline button:hover { background: #f5f5f5; transform: translateY(-1px); } .array-editor .button-group-inline button:active { transform: translateY(1px); border-bottom-width: 2px; } .array-editor-list { max-height: 200px; overflow-y: auto; border: 1px solid #eee; border-radius: 3px; background: #fff; margin: 4px 0; padding: 4px; box-shadow: inset 0 2px 4px rgba(0,0,0,0.05); } .array-editor-list:empty { padding: 8px; text-align: center; color: #999; } .array-editor-list:empty::after { font-size: 12px; content: attr(data-empty); } .array-item:hover { background: #f1f3f5 !important; } .array-item span { flex: 1 !important; font-size: 13px !important; color: #495057 !important; line-height: 1.4 !important; margin-right: 8px !important; word-break: break-all !important; user-select: text !important; cursor: text !important; } .array-item button { width: 18px !important; height: 18px !important; min-width: 18px !important; padding: 0 !important; border: none !important; border-radius: 3px !important; background: transparent !important; color: #adb5bd !important; font-size: 14px !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; transition: all 0.2s !important; opacity: 0 !important; user-select: none !important; } .array-item:hover button { opacity: 1 !important; color: #495057 !important; } .array-item button:hover { background: #e9ecef !important; color: #212529 !important; } mark { background: #e9ecef; color: #495057; padding: 0 2px; border-radius: 2px; font-weight: 500; } .array-editor-list::-webkit-scrollbar { width: 6px; height: 6px; } .array-editor-list::-webkit-scrollbar-track { background: #f8f9fa; border-radius: 3px; } .array-editor-list::-webkit-scrollbar-thumb { background: #dee2e6; border-radius: 3px; transition: background 0.2s; } .array-editor-list::-webkit-scrollbar-thumb:hover { background: #adb5bd; } .array-editor-list::-webkit-scrollbar-corner { background: #f8f9fa; } .array-editor-count { background: #999; color: white; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: normal; line-height: 1.4 !important; } .array-editor-content { display: none; } .array-editor-content.expanded { display: block; } .array-editor .array-item { display: flex !important; align-items: center !important; padding: 8px !important; margin: 2px 0 !important; background: #f5f5f5 !important; border-radius: 3px !important; width: auto !important; border-left: 4px solid transparent !important; } .array-editor .array-item span { flex: 1 !important; margin-right: 10px !important; word-break: break-all !important; padding-right: 10px !important; line-height: 1.4 !important; text-align: left !important; } .array-editor .array-item button { padding: 0 !important; width: 20px !important; height: 20px !important; line-height: 1 !important; background: rgba(0, 0, 0, 0.6) !important; color: white !important; border: none !important; border-radius: 50% !important; cursor: pointer !important; font-size: 14px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; min-width: 20px !important; max-width: 20px !important; margin: 0 !important; flex-shrink: 0 !important; float: none !important; line-height: 0 !important; padding-bottom: 2px !important; } .array-editor .array-item button:hover { background: #000000 !important; } .checkbox-row { display: flex; justify-content: space-between; margin-bottom: 5px; } .checkbox-row label { flex: 1; margin-right: 10px; white-space: nowrap; } .checkbox-row label:last-child { margin-right: 0; } .config-group { margin: 5px 0; padding: 0; } .config-section-toggle { width: 100%; padding: 8px 12px; background: #f5f5f5; border: none; border-radius: 4px; text-align: left; cursor: pointer; display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-weight: 700; color: #333; font-size: 14px; font: bold 14px/1.4 Arial, sans-serif !important; } .config-section-toggle:hover { background: #e8e8e8; } .config-section-indicator { transition: transform 0.3s ease; } .config-section-toggle.collapsed .config-section-indicator { transform: rotate(-90deg); } .config-section-content { max-width: 100% !important; transition: max-height 0.3s ease, opacity 0.3s ease; max-height: 300px; opacity: 1; overflow-y: auto; padding-right: 8px; } .config-section-content::-webkit-scrollbar { width: 6px; } .config-section-content::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px; } .config-section-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; } .config-section-content::-webkit-scrollbar-thumb:hover { background: #aaa; } .config-section-toggle.collapsed + .config-section-content { max-width: 100% !important; max-height: 0; opacity: 0; margin: 0; padding: 0; overflow: hidden; } #save-domain-config { background: #4CAF50 !important; color: white !important; border: none !important; border-radius: 3px !important; } #save-domain-config:hover { background: #45a049 !important; } .array-editor { margin: 4px 0; border: 1px solid #ddd; border-radius: 4px; padding: 6px; background: #fff; } .array-editor-search-input { width: 100% !important; flex: 1 1 auto !important; text-align: center !important; margin: 5px 0 !important; } .array-editor-search-input::placeholder { text-align: center !important; } .array-editor-linkimport-input { flex: 1 1 50px !important; min-width: 10px !important; margin: 4px !important; padding: 6px 12px !important; border: 1px solid #e0e0e0 !important; border-radius: 4px !important; height: 32px !important; font-size: 14px !important; box-sizing: border-box !important; transition: all 0.2s ease !important; background-color: #fafafa !important; color: #333 !important; } .array-editor-linkimport-input:hover { border-color: #bdbdbd !important; background-color: #fff !important; } .array-editor-linkimport-input:focus { border-color: #2196F3 !important; background-color: #fff !important; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1) !important; outline: none !important; } .array-editor-additem-input,.global-url-input-row input,.array-editor-additem-input-regex { flex: 1 1 50px !important; min-width: 10px !important; margin: 4px !important; padding: 6px 12px !important; border: 1px solid #e0e0e0 !important; border-radius: 4px !important; height: 32px !important; font-size: 14px !important; box-sizing: border-box !important; transition: all 0.2s ease !important; background-color: #fafafa !important; color: #333 !important; } .array-editor-additem-input:hover ,.global-url-input-row input:hover ,.array-editor-additem-input-regex:hover{ border-color: #bdbdbd !important; background-color: #fff !important; } .array-editor-additem-input:focus ,.global-url-input-row input:focus ,.array-editor-additem-input-regex:focus { border-color: #2196F3 !important; background-color: #fff !important; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1) !important; outline: none !important; } .array-editor-search-input { flex: 1 1 50px !important; min-width: 10px !important; margin: 4px !important; padding: 6px 12px !important; border: 1px solid #e0e0e0 !important; border-radius: 4px !important; height: 25px !important; font-size: 14px !important; box-sizing: border-box !important; transition: all 0.2s ease !important; background-color: #fafafa !important; color: #333 !important; align-items: center !important; } .array-editor-search-input:hover { border-color: #bdbdbd !important; background-color: #fff !important; } .array-editor-search-input:focus { border-color: #2196F3 !important; background-color: #fff !important; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1) !important; outline: none !important; } .button-group-inline { display: flex !important; flex-wrap: wrap !important; gap: 2px !important; margin-left: auto !important; } .checkbox-row label:hover:before { border-color: #2196F3 !important; } .global-url-section { margin-top: 10px !important; background: #f8f9fa !important; border-radius: 4px !important; padding: 8px !important; } .global-url-input-row { display: flex !important; gap: 8px !important; margin-bottom: 8px !important; } .global-url-input-row input { flex: 1 !important; padding: 6px 12px !important; border: 1px solid #ddd !important; border-radius: 4px !important; font-size: 13px !important; } .global-url-input-row input:hover { border-color: #bdbdbd !important; background-color: #fff !important; } .global-url-input-row input:focus { border-color: #2196F3 !important; background-color: #fff !important; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1) !important; outline: none !important; } .global-url-list { max-height: 200px !important; overflow-y: auto !important; margin-top: 8px !important; } .global-url-item { display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 6px 8px !important; background: white !important; border-radius: 4px !important; margin-bottom: 4px !important; border: 1px solid #eee !important; } .global-url-item span { flex: 1 !important; margin-right: 8px !important; font-size: 13px !important; word-break: break-all !important; } .global-url-item button { width: 20px !important; height: 20px !important; min-width: 20px !important; padding: 0 !important; background: rgba(0, 0, 0, 0.6) !important; color: white !important; border: none !important; border-radius: 50% !important; cursor: pointer !important; font-size: 14px !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: background 0.2s !important; } .global-url-item button:hover { background: rgba(0, 0, 0, 0.8) !important; } #time-interval { padding: 6px 8px !important; border: 1px solid #e0e0e0 !important; border-radius: 4px !important; height: 32px !important; font-size: 14px !important; background-color: #fafafa !important; color: #333 !important; cursor: pointer !important; transition: all 0.2s ease !important; box-sizing: border-box !important; display: inline-flex !important; align-items: center !important; vertical-align: middle !important; margin: 0 !important; min-width: 70px !important; width: auto !important; } #time-interval:hover { border-color: #bdbdbd !important; background-color: #fff !important; } #time-interval:focus { border-color: #2196F3 !important; background-color: #fff !important; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1) !important; outline: none !important; } .global-url-input-row { display: flex !important; gap: 8px !important; margin-bottom: 8px !important; align-items: center !important; } .global-url-input-row input, .global-url-input-row select, .global-url-input-row button { margin: 0 !important; height: 32px !important; box-sizing: border-box !important; } .external-links{ align-items: flex-start !important; } .global-url-input-row button{ white-space: nowrap !important; } .config-section[data-section="sync"] input { display: block !important; width: 100% !important; margin-bottom: 8px !important; padding: 6px 12px !important; border: 1px solid #ddd !important; border-radius: 4px !important; height: 32px !important; box-sizing: border-box !important; } .config-section[data-section="sync"] .config-section-content button { display: block !important; width: 100% !important; margin-bottom: 8px !important; padding: 6px 12px !important; border: 1px solid #ddd !important; border-radius: 4px !important; background-color: #fff !important; cursor: pointer !important; transition: all 0.2s !important; } .config-section[data-section="sync"] button:hover { background-color: #f5f5f5 !important; } #sync-delete { color: #ff4444 !important; border-color: #ff4444 !important; } #sync-delete:hover { background-color: #fff5f5 !important; } #sync-status { margin-top: 8px !important; padding: 8px !important; border-radius: 4px !important; background-color: #f5f5f5 !important; min-height: 20px !important; } `);
  35. const LANGUAGE_TEMPLATES = { 'zh-CN': { 'settings_title': '面板设置', 'settings_language': '语言', 'settings_expand_mode': '展开方式', 'settings_expand_hover': '悬停展开', 'settings_expand_click': '点击展开', 'settings_block_button_mode': '屏蔽按钮显示方式', 'settings_block_hover': '悬停显示', 'settings_block_always': '总是显示', 'settings_horizontal_position': '水平位置', 'settings_collapsed_width': '收起宽度', 'settings_expanded_width': '展开宽度', 'settings_cancel': '取消', 'settings_save': '保存', 'panel_top_title': '通用论坛屏蔽', 'panel_top_current_domain': '当前域名:', 'panel_top_page_type': '当前页面类型:', 'panel_top_page_type_main': '主页', 'panel_top_page_type_sub': '分页', 'panel_top_page_type_content': '内容页', 'panel_top_page_type_unknown': '未匹配页面类型', 'panel_top_enable_domain': '启用此域名配置', 'panel_top_settings': '设置', 'panel_top_settings_title': '面板设置', 'panel_top_settings_button': '⚙ 设置', 'global_config_title': '全局配置', 'global_config_keywords': '全局关键词', 'global_config_usernames': '全局用户名', 'global_config_share_keywords': '主页/内容页共享关键词', 'global_config_share_usernames': '主页/内容页共享用户名', 'global_config_linkimport_input_placeholder': '输入配置链接', 'global_config_add_global_url': '添加', 'global_config_apply_global_apply': '应用', 'keywords_config_title': '关键词配置', 'keywords_config_keywords_list_title': '关键词列表', 'keywords_config_keywords_regex_title': '关键词正则表达式', 'usernames_config_title': '用户名配置', 'usernames_config_usernames_list_title': '用户名列表', 'usernames_config_usernames_regex_title': '用户名正则表达式', 'url_patterns_title': 'URL 匹配模式', 'url_patterns_main_page_url_patterns_title': '主页URL模式', 'url_patterns_sub_page_url_patterns_title': '分页URL模式', 'url_patterns_content_page_url_patterns_title': '内容页URL模式', 'xpath_config_title': 'XPath 配置', 'xpath_config_main_and_sub_page_keywords_title': '主页/分页标题XPath', 'xpath_config_main_and_sub_page_usernames_title': '主页/分页用户XPath', 'xpath_config_content_page_keywords_title': '内容页关键词XPath', 'xpath_config_content_page_usernames_title': '内容页用户XPath', 'sync_config_title': '云端同步', 'sync_config_server_url': '服务器地址', 'sync_config_user_key': '用户密钥', 'sync_config_apply': '同步', 'sync_config_delete': '删除云端配置', 'sync_panel_status_connect_failed': '连接失败: ', 'sync_panel_status_connect_error': '连接错误: ', 'sync_panel_status_connect_success': '连接成功', 'sync_panel_status_sync_failed': '同步失败: ', 'sync_panel_status_input_error': '请先设置服务器地址和用户密钥', 'sync_panel_status_delete_success': '删除云端配置成功', 'sync_panel_status_delete_failed': '删除云端配置失败: ', 'sync_panel_status_delete_confirm': '确定要删除云端配置吗?此操作不可恢复。', 'sync_panel_status_connect_server_success': '已连接到云端', 'sync_panel_status_client_to_server_success': '本地配置已保存至云端!', 'sync_panel_status_config_updated': '已同步云端配置', 'sync_panel_status_config_conflict_1': '检测到配置冲突!', 'sync_panel_status_config_conflict_2': '云端配置时间: ', 'sync_panel_status_config_conflict_3': '本地配置时间: ', 'sync_panel_status_config_conflict_4': '是否使用云端配置? (点击确定使用云端配置,点击取消使用本地配置)', 'sync_panel_status_config_conflict_cloud_newer': '云端配置较新', 'sync_panel_status_config_conflict_local_newer': '本地配置较新', 'sync_panel_status_disconnect': '连接已断开,尝试重新连接...', 'array_editor_add_item_input_placeholder': '请输入', 'array_editor_add_item_input_placeholder_regex': '请输入正则表达式', 'array_editor_add_item': '添加', 'array_editor_add_item_title': '添加新项目', 'array_editor_clear_allitem': '清空', 'array_editor_clear_allitem_title': '清空列表', 'array_editor_search_input_placeholder': '搜索...', 'array_editor_list_empty_placeholder': '暂无数据', 'array_editor_linkimport_input_placeholder': '请输入链接', 'array_editor_linkimport_input_button': '链接导入', 'array_editor_linkimport_input_button_title': '从链接导入列表', 'array_editor_fileimport_input_button': '文件导入', 'array_editor_fileimport_input_button_title': '从文件导入列表', 'array_editor_export_button': '导出', 'array_editor_export_button_title': '导出列表到文件', 'panel_bottom_export_button': '导出配置', 'panel_bottom_export_button': '导出配置', 'panel_bottom_import_button': '导入配置', 'panel_bottom_delete_button': '删除当前域名配置', 'panel_bottom_save_button': '保存', 'alert_delete_confirm': '确定要删除吗?此操作不可恢复。', 'alert_clear_confirm': '确定要清空整个列表吗?此操作不可恢复。', 'alert_invalid_file': '请选择有效的配置文件', 'alert_import_error': '导入配置失败', 'alert_list_empty': '列表已经是空的了', 'alert_url_exists': '该URL已存在!', 'alert_enter_url': '请输入有效的链接', 'block_button_title': '屏蔽用户: ' }, 'en-US': { 'settings_title': 'Panel Settings', 'settings_language': 'Language', 'settings_expand_mode': 'Expand Mode', 'settings_expand_hover': 'Expand on Hover', 'settings_expand_click': 'Expand on Click', 'settings_block_button_mode': 'Block Button Display Mode', 'settings_block_hover': 'Show on Hover', 'settings_block_always': 'Always Show', 'settings_horizontal_position': 'Horizontal Position', 'settings_collapsed_width': 'Collapsed Width', 'settings_expanded_width': 'Expanded Width', 'settings_cancel': 'Cancel', 'settings_save': 'Save', 'panel_top_title': 'Universal Forum Filter', 'panel_top_current_domain': 'Current Domain: ', 'panel_top_page_type': 'Current Page Type: ', 'panel_top_page_type_main': 'Main Page', 'panel_top_page_type_sub': 'Sub Page', 'panel_top_page_type_content': 'Content Page', 'panel_top_page_type_unknown': 'Unknown Page Type', 'panel_top_enable_domain': 'Enable Domain Config', 'panel_top_settings': 'Settings', 'panel_top_settings_title': 'Panel Settings', 'panel_top_settings_button': '⚙ Settings', 'global_config_title': 'Global Configuration', 'global_config_keywords': 'Global Keywords', 'global_config_usernames': 'Global Usernames', 'global_config_share_keywords': 'Main/Content Page Shared Keywords', 'global_config_share_usernames': 'Main/Content Page Shared Usernames', 'global_config_linkimport_input_placeholder': 'Enter config link', 'global_config_add_global_url': 'Add', 'global_config_apply_global_apply': 'Apply', 'keywords_config_title': 'Keywords Configuration', 'keywords_config_keywords_list_title': 'Keywords List', 'keywords_config_keywords_regex_title': 'Keywords Regex', 'usernames_config_title': 'Usernames Configuration', 'usernames_config_usernames_list_title': 'Usernames List', 'usernames_config_usernames_regex_title': 'Usernames Regex', 'url_patterns_title': 'URL Patterns', 'url_patterns_main_page_url_patterns_title': 'Main Page URL Patterns', 'url_patterns_sub_page_url_patterns_title': 'Sub Page URL Patterns', 'url_patterns_content_page_url_patterns_title': 'Content Page URL Patterns', 'xpath_config_title': 'XPath Configuration', 'xpath_config_main_and_sub_page_keywords_title': 'Main/Sub Page Title XPath', 'xpath_config_main_and_sub_page_usernames_title': 'Main/Sub Page User XPath', 'xpath_config_content_page_keywords_title': 'Content Page Keywords XPath', 'xpath_config_content_page_usernames_title': 'Content Page User XPath', 'sync_config_title': 'Cloud Sync', 'sync_config_server_url': 'Server URL', 'sync_config_user_key': 'User Key', 'sync_config_apply': 'Sync', 'sync_config_delete': 'Delete Cloud Config', 'sync_panel_status_connect_failed': 'Connection Failed: ', 'sync_panel_status_connect_error': 'Connection Error: ', 'sync_panel_status_connect_success': 'Connected Successfully', 'sync_panel_status_sync_failed': 'Sync Failed: ', 'sync_panel_status_input_error': 'Please set server URL and user key first', 'sync_panel_status_delete_success': 'Cloud config deleted successfully', 'sync_panel_status_delete_failed': 'Failed to delete cloud config: ', 'sync_panel_status_delete_confirm': 'Are you sure you want to delete cloud config? This action cannot be undone.', 'sync_panel_status_connect_server_success': 'Connected to cloud', 'sync_panel_status_client_to_server_success': 'Local config saved to cloud!', 'sync_panel_status_config_updated': 'Cloud config synced', 'sync_panel_status_config_conflict_1': 'Config conflict detected!', 'sync_panel_status_config_conflict_2': 'Cloud config time: ', 'sync_panel_status_config_conflict_3': 'Local config time: ', 'sync_panel_status_config_conflict_4': 'Use cloud config? (Click OK to use cloud config, Cancel to use local config)', 'sync_panel_status_config_conflict_cloud_newer': 'Cloud config is newer', 'sync_panel_status_config_conflict_local_newer': 'Local config is newer', 'sync_panel_status_disconnect': 'Disconnected, attempting to reconnect...', 'array_editor_add_item_input_placeholder': 'Please enter', 'array_editor_add_item_input_placeholder_regex': 'Please enter regex', 'array_editor_add_item': 'Add', 'array_editor_add_item_title': 'Add New Item', 'array_editor_clear_allitem': 'Clear All', 'array_editor_clear_allitem_title': 'Clear List', 'array_editor_search_input_placeholder': 'Search...', 'array_editor_list_empty_placeholder': 'No Data', 'array_editor_linkimport_input_placeholder': 'Enter link', 'array_editor_linkimport_input_button': 'Import from Link', 'array_editor_linkimport_input_button_title': 'Import from Link', 'array_editor_fileimport_input_button': 'Import from File', 'array_editor_fileimport_input_button_title': 'Import from File', 'array_editor_export_button': 'Export', 'array_editor_export_button_title': 'Export List to File', 'panel_bottom_export_button': 'Export Config', 'panel_bottom_import_button': 'Import Config', 'panel_bottom_delete_button': 'Delete Current Domain Config', 'panel_bottom_save_button': 'Save', 'alert_delete_confirm': 'Are you sure you want to delete? This action cannot be undone.', 'alert_clear_confirm': 'Are you sure you want to clear the entire list? This action cannot be undone.', 'alert_invalid_file': 'Please select a valid configuration file', 'alert_import_error': 'Failed to import configuration', 'alert_list_empty': 'The list is already empty', 'alert_url_exists': 'This URL already exists!', 'alert_enter_url': 'Please enter a valid link', 'block_button_title': 'Block User: ' }, 'ja-JP': { 'settings_title': 'パネル設定', 'settings_language': '言語', 'settings_expand_mode': '展開モード', 'settings_expand_hover': 'ホバーで展開', 'settings_expand_click': 'クリックで展開', 'settings_block_button_mode': 'ブロックボタン表示モード', 'settings_block_hover': 'ホバーで表示', 'settings_block_always': '常に表示', 'settings_horizontal_position': '水平位置', 'settings_collapsed_width': '折りたたみ幅', 'settings_expanded_width': '展開幅', 'settings_cancel': 'キャンセル', 'settings_save': '保存', 'panel_top_title': '汎用フォーラムフィルター', 'panel_top_current_domain': '現在のドメイン:', 'panel_top_page_type': '現在のページタイプ:', 'panel_top_page_type_main': 'メインページ', 'panel_top_page_type_sub': 'サブページ', 'panel_top_page_type_content': 'コンテンツページ', 'panel_top_page_type_unknown': '不明なページタイプ', 'panel_top_enable_domain': 'このドメイン設定を有効にする', 'panel_top_settings': '設定', 'panel_top_settings_title': 'パネル設定', 'panel_top_settings_button': '⚙ 設定', 'global_config_title': 'グローバル設定', 'global_config_keywords': 'グローバルキーワード', 'global_config_usernames': 'グローバルユーザー名', 'global_config_share_keywords': 'メイン/コンテンツページ共有キーワード', 'global_config_share_usernames': 'メイン/コンテンツページ共有ユーザー名', 'global_config_linkimport_input_placeholder': '設定リンクを入力', 'global_config_add_global_url': '追加', 'global_config_apply_global_apply': '適用', 'keywords_config_title': 'キーワード設定', 'keywords_config_keywords_list_title': 'キーワードリスト', 'keywords_config_keywords_regex_title': 'キーワード正規表現', 'usernames_config_title': 'ユーザー名設定', 'usernames_config_usernames_list_title': 'ユーザー名リスト', 'usernames_config_usernames_regex_title': 'ユーザー名正規表現', 'url_patterns_title': 'URLパターン', 'url_patterns_main_page_url_patterns_title': 'メインページURLパターン', 'url_patterns_sub_page_url_patterns_title': 'サブページURLパターン', 'url_patterns_content_page_url_patterns_title': 'コンテンツページURLパターン', 'xpath_config_title': 'XPath設定', 'xpath_config_main_and_sub_page_keywords_title': 'メイン/サブページタイトルXPath', 'xpath_config_main_and_sub_page_usernames_title': 'メイン/サブページユーザーXPath', 'xpath_config_content_page_keywords_title': 'コンテンツページキーワードXPath', 'xpath_config_content_page_usernames_title': 'コンテンツページユーザーXPath', 'sync_config_title': 'クラウド同期', 'sync_config_server_url': 'サーバーアドレス', 'sync_config_user_key': 'ユーザーキー', 'sync_config_apply': '同期', 'sync_config_delete': 'クラウド設定を削除', 'sync_panel_status_connect_failed': '接続失敗: ', 'sync_panel_status_connect_error': '接続エラー: ', 'sync_panel_status_connect_success': '接続成功', 'sync_panel_status_sync_failed': '同期失敗: ', 'sync_panel_status_input_error': 'サーバーアドレスとユーザーキーを設定してください', 'sync_panel_status_delete_success': 'クラウド設定の削除に成功しました', 'sync_panel_status_delete_failed': 'クラウド設定の削除に失敗しました: ', 'sync_panel_status_delete_confirm': 'クラウド設定を削除してもよろしいですか?この操作は元に戻せません。', 'sync_panel_status_connect_server_success': 'クラウドに接続しました', 'sync_panel_status_client_to_server_success': 'ローカル設定をクラウドに保存しました!', 'sync_panel_status_config_updated': 'クラウド設定を同期しました', 'sync_panel_status_config_conflict_1': '設定の競合を検出しました!', 'sync_panel_status_config_conflict_2': 'クラウド設定の時刻: ', 'sync_panel_status_config_conflict_3': 'ローカル設定の時刻: ', 'sync_panel_status_config_conflict_4': 'クラウド設定を使用しますか?(OKでクラウド設定を使用、キャンセルでローカル設定を使用)', 'sync_panel_status_config_conflict_cloud_newer': 'クラウド設定の方が新しいです', 'sync_panel_status_config_conflict_local_newer': 'ローカル設定の方が新しいです', 'sync_panel_status_disconnect': '接続が切断されました。再接続を試みています...', 'array_editor_add_item_input_placeholder': '入力してください', 'array_editor_add_item_input_placeholder_regex': '正規表現を入力', 'array_editor_add_item': '追加', 'array_editor_add_item_title': '新規項目追加', 'array_editor_clear_allitem': 'すべてクリア', 'array_editor_clear_allitem_title': 'リストをクリア', 'array_editor_search_input_placeholder': '検索...', 'array_editor_list_empty_placeholder': 'データなし', 'array_editor_linkimport_input_placeholder': 'リンクを入力', 'array_editor_linkimport_input_button': 'リンクからインポート', 'array_editor_linkimport_input_button_title': 'リンクからインポート', 'array_editor_fileimport_input_button': 'ファイルからインポート', 'array_editor_fileimport_input_button_title': 'ファイルからインポート', 'array_editor_export_button': 'エクスポート', 'array_editor_export_button_title': 'リストをファイルにエクスポート', 'panel_bottom_export_button': '設定をエクスポート', 'panel_bottom_import_button': '設定をインポート', 'panel_bottom_delete_button': '現在のドメイン設定を削除', 'panel_bottom_save_button': '保存', 'alert_delete_confirm': '本当に削除しますか?この操作は元に戻せません。', 'alert_clear_confirm': '本当にリスト全体をクリアしますか?この操作は元に戻せません。', 'alert_invalid_file': '有効な設定ファイルを選択してください', 'alert_import_error': '設定のインポートに失敗しました', 'alert_list_empty': 'リストは既に空です', 'alert_url_exists': 'このURLは既に存在します!', 'alert_enter_url': '有効なリンクを入力してください', 'block_button_title': 'ユーザーをブロック: ' }, 'ko-KR': { 'settings_title': '패널 설정', 'settings_language': '언어', 'settings_expand_mode': '확장 모드', 'settings_expand_hover': '호버 시 확장', 'settings_expand_click': '클릭 시 확장', 'settings_block_button_mode': '차단 버튼 표시 모드', 'settings_block_hover': '호버 시 표시', 'settings_block_always': '항상 표시', 'settings_horizontal_position': '수평 위치', 'settings_collapsed_width': '축소 너비', 'settings_expanded_width': '확장 너비', 'settings_cancel': '취소', 'settings_save': '저장', 'panel_top_title': '범용 포럼 필터', 'panel_top_current_domain': '현재 도메인: ', 'panel_top_page_type': '현재 페이지 유형: ', 'panel_top_page_type_main': '메인 페이지', 'panel_top_page_type_sub': '서브 페이지', 'panel_top_page_type_content': '콘텐츠 페이지', 'panel_top_page_type_unknown': '알 수 없는 페이지 유형', 'panel_top_enable_domain': '이 도메인 설정 활성화', 'panel_top_settings': '설정', 'panel_top_settings_title': '패널 설정', 'panel_top_settings_button': '⚙ 설정', 'global_config_title': '전역 설정', 'global_config_keywords': '전역 키워드', 'global_config_usernames': '전역 사용자 이름', 'global_config_share_keywords': '메인/콘텐츠 페이지 공유 키워드', 'global_config_share_usernames': '메인/콘텐츠 페이지 공유 사용자 이름', 'global_config_linkimport_input_placeholder': '설정 링크 입력', 'global_config_add_global_url': '추가', 'global_config_apply_global_apply': '적용', 'keywords_config_title': '키워드 설정', 'keywords_config_keywords_list_title': '키워드 목록', 'keywords_config_keywords_regex_title': '키워드 정규식', 'usernames_config_title': '사용자 이름 설정', 'usernames_config_usernames_list_title': '사용자 이름 목록', 'usernames_config_usernames_regex_title': '사용자 이름 정규식', 'url_patterns_title': 'URL 패턴', 'url_patterns_main_page_url_patterns_title': '메인 페이지 URL 패턴', 'url_patterns_sub_page_url_patterns_title': '서브 페이지 URL 패턴', 'url_patterns_content_page_url_patterns_title': '콘텐츠 페이지 URL 패턴', 'xpath_config_title': 'XPath 설정', 'xpath_config_main_and_sub_page_keywords_title': '메인/서브 페이지 제목 XPath', 'xpath_config_main_and_sub_page_usernames_title': '메인/서브 페이지 사용자 XPath', 'xpath_config_content_page_keywords_title': '콘텐츠 페이지 키워드 XPath', 'xpath_config_content_page_usernames_title': '콘텐츠 페이지 사용자 XPath', 'sync_config_title': '클라우드 동기화', 'sync_config_server_url': '서버 주소', 'sync_config_user_key': '사용자 키', 'sync_config_apply': '동기화', 'sync_config_delete': '클라우드 설정 삭제', 'sync_panel_status_connect_failed': '연결 실패: ', 'sync_panel_status_connect_error': '연결 오류: ', 'sync_panel_status_connect_success': '연결 성공', 'sync_panel_status_sync_failed': '동기화 실패: ', 'sync_panel_status_input_error': '서버 주소와 사용자 키를 먼저 설정하세요', 'sync_panel_status_delete_success': '클라우드 설정 삭제 성공', 'sync_panel_status_delete_failed': '클라우드 설정 삭제 실패: ', 'sync_panel_status_delete_confirm': '클라우드 설정을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', 'sync_panel_status_connect_server_success': '클라우드에 연결됨', 'sync_panel_status_client_to_server_success': '로컬 설정이 클라우드에 저장되었습니다!', 'sync_panel_status_config_updated': '클라우드 설정이 동기화되었습니다', 'sync_panel_status_config_conflict_1': '설정 충돌이 감지되었습니다!', 'sync_panel_status_config_conflict_2': '클라우드 설정 시간: ', 'sync_panel_status_config_conflict_3': '로컬 설정 시간: ', 'sync_panel_status_config_conflict_4': '클라우드 설정을 사용하시겠습니까? (확인을 클릭하여 클라우드 설정 사용, 취소를 클릭하여 로컬 설정 사용)', 'sync_panel_status_config_conflict_cloud_newer': '클라우드 설정이 더 최신입니다', 'sync_panel_status_config_conflict_local_newer': '로컬 설정이 더 최신입니다', 'sync_panel_status_disconnect': '연결이 끊어졌습니다. 다시 연결을 시도합니다...', 'array_editor_add_item_input_placeholder': '입력하세요', 'array_editor_add_item_input_placeholder_regex': '정규식 입력', 'array_editor_add_item': '추가', 'array_editor_add_item_title': '새 항목 추가', 'array_editor_clear_allitem': '모두 지우기', 'array_editor_clear_allitem_title': '목록 지우기', 'array_editor_search_input_placeholder': '검색...', 'array_editor_list_empty_placeholder': '데이터 없음', 'array_editor_linkimport_input_placeholder': '링크 입력', 'array_editor_linkimport_input_button': '링크에서 가져오기', 'array_editor_linkimport_input_button_title': '링크에서 가져오기', 'array_editor_fileimport_input_button': '파일에서 가져오기', 'array_editor_fileimport_input_button_title': '파일에서 가져오기', 'array_editor_export_button': '내보내기', 'array_editor_export_button_title': '목록을 파일로 내보내기', 'panel_bottom_export_button': '설정 내보내기', 'panel_bottom_import_button': '설정 가져오기', 'panel_bottom_delete_button': '현재 도메인 설정 삭제', 'panel_bottom_save_button': '저장', 'alert_delete_confirm': '정말로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', 'alert_clear_confirm': '정말로 전체 목록을 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.', 'alert_invalid_file': '유효한 설정 파일을 선택하세요', 'alert_import_error': '설정 가져오기 실패', 'alert_list_empty': '목록이 이미 비어 있습니다', 'alert_url_exists': '이 URL이 이미 존재합니다!', 'alert_enter_url': '유효한 링크를 입력하세요', 'block_button_title': '사용자 차단: ' }, 'ru-RU': { 'settings_title': 'Настройки панели', 'settings_language': 'Язык', 'settings_expand_mode': 'Режим развертывания', 'settings_expand_hover': 'Развернуть при наведении', 'settings_expand_click': 'Развернуть при клике', 'settings_block_button_mode': 'Режим отображения кнопки блокировки', 'settings_block_hover': 'Показать при наведении', 'settings_block_always': 'Показывать всегда', 'settings_horizontal_position': 'Горизонтальное положение', 'settings_collapsed_width': 'Ширина в свернутом виде', 'settings_expanded_width': 'Ширина в развернутом виде', 'settings_cancel': 'Отмена', 'settings_save': 'Сохранить', 'panel_top_title': 'Универсальный фильтр форума', 'panel_top_current_domain': 'Текущий домен: ', 'panel_top_page_type': 'Тип текущей страницы: ', 'panel_top_page_type_main': 'Главная страница', 'panel_top_page_type_sub': 'Подстраница', 'panel_top_page_type_content': 'Страница контента', 'panel_top_page_type_unknown': 'Неизвестный тип страницы', 'panel_top_enable_domain': 'Включить настройки домена', 'panel_top_settings': 'Настройки', 'panel_top_settings_title': 'Настройки панели', 'panel_top_settings_button': '⚙ Настройки', 'global_config_title': 'Глобальные настройки', 'global_config_keywords': 'Глобальные ключевые слова', 'global_config_usernames': 'Глобальные имена пользователей', 'global_config_share_keywords': 'Общие ключевые слова главной/контентной страницы', 'global_config_share_usernames': 'Общие имена пользователей главной/контентной страницы', 'global_config_linkimport_input_placeholder': 'Введите ссылку конфигурации', 'global_config_add_global_url': 'Добавить', 'global_config_apply_global_apply': 'Применить', 'keywords_config_title': 'Настройки ключевых слов', 'keywords_config_keywords_list_title': 'Список ключевых слов', 'keywords_config_keywords_regex_title': 'Регулярные выражения ключевых слов', 'usernames_config_title': 'Настройки имен пользователей', 'usernames_config_usernames_list_title': 'Список имен пользователей', 'usernames_config_usernames_regex_title': 'Регулярные выражения имен пользователей', 'url_patterns_title': 'Шаблоны URL', 'url_patterns_main_page_url_patterns_title': 'Шаблоны URL главной страницы', 'url_patterns_sub_page_url_patterns_title': 'Шаблоны URL подстраниц', 'url_patterns_content_page_url_patterns_title': 'Шаблоны URL страниц контента', 'xpath_config_title': 'Настройки XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath заголовков главной/подстраниц', 'xpath_config_main_and_sub_page_usernames_title': 'XPath пользователей главной/подстраниц', 'xpath_config_content_page_keywords_title': 'XPath ключевых слов страницы контента', 'xpath_config_content_page_usernames_title': 'XPath пользователей страницы контента', 'sync_config_title': 'Облачная синхронизация', 'sync_config_server_url': 'Адрес сервера', 'sync_config_user_key': 'Ключ пользователя', 'sync_config_apply': 'Синхронизировать', 'sync_config_delete': 'Удалить облачную конфигурацию', 'sync_panel_status_connect_failed': 'Ошибка подключения: ', 'sync_panel_status_connect_error': 'Ошибка соединения: ', 'sync_panel_status_connect_success': 'Подключение успешно', 'sync_panel_status_sync_failed': 'Ошибка синхронизации: ', 'sync_panel_status_input_error': 'Пожалуйста, сначала установите адрес сервера и ключ пользователя', 'sync_panel_status_delete_success': 'Облачная конфигурация успешно удалена', 'sync_panel_status_delete_failed': 'Не удалось удалить облачную конфигурацию: ', 'sync_panel_status_delete_confirm': 'Вы уверены, что хотите удалить облачную конфигурацию? Это действие нельзя отменить.', 'sync_panel_status_connect_server_success': 'Подключено к облаку', 'sync_panel_status_client_to_server_success': 'Локальная конфигурация сохранена в облаке!', 'sync_panel_status_config_updated': 'Облачная конфигурация синхронизирована', 'sync_panel_status_config_conflict_1': 'Обнаружен конфликт конфигурации!', 'sync_panel_status_config_conflict_2': 'Время облачной конфигурации: ', 'sync_panel_status_config_conflict_3': 'Время локальной конфигурации: ', 'sync_panel_status_config_conflict_4': 'Использовать облачную конфигурацию? (Нажмите OK для использования облачной конфигурации, Отмена для использования локальной)', 'sync_panel_status_config_conflict_cloud_newer': 'Облачная конфигурация новее', 'sync_panel_status_config_conflict_local_newer': 'Локальная конфигурация новее', 'sync_panel_status_disconnect': 'Соединение потеряно, попытка переподключения...', 'array_editor_add_item_input_placeholder': 'Введите значение', 'array_editor_add_item_input_placeholder_regex': 'Введите регулярное выражение', 'array_editor_add_item': 'Добавить', 'array_editor_add_item_title': 'Добавить новый элемент', 'array_editor_clear_allitem': 'Очистить все', 'array_editor_clear_allitem_title': 'Очистить список', 'array_editor_search_input_placeholder': 'Поиск...', 'array_editor_list_empty_placeholder': 'Нет данных', 'array_editor_linkimport_input_placeholder': 'Введите ссылку', 'array_editor_linkimport_input_button': 'Импорт из ссылки', 'array_editor_linkimport_input_button_title': 'Импорт из ссылки', 'array_editor_fileimport_input_button': 'Импорт из файла', 'array_editor_fileimport_input_button_title': 'Импорт из файла', 'array_editor_export_button': 'Экспорт', 'array_editor_export_button_title': 'Экспорт списка в файл', 'panel_bottom_export_button': 'Экспорт настроек', 'panel_bottom_import_button': 'Импорт настроек', 'panel_bottom_delete_button': 'Удалить настройки текущего домена', 'panel_bottom_save_button': 'Сохранить', 'alert_delete_confirm': 'Вы уверены, что хотите удалить? Это действие нельзя отменить.', 'alert_clear_confirm': 'Вы уверены, что хотите очистить весь список? Это действие нельзя отменить.', 'alert_invalid_file': 'Пожалуйста, выберите действительный файл конфигурации', 'alert_import_error': 'Не удалось импортировать настройки', 'alert_list_empty': 'Список уже пуст', 'alert_url_exists': 'Этот URL уже существует!', 'alert_enter_url': 'Пожалуйста, введите действительную ссылку', 'block_button_title': 'Заблокировать пользователя: ' }, 'fr-FR': { 'settings_title': 'Paramètres du panneau', 'settings_language': 'Langue', 'settings_expand_mode': "Mode d'expansion", 'settings_expand_hover': 'Développer au survol', 'settings_expand_click': 'Développer au clic', 'settings_block_button_mode': 'Mode du bouton de blocage', 'settings_block_hover': 'Afficher au survol', 'settings_block_always': 'Toujours afficher', 'settings_horizontal_position': 'Position horizontale', 'settings_collapsed_width': 'Largeur réduite', 'settings_expanded_width': 'Largeur développée', 'settings_cancel': 'Annuler', 'settings_save': 'Enregistrer', 'panel_top_title': 'Filtre de forum universel', 'panel_top_current_domain': 'Domaine actuel : ', 'panel_top_page_type': 'Type de page actuel : ', 'panel_top_page_type_main': 'Page principale', 'panel_top_page_type_sub': 'Sous-page', 'panel_top_page_type_content': 'Page de contenu', 'panel_top_page_type_unknown': 'Type de page inconnu', 'panel_top_enable_domain': 'Activer la configuration du domaine', 'panel_top_settings': 'Paramètres', 'panel_top_settings_title': 'Paramètres du panneau', 'panel_top_settings_button': '⚙ Paramètres', 'global_config_title': 'Configuration globale', 'global_config_keywords': 'Mots-clés globaux', 'global_config_usernames': "Noms d'utilisateur globaux", 'global_config_share_keywords': 'Mots-clés partagés principale/contenu', 'global_config_share_usernames': "Noms d'utilisateur partagés principale/contenu", 'global_config_linkimport_input_placeholder': 'Entrez le lien de configuration', 'global_config_add_global_url': 'Ajouter', 'global_config_apply_global_apply': 'Appliquer', 'keywords_config_title': 'Configuration des mots-clés', 'keywords_config_keywords_list_title': 'Liste des mots-clés', 'keywords_config_keywords_regex_title': 'Expressions régulières des mots-clés', 'usernames_config_title': "Configuration des noms d'utilisateur", 'usernames_config_usernames_list_title': "Liste des noms d'utilisateur", 'usernames_config_usernames_regex_title': "Expressions régulières des noms d'utilisateur", 'url_patterns_title': 'Modèles URL', 'url_patterns_main_page_url_patterns_title': 'Modèles URL page principale', 'url_patterns_sub_page_url_patterns_title': 'Modèles URL sous-pages', 'url_patterns_content_page_url_patterns_title': 'Modèles URL pages de contenu', 'xpath_config_title': 'Configuration XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath titre principale/sous-pages', 'xpath_config_main_and_sub_page_usernames_title': 'XPath utilisateur principale/sous-pages', 'xpath_config_content_page_keywords_title': 'XPath mots-clés page de contenu', 'xpath_config_content_page_usernames_title': 'XPath utilisateur page de contenu', 'sync_config_title': 'Synchronisation cloud', 'sync_config_server_url': 'Adresse du serveur', 'sync_config_user_key': 'Clé utilisateur', 'sync_config_apply': 'Synchroniser', 'sync_config_delete': 'Supprimer la configuration cloud', 'sync_panel_status_connect_failed': 'Échec de la connexion : ', 'sync_panel_status_connect_error': 'Erreur de connexion : ', 'sync_panel_status_connect_success': 'Connexion réussie', 'sync_panel_status_sync_failed': 'Échec de la synchronisation : ', 'sync_panel_status_input_error': "Veuillez d'abord configurer l'adresse du serveur et la clé utilisateur", 'sync_panel_status_delete_success': 'Configuration cloud supprimée avec succès', 'sync_panel_status_delete_failed': 'Échec de la suppression de la configuration cloud : ', 'sync_panel_status_delete_confirm': 'Êtes-vous sûr de vouloir supprimer la configuration cloud ? Cette action est irréversible.', 'sync_panel_status_connect_server_success': 'Connecté au cloud', 'sync_panel_status_client_to_server_success': 'Configuration locale sauvegardée dans le cloud !', 'sync_panel_status_config_updated': 'Configuration cloud synchronisée', 'sync_panel_status_config_conflict_1': 'Conflit de configuration détecté !', 'sync_panel_status_config_conflict_2': 'Date de la configuration cloud : ', 'sync_panel_status_config_conflict_3': 'Date de la configuration locale : ', 'sync_panel_status_config_conflict_4': 'Utiliser la configuration cloud ? (Cliquez OK pour utiliser la configuration cloud, Annuler pour utiliser la configuration locale)', 'sync_panel_status_config_conflict_cloud_newer': 'La configuration cloud est plus récente', 'sync_panel_status_config_conflict_local_newer': 'La configuration locale est plus récente', 'sync_panel_status_disconnect': 'Connexion perdue, tentative de reconnexion...', 'array_editor_add_item_input_placeholder': 'Entrez une valeur', 'array_editor_add_item_input_placeholder_regex': 'Entrez une expression régulière', 'array_editor_add_item': 'Ajouter', 'array_editor_add_item_title': 'Ajouter un nouvel élément', 'array_editor_clear_allitem': 'Tout effacer', 'array_editor_clear_allitem_title': 'Effacer la liste', 'array_editor_search_input_placeholder': 'Rechercher...', 'array_editor_list_empty_placeholder': 'Aucune donnée', 'array_editor_linkimport_input_placeholder': 'Entrez un lien', 'array_editor_linkimport_input_button': 'Importer depuis un lien', 'array_editor_linkimport_input_button_title': 'Importer depuis un lien', 'array_editor_fileimport_input_button': 'Importer depuis un fichier', 'array_editor_fileimport_input_button_title': 'Importer depuis un fichier', 'array_editor_export_button': 'Exporter', 'array_editor_export_button_title': 'Exporter la liste vers un fichier', 'panel_bottom_export_button': 'Exporter la configuration', 'panel_bottom_import_button': 'Importer la configuration', 'panel_bottom_delete_button': 'Supprimer la configuration du domaine', 'panel_bottom_save_button': 'Enregistrer', 'alert_delete_confirm': 'Êtes-vous sûr de vouloir supprimer ? Cette action est irréversible.', 'alert_clear_confirm': 'Êtes-vous sûr de vouloir effacer toute la liste ? Cette action est irréversible.', 'alert_invalid_file': 'Veuillez sélectionner un fichier de configuration valide', 'alert_import_error': "Échec de l'importation de la configuration", 'alert_list_empty': 'La liste est déjà vide', 'alert_url_exists': 'Cette URL existe déjà !', 'alert_enter_url': 'Veuillez entrer un lien valide', 'block_button_title': 'Bloquer l\'utilisateur: ' }, 'de-DE': { 'settings_title': 'Panel-Einstellungen', 'settings_language': 'Sprache', 'settings_expand_mode': 'Erweiterungsmodus', 'settings_expand_hover': 'Beim Hover erweitern', 'settings_expand_click': 'Beim Klick erweitern', 'settings_block_button_mode': 'Blockierknopf-Anzeigemodus', 'settings_block_hover': 'Beim Hover anzeigen', 'settings_block_always': 'Immer anzeigen', 'settings_horizontal_position': 'Horizontale Position', 'settings_collapsed_width': 'Eingeklappte Breite', 'settings_expanded_width': 'Ausgeklappte Breite', 'settings_cancel': 'Abbrechen', 'settings_save': 'Speichern', 'panel_top_title': 'Universeller Forum-Filter', 'panel_top_current_domain': 'Aktuelle Domain: ', 'panel_top_page_type': 'Aktueller Seitentyp: ', 'panel_top_page_type_main': 'Hauptseite', 'panel_top_page_type_sub': 'Unterseite', 'panel_top_page_type_content': 'Inhaltsseite', 'panel_top_page_type_unknown': 'Unbekannter Seitentyp', 'panel_top_enable_domain': 'Domain-Konfiguration aktivieren', 'panel_top_settings': 'Einstellungen', 'panel_top_settings_title': 'Panel-Einstellungen', 'panel_top_settings_button': '⚙ Einstellungen', 'global_config_title': 'Globale Konfiguration', 'global_config_keywords': 'Globale Schlüsselwörter', 'global_config_usernames': 'Globale Benutzernamen', 'global_config_share_keywords': 'Geteilte Schlüsselwörter Haupt/Inhalt', 'global_config_share_usernames': 'Geteilte Benutzernamen Haupt/Inhalt', 'global_config_linkimport_input_placeholder': 'Konfigurationslink eingeben', 'global_config_add_global_url': 'Hinzufügen', 'global_config_apply_global_apply': 'Anwenden', 'keywords_config_title': 'Schlüsselwort-Konfiguration', 'keywords_config_keywords_list_title': 'Schlüsselwörterliste', 'keywords_config_keywords_regex_title': 'Schlüsselwörter-Regex', 'usernames_config_title': 'Benutzernamen-Konfiguration', 'usernames_config_usernames_list_title': 'Benutzernamen-Liste', 'usernames_config_usernames_regex_title': 'Benutzernamen-Regex', 'url_patterns_title': 'URL-Muster', 'url_patterns_main_page_url_patterns_title': 'Hauptseiten-URL-Muster', 'url_patterns_sub_page_url_patterns_title': 'Unterseiten-URL-Muster', 'url_patterns_content_page_url_patterns_title': 'Inhaltsseiten-URL-Muster', 'xpath_config_title': 'XPath-Konfiguration', 'xpath_config_main_and_sub_page_keywords_title': 'Haupt/Unterseiten-Titel-XPath', 'xpath_config_main_and_sub_page_usernames_title': 'Haupt/Unterseiten-Benutzer-XPath', 'xpath_config_content_page_keywords_title': 'Inhaltsseiten-Schlüsselwörter-XPath', 'xpath_config_content_page_usernames_title': 'Inhaltsseiten-Benutzer-XPath', 'sync_config_title': 'Cloud-Synchronisation', 'sync_config_server_url': 'Server-Adresse', 'sync_config_user_key': 'Benutzerschlüssel', 'sync_config_apply': 'Synchronisieren', 'sync_config_delete': 'Cloud-Konfiguration löschen', 'sync_panel_status_connect_failed': 'Verbindung fehlgeschlagen: ', 'sync_panel_status_connect_error': 'Verbindungsfehler: ', 'sync_panel_status_connect_success': 'Verbindung erfolgreich', 'sync_panel_status_sync_failed': 'Synchronisation fehlgeschlagen: ', 'sync_panel_status_input_error': 'Bitte geben Sie zuerst Server-Adresse und Benutzerschlüssel ein', 'sync_panel_status_delete_success': 'Cloud-Konfiguration erfolgreich gelöscht', 'sync_panel_status_delete_failed': 'Löschen der Cloud-Konfiguration fehlgeschlagen: ', 'sync_panel_status_delete_confirm': 'Sind Sie sicher, dass Sie die Cloud-Konfiguration löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.', 'sync_panel_status_connect_server_success': 'Mit Cloud verbunden', 'sync_panel_status_client_to_server_success': 'Lokale Konfiguration wurde in der Cloud gespeichert!', 'sync_panel_status_config_updated': 'Cloud-Konfiguration synchronisiert', 'sync_panel_status_config_conflict_1': 'Konfigurationskonflikt erkannt!', 'sync_panel_status_config_conflict_2': 'Cloud-Konfiguration Zeit: ', 'sync_panel_status_config_conflict_3': 'Lokale Konfiguration Zeit: ', 'sync_panel_status_config_conflict_4': 'Cloud-Konfiguration verwenden? (OK für Cloud-Konfiguration, Abbrechen für lokale Konfiguration)', 'sync_panel_status_config_conflict_cloud_newer': 'Cloud-Konfiguration ist neuer', 'sync_panel_status_config_conflict_local_newer': 'Lokale Konfiguration ist neuer', 'sync_panel_status_disconnect': 'Verbindung getrennt, versuche neu zu verbinden...', 'array_editor_add_item_input_placeholder': 'Wert eingeben', 'array_editor_add_item_input_placeholder_regex': 'Regulären Ausdruck eingeben', 'array_editor_add_item': 'Hinzufügen', 'array_editor_add_item_title': 'Neues Element hinzufügen', 'array_editor_clear_allitem': 'Alles löschen', 'array_editor_clear_allitem_title': 'Liste löschen', 'array_editor_search_input_placeholder': 'Suchen...', 'array_editor_list_empty_placeholder': 'Keine Daten', 'array_editor_linkimport_input_placeholder': 'Link eingeben', 'array_editor_linkimport_input_button': 'Von Link importieren', 'array_editor_linkimport_input_button_title': 'Von Link importieren', 'array_editor_fileimport_input_button': 'Aus Datei importieren', 'array_editor_fileimport_input_button_title': 'Aus Datei importieren', 'array_editor_export_button': 'Exportieren', 'array_editor_export_button_title': 'Liste in Datei exportieren', 'panel_bottom_export_button': 'Konfiguration exportieren', 'panel_bottom_import_button': 'Konfiguration importieren', 'panel_bottom_delete_button': 'Aktuelle Domain-Konfiguration löschen', 'panel_bottom_save_button': 'Speichern', 'alert_delete_confirm': 'Sind Sie sicher, dass Sie löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.', 'alert_clear_confirm': 'Sind Sie sicher, dass Sie die gesamte Liste löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.', 'alert_invalid_file': 'Bitte wählen Sie eine gültige Konfigurationsdatei', 'alert_import_error': 'Fehler beim Importieren der Konfiguration', 'alert_list_empty': 'Die Liste ist bereits leer', 'alert_url_exists': 'Diese URL existiert bereits!', 'alert_enter_url': 'Bitte geben Sie einen gültigen Link ein', 'block_button_title': 'Benutzer blockieren: ' }, 'it-IT': { 'settings_title': 'Impostazioni pannello', 'settings_language': 'Lingua', 'settings_expand_mode': 'Modalità espansione', 'settings_expand_hover': 'Espandi al passaggio', 'settings_expand_click': 'Espandi al clic', 'settings_block_button_mode': 'Modalità pulsante blocco', 'settings_block_hover': 'Mostra al passaggio', 'settings_block_always': 'Mostra sempre', 'settings_horizontal_position': 'Posizione orizzontale', 'settings_collapsed_width': 'Larghezza chiusa', 'settings_expanded_width': 'Larghezza aperta', 'settings_cancel': 'Annulla', 'settings_save': 'Salva', 'panel_top_title': 'Filtro forum universale', 'panel_top_current_domain': 'Dominio attuale: ', 'panel_top_page_type': 'Tipo pagina attuale: ', 'panel_top_page_type_main': 'Pagina principale', 'panel_top_page_type_sub': 'Sottopagina', 'panel_top_page_type_content': 'Pagina contenuto', 'panel_top_page_type_unknown': 'Tipo pagina sconosciuto', 'panel_top_enable_domain': 'Attiva configurazione dominio', 'panel_top_settings': 'Impostazioni', 'panel_top_settings_title': 'Impostazioni pannello', 'panel_top_settings_button': '⚙ Impostazioni', 'global_config_title': 'Configurazione globale', 'global_config_keywords': 'Parole chiave globali', 'global_config_usernames': 'Nomi utente globali', 'global_config_share_keywords': 'Parole chiave condivise principale/contenuto', 'global_config_share_usernames': 'Nomi utente condivisi principale/contenuto', 'global_config_linkimport_input_placeholder': 'Inserisci link configurazione', 'global_config_add_global_url': 'Aggiungi', 'global_config_apply_global_apply': 'Applica', 'keywords_config_title': 'Configurazione parole chiave', 'keywords_config_keywords_list_title': 'Lista parole chiave', 'keywords_config_keywords_regex_title': 'Regex parole chiave', 'usernames_config_title': 'Configurazione nomi utente', 'usernames_config_usernames_list_title': 'Lista nomi utente', 'usernames_config_usernames_regex_title': 'Regex nomi utente', 'url_patterns_title': 'Pattern URL', 'url_patterns_main_page_url_patterns_title': 'Pattern URL pagina principale', 'url_patterns_sub_page_url_patterns_title': 'Pattern URL sottopagine', 'url_patterns_content_page_url_patterns_title': 'Pattern URL pagine contenuto', 'xpath_config_title': 'Configurazione XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath titolo principale/sottopagine', 'xpath_config_main_and_sub_page_usernames_title': 'XPath utente principale/sottopagine', 'xpath_config_content_page_keywords_title': 'XPath parole chiave pagina contenuto', 'xpath_config_content_page_usernames_title': 'XPath utente pagina contenuto', 'sync_config_title': 'Sincronizzazione cloud', 'sync_config_server_url': 'Indirizzo server', 'sync_config_user_key': 'Chiave utente', 'sync_config_apply': 'Sincronizza', 'sync_config_delete': 'Elimina configurazione cloud', 'sync_panel_status_connect_failed': 'Connessione fallita: ', 'sync_panel_status_connect_error': 'Errore di connessione: ', 'sync_panel_status_connect_success': 'Connessione riuscita', 'sync_panel_status_sync_failed': 'Sincronizzazione fallita: ', 'sync_panel_status_input_error': 'Inserisci prima indirizzo server e chiave utente', 'sync_panel_status_delete_success': 'Configurazione cloud eliminata con successo', 'sync_panel_status_delete_failed': 'Eliminazione configurazione cloud fallita: ', 'sync_panel_status_delete_confirm': 'Sei sicuro di voler eliminare la configurazione cloud? Questa azione non può essere annullata.', 'sync_panel_status_connect_server_success': 'Connesso al cloud', 'sync_panel_status_client_to_server_success': 'Configurazione locale salvata nel cloud!', 'sync_panel_status_config_updated': 'Configurazione cloud sincronizzata', 'sync_panel_status_config_conflict_1': 'Rilevato conflitto di configurazione!', 'sync_panel_status_config_conflict_2': 'Data configurazione cloud: ', 'sync_panel_status_config_conflict_3': 'Data configurazione locale: ', 'sync_panel_status_config_conflict_4': 'Usare la configurazione cloud? (OK per usare cloud, Annulla per usare locale)', 'sync_panel_status_config_conflict_cloud_newer': 'La configurazione cloud è più recente', 'sync_panel_status_config_conflict_local_newer': 'La configurazione locale è più recente', 'sync_panel_status_disconnect': 'Connessione persa, tentativo di riconnessione...', 'array_editor_add_item_input_placeholder': 'Inserisci valore', 'array_editor_add_item_input_placeholder_regex': 'Inserisci espressione regolare', 'array_editor_add_item': 'Aggiungi', 'array_editor_add_item_title': 'Aggiungi nuovo elemento', 'array_editor_clear_allitem': 'Cancella tutto', 'array_editor_clear_allitem_title': 'Cancella lista', 'array_editor_search_input_placeholder': 'Cerca...', 'array_editor_list_empty_placeholder': 'Nessun dato', 'array_editor_linkimport_input_placeholder': 'Inserisci link', 'array_editor_linkimport_input_button': 'Importa da link', 'array_editor_linkimport_input_button_title': 'Importa da link', 'array_editor_fileimport_input_button': 'Importa da file', 'array_editor_fileimport_input_button_title': 'Importa da file', 'array_editor_export_button': 'Esporta', 'array_editor_export_button_title': 'Esporta lista in file', 'panel_bottom_export_button': 'Esporta configurazione', 'panel_bottom_import_button': 'Importa configurazione', 'panel_bottom_delete_button': 'Elimina configurazione dominio attuale', 'panel_bottom_save_button': 'Salva', 'alert_delete_confirm': 'Sei sicuro di voler eliminare? Questa azione non può essere annullata.', 'alert_clear_confirm': 'Sei sicuro di voler cancellare tutta la lista? Questa azione non può essere annullata.', 'alert_invalid_file': 'Seleziona un file di configurazione valido', 'alert_import_error': 'Impossibile importare la configurazione', 'alert_list_empty': 'La lista è già vuota', 'alert_url_exists': 'Questo URL esiste già!', 'alert_enter_url': 'Inserisci un link valido', 'block_button_title': 'Blocca utente: ' }, 'th-TH': { 'settings_title': 'ตั้งค่าแผง', 'settings_language': 'ภาษา', 'settings_expand_mode': 'โหมดขยาย', 'settings_expand_hover': 'ขยายเมื่อชี้', 'settings_expand_click': 'ขยายเมื่อคลิก', 'settings_block_button_mode': 'โหมดแสดงปุ่มบล็อก', 'settings_block_hover': 'แสดงเมื่อชี้', 'settings_block_always': 'แสดงตลอด', 'settings_horizontal_position': 'ตำแหน่งแนวนอน', 'settings_collapsed_width': 'ความกว้างเมื่อยุบ', 'settings_expanded_width': 'ความกว้างเมื่อขยาย', 'settings_cancel': 'ยกเลิก', 'settings_save': 'บันทึก', 'panel_top_title': 'ตัวกรองฟอรัมสากล', 'panel_top_current_domain': 'โดเมนปัจจุบัน: ', 'panel_top_page_type': 'ประเภทหน้าปัจจุบัน: ', 'panel_top_page_type_main': 'หน้าหลัก', 'panel_top_page_type_sub': 'หน้าย่อย', 'panel_top_page_type_content': 'หน้าเนื้อหา', 'panel_top_page_type_unknown': 'ไม่ทราบประเภทหน้า', 'panel_top_enable_domain': 'เปิดใช้งานการตั้งค่าโดเมนนี้', 'panel_top_settings': 'ตั้งค่า', 'panel_top_settings_title': 'ตั้งค่าแผง', 'panel_top_settings_button': '⚙ ตั้งค่า', 'global_config_title': 'การตั้งค่าทั่วไป', 'global_config_keywords': 'คำสำคัญทั่วไป', 'global_config_usernames': 'ชื่อผู้ใช้ทั่วไป', 'global_config_share_keywords': 'คำสำคัญร่วมหน้าหลัก/เนื้อหา', 'global_config_share_usernames': 'ชื่อผู้ใช้ร่วมหน้าหลัก/เนื้อหา', 'global_config_linkimport_input_placeholder': 'ป้อนลิงก์การตั้งค่า', 'global_config_add_global_url': 'เพิ่ม', 'global_config_apply_global_apply': 'นำไปใช้', 'keywords_config_title': 'การตั้งค่าคำสำคัญ', 'keywords_config_keywords_list_title': 'รายการคำสำคัญ', 'keywords_config_keywords_regex_title': 'Regex คำสำคัญ', 'usernames_config_title': 'การตั้งค่าชื่อผู้ใช้', 'usernames_config_usernames_list_title': 'รายการชื่อผู้ใช้', 'usernames_config_usernames_regex_title': 'Regex ชื่อผู้ใช้', 'url_patterns_title': 'รูปแบบ URL', 'url_patterns_main_page_url_patterns_title': 'รูปแบบ URL หน้าหลัก', 'url_patterns_sub_page_url_patterns_title': 'รูปแบบ URL หน้าย่อย', 'url_patterns_content_page_url_patterns_title': 'รูปแบบ URL หน้าเนื้อหา', 'xpath_config_title': 'การตั้งค่า XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath หัวข้อหน้าหลัก/ย่อย', 'xpath_config_main_and_sub_page_usernames_title': 'XPath ผู้ใช้หน้าหลัก/ย่อย', 'xpath_config_content_page_keywords_title': 'XPath คำสำคัญหน้าเนื้อหา', 'xpath_config_content_page_usernames_title': 'XPath ผู้ใช้หน้าเนื้อหา', 'sync_config_title': 'การซิงค์คลาวด์', 'sync_config_server_url': 'ที่อยู่เซิร์ฟเวอร์', 'sync_config_user_key': 'คีย์ผู้ใช้', 'sync_config_apply': 'ซิงค์', 'sync_config_delete': 'ลบการตั้งค่าคลาวด์', 'sync_panel_status_connect_failed': 'การเชื่อมต่อล้มเหลว: ', 'sync_panel_status_connect_error': 'ข้อผิดพลาดในการเชื่อมต่อ: ', 'sync_panel_status_connect_success': 'เชื่อมต่อสำเร็จ', 'sync_panel_status_sync_failed': 'การซิงค์ล้มเหลว: ', 'sync_panel_status_input_error': 'กรุณาตั้งค่าที่อยู่เซิร์ฟเวอร์และคีย์ผู้ใช้ก่อน', 'sync_panel_status_delete_success': 'ลบการตั้งค่าคลาวด์สำเร็จ', 'sync_panel_status_delete_failed': 'ลบการตั้งค่าคลาวด์ล้มเหลว: ', 'sync_panel_status_delete_confirm': 'คุณแน่ใจหรือไม่ที่จะลบการตั้งค่าคลาวด์? การดำเนินการนี้ไม่สามารถย้อนกลับได้', 'sync_panel_status_connect_server_success': 'เชื่อมต่อกับคลาวด์แล้ว', 'sync_panel_status_client_to_server_success': 'บันทึกการตั้งค่าในเครื่องไปยังคลาวด์แล้ว!', 'sync_panel_status_config_updated': 'ซิงค์การตั้งค่าคลาวด์แล้ว', 'sync_panel_status_config_conflict_1': 'ตรวจพบการตั้งค่าที่ขัดแย้ง!', 'sync_panel_status_config_conflict_2': 'เวลาการตั้งค่าคลาวด์: ', 'sync_panel_status_config_conflict_3': 'เวลาการตั้งค่าในเครื่อง: ', 'sync_panel_status_config_conflict_4': 'ต้องการใช้การตั้งค่าคลาวด์หรือไม่? (กด OK เพื่อใช้การตั้งค่าคลาวด์, กด Cancel เพื่อใช้การตั้งค่าในเครื่อง)', 'sync_panel_status_config_conflict_cloud_newer': 'การตั้งค่าคลาวด์ใหม่กว่า', 'sync_panel_status_config_conflict_local_newer': 'การตั้งค่าในเครื่องใหม่กว่า', 'sync_panel_status_disconnect': 'การเชื่อมต่อขาดหาย กำลังพยายามเชื่อมต่อใหม่...', 'array_editor_add_item_input_placeholder': 'กรุณาป้อน', 'array_editor_add_item_input_placeholder_regex': 'กรุณาป้อน regex', 'array_editor_add_item': 'เพิ่ม', 'array_editor_add_item_title': 'เพิ่มรายการใหม่', 'array_editor_clear_allitem': 'ล้าง', 'array_editor_clear_allitem_title': 'ล้างรายการ', 'array_editor_search_input_placeholder': 'ค้นหา...', 'array_editor_list_empty_placeholder': 'ไม่มีข้อมูล', 'array_editor_linkimport_input_placeholder': 'กรุณาป้อนลิงก์', 'array_editor_linkimport_input_button': 'นำเข้าจากลิงก์', 'array_editor_linkimport_input_button_title': 'นำเข้ารายการจากลิงก์', 'array_editor_fileimport_input_button': 'นำเข้าจากไฟล์', 'array_editor_fileimport_input_button_title': 'นำเข้ารายการจากไฟล์', 'array_editor_export_button': 'ส่งออก', 'array_editor_export_button_title': 'ส่งออกรายการไปยังไฟล์', 'panel_bottom_export_button': 'ส่งออกการตั้งค่า', 'panel_bottom_import_button': 'นำเข้าการตั้งค่า', 'panel_bottom_delete_button': 'ลบการตั้งค่าโดเมนปัจจุบัน', 'panel_bottom_save_button': 'บันทึก', 'alert_delete_confirm': 'คุณแน่ใจหรือไม่ที่จะลบ? การดำเนินการนี้ไม่สามารถย้อนกลับได้', 'alert_clear_confirm': 'คุณแน่ใจหรือไม่ที่จะล้างรายการทั้งหมด? การดำเนินการนี้ไม่สามารถย้อนกลับได้', 'alert_invalid_file': 'กรุณาเลือกไฟล์การตั้งค่าที่ถูกต้อง', 'alert_import_error': 'นำเข้าการตั้งค่าล้มเหลว', 'alert_list_empty': 'รายการว่างเปล่าแล้ว', 'alert_url_exists': 'URL นี้มีอยู่แล้ว!', 'alert_enter_url': 'กรุณาป้อนลิงก์ที่ถูกต้อง', 'block_button_title': 'บล็อกผู้ใช้: ' }, 'es-ES': { 'settings_title': 'Configuración del Panel', 'settings_language': 'Idioma', 'settings_expand_mode': 'Modo de Expansión', 'settings_expand_hover': 'Expandir al Pasar', 'settings_expand_click': 'Expandir al Hacer Clic', 'settings_block_button_mode': 'Modo de Visualización del Botón de Bloqueo', 'settings_block_hover': 'Mostrar al Pasar', 'settings_block_always': 'Mostrar Siempre', 'settings_horizontal_position': 'Posición Horizontal', 'settings_collapsed_width': 'Ancho Contraído', 'settings_expanded_width': 'Ancho Expandido', 'settings_cancel': 'Cancelar', 'settings_save': 'Guardar', 'panel_top_title': 'Filtro Universal de Foros', 'panel_top_current_domain': 'Dominio Actual: ', 'panel_top_page_type': 'Tipo de Página Actual: ', 'panel_top_page_type_main': 'Página Principal', 'panel_top_page_type_sub': 'Subpágina', 'panel_top_page_type_content': 'Página de Contenido', 'panel_top_page_type_unknown': 'Tipo de Página Desconocido', 'panel_top_enable_domain': 'Habilitar Configuración de Dominio', 'panel_top_settings': 'Configuración', 'panel_top_settings_title': 'Configuración del Panel', 'panel_top_settings_button': '⚙ Configuración', 'global_config_title': 'Configuración Global', 'global_config_keywords': 'Palabras Clave Globales', 'global_config_usernames': 'Nombres de Usuario Globales', 'global_config_share_keywords': 'Palabras Clave Compartidas Principal/Contenido', 'global_config_share_usernames': 'Nombres de Usuario Compartidos Principal/Contenido', 'global_config_linkimport_input_placeholder': 'Introducir enlace de configuración', 'global_config_add_global_url': 'Añadir', 'global_config_apply_global_apply': 'Aplicar', 'keywords_config_title': 'Configuración de Palabras Clave', 'keywords_config_keywords_list_title': 'Lista de Palabras Clave', 'keywords_config_keywords_regex_title': 'Regex de Palabras Clave', 'usernames_config_title': 'Configuración de Nombres de Usuario', 'usernames_config_usernames_list_title': 'Lista de Nombres de Usuario', 'usernames_config_usernames_regex_title': 'Regex de Nombres de Usuario', 'url_patterns_title': 'Patrones de URL', 'url_patterns_main_page_url_patterns_title': 'Patrones URL de Página Principal', 'url_patterns_sub_page_url_patterns_title': 'Patrones URL de Subpágina', 'url_patterns_content_page_url_patterns_title': 'Patrones URL de Página de Contenido', 'xpath_config_title': 'Configuración XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath de Título Principal/Subpágina', 'xpath_config_main_and_sub_page_usernames_title': 'XPath de Usuario Principal/Subpágina', 'xpath_config_content_page_keywords_title': 'XPath de Palabras Clave de Contenido', 'xpath_config_content_page_usernames_title': 'XPath de Usuario de Contenido', 'sync_config_title': 'Sincronización en la Nube', 'sync_config_server_url': 'Dirección del Servidor', 'sync_config_user_key': 'Clave de Usuario', 'sync_config_apply': 'Sincronizar', 'sync_config_delete': 'Eliminar Configuración en la Nube', 'sync_panel_status_connect_failed': 'Error de conexión: ', 'sync_panel_status_connect_error': 'Error de conexión: ', 'sync_panel_status_connect_success': 'Conexión exitosa', 'sync_panel_status_sync_failed': 'Error de sincronización: ', 'sync_panel_status_input_error': 'Por favor, configure primero la dirección del servidor y la clave de usuario', 'sync_panel_status_delete_success': 'Configuración en la nube eliminada con éxito', 'sync_panel_status_delete_failed': 'Error al eliminar la configuración en la nube: ', 'sync_panel_status_delete_confirm': '¿Está seguro de que desea eliminar la configuración en la nube? Esta acción no se puede deshacer.', 'sync_panel_status_connect_server_success': 'Conectado a la nube', 'sync_panel_status_client_to_server_success': '¡Configuración local guardada en la nube!', 'sync_panel_status_config_updated': 'Configuración en la nube sincronizada', 'sync_panel_status_config_conflict_1': '¡Se detectó un conflicto de configuración!', 'sync_panel_status_config_conflict_2': 'Hora de configuración en la nube: ', 'sync_panel_status_config_conflict_3': 'Hora de configuración local: ', 'sync_panel_status_config_conflict_4': '¿Usar configuración en la nube? (Aceptar para usar la configuración en la nube, Cancelar para usar la configuración local)', 'sync_panel_status_config_conflict_cloud_newer': 'La configuración en la nube es más reciente', 'sync_panel_status_config_conflict_local_newer': 'La configuración local es más reciente', 'sync_panel_status_disconnect': 'Conexión perdida, intentando reconectar...', 'array_editor_add_item_input_placeholder': 'Por favor, introduce', 'array_editor_add_item_input_placeholder_regex': 'Por favor, introduce regex', 'array_editor_add_item': 'Añadir', 'array_editor_add_item_title': 'Añadir Nuevo Elemento', 'array_editor_clear_allitem': 'Limpiar', 'array_editor_clear_allitem_title': 'Limpiar Lista', 'array_editor_search_input_placeholder': 'Buscar...', 'array_editor_list_empty_placeholder': 'Sin Datos', 'array_editor_linkimport_input_placeholder': 'Por favor, introduce el enlace', 'array_editor_linkimport_input_button': 'Importar desde Enlace', 'array_editor_linkimport_input_button_title': 'Importar Lista desde Enlace', 'array_editor_fileimport_input_button': 'Importar desde Archivo', 'array_editor_fileimport_input_button_title': 'Importar Lista desde Archivo', 'array_editor_export_button': 'Exportar', 'array_editor_export_button_title': 'Exportar Lista a Archivo', 'panel_bottom_export_button': 'Exportar Configuración', 'panel_bottom_import_button': 'Importar Configuración', 'panel_bottom_delete_button': 'Eliminar Configuración de Dominio Actual', 'panel_bottom_save_button': 'Guardar', 'alert_delete_confirm': '¿Estás seguro de que quieres eliminar? Esta acción no se puede deshacer.', 'alert_clear_confirm': '¿Estás seguro de que quieres limpiar toda la lista? Esta acción no se puede deshacer.', 'alert_invalid_file': 'Por favor, selecciona un archivo de configuración válido', 'alert_import_error': 'Error al importar configuración', 'alert_list_empty': 'La lista ya está vacía', 'alert_url_exists': '¡Esta URL ya existe!', 'alert_enter_url': 'Por favor, introduce un enlace válido', 'block_button_title': 'Bloquear Usuario: ' }, 'pt-PT': { 'settings_title': 'Configurações do Painel', 'settings_language': 'Idioma', 'settings_expand_mode': 'Modo de Expansão', 'settings_expand_hover': 'Expandir ao Passar', 'settings_expand_click': 'Expandir ao Clicar', 'settings_block_button_mode': 'Modo de Exibição do Botão de Bloqueio', 'settings_block_hover': 'Mostrar ao Passar', 'settings_block_always': 'Mostrar Sempre', 'settings_horizontal_position': 'Posição Horizontal', 'settings_collapsed_width': 'Largura Recolhida', 'settings_expanded_width': 'Largura Expandida', 'settings_cancel': 'Cancelar', 'settings_save': 'Salvar', 'panel_top_title': 'Filtro Universal de Fóruns', 'panel_top_current_domain': 'Domínio Atual: ', 'panel_top_page_type': 'Tipo de Página Atual: ', 'panel_top_page_type_main': 'Página Principal', 'panel_top_page_type_sub': 'Subpágina', 'panel_top_page_type_content': 'Página de Conteúdo', 'panel_top_page_type_unknown': 'Tipo de Página Desconhecido', 'panel_top_enable_domain': 'Ativar Configuração do Domínio', 'panel_top_settings': 'Configurações', 'panel_top_settings_title': 'Configurações do Painel', 'panel_top_settings_button': '⚙ Configurações', 'global_config_title': 'Configuração Global', 'global_config_keywords': 'Palavras-chave Globais', 'global_config_usernames': 'Nomes de Usuário Globais', 'global_config_share_keywords': 'Palavras-chave Compartilhadas Principal/Conteúdo', 'global_config_share_usernames': 'Nomes de Usuário Compartilhados Principal/Conteúdo', 'global_config_linkimport_input_placeholder': 'Inserir link de configuração', 'global_config_add_global_url': 'Adicionar', 'global_config_apply_global_apply': 'Aplicar', 'keywords_config_title': 'Configuração de Palavras-chave', 'keywords_config_keywords_list_title': 'Lista de Palavras-chave', 'keywords_config_keywords_regex_title': 'Regex de Palavras-chave', 'usernames_config_title': 'Configuração de Nomes de Usuário', 'usernames_config_usernames_list_title': 'Lista de Nomes de Usuário', 'usernames_config_usernames_regex_title': 'Regex de Nomes de Usuário', 'url_patterns_title': 'Padrões de URL', 'url_patterns_main_page_url_patterns_title': 'Padrões URL da Página Principal', 'url_patterns_sub_page_url_patterns_title': 'Padrões URL da Subpágina', 'url_patterns_content_page_url_patterns_title': 'Padrões URL da Página de Conteúdo', 'xpath_config_title': 'Configuração XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath do Título Principal/Subpágina', 'xpath_config_main_and_sub_page_usernames_title': 'XPath do Usuário Principal/Subpágina', 'xpath_config_content_page_keywords_title': 'XPath de Palavras-chave do Conteúdo', 'xpath_config_content_page_usernames_title': 'XPath do Usuário do Conteúdo', 'sync_config_title': 'Sincronização na Nuvem', 'sync_config_server_url': 'Endereço do Servidor', 'sync_config_user_key': 'Chave do Usuário', 'sync_config_apply': 'Sincronizar', 'sync_config_delete': 'Excluir Configuração da Nuvem', 'sync_panel_status_connect_failed': 'Falha na conexão: ', 'sync_panel_status_connect_error': 'Erro de conexão: ', 'sync_panel_status_connect_success': 'Conexão bem-sucedida', 'sync_panel_status_sync_failed': 'Falha na sincronização: ', 'sync_panel_status_input_error': 'Por favor, defina primeiro o endereço do servidor e a chave do usuário', 'sync_panel_status_delete_success': 'Configuração da nuvem excluída com sucesso', 'sync_panel_status_delete_failed': 'Falha ao excluir configuração da nuvem: ', 'sync_panel_status_delete_confirm': 'Tem certeza que deseja excluir a configuração da nuvem? Esta ação não pode ser desfeita.', 'sync_panel_status_connect_server_success': 'Conectado à nuvem', 'sync_panel_status_client_to_server_success': 'Configuração local salva na nuvem!', 'sync_panel_status_config_updated': 'Configuração da nuvem sincronizada', 'sync_panel_status_config_conflict_1': 'Detectado conflito de configuração!', 'sync_panel_status_config_conflict_2': 'Hora da configuração na nuvem: ', 'sync_panel_status_config_conflict_3': 'Hora da configuração local: ', 'sync_panel_status_config_conflict_4': 'Usar configuração da nuvem? (OK para usar configuração da nuvem, Cancelar para usar configuração local)', 'sync_panel_status_config_conflict_cloud_newer': 'Configuração da nuvem é mais recente', 'sync_panel_status_config_conflict_local_newer': 'Configuração local é mais recente', 'sync_panel_status_disconnect': 'Conexão perdida, tentando reconectar...', 'array_editor_add_item_input_placeholder': 'Por favor, insira', 'array_editor_add_item_input_placeholder_regex': 'Por favor, insira regex', 'array_editor_add_item': 'Adicionar', 'array_editor_add_item_title': 'Adicionar Novo Item', 'array_editor_clear_allitem': 'Limpar', 'array_editor_clear_allitem_title': 'Limpar Lista', 'array_editor_search_input_placeholder': 'Pesquisar...', 'array_editor_list_empty_placeholder': 'Sem Dados', 'array_editor_linkimport_input_placeholder': 'Por favor, insira o link', 'array_editor_linkimport_input_button': 'Importar do Link', 'array_editor_linkimport_input_button_title': 'Importar Lista do Link', 'array_editor_fileimport_input_button': 'Importar do Arquivo', 'array_editor_fileimport_input_button_title': 'Importar Lista do Arquivo', 'array_editor_export_button': 'Exportar', 'array_editor_export_button_title': 'Exportar Lista para Arquivo', 'panel_bottom_export_button': 'Exportar Configuração', 'panel_bottom_import_button': 'Importar Configuração', 'panel_bottom_delete_button': 'Excluir Configuração do Domínio Atual', 'panel_bottom_save_button': 'Salvar', 'alert_delete_confirm': 'Tem certeza que deseja excluir? Esta ação não pode ser desfeita.', 'alert_clear_confirm': 'Tem certeza que deseja limpar toda a lista? Esta ação não pode ser desfeita.', 'alert_invalid_file': 'Por favor, selecione um arquivo de configuração válido', 'alert_import_error': 'Falha ao importar configuração', 'alert_list_empty': 'A lista já está vazia', 'alert_url_exists': 'Esta URL já existe!', 'alert_enter_url': 'Por favor, insira um link válido', 'block_button_title': 'Bloquear Usuário: ' }, 'hi-IN': { 'settings_title': 'पैनल सेटिंग्स', 'settings_language': 'भाषा', 'settings_expand_mode': 'विस्तार मोड', 'settings_expand_hover': 'होवर पर विस्तार', 'settings_expand_click': 'क्लिक पर विस्तार', 'settings_block_button_mode': 'ब्लॉक बटन प्रदर्शन मोड', 'settings_block_hover': 'होवर पर दिखाएं', 'settings_block_always': 'हमेशा दिखाएं', 'settings_horizontal_position': 'क्षैतिज स्थिति', 'settings_collapsed_width': 'संकुचित चौड़ाई', 'settings_expanded_width': 'विस्तारित चौड़ाई', 'settings_cancel': 'रद्द करें', 'settings_save': 'सहेजें', 'panel_top_title': 'यूनिवर्सल फोरम फिल्टर', 'panel_top_current_domain': 'वर्तमान डोमेन: ', 'panel_top_page_type': 'वर्तमान पृष्ठ प्रकार: ', 'panel_top_page_type_main': 'मुख्य पृष्ठ', 'panel_top_page_type_sub': 'उप पृष्ठ', 'panel_top_page_type_content': 'सामग्री पृष्ठ', 'panel_top_page_type_unknown': 'अज्ञात पृष्ठ प्रकार', 'panel_top_enable_domain': 'डोमेन कॉन्फ़िगरेशन सक्षम करें', 'panel_top_settings': 'सेटिंग्स', 'panel_top_settings_title': 'पैनल सेटिंग्स', 'panel_top_settings_button': '⚙ सेटिंग्स', 'global_config_title': 'वैश्विक कॉन्फ़िगरेशन', 'global_config_keywords': 'वैश्विक कीवर्ड', 'global_config_usernames': 'वैश्विक उपयोगकर्ता नाम', 'global_config_share_keywords': 'मुख्य/सामग्री पृष्ठ साझा कीवर्ड', 'global_config_share_usernames': 'मुख्य/सामग्री पृष्ठ साझा उपयोगकर्ता नाम', 'global_config_linkimport_input_placeholder': 'कॉन्फ़िग लिंक दर्ज करें', 'global_config_add_global_url': 'जोड़ें', 'global_config_apply_global_apply': 'लागू करें', 'keywords_config_title': 'कीवर्ड कॉन्फ़िगरेशन', 'keywords_config_keywords_list_title': 'कीवर्ड सूची', 'keywords_config_keywords_regex_title': 'कीवर्ड रेगेक्स', 'usernames_config_title': 'उपयोगकर्ता नाम कॉन्फ़िगरेशन', 'usernames_config_usernames_list_title': 'उपयोगकर्ता नाम सूची', 'usernames_config_usernames_regex_title': 'उपयोगकर्ता नाम रेगेक्स', 'url_patterns_title': 'URL पैटर्न', 'url_patterns_main_page_url_patterns_title': 'मुख्य पृष्ठ URL पैटर्न', 'url_patterns_sub_page_url_patterns_title': 'उप पृष्ठ URL पैटर्न', 'url_patterns_content_page_url_patterns_title': 'सामग्री पृष्ठ URL पैटर्न', 'xpath_config_title': 'XPath कॉन्फ़िगरेशन', 'xpath_config_main_and_sub_page_keywords_title': 'मुख्य/उप पृष्ठ शीर्षक XPath', 'xpath_config_main_and_sub_page_usernames_title': 'मुख्य/उप पृष्ठ उपयोगकर्ता XPath', 'xpath_config_content_page_keywords_title': 'सामग्री पृष्ठ कीवर्ड XPath', 'xpath_config_content_page_usernames_title': 'सामग्री पृष्ठ उपयोगकर्ता XPath', 'sync_config_title': 'क्लाउड सिंक', 'sync_config_server_url': 'सर्वर पता', 'sync_config_user_key': 'उपयोगकर्ता कुंजी', 'sync_config_apply': 'सिंक करें', 'sync_config_delete': 'क्लाउड कॉन्फ़िग हटाएं', 'sync_panel_status_connect_failed': 'कनेक्शन विफल: ', 'sync_panel_status_connect_error': 'कनेक्शन त्रुटि: ', 'sync_panel_status_connect_success': 'कनेक्शन सफल', 'sync_panel_status_sync_failed': 'सिंक विफल: ', 'sync_panel_status_input_error': 'कृपया पहले सर्वर पता और उपयोगकर्ता कुंजी सेट करें', 'sync_panel_status_delete_success': 'क्लाउड कॉन्फ़िग सफलतापूर्वक हटाया गया', 'sync_panel_status_delete_failed': 'क्लाउड कॉन्फ़िग हटाने में विफल: ', 'sync_panel_status_delete_confirm': 'क्या आप वाकई क्लाउड कॉन्फ़िग हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।', 'sync_panel_status_connect_server_success': 'क्लाउड से कनेक्ट हो गया', 'sync_panel_status_client_to_server_success': 'स्थानीय कॉन्फ़िग क्लाउड में सहेजा गया!', 'sync_panel_status_config_updated': 'क्लाउड कॉन्फ़िग सिंक हो गया', 'sync_panel_status_config_conflict_1': 'कॉन्फ़िग विरोध का पता चला!', 'sync_panel_status_config_conflict_2': 'क्लाउड कॉन्फ़िग समय: ', 'sync_panel_status_config_conflict_3': 'स्थानीय कॉन्फ़िग समय: ', 'sync_panel_status_config_conflict_4': 'क्या क्लाउड कॉन्फ़िग का उपयोग करें? (क्लाउड कॉन्फ़िग के लिए OK, स्थानीय कॉन्फ़िग के लिए रद्द करें)', 'sync_panel_status_config_conflict_cloud_newer': 'क्लाउड कॉन्फ़िग नया है', 'sync_panel_status_config_conflict_local_newer': 'स्थानीय कॉन्फ़िग नया है', 'sync_panel_status_disconnect': 'कनेक्शन टूट गया, पुनः कनेक्ट करने का प्रयास कर रहा है...', 'array_editor_add_item_input_placeholder': 'कृपया दर्ज करें', 'array_editor_add_item_input_placeholder_regex': 'कृपया रेगेक्स दर्ज करें', 'array_editor_add_item': 'जोड़ें', 'array_editor_add_item_title': 'नई आइटम जोड़ें', 'array_editor_clear_allitem': 'साफ़ करें', 'array_editor_clear_allitem_title': 'सूची साफ़ करें', 'array_editor_search_input_placeholder': 'खोजें...', 'array_editor_list_empty_placeholder': 'कोई डेटा नहीं', 'array_editor_linkimport_input_placeholder': 'लिंक दर्ज करें', 'array_editor_linkimport_input_button': 'लिंक से आयात', 'array_editor_linkimport_input_button_title': 'लिंक से सूची आयात करें', 'array_editor_fileimport_input_button': 'फ़ाइल से आयात', 'array_editor_fileimport_input_button_title': 'फ़ाइल से सूची आयात करें', 'array_editor_export_button': 'निर्यात', 'array_editor_export_button_title': 'सूची को फ़ाइल में निर्यात करें', 'panel_bottom_export_button': 'कॉन्फ़िग निर्यात करें', 'panel_bottom_import_button': 'कॉन्फ़िग आयात करें', 'panel_bottom_delete_button': 'वर्तमान डोमेन कॉन्फ़िग हटाएं', 'panel_bottom_save_button': 'सहेजें', 'alert_delete_confirm': 'क्या आप वाकई हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।', 'alert_clear_confirm': 'क्या आप वाकई पूरी सूची साफ़ करना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।', 'alert_invalid_file': 'कृपया वैध कॉन्फ़िग फ़ाइल चुनें', 'alert_import_error': 'कॉन्फ़िग आयात विफल', 'alert_list_empty': 'सूची पहले से ही खाली है', 'alert_url_exists': 'यह URL पहले से मौजूद है!', 'alert_enter_url': 'कृपया वैध लिंक दर्ज करें', 'block_button_title': 'उपयोगकर्ता को ब्लॉक करें: ' }, 'id-ID': { 'settings_title': 'Pengaturan Panel', 'settings_language': 'Bahasa', 'settings_expand_mode': 'Mode Ekspansi', 'settings_expand_hover': 'Ekspansi saat Hover', 'settings_expand_click': 'Ekspansi saat Klik', 'settings_block_button_mode': 'Mode Tampilan Tombol Blokir', 'settings_block_hover': 'Tampilkan saat Hover', 'settings_block_always': 'Selalu Tampilkan', 'settings_horizontal_position': 'Posisi Horizontal', 'settings_collapsed_width': 'Lebar Terlipat', 'settings_expanded_width': 'Lebar Terekspansi', 'settings_cancel': 'Batal', 'settings_save': 'Simpan', 'panel_top_title': 'Filter Forum Universal', 'panel_top_current_domain': 'Domain Saat Ini: ', 'panel_top_page_type': 'Tipe Halaman Saat Ini: ', 'panel_top_page_type_main': 'Halaman Utama', 'panel_top_page_type_sub': 'Halaman Sub', 'panel_top_page_type_content': 'Halaman Konten', 'panel_top_page_type_unknown': 'Tipe Halaman Tidak Dikenal', 'panel_top_enable_domain': 'Aktifkan Konfigurasi Domain', 'panel_top_settings': 'Pengaturan', 'panel_top_settings_title': 'Pengaturan Panel', 'panel_top_settings_button': '⚙ Pengaturan', 'global_config_title': 'Konfigurasi Global', 'global_config_keywords': 'Kata Kunci Global', 'global_config_usernames': 'Nama Pengguna Global', 'global_config_share_keywords': 'Kata Kunci Bersama Halaman Utama/Konten', 'global_config_share_usernames': 'Nama Pengguna Bersama Halaman Utama/Konten', 'global_config_linkimport_input_placeholder': 'Masukkan tautan konfigurasi', 'global_config_add_global_url': 'Tambah', 'global_config_apply_global_apply': 'Terapkan', 'keywords_config_title': 'Konfigurasi Kata Kunci', 'keywords_config_keywords_list_title': 'Daftar Kata Kunci', 'keywords_config_keywords_regex_title': 'Regex Kata Kunci', 'usernames_config_title': 'Konfigurasi Nama Pengguna', 'usernames_config_usernames_list_title': 'Daftar Nama Pengguna', 'usernames_config_usernames_regex_title': 'Regex Nama Pengguna', 'url_patterns_title': 'Pola URL', 'url_patterns_main_page_url_patterns_title': 'Pola URL Halaman Utama', 'url_patterns_sub_page_url_patterns_title': 'Pola URL Halaman Sub', 'url_patterns_content_page_url_patterns_title': 'Pola URL Halaman Konten', 'xpath_config_title': 'Konfigurasi XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath Judul Halaman Utama/Sub', 'xpath_config_main_and_sub_page_usernames_title': 'XPath Pengguna Halaman Utama/Sub', 'xpath_config_content_page_keywords_title': 'XPath Kata Kunci Halaman Konten', 'xpath_config_content_page_usernames_title': 'XPath Pengguna Halaman Konten', 'sync_config_title': 'Sinkronisasi Cloud', 'sync_config_server_url': 'Alamat Server', 'sync_config_user_key': 'Kunci Pengguna', 'sync_config_apply': 'Sinkronkan', 'sync_config_delete': 'Hapus Konfigurasi Cloud', 'sync_panel_status_connect_failed': 'Gagal terhubung: ', 'sync_panel_status_connect_error': 'Error koneksi: ', 'sync_panel_status_connect_success': 'Berhasil terhubung', 'sync_panel_status_sync_failed': 'Sinkronisasi gagal: ', 'sync_panel_status_input_error': 'Harap atur alamat server dan kunci pengguna terlebih dahulu', 'sync_panel_status_delete_success': 'Berhasil menghapus konfigurasi cloud', 'sync_panel_status_delete_failed': 'Gagal menghapus konfigurasi cloud: ', 'sync_panel_status_delete_confirm': 'Apakah Anda yakin ingin menghapus konfigurasi cloud? Tindakan ini tidak dapat dibatalkan.', 'sync_panel_status_connect_server_success': 'Terhubung ke cloud', 'sync_panel_status_client_to_server_success': 'Konfigurasi lokal telah disimpan ke cloud!', 'sync_panel_status_config_updated': 'Konfigurasi cloud telah disinkronkan', 'sync_panel_status_config_conflict_1': 'Terdeteksi konflik konfigurasi!', 'sync_panel_status_config_conflict_2': 'Waktu konfigurasi cloud: ', 'sync_panel_status_config_conflict_3': 'Waktu konfigurasi lokal: ', 'sync_panel_status_config_conflict_4': 'Gunakan konfigurasi cloud? (Klik OK untuk menggunakan konfigurasi cloud, Batal untuk menggunakan konfigurasi lokal)', 'sync_panel_status_config_conflict_cloud_newer': 'Konfigurasi cloud lebih baru', 'sync_panel_status_config_conflict_local_newer': 'Konfigurasi lokal lebih baru', 'sync_panel_status_disconnect': 'Koneksi terputus, mencoba menghubungkan kembali...', 'array_editor_add_item_input_placeholder': 'Silakan masukkan', 'array_editor_add_item_input_placeholder_regex': 'Silakan masukkan regex', 'array_editor_add_item': 'Tambah', 'array_editor_add_item_title': 'Tambah Item Baru', 'array_editor_clear_allitem': 'Bersihkan', 'array_editor_clear_allitem_title': 'Bersihkan Daftar', 'array_editor_search_input_placeholder': 'Cari...', 'array_editor_list_empty_placeholder': 'Tidak Ada Data', 'array_editor_linkimport_input_placeholder': 'Masukkan tautan', 'array_editor_linkimport_input_button': 'Impor dari Tautan', 'array_editor_linkimport_input_button_title': 'Impor Daftar dari Tautan', 'array_editor_fileimport_input_button': 'Impor dari File', 'array_editor_fileimport_input_button_title': 'Impor Daftar dari File', 'array_editor_export_button': 'Ekspor', 'array_editor_export_button_title': 'Ekspor Daftar ke File', 'panel_bottom_export_button': 'Ekspor Konfigurasi', 'panel_bottom_import_button': 'Impor Konfigurasi', 'panel_bottom_delete_button': 'Hapus Konfigurasi Domain Saat Ini', 'panel_bottom_save_button': 'Simpan', 'alert_delete_confirm': 'Anda yakin ingin menghapus? Tindakan ini tidak dapat dibatalkan.', 'alert_clear_confirm': 'Anda yakin ingin membersihkan seluruh daftar? Tindakan ini tidak dapat dibatalkan.', 'alert_invalid_file': 'Silakan pilih file konfigurasi yang valid', 'alert_import_error': 'Gagal mengimpor konfigurasi', 'alert_list_empty': 'Daftar sudah kosong', 'alert_url_exists': 'URL ini sudah ada!', 'alert_enter_url': 'Silakan masukkan tautan yang valid', 'block_button_title': 'Blokir Pengguna: ' }, 'vi-VN': { 'settings_title': 'Cài đặt Panel', 'settings_language': 'Ngôn ngữ', 'settings_expand_mode': 'Chế độ Mở rộng', 'settings_expand_hover': 'Mở rộng khi Di chuột', 'settings_expand_click': 'Mở rộng khi Nhấp chuột', 'settings_block_button_mode': 'Chế độ Hiển thị Nút Chặn', 'settings_block_hover': 'Hiển thị khi Di chuột', 'settings_block_always': 'Luôn Hiển thị', 'settings_horizontal_position': 'Vị trí Ngang', 'settings_collapsed_width': 'Độ rộng Thu gọn', 'settings_expanded_width': 'Độ rộng Mở rộng', 'settings_cancel': 'Hủy', 'settings_save': 'Lưu', 'panel_top_title': 'Bộ lọc Diễn đàn Phổ thông', 'panel_top_current_domain': 'Tên miền Hiện tại: ', 'panel_top_page_type': 'Loại Trang Hiện tại: ', 'panel_top_page_type_main': 'Trang Chính', 'panel_top_page_type_sub': 'Trang Phụ', 'panel_top_page_type_content': 'Trang Nội dung', 'panel_top_page_type_unknown': 'Loại Trang Không xác định', 'panel_top_enable_domain': 'Bật Cấu hình Tên miền', 'panel_top_settings': 'Cài đặt', 'panel_top_settings_title': 'Cài đặt Panel', 'panel_top_settings_button': '⚙ Cài đặt', 'global_config_title': 'Cấu hình Toàn cục', 'global_config_keywords': 'Từ khóa Toàn cục', 'global_config_usernames': 'Tên người dùng Toàn cục', 'global_config_share_keywords': 'Từ khóa Chung Trang Chính/Nội dung', 'global_config_share_usernames': 'Tên người dùng Chung Trang Chính/Nội dung', 'global_config_linkimport_input_placeholder': 'Nhập liên kết cấu hình', 'global_config_add_global_url': 'Thêm', 'global_config_apply_global_apply': 'Áp dụng', 'keywords_config_title': 'Cấu hình Từ khóa', 'keywords_config_keywords_list_title': 'Danh sách Từ khóa', 'keywords_config_keywords_regex_title': 'Regex Từ khóa', 'usernames_config_title': 'Cấu hình Tên người dùng', 'usernames_config_usernames_list_title': 'Danh sách Tên người dùng', 'usernames_config_usernames_regex_title': 'Regex Tên người dùng', 'url_patterns_title': 'Mẫu URL', 'url_patterns_main_page_url_patterns_title': 'Mẫu URL Trang Chính', 'url_patterns_sub_page_url_patterns_title': 'Mẫu URL Trang Phụ', 'url_patterns_content_page_url_patterns_title': 'Mẫu URL Trang Nội dung', 'xpath_config_title': 'Cấu hình XPath', 'xpath_config_main_and_sub_page_keywords_title': 'XPath Tiêu đề Trang Chính/Phụ', 'xpath_config_main_and_sub_page_usernames_title': 'XPath Người dùng Trang Chính/Phụ', 'xpath_config_content_page_keywords_title': 'XPath Từ khóa Trang Nội dung', 'xpath_config_content_page_usernames_title': 'XPath Người dùng Trang Nội dung', 'sync_config_title': 'Đồng bộ hóa Đám mây', 'sync_config_server_url': 'Địa chỉ Máy chủ', 'sync_config_user_key': 'Khóa Người dùng', 'sync_config_apply': 'Đồng bộ', 'sync_config_delete': 'Xóa Cấu hình Đám mây', 'sync_panel_status_connect_failed': 'Kết nối thất bại: ', 'sync_panel_status_connect_error': 'Lỗi kết nối: ', 'sync_panel_status_connect_success': 'Kết nối thành công', 'sync_panel_status_sync_failed': 'Đồng bộ thất bại: ', 'sync_panel_status_input_error': 'Vui lòng thiết lập địa chỉ máy chủ và khóa người dùng trước', 'sync_panel_status_delete_success': 'Xóa cấu hình đám mây thành công', 'sync_panel_status_delete_failed': 'Xóa cấu hình đám mây thất bại: ', 'sync_panel_status_delete_confirm': 'Bạn có chắc muốn xóa cấu hình đám mây? Hành động này không thể hoàn tác.', 'sync_panel_status_connect_server_success': 'Đã kết nối tới đám mây', 'sync_panel_status_client_to_server_success': 'Cấu hình cục bộ đã được lưu lên đám mây!', 'sync_panel_status_config_updated': 'Đã đồng bộ cấu hình đám mây', 'sync_panel_status_config_conflict_1': 'Phát hiện xung đột cấu hình!', 'sync_panel_status_config_conflict_2': 'Thời gian cấu hình đám mây: ', 'sync_panel_status_config_conflict_3': 'Thời gian cấu hình cục bộ: ', 'sync_panel_status_config_conflict_4': 'Sử dụng cấu hình đám mây? (Nhấn OK để dùng cấu hình đám mây, Hủy để dùng cấu hình cục bộ)', 'sync_panel_status_config_conflict_cloud_newer': 'Cấu hình đám mây mới hơn', 'sync_panel_status_config_conflict_local_newer': 'Cấu hình cục bộ mới hơn', 'sync_panel_status_disconnect': 'Mất kết nối, đang thử kết nối lại...', 'array_editor_add_item_input_placeholder': 'Vui lòng nhập', 'array_editor_add_item_input_placeholder_regex': 'Vui lòng nhập regex', 'array_editor_add_item': 'Thêm', 'array_editor_add_item_title': 'Thêm Mục mới', 'array_editor_clear_allitem': 'Xóa tất cả', 'array_editor_clear_allitem_title': 'Xóa Danh sách', 'array_editor_search_input_placeholder': 'Tìm kiếm...', 'array_editor_list_empty_placeholder': 'Không có Dữ liệu', 'array_editor_linkimport_input_placeholder': 'Nhập liên kết', 'array_editor_linkimport_input_button': 'Nhập từ Liên kết', 'array_editor_linkimport_input_button_title': 'Nhập Danh sách từ Liên kết', 'array_editor_fileimport_input_button': 'Nhập từ Tệp', 'array_editor_fileimport_input_button_title': 'Nhập Danh sách từ Tệp', 'array_editor_export_button': 'Xuất', 'array_editor_export_button_title': 'Xuất Danh sách ra Tệp', 'panel_bottom_export_button': 'Xuất Cấu hình', 'panel_bottom_import_button': 'Nhập Cấu hình', 'panel_bottom_delete_button': 'Xóa Cấu hình Tên miền Hiện tại', 'panel_bottom_save_button': 'Lưu', 'alert_delete_confirm': 'Bạn có chắc muốn xóa? Hành động này không thể hoàn tác.', 'alert_clear_confirm': 'Bạn có chắc muốn xóa toàn bộ danh sách? Hành động này không thể hoàn tác.', 'alert_invalid_file': 'Vui lòng chọn tệp cấu hình hợp lệ', 'alert_import_error': 'Nhập cấu hình thất bại', 'alert_list_empty': 'Danh sách đã trống', 'alert_url_exists': 'URL này đã tồn tại!', 'alert_enter_url': 'Vui lòng nhập liên kết hợp lệ', 'block_button_title': 'Chặn Người dùng: ' } }
  36. let GLOBAL_CONFIG = {
  37. "GLOBAL_KEYWORDS": false,
  38. "GLOBAL_USERNAMES": false,
  39. "SHOW_BLOCK_BUTTON": "hover",
  40. "TIME_INTERVAL": 30,
  41. "LANGUAGE": navigator.language,
  42. "SHOW_WORD_SEGMENTATION": false,
  43. "GLOBAL_CONFIG_URL": [],
  44. "CONFIG_SECTION_COLLAPSED":{
  45. "global_SECTION_COLLAPSED": true,
  46. "keywords_SECTION_COLLAPSED": true,
  47. "usernames_SECTION_COLLAPSED": true,
  48. "url_SECTION_COLLAPSED": true,
  49. "xpath_SECTION_COLLAPSED": true,
  50. "sync_SECTION_COLLAPSED": true,
  51. },
  52. "EDITOR_STATES": {
  53. "keywords": false,
  54. "keywords_regex": false,
  55. "usernames": false,
  56. "usernames_regex": false,
  57. "mainpage_url_patterns": false,
  58. "subpage_url_patterns": false,
  59. "contentpage_url_patterns": false,
  60. "main_and_sub_page_title_xpath": false,
  61. "main_and_sub_page_user_xpath": false,
  62. "contentpage_title_xpath": false,
  63. "contentpage_user_xpath": false
  64. },
  65. "SYNC_CONFIG": {
  66. "server_url": "",
  67. "user_key": "",
  68. "lastSyncTime": 0
  69. }
  70. }
  71. const SAMPLE_TEMPLATE = {
  72. "domain": "",
  73. "enabled": true,
  74. "compatibilityMode": false,
  75. "mainPageUrlPatterns": [],
  76. "subPageUrlPatterns": [],
  77. "contentPageUrlPatterns": [],
  78. "shareKeywordsAcrossPages": false,
  79. "shareUsernamesAcrossPages": true,
  80. "mainAndSubPageKeywords": {
  81. "whitelistMode": false,
  82. "xpath": [],
  83. "keywords": [],
  84. "regexPatterns": []
  85. },
  86. "mainAndSubPageUserKeywords": {
  87. "whitelistMode": false,
  88. "xpath": [],
  89. "keywords": [],
  90. "regexPatterns": []
  91. },
  92. "contentPageKeywords": {
  93. "whitelistMode": false,
  94. "xpath": [],
  95. "keywords": [],
  96. "regexPatterns": []
  97. },
  98. "contentPageUserKeywords": {
  99. "whitelistMode": false,
  100. "xpath": [],
  101. "keywords": [],
  102. "regexPatterns": []
  103. }
  104. }
  105. const DEFAULT_CONFIG = [
  106. {
  107. "domain": "linux.do",
  108. "mainPageUrlPatterns": ['^(?!.*\\/t\\/topic\\/).*',],
  109. "subPageUrlPatterns": [],
  110. "contentPageUrlPatterns": ['^/t/topic/.*'],
  111. "mainAndSubPageKeywords": {
  112. "xpath": ['//tr[@data-topic-id]//a[@role="heading"]//span/text()']
  113. },
  114. "mainAndSubPageUserKeywords": {
  115. "xpath": ['//tr[@data-topic-id]//td[@class="posters topic-list-data"]//a[@data-user-card]/@data-user-card']
  116. },
  117. "contentPageKeywords": {
  118. "xpath": ['//div//article[@role="region"]//p[@dir="auto"]/text()']
  119. },
  120. "contentPageUserKeywords": {
  121. "xpath": ['//div//article[@role="region"]//div[@role="heading"]//a[@data-user-card]/@data-user-card']
  122. }
  123. },
  124. {
  125. "domain": "nodeseek.com",
  126. "mainPageUrlPatterns": ['^/$','^/categories/[^/]+/?$','^/search.*','^/\\?sortBy.*','^/award.*'],
  127. "subPageUrlPatterns": ['/page*'],
  128. "contentPageUrlPatterns": ['/post*'],
  129. "mainAndSubPageKeywords": {
  130. "xpath": ['//li[@class="post-list-item"]//div[@class="post-title"]//a/text()']
  131. },
  132. "mainAndSubPageUserKeywords": {
  133. "xpath": ['//li[@class="post-list-item"]//div[@class="post-info"]//a/text()']
  134. },
  135. "contentPageKeywords": {
  136. "xpath": ['//li[@class="content-item"]//article[@class="post-content"]//p/text()']
  137. },
  138. "contentPageUserKeywords": {
  139. "xpath": ['//li[@class="content-item"]//a[@class="author-name"]/text()']
  140. }
  141. },
  142. {
  143. "domain": "nodeloc.com",
  144. "mainPageUrlPatterns": ['^/$','^/t/.*'],
  145. "subPageUrlPatterns": [],
  146. "contentPageUrlPatterns": ['^/d/.*'],
  147. "mainAndSubPageKeywords": {
  148. "xpath": ['//li//h2[@class="DiscussionListItem-title"]/text()']
  149. },
  150. "mainAndSubPageUserKeywords": {
  151. "xpath": ['//li//a[@class="DiscussionListItem-author"]/split(" ",0,data-original-title)']
  152. },
  153. "contentPageKeywords": {
  154. "xpath": ['//div[@class="PostStream-item"]//div[@class="Post-body"]//p/text()']
  155. },
  156. "contentPageUserKeywords": {
  157. "xpath": ['//div[@class="PostStream-item"]//li[@class="item-user"]//span/text()']
  158. }
  159. },
  160. {
  161. "domain": "hostloc.com",
  162. "mainPageUrlPatterns": ['^/forum-.*$','/forum\\.php\\?mod=forumdisplay.*'],
  163. "subPageUrlPatterns": [],
  164. "contentPageUrlPatterns": ['^/thread-.*$','/forum\\.php\\?mod=viewthread.*'],
  165. "mainAndSubPageKeywords": {
  166. "xpath": ['//tbody//a[@class="s xst"]/text()','//li/a/text()']
  167. },
  168. "mainAndSubPageUserKeywords": {
  169. "xpath": ['//tbody//td[@class="by"]//a/text()','//li//span[@class="by"]/text()']
  170. },
  171. "contentPageKeywords": {
  172. "xpath": ['//div[@id]//td[@class="t_f"]/text()','//div[@class="plc cl"]//div[@class="message"]/text()']
  173. },
  174. "contentPageUserKeywords": {
  175. "xpath": ['//div[@id]//tbody//a[@class="xw1"]/text()','//div[@class="plc cl"]//a[@class="blue"]/text()']
  176. }
  177. },
  178. {
  179. "domain": "bbs.3dmgame.com",
  180. "mainPageUrlPatterns": ['^/forum-.*$','/forum\\.php\\?mod=forumdisplay.*'],
  181. "subPageUrlPatterns": [],
  182. "contentPageUrlPatterns": ['^/thread-.*$','/forum\\.php\\?mod=viewthread.*'],
  183. "mainAndSubPageKeywords": {
  184. "xpath": ['//tbody//a[@class="s xst"]/text()','//li/a/text()']
  185. },
  186. "mainAndSubPageUserKeywords": {
  187. "xpath": ['//tbody//td[@class="by"]//a/text()','//li//span[@class="by"]/text()']
  188. },
  189. "contentPageKeywords": {
  190. "xpath": ['//div[@id]//td[@class="t_f"]/text()','//div[@class="plc cl"]//div[@class="message"]/text()']
  191. },
  192. "contentPageUserKeywords": {
  193. "xpath": ['//div[@id]//tbody//a[@class="xw1"]/text()','//div[@class="plc cl"]//a[@class="blue"]/text()']
  194. }
  195. },
  196. {
  197. "domain": "right.com.cn",
  198. "mainPageUrlPatterns": ['^/forum/forum-.*$'],
  199. "subPageUrlPatterns": [],
  200. "contentPageUrlPatterns": ['^/forum/thread-.*$'],
  201. "mainAndSubPageKeywords": {
  202. "xpath": ['//tbody//a[@class="s xst"]/text()','//li/a/text()']
  203. },
  204. "mainAndSubPageUserKeywords": {
  205. "xpath": ['//tbody//td[@class="by"]//a/text()','//li//span[@class="by"]/text()']
  206. },
  207. "contentPageKeywords": {
  208. "xpath": ['//div[@id]//td[@class="t_f"]/text()','//div[@class="plc cl"]//div[@class="message"]/text()']
  209. },
  210. "contentPageUserKeywords": {
  211. "xpath": ['//div[@id]//tbody//a[@class="xw1"]/text()','//div[@class="plc cl"]//a[@class="blue"]/text()']
  212. }
  213. },
  214. {
  215. "domain": "bbs.nga.cn",
  216. "mainPageUrlPatterns": ['^/thread.*'],
  217. "subPageUrlPatterns": [],
  218. "contentPageUrlPatterns": ['^/read.*'],
  219. "mainAndSubPageKeywords": {
  220. "xpath": ['//tbody//a[@class="topic"]/text()']
  221. },
  222. "mainAndSubPageUserKeywords": {
  223. "xpath": ['//tbody//a[@class="author"]/split(" ",1,title)']
  224. },
  225. "contentPageKeywords": {
  226. "xpath": ['//tbody//span[contains(@class,"postcontent")]/text()']
  227. },
  228. "contentPageUserKeywords": {
  229. "xpath": ['//tbody//a[contains(@class,"userlink")]/split(=,-1,href)']
  230. }
  231. },
  232. {
  233. "domain": "tieba.baidu.com",
  234. "mainPageUrlPatterns": ['^/f\\?kw=.*'],
  235. "subPageUrlPatterns": [],
  236. "contentPageUrlPatterns": ['^/p/.*'],
  237. "mainAndSubPageKeywords": {
  238. "xpath": ['//li//div[@class="threadlist_title pull_left j_th_tit "]//a[@class="j_th_tit "]/text()']
  239. },
  240. "mainAndSubPageUserKeywords": {
  241. "xpath": ['//li//span[@class="frs-author-name-wrap"]//a[contains(@class,"frs-author-name")]/text()']
  242. },
  243. "contentPageKeywords": {
  244. "xpath": ['//div//div[@class="d_post_content_main "]//div[@class="d_post_content j_d_post_content "]/text()',
  245. '//li//span[@class="lzl_content_main"]/text()']
  246. },
  247. "contentPageUserKeywords": {
  248. "xpath": ['//div//div[@class="d_author"]//a[@alog-group="p_author"]/text()',
  249. '//li//a[@alog-group="p_author"]/text()']
  250. }
  251. },
  252. {
  253. "domain": "v2ex.com",
  254. "mainPageUrlPatterns": ['^/$','^/\\?tab=.*','^/recent.*'],
  255. "subPageUrlPatterns": [],
  256. "contentPageUrlPatterns": ['^/t/.*'],
  257. "mainAndSubPageKeywords": {
  258. "xpath": ['//div[@class="cell item"]//span[@class="item_title"]/a/text()']
  259. },
  260. "mainAndSubPageUserKeywords": {
  261. "xpath": ['//div[@class="cell item"]//span[@class="topic_info"]//strong//a/text()','//div[@class="cell item"]//strong/a/text()']
  262. },
  263. "contentPageKeywords": {
  264. "xpath": ['//div[@class="cell"]//div[@class="reply_content"]/text()']
  265. },
  266. "contentPageUserKeywords": {
  267. "xpath": ['//div[@class="cell"]//strong/a/text()']
  268. }
  269. },
  270. {
  271. "domain": "zhihu.com",
  272. "mainPageUrlPatterns": ['^/$','^/hot.*','^/follow.*','^/zvideo.*'],
  273. "subPageUrlPatterns": [],
  274. "contentPageUrlPatterns": ['^/question/.*'],
  275. "mainAndSubPageKeywords": {
  276. "xpath": ['//div[@class="Card TopstoryItem TopstoryItem-isRecommend"]//a[@data-za-detail-view-element_name="Title"]/text()',
  277. '//section[@class="HotItem"]//h2[@class="HotItem-title"]/text()']
  278. },
  279. "mainAndSubPageUserKeywords": {
  280. "xpath": ['//div[@class="Card TopstoryItem TopstoryItem-isRecommend"]//a[@class="UserLink-link"]/text()']
  281. },
  282. "contentPageKeywords": {
  283. "xpath": ['//div[@class="List-item"]//div[@class="RichContent-inner"]//span/p/text()']
  284. },
  285. "contentPageUserKeywords": {
  286. "xpath": ['//div[@class="List-item"]//a[@class="UserLink-link"]/text()']
  287. }
  288. },
  289. {
  290. "domain": "douban.com",
  291. "mainPageUrlPatterns": ['^/group/explore.*','^/group/\\d+/.*'],
  292. "subPageUrlPatterns": [],
  293. "contentPageUrlPatterns": ['^/group/topic/.*'],
  294. "mainAndSubPageKeywords": {
  295. "xpath": ['//div[@class="channel-item"]//div[@class="bd"]//h3//a/text()',
  296. '//tr//td[@class="title"]//a/text()']
  297. },
  298. "mainAndSubPageUserKeywords": {
  299. "xpath": ['//tr//td[@nowrap]//a/text()']
  300. },
  301. "contentPageKeywords": {
  302. "xpath": ['//li//div[@class="reply-content"]//p/text()']
  303. },
  304. "contentPageUserKeywords": {
  305. "xpath": ['//li//h4//a/text()']
  306. }
  307. },
  308. {
  309. "domain": "lowendtalk.com",
  310. "mainPageUrlPatterns": ['^/$','^/categories/[^/]+/$'],
  311. "subPageUrlPatterns": ['/discussions/p*','^/categories/[^/]+/?$'],
  312. "contentPageUrlPatterns": ['^/discussion/\\d+/.*'],
  313. "mainAndSubPageKeywords": {
  314. "xpath": ['//li//div[@class="Title"]//a/text()']
  315. },
  316. "mainAndSubPageUserKeywords": {
  317. "xpath": ['//li//span[contains(@class,"DiscussionAuthor")]//a/text()']
  318. },
  319. "contentPageKeywords": {
  320. "xpath": ['//li//div[@class="Item-Body"]//p/text()']
  321. },
  322. "contentPageUserKeywords": {
  323. "xpath": ['//li//a[@class="Username"]/text()']
  324. }
  325. },
  326. {
  327. "domain": "reddit.com",
  328. "mainPageUrlPatterns": ['^/$','feed=home','^/r/[^/]+/$'],
  329. "subPageUrlPatterns": [],
  330. "contentPageUrlPatterns": ['^/r/.*/comments/.*'],
  331. "mainAndSubPageKeywords": {
  332. "xpath": ['//article[@aria-label]/@aria-label']
  333. },
  334. "mainAndSubPageUserKeywords": {
  335. "xpath": []
  336. },
  337. "contentPageKeywords": {
  338. "xpath": ['//shreddit-comment//div[@slot="comment"]//p/text()']
  339. },
  340. "contentPageUserKeywords": {
  341. "xpath": ['//shreddit-comment//faceplate-tracker[@source="post_detail"]//a/text()']
  342. }
  343. }
  344. ]
  345. let wsConnection = null;
  346. function loadUserConfig() {
  347. try {
  348. let userConfig = GM_getValue('userConfig');
  349. let globalConfig = GM_getValue('globalConfig');
  350. if(globalConfig){
  351. const parsedConfig = JSON.parse(globalConfig);
  352. for (const key in parsedConfig) {
  353. if (key in GLOBAL_CONFIG) {
  354. GLOBAL_CONFIG[key] = parsedConfig[key];
  355. }
  356. }
  357. }else{
  358. GM_setValue('globalConfig', JSON.stringify(GLOBAL_CONFIG));
  359. }
  360. if (userConfig) {
  361. userConfig = JSON.parse(userConfig);
  362. }
  363. if (!userConfig || !Array.isArray(userConfig)) {
  364. userConfig = [];
  365. }
  366. let isNewConfig = false;
  367. DEFAULT_CONFIG.forEach(defaultItem => {
  368. if (defaultItem.domain === 'hostloc.com') {
  369. }
  370. const existingConfig = userConfig.find(config => config.domain === defaultItem.domain);
  371. if (!existingConfig) {
  372. const newConfig = structuredClone(SAMPLE_TEMPLATE);
  373. Object.assign(newConfig, defaultItem);
  374. userConfig.push(newConfig);
  375. isNewConfig = true;
  376. if (defaultItem.domain === 'hostloc.com') {
  377. }
  378. }
  379. });
  380. if (isNewConfig) {
  381. saveUserConfig(userConfig);
  382. }
  383. return userConfig;
  384. } catch (error) {
  385. console.error('加载配置失败:', error);
  386. return DEFAULT_CONFIG;
  387. }
  388. }
  389. let isFirstLoad = true;
  390. let userConfig = loadUserConfig();
  391. function saveUserConfig(config) {
  392. try {
  393. GM_setValue('userConfig', JSON.stringify(config));
  394. if(isFirstLoad){
  395. isFirstLoad = false;
  396. }else{
  397. updateUserConfig();
  398. }
  399. } catch (error) {
  400. console.error('保存配置失败:', error);
  401. }
  402. }
  403. function saveGlobalConfig() {
  404. try {
  405. GM_setValue('globalConfig', JSON.stringify(GLOBAL_CONFIG));
  406. } catch (error) {
  407. console.error('保存全局配置失败:', error);
  408. }
  409. }
  410. function updateUserConfig(){
  411. userConfig = loadUserConfig();
  412. }
  413. function getDomainConfig(domain){
  414. return userConfig.find(config => config.domain === domain);
  415. }
  416. function addDomainConfig(configData) {
  417. if (!configData || !configData.domain) {
  418. console.error('添加配置失败: domain 是必填字段');
  419. return {
  420. success: false,
  421. message: 'domain 是必填字段'
  422. };
  423. }
  424. const existingConfig = getDomainConfig(configData.domain);
  425. if (existingConfig) {
  426. console.error('添加配置失败: 已存在相同domain的配置');
  427. return {
  428. success: false,
  429. message: '已存在相同domain的配置'
  430. };
  431. }
  432. const newConfig = JSON.parse(JSON.stringify(SAMPLE_TEMPLATE));
  433. Object.assign(newConfig, configData);
  434. userConfig.push(newConfig);
  435. saveUserConfig(userConfig);
  436. return {
  437. success: true,
  438. message: '配置添加成功',
  439. config: newConfig
  440. };
  441. }
  442. function removeDomainConfig(domain){
  443. userConfig = userConfig.filter(config => config.domain !== domain);
  444. saveUserConfig(userConfig);
  445. return {
  446. success: true,
  447. message: '配置删除成功',
  448. config: userConfig
  449. };
  450. }
  451. function updateDomainConfigOverride(domain, configData){
  452. const index = userConfig.findIndex(config => config.domain === domain);
  453. if (index !== -1) {
  454. userConfig[index] = configData;
  455. saveUserConfig(userConfig);
  456. return {
  457. success: true,
  458. message: '配置更新成功',
  459. config: userConfig[index]
  460. };
  461. }
  462. }
  463. function updateDomainConfig(domain, configData) {
  464. const index = userConfig.findIndex(config => config.domain === domain);
  465. if (index !== -1) {
  466. const existingConfig = JSON.parse(JSON.stringify(userConfig[index]));
  467. for (const key in configData) {
  468. if (Array.isArray(configData[key])) {
  469. existingConfig[key] = [...new Set([
  470. ...(existingConfig[key] || []),
  471. ...configData[key]
  472. ])];
  473. } else if (typeof configData[key] === 'object' && configData[key] !== null) {
  474. existingConfig[key] = existingConfig[key] || {};
  475. for (const subKey in configData[key]) {
  476. if (Array.isArray(configData[key][subKey])) {
  477. existingConfig[key][subKey] = [...new Set([
  478. ...(existingConfig[key][subKey] || []),
  479. ...configData[key][subKey]
  480. ])];
  481. } else {
  482. existingConfig[key][subKey] = configData[key][subKey];
  483. }
  484. }
  485. } else {
  486. existingConfig[key] = configData[key];
  487. }
  488. }
  489. userConfig[index] = existingConfig;
  490. saveUserConfig(userConfig);
  491. return {
  492. success: true,
  493. message: '配置更新成功',
  494. config: userConfig[index]
  495. };
  496. }
  497. return {
  498. success: false,
  499. message: '未找到指定域名的配置',
  500. config: null
  501. };
  502. }
  503. function validateXPathRelationship(parentXPath, childXPath) {
  504. const cleanChildXPath = childXPath.replace(/^\/+/, '');
  505. const cleanParentXPath = parentXPath.replace(/\/+$/, '');
  506. const combinedXPath = `${cleanParentXPath}//${cleanChildXPath}`;
  507. const result = document.evaluate(
  508. combinedXPath,
  509. document,
  510. null,
  511. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  512. null
  513. );
  514. const matchCount = result.snapshotLength;
  515. return {
  516. isValid: matchCount > 0,
  517. matchCount: matchCount,
  518. elements: Array.from({length: matchCount}, (_, i) => result.snapshotItem(i)),
  519. combinedXPath: combinedXPath
  520. };
  521. }
  522. //获取数组中的元素,支持负索引。
  523. function getArrayElement(array, index) {
  524. if (!Array.isArray(array)) {
  525. throw new Error("第一个参数必须是数组。");
  526. }
  527. const numericIndex = Number(index);
  528. if (isNaN(numericIndex)) {
  529. throw new Error("索引必须是有效的数字或数字字符串。");
  530. }
  531. const adjustedIndex = numericIndex < 0 ? array.length + numericIndex : numericIndex;
  532. if (adjustedIndex < 0 || adjustedIndex >= array.length) {
  533. throw new RangeError("索引超出范围。");
  534. }
  535. return array[adjustedIndex];
  536. }
  537. function getElementsByText(xpath, searchText, useRegex = false, whitelistMode = false) {
  538. let isSplit = false;
  539. let split_char;
  540. let split_get_target_char_index;
  541. let isAttrSplit = false;
  542. let attrSplitAttr_attrname;
  543. //如果有自定义方法split,则使用自定义方法split
  544. if(xpath.includes('/split')){
  545. const args_str = xpath.split('/split')[1];
  546. xpath = xpath.split('/split')[0];
  547. const regex = /\(([^)]+)\)/;
  548. const match = args_str.match(regex);
  549. if (match) {
  550. const params = match[1].split(',').map(param => param.trim().replace(/^['"]|['"]$/g, ''));
  551. if(params.length == 3){
  552. split_char = params[0];
  553. split_get_target_char_index = params[1];
  554. attrSplitAttr_attrname = params[2];
  555. isAttrSplit = true;
  556. isSplit = true;
  557. }
  558. }
  559. }
  560. const result = document.evaluate(
  561. xpath,
  562. document,
  563. null,
  564. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  565. null
  566. );
  567. if (result.snapshotLength === 0) {
  568. return [];
  569. }
  570. const elements = [];
  571. const searchPattern = useRegex ? new RegExp(searchText, 'i') : (searchText ? searchText.toLowerCase() : null);
  572. for (let i = 0; i < result.snapshotLength; i++) {
  573. const element = result.snapshotItem(i);
  574. if (!searchText) {
  575. elements.push(element);
  576. continue;
  577. }
  578. let elementText;
  579. let isMatch;
  580. if(isSplit){
  581. if(isAttrSplit){
  582. let args_array = element.getAttribute(attrSplitAttr_attrname).split(split_char);
  583. //支持负索引
  584. elementText = getArrayElement(args_array, split_get_target_char_index);
  585. isMatch = useRegex ?
  586. searchPattern.test(elementText) :
  587. elementText.toLowerCase().includes(searchPattern);
  588. }
  589. }
  590. else{
  591. elementText = element.textContent.trim();
  592. isMatch = useRegex ?
  593. searchPattern.test(elementText) :
  594. elementText.toLowerCase().includes(searchPattern);
  595. }
  596. if (whitelistMode ? !isMatch : isMatch) {
  597. elements.push(element);
  598. }
  599. }
  600. return elements;
  601. }
  602. function extractStrings(input) {
  603. const result = [];
  604. let temp = '';
  605. for (let i = 0; i < input.length; i++) {
  606. if (input[i] === '/') {
  607. if (temp) {
  608. result.push(temp);
  609. temp = '';
  610. }
  611. continue;
  612. }
  613. temp += input[i];
  614. }
  615. if (temp) result.push(temp);
  616. return result;
  617. }
  618. function findTargetAncestor(xpath, element) {
  619. let cleanXPath = xpath;
  620. if (!xpath.endsWith('/text()')) {
  621. cleanXPath = xpath.replace(/\/text\(\)$/, '');
  622. }
  623. if (cleanXPath.includes('/@')) {
  624. cleanXPath = cleanXPath.split('/@')[0];
  625. }
  626. const xpathParts = extractStrings(cleanXPath);
  627. if (xpathParts.length > 2) {
  628. let elementNode = element;
  629. if (element.nodeType === Node.TEXT_NODE) {
  630. elementNode = element.parentElement;
  631. } else if (element.nodeType === Node.ATTRIBUTE_NODE) {
  632. elementNode = element.ownerElement;
  633. }
  634. const intermediateSelector = parseXPathPart(xpathParts[1]);
  635. const intermediateElement = elementNode.closest(intermediateSelector);
  636. if (intermediateElement) {
  637. const rootSelector = parseXPathPart(xpathParts[0]);
  638. let targetElement = intermediateElement.closest(rootSelector);
  639. if (targetElement === intermediateElement) {
  640. targetElement = intermediateElement.parentElement.closest(rootSelector);
  641. }
  642. return {
  643. targetElement,
  644. firstElementInXPath: xpathParts[0]
  645. };
  646. }
  647. }
  648. const firstElementMatch = xpath.match(/\/+([a-zA-Z0-9_-]+(?:\[[^\]]+\])?)/)?.[1];
  649. if (!firstElementMatch) {
  650. return {
  651. targetElement: element,
  652. firstElementInXPath: null
  653. };
  654. }
  655. let [elementType, attributeSelector] = firstElementMatch.includes('[') ?
  656. firstElementMatch.split('[') :
  657. [firstElementMatch, null];
  658. const cleanAttributeSelector = attributeSelector?.replace(']', '');
  659. let elementNode = element;
  660. if (element.nodeType === Node.TEXT_NODE) {
  661. elementNode = element.parentElement;
  662. } else if (element.nodeType === Node.ATTRIBUTE_NODE) {
  663. elementNode = element.ownerElement;
  664. }
  665. const cssSelector = cleanAttributeSelector ?
  666. `${elementType}[${cleanAttributeSelector.replace('@', '')}]` :
  667. elementType;
  668. const targetElement = elementType && elementNode ?
  669. elementNode.closest(cssSelector) :
  670. elementNode;
  671. return {
  672. targetElement,
  673. firstElementInXPath: firstElementMatch
  674. };
  675. }
  676. function parseXPathPart(xpathPart) {
  677. const elementMatch = xpathPart.match(/([a-zA-Z0-9_-]+)(?:\[(.*?)\])?/);
  678. if (!elementMatch) return xpathPart;
  679. const [, tag, attribute] = elementMatch;
  680. if (!attribute) return tag;
  681. const attrMatch = attribute.match(/@([a-zA-Z0-9_-]+)(?:=['"]([^'"]+)['"])?/);
  682. if (!attrMatch) return tag;
  683. const [, attrName, attrValue] = attrMatch;
  684. return attrValue ?
  685. `${tag}[${attrName}="${attrValue}"]` :
  686. `${tag}[${attrName}]`;
  687. }
  688. function compatibilityRemovalHandler(element) {
  689. //TODO:通用兼容模式,未完成
  690. const clearNodes = (node) => {
  691. if (node.nodeType === Node.TEXT_NODE) {
  692. node.textContent = '';
  693. return;
  694. }
  695. if (node.nodeType === Node.ELEMENT_NODE) {
  696. if (node !== element && node.nodeName === element.nodeName) {
  697. return;
  698. }
  699. const urlAttributes = ['href', 'src', 'data-src', 'data-original', 'background', 'poster'];
  700. urlAttributes.forEach(attr => {
  701. if (node.hasAttribute(attr)) {
  702. node.removeAttribute(attr);
  703. }
  704. });
  705. if (node.nodeName === 'IMG') {
  706. while (node.attributes.length > 0) {
  707. node.removeAttribute(node.attributes[0].name);
  708. }
  709. }
  710. }
  711. Array.from(node.childNodes).forEach(child => {
  712. clearNodes(child);
  713. });
  714. };
  715. clearNodes(element);
  716. }
  717. function removeElementsByText(xpath, searchText, useRegex = false, whitelistMode = false) {
  718. let xpath_before = xpath;
  719. if (xpath.endsWith('text()')) {
  720. xpath = xpath.replace(/\/text\(\)$/, '');
  721. }
  722. const elements = getElementsByText(xpath, searchText, useRegex, whitelistMode);
  723. if (elements.length === 0) {
  724. return;
  725. }
  726. elements.forEach((element, index) => {
  727. const { targetElement, firstElementInXPath } = findTargetAncestor(xpath, element);
  728. const currentConfig = getDomainConfig(getCurrentDomain());
  729. const isContentPage = currentConfig.contentPageUrlPatterns
  730. .some(pattern => new RegExp(pattern).test(getSplitUrl()));
  731. if(isContentPage && getCurrentDomain().includes('reddit.com')){
  732. compatibilityRemovalHandler(targetElement);
  733. }
  734. else{
  735. targetElement.parentNode?.removeChild(targetElement);
  736. }
  737. });
  738. }
  739. function removeOverflowHidden(element) {
  740. let currentElement = element;
  741. let upCount = 0;
  742. while (currentElement && upCount < 3) {
  743. const computedStyle = window.getComputedStyle(currentElement);
  744. if (computedStyle.overflow === 'hidden') {
  745. currentElement.style.overflow = 'visible';
  746. } else {
  747. }
  748. currentElement = currentElement.parentElement;
  749. upCount++;
  750. }
  751. function processChildren(el, depth) {
  752. if (!el || depth > 3) return;
  753. const children = el.children;
  754. for (const child of children) {
  755. const computedStyle = window.getComputedStyle(child);
  756. if (computedStyle.overflow === 'hidden') {
  757. child.style.overflow = 'visible';
  758. } else {
  759. }
  760. processChildren(child, depth + 1);
  761. }
  762. }
  763. processChildren(element, 1);
  764. }
  765. function addBlockButtonsToUsernames(xpath, isContentPage) {
  766. if (!xpath) return;
  767. let attributeXpath = xpath;
  768. let attributeElements;
  769. let attr;
  770. let isAttribute = false;
  771. let isSplit = false;
  772. let split_Xpath;
  773. let split_char;
  774. let split_get_target_char_index;
  775. let attrSplitElements;
  776. let attrSplitAttr_attrname;
  777. let isAttrSplit = false;
  778. let textSplitElements;
  779. let isTextSplit = false;
  780. //两种情况,第一种是用户名在属性里,例如:username="用户名 2024-01-01"
  781. //第二种是用户名在文本里,例如:<div>用户名 2024-01-01</div>
  782. //自定义split方法处理这两种情况
  783. //两种参数的情况就是用户名在文本里,第一个参数是分隔符,第二个参数是目标字符的索引,还没实现
  784. //三种参数的情况就是用户名在属性里,第一个参数是分隔符,第二个参数是目标字符的索引,第三个参数是属性名
  785. if (xpath.includes('/split')) {
  786. isSplit = true;
  787. const args_str = xpath.split('/split')[1];
  788. const regex = /\(([^)]+)\)/;
  789. const match = args_str.match(regex);
  790. if (match) {
  791. isSplit = true;
  792. split_Xpath = xpath.split('/split')[0];
  793. const params = match[1].split(',').map(param => param.trim().replace(/^['"]|['"]$/g, ''));
  794. if(params.length >= 2){
  795. split_char = params[0];
  796. split_get_target_char_index = params[1];
  797. if(params.length == 2){
  798. //TODO: 处理两个参数的情况
  799. isTextSplit = true;
  800. }
  801. if(params.length == 3){
  802. attrSplitAttr_attrname = params[2];
  803. attrSplitElements = document.evaluate(split_Xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  804. isAttrSplit = true;
  805. xpath = split_Xpath;
  806. }
  807. }
  808. }
  809. }
  810. if (attributeXpath.includes('/@') && isSplit == false) {
  811. attr = attributeXpath.split('/@')[1];
  812. attributeXpath = attributeXpath.split('/@')[0];
  813. attributeElements = document.evaluate(attributeXpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  814. isAttribute = true;
  815. }
  816. const elements = document.evaluate(
  817. xpath,
  818. document,
  819. null,
  820. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  821. null
  822. );
  823. const elementsArray = [];
  824. if (isAttribute && isSplit == false) {
  825. for (let i = 0; i < attributeElements.snapshotLength; i++) {
  826. elementsArray.push(attributeElements.snapshotItem(i));
  827. }
  828. }
  829. else if(isAttrSplit){
  830. for (let i = 0; i < attrSplitElements.snapshotLength; i++) {
  831. elementsArray.push(attrSplitElements.snapshotItem(i));
  832. }
  833. }
  834. else{
  835. for (let i = 0; i < elements.snapshotLength; i++) {
  836. elementsArray.push(elements.snapshotItem(i));
  837. }
  838. }
  839. elementsArray.forEach((element, index) => {
  840. let username;
  841. if(isAttribute && isSplit == false){
  842. username = element.getAttribute(attr);
  843. }
  844. else if(isAttrSplit){
  845. let args_array = element.getAttribute(attrSplitAttr_attrname).split(split_char);
  846. username = getArrayElement(args_array, split_get_target_char_index);
  847. }
  848. else{
  849. username = element.textContent.trim();
  850. }
  851. const { targetElement } = findTargetAncestor(xpath, element);
  852. if (targetElement) {
  853. removeOverflowHidden(element.parentNode);
  854. const existingButton = targetElement.querySelector('.block-user-btn');
  855. if (existingButton) {
  856. return;
  857. }
  858. try {
  859. const blockButton = document.createElement('div');
  860. blockButton.className = 'block-user-btn';
  861. blockButton.setAttribute('data-username', username);
  862. blockButton.textContent = '×';
  863. blockButton.title = setTextfromTemplate('block_button_title') + `${username}`;
  864. blockButton.style.cssText = `
  865. display: ${PANEL_SETTINGS.showBlockButton === 'always' ? 'inline-flex' : 'none'};
  866. align-items: center !important;
  867. justify-content: center !important;
  868. margin-left: 5px !important;
  869. padding: 0 !important;
  870. width: 1.2em !important;
  871. height: 1.2em !important;
  872. background: rgba(0, 0, 0, 0.6) !important;
  873. color: #fff !important;
  874. border-radius: 4px !important;
  875. cursor: pointer !important;
  876. z-index: 9999 !important;
  877. transition: all 0.3s !important;
  878. user-select: none !important;
  879. font-size: inherit !important;
  880. line-height: 1 !important;
  881. vertical-align: middle !important;
  882. text-align: center !important;
  883. position: relative !important;
  884. `;
  885. const wrapper = document.createElement('span');
  886. wrapper.style.cssText = `
  887. display: inline-flex;
  888. align-items: center;
  889. position: relative;
  890. `;
  891. if (element.parentNode) {
  892. element.parentNode.insertBefore(wrapper, element);
  893. wrapper.appendChild(element);
  894. wrapper.appendChild(blockButton);
  895. blockButton.addEventListener('click', (e) => {
  896. e.preventDefault();
  897. e.stopPropagation();
  898. const username = blockButton.getAttribute('data-username');
  899. const currentConfig = getDomainConfig(getCurrentDomain());
  900. if (currentConfig) {
  901. const configKey = isContentPage ? 'contentPageUserKeywords' : 'mainAndSubPageUserKeywords';
  902. if (!currentConfig[configKey].keywords) {
  903. currentConfig[configKey].keywords = [];
  904. }
  905. if (!currentConfig[configKey].keywords.includes(username)) {
  906. currentConfig[configKey].keywords.push(username);
  907. updateDomainConfig(getCurrentDomain(), currentConfig);
  908. debouncedHandleElements();
  909. updatePanelContent();
  910. } else {
  911. }
  912. }
  913. });
  914. const textSpan = document.createElement('span');
  915. textSpan.textContent = '×';
  916. textSpan.style.cssText = `
  917. position: absolute;
  918. top: 50%;
  919. left: 50%;
  920. transform: translate(-50%, -50%);
  921. line-height: 0;
  922. `;
  923. blockButton.textContent = '';
  924. blockButton.appendChild(textSpan);
  925. if (PANEL_SETTINGS.showBlockButton === 'hover') {
  926. wrapper.addEventListener('mouseenter', () => {
  927. blockButton.style.display = 'inline-flex';
  928. });
  929. wrapper.addEventListener('mouseleave', () => {
  930. blockButton.style.display = 'none';
  931. });
  932. }
  933. blockButton.addEventListener('mouseenter', () => {
  934. blockButton.style.background = 'rgba(0, 0, 0, 0.8)';
  935. });
  936. blockButton.addEventListener('mouseleave', () => {
  937. blockButton.style.background = 'rgba(0, 0, 0, 0.6)';
  938. });
  939. } else {
  940. console.warn(`无法为用户名 ${username} 添加屏蔽按钮:父元素不存在`);
  941. }
  942. } catch (error) {
  943. console.warn(`为用户名 ${username} 添加屏蔽按钮时发生错误:`, error);
  944. }
  945. }
  946. });
  947. }
  948. function getCurrentDomain(){
  949. const currentUrl = new URL(window.location.href);
  950. const baseDomain = currentUrl.hostname.replace(/^www\./, '');
  951. return baseDomain;
  952. }
  953. function removeCSS(cssSelector, attribute = null, value = null) {
  954. try {
  955. if (typeof cssSelector !== 'string') {
  956. console.error('cssSelector必须是字符串格式');
  957. return;
  958. }
  959. const elements = document.querySelectorAll(cssSelector);
  960. if (elements.length === 0) {
  961. return;
  962. }
  963. elements.forEach(element => {
  964. if (attribute) {
  965. if (value) {
  966. if (element.style[attribute] === value) {
  967. element.style[attribute] = '';
  968. }
  969. } else {
  970. element.style[attribute] = '';
  971. }
  972. } else {
  973. element.removeAttribute('style');
  974. if (cssSelector.startsWith('.')) {
  975. const className = cssSelector.substring(1);
  976. element.classList.remove(className);
  977. }
  978. }
  979. });
  980. Array.from(document.styleSheets).forEach(styleSheet => {
  981. try {
  982. const rules = styleSheet.cssRules || styleSheet.rules;
  983. for (let i = rules.length - 1; i >= 0; i--) {
  984. if (rules[i].selectorText === cssSelector) {
  985. styleSheet.deleteRule(i);
  986. }
  987. }
  988. } catch (e) {
  989. }
  990. });
  991. } catch (error) {
  992. console.error('移除CSS时发生错误:', error);
  993. }
  994. }
  995. function removeWebsiteCSS() {
  996. if(getCurrentDomain().includes('v2ex.com')){
  997. removeCSS('.collapsed', 'display', 'none');
  998. }
  999. }
  1000. function handleElements() {
  1001. debouncedRemoveWebsiteCSS();
  1002. const currentConfig = getDomainConfig(getCurrentDomain());
  1003. if (currentConfig && currentConfig.enabled) {
  1004. const isMainOrSubPage = [...currentConfig.mainPageUrlPatterns, ...currentConfig.subPageUrlPatterns]
  1005. .some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1006. const isContentPage = currentConfig.contentPageUrlPatterns
  1007. .some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1008. let keywords = [];
  1009. let keywords_regex = [];
  1010. let usernames = [];
  1011. let usernames_regex = [];
  1012. if (GLOBAL_CONFIG.GLOBAL_KEYWORDS) {
  1013. keywords = [...new Set(
  1014. userConfig.reduce((acc, config) => [
  1015. ...acc,
  1016. ...(config.mainAndSubPageKeywords?.keywords || []),
  1017. ...(config.contentPageKeywords?.keywords || [])
  1018. ], [])
  1019. )];
  1020. keywords_regex = [...new Set(
  1021. userConfig.reduce((acc, config) => [
  1022. ...acc,
  1023. ...(config.mainAndSubPageKeywords?.regexPatterns || []),
  1024. ...(config.contentPageKeywords?.regexPatterns || [])
  1025. ], [])
  1026. )];
  1027. } else if (currentConfig.shareKeywordsAcrossPages) {
  1028. keywords = [...new Set([
  1029. ...(currentConfig.mainAndSubPageKeywords?.keywords || []),
  1030. ...(currentConfig.contentPageKeywords?.keywords || [])
  1031. ])];
  1032. keywords_regex = [...new Set([
  1033. ...(currentConfig.mainAndSubPageKeywords?.regexPatterns || []),
  1034. ...(currentConfig.contentPageKeywords?.regexPatterns || [])
  1035. ])];
  1036. } else {
  1037. if (isMainOrSubPage) {
  1038. keywords = currentConfig.mainAndSubPageKeywords?.keywords || [];
  1039. keywords_regex = currentConfig.mainAndSubPageKeywords?.regexPatterns || [];
  1040. } else if (isContentPage) {
  1041. keywords = currentConfig.contentPageKeywords?.keywords || [];
  1042. keywords_regex = currentConfig.contentPageKeywords?.regexPatterns || [];
  1043. }
  1044. }
  1045. if (GLOBAL_CONFIG.GLOBAL_USERNAMES) {
  1046. usernames = [...new Set(
  1047. userConfig.reduce((acc, config) => [
  1048. ...acc,
  1049. ...(config.mainAndSubPageUserKeywords?.keywords || []),
  1050. ...(config.contentPageUserKeywords?.keywords || [])
  1051. ], [])
  1052. )];
  1053. usernames_regex = [...new Set(
  1054. userConfig.reduce((acc, config) => [
  1055. ...acc,
  1056. ...(config.mainAndSubPageUserKeywords?.regexPatterns || []),
  1057. ...(config.contentPageUserKeywords?.regexPatterns || [])
  1058. ], [])
  1059. )];
  1060. } else if (currentConfig.shareUsernamesAcrossPages) {
  1061. usernames = [...new Set([
  1062. ...(currentConfig.mainAndSubPageUserKeywords?.keywords || []),
  1063. ...(currentConfig.contentPageUserKeywords?.keywords || [])
  1064. ])];
  1065. usernames_regex = [...new Set([
  1066. ...(currentConfig.mainAndSubPageUserKeywords?.regexPatterns || []),
  1067. ...(currentConfig.contentPageUserKeywords?.regexPatterns || [])
  1068. ])];
  1069. } else {
  1070. if (isMainOrSubPage) {
  1071. usernames = currentConfig.mainAndSubPageUserKeywords?.keywords || [];
  1072. usernames_regex = currentConfig.mainAndSubPageUserKeywords?.regexPatterns || [];
  1073. } else if (isContentPage) {
  1074. usernames = currentConfig.contentPageUserKeywords?.keywords || [];
  1075. usernames_regex = currentConfig.contentPageUserKeywords?.regexPatterns || [];
  1076. }
  1077. }
  1078. if (isMainOrSubPage) {
  1079. if (currentConfig.mainAndSubPageKeywords?.xpath?.length > 0) {
  1080. currentConfig.mainAndSubPageKeywords.xpath.forEach(xpath => {
  1081. if (keywords?.length > 0) {
  1082. keywords.forEach(keyword => {
  1083. removeElementsByText(xpath, keyword, false);
  1084. });
  1085. }
  1086. if (keywords_regex?.length > 0) {
  1087. keywords_regex.forEach(pattern => {
  1088. removeElementsByText(xpath, pattern, true);
  1089. });
  1090. }
  1091. });
  1092. }
  1093. if (currentConfig.mainAndSubPageUserKeywords?.xpath?.length > 0) {
  1094. currentConfig.mainAndSubPageUserKeywords.xpath.forEach(xpath => {
  1095. addBlockButtonsToUsernames(xpath, false);
  1096. if (usernames?.length > 0) {
  1097. usernames.forEach(keyword => {
  1098. removeElementsByText(xpath, keyword, false);
  1099. });
  1100. }
  1101. if (usernames_regex?.length > 0) {
  1102. usernames_regex.forEach(pattern => {
  1103. removeElementsByText(xpath, pattern, true);
  1104. });
  1105. }
  1106. });
  1107. }
  1108. }
  1109. if (isContentPage) {
  1110. if (currentConfig.contentPageKeywords?.xpath?.length > 0) {
  1111. currentConfig.contentPageKeywords.xpath.forEach(xpath => {
  1112. if (keywords?.length > 0) {
  1113. keywords.forEach(keyword => {
  1114. removeElementsByText(xpath, keyword, false);
  1115. });
  1116. }
  1117. if (keywords_regex?.length > 0) {
  1118. keywords_regex.forEach(pattern => {
  1119. removeElementsByText(xpath, pattern, true);
  1120. });
  1121. }
  1122. });
  1123. }
  1124. if (currentConfig.contentPageUserKeywords?.xpath?.length > 0) {
  1125. currentConfig.contentPageUserKeywords.xpath.forEach(xpath => {
  1126. addBlockButtonsToUsernames(xpath, true);
  1127. if (usernames?.length > 0) {
  1128. usernames.forEach(keyword => {
  1129. removeElementsByText(xpath, keyword, false);
  1130. });
  1131. }
  1132. if (usernames_regex?.length > 0) {
  1133. usernames_regex.forEach(pattern => {
  1134. removeElementsByText(xpath, pattern, true);
  1135. });
  1136. }
  1137. });
  1138. }
  1139. }
  1140. }
  1141. }
  1142. function debounce(func, wait) {
  1143. let timeout;
  1144. return function executedFunction(...args) {
  1145. const later = () => {
  1146. clearTimeout(timeout);
  1147. func(...args);
  1148. };
  1149. clearTimeout(timeout);
  1150. timeout = setTimeout(later, wait);
  1151. };
  1152. }
  1153. const debouncedHandleElements = debounce(handleElements, 300);
  1154. const debouncedRemoveWebsiteCSS = debounce(removeWebsiteCSS, 300);
  1155. function getPageType() {
  1156. const currentConfig = getDomainConfig(getCurrentDomain());
  1157. if (!currentConfig) return 'unknown';
  1158. const isMainPage = currentConfig.mainPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1159. const isSubPage = currentConfig.subPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1160. const isContentPage = currentConfig.contentPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1161. if (isMainPage) return 'main';
  1162. if (isSubPage) return 'sub';
  1163. if (isContentPage) return 'content';
  1164. return 'unknown';
  1165. }
  1166. function updatePanelContent() {
  1167. const panel = document.getElementById('forum-filter-panel');
  1168. if (!panel) return;
  1169. const currentConfig = getDomainConfig(getCurrentDomain()) || SAMPLE_TEMPLATE;
  1170. const isMainPage = currentConfig.mainPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1171. const isSubPage = currentConfig.subPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1172. const isContentPage = currentConfig.contentPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  1173. let pageType = setTextfromTemplate('panel_top_page_type_unknown');
  1174. if (isMainPage) pageType = setTextfromTemplate('panel_top_page_type_main');
  1175. else if (isSubPage) pageType = setTextfromTemplate('panel_top_page_type_sub');
  1176. else if (isContentPage) pageType = setTextfromTemplate('panel_top_page_type_content');
  1177. panel.querySelector('#page-type-value').textContent = pageType;
  1178. panel.querySelector('#domain-info-text').textContent = setTextfromTemplate('panel_top_current_domain');
  1179. panel.querySelector('#domain-info-value').textContent = getCurrentDomain();
  1180. panel.querySelector('#domain-enabled').checked = currentConfig.enabled;
  1181. panel.querySelector('#global-keywords').checked = GLOBAL_CONFIG.GLOBAL_KEYWORDS;
  1182. panel.querySelector('#global-usernames').checked = GLOBAL_CONFIG.GLOBAL_USERNAMES;
  1183. panel.querySelector('#share-keywords').checked = currentConfig.shareKeywordsAcrossPages;
  1184. panel.querySelector('#share-usernames').checked = currentConfig.shareUsernamesAcrossPages;
  1185. const mainPatternsEditor = createArrayEditor(
  1186. setTextfromTemplate('url_patterns_main_page_url_patterns_title'),
  1187. currentConfig.mainPageUrlPatterns || [],
  1188. (item) => {
  1189. if (!currentConfig.mainPageUrlPatterns) {
  1190. currentConfig.mainPageUrlPatterns = [];
  1191. }
  1192. currentConfig.mainPageUrlPatterns.push(item);
  1193. saveUserConfig(userConfig);
  1194. debouncedHandleElements();
  1195. },
  1196. (index) => {
  1197. currentConfig.mainPageUrlPatterns.splice(index, 1);
  1198. saveUserConfig(userConfig);
  1199. debouncedHandleElements();
  1200. },
  1201. 'main-patterns-editor',
  1202. true
  1203. );
  1204. const subPatternsEditor = createArrayEditor(
  1205. setTextfromTemplate('url_patterns_sub_page_url_patterns_title'),
  1206. currentConfig.subPageUrlPatterns || [],
  1207. (item) => {
  1208. if (!currentConfig.subPageUrlPatterns) {
  1209. currentConfig.subPageUrlPatterns = [];
  1210. }
  1211. currentConfig.subPageUrlPatterns.push(item);
  1212. saveUserConfig(userConfig);
  1213. debouncedHandleElements();
  1214. },
  1215. (index) => {
  1216. currentConfig.subPageUrlPatterns.splice(index, 1);
  1217. saveUserConfig(userConfig);
  1218. debouncedHandleElements();
  1219. },
  1220. 'sub-patterns-editor',
  1221. true
  1222. );
  1223. const contentPatternsEditor = createArrayEditor(
  1224. setTextfromTemplate('url_patterns_content_page_url_patterns_title'),
  1225. currentConfig.contentPageUrlPatterns || [],
  1226. (item) => {
  1227. if (!currentConfig.contentPageUrlPatterns) {
  1228. currentConfig.contentPageUrlPatterns = [];
  1229. }
  1230. currentConfig.contentPageUrlPatterns.push(item);
  1231. saveUserConfig(userConfig);
  1232. debouncedHandleElements();
  1233. },
  1234. (index) => {
  1235. currentConfig.contentPageUrlPatterns.splice(index, 1);
  1236. saveUserConfig(userConfig);
  1237. debouncedHandleElements();
  1238. },
  1239. 'content-patterns-editor',
  1240. true
  1241. );
  1242. const mainPatternsContainer = panel.querySelector('#main-patterns-editor');
  1243. const subPatternsContainer = panel.querySelector('#sub-patterns-editor');
  1244. const contentPatternsContainer = panel.querySelector('#content-patterns-editor');
  1245. mainPatternsContainer.innerHTML = '';
  1246. subPatternsContainer.innerHTML = '';
  1247. contentPatternsContainer.innerHTML = '';
  1248. mainPatternsContainer.appendChild(mainPatternsEditor);
  1249. subPatternsContainer.appendChild(subPatternsEditor);
  1250. contentPatternsContainer.appendChild(contentPatternsEditor);
  1251. const mainTitleXPathEditor = createArrayEditor(
  1252. setTextfromTemplate('xpath_config_main_and_sub_page_keywords_title'),
  1253. currentConfig.mainAndSubPageKeywords?.xpath || [],
  1254. (item) => {
  1255. if (!currentConfig.mainAndSubPageKeywords) {
  1256. currentConfig.mainAndSubPageKeywords = { xpath: [] };
  1257. }
  1258. if (!currentConfig.mainAndSubPageKeywords.xpath) {
  1259. currentConfig.mainAndSubPageKeywords.xpath = [];
  1260. }
  1261. currentConfig.mainAndSubPageKeywords.xpath.push(item);
  1262. saveUserConfig(userConfig);
  1263. debouncedHandleElements();
  1264. },
  1265. (index) => {
  1266. currentConfig.mainAndSubPageKeywords.xpath.splice(index, 1);
  1267. saveUserConfig(userConfig);
  1268. debouncedHandleElements();
  1269. },
  1270. 'title-xpath-editor'
  1271. );
  1272. const mainUserXPathEditor = createArrayEditor(
  1273. setTextfromTemplate('xpath_config_main_and_sub_page_usernames_title'),
  1274. currentConfig.mainAndSubPageUserKeywords?.xpath || [],
  1275. (item) => {
  1276. if (!currentConfig.mainAndSubPageUserKeywords) {
  1277. currentConfig.mainAndSubPageUserKeywords = { xpath: [] };
  1278. }
  1279. if (!currentConfig.mainAndSubPageUserKeywords.xpath) {
  1280. currentConfig.mainAndSubPageUserKeywords.xpath = [];
  1281. }
  1282. currentConfig.mainAndSubPageUserKeywords.xpath.push(item);
  1283. saveUserConfig(userConfig);
  1284. debouncedHandleElements();
  1285. },
  1286. (index) => {
  1287. currentConfig.mainAndSubPageUserKeywords.xpath.splice(index, 1);
  1288. saveUserConfig(userConfig);
  1289. debouncedHandleElements();
  1290. },
  1291. 'user-xpath-editor'
  1292. );
  1293. const contentTitleXPathEditor = createArrayEditor(
  1294. setTextfromTemplate('xpath_config_content_page_keywords_title'),
  1295. currentConfig.contentPageKeywords?.xpath || [],
  1296. (item) => {
  1297. if (!currentConfig.contentPageKeywords) {
  1298. currentConfig.contentPageKeywords = { xpath: [] };
  1299. }
  1300. if (!currentConfig.contentPageKeywords.xpath) {
  1301. currentConfig.contentPageKeywords.xpath = [];
  1302. }
  1303. currentConfig.contentPageKeywords.xpath.push(item);
  1304. saveUserConfig(userConfig);
  1305. debouncedHandleElements();
  1306. },
  1307. (index) => {
  1308. currentConfig.contentPageKeywords.xpath.splice(index, 1);
  1309. saveUserConfig(userConfig);
  1310. debouncedHandleElements();
  1311. },
  1312. 'content-title-xpath-editor'
  1313. );
  1314. const contentUserXPathEditor = createArrayEditor(
  1315. setTextfromTemplate('xpath_config_content_page_usernames_title'),
  1316. currentConfig.contentPageUserKeywords?.xpath || [],
  1317. (item) => {
  1318. if (!currentConfig.contentPageUserKeywords) {
  1319. currentConfig.contentPageUserKeywords = { xpath: [] };
  1320. }
  1321. if (!currentConfig.contentPageUserKeywords.xpath) {
  1322. currentConfig.contentPageUserKeywords.xpath = [];
  1323. }
  1324. currentConfig.contentPageUserKeywords.xpath.push(item);
  1325. saveUserConfig(userConfig);
  1326. debouncedHandleElements();
  1327. },
  1328. (index) => {
  1329. currentConfig.contentPageUserKeywords.xpath.splice(index, 1);
  1330. saveUserConfig(userConfig);
  1331. debouncedHandleElements();
  1332. },
  1333. 'content-user-xpath-editor'
  1334. );
  1335. const mainTitleXPathContainer = panel.querySelector('#main-title-xpath-editor');
  1336. const mainUserXPathContainer = panel.querySelector('#main-user-xpath-editor');
  1337. const contentTitleXPathContainer = panel.querySelector('#content-title-xpath-editor');
  1338. const contentUserXPathContainer = panel.querySelector('#content-user-xpath-editor');
  1339. mainTitleXPathContainer.innerHTML = '';
  1340. mainUserXPathContainer.innerHTML = '';
  1341. contentTitleXPathContainer.innerHTML = '';
  1342. contentUserXPathContainer.innerHTML = '';
  1343. mainTitleXPathContainer.appendChild(mainTitleXPathEditor);
  1344. mainUserXPathContainer.appendChild(mainUserXPathEditor);
  1345. contentTitleXPathContainer.appendChild(contentTitleXPathEditor);
  1346. contentUserXPathContainer.appendChild(contentUserXPathEditor);
  1347. const keywordsContainer = panel.querySelector('#keywords-container');
  1348. const usernamesContainer = panel.querySelector('#usernames-container');
  1349. keywordsContainer.innerHTML = '';
  1350. usernamesContainer.innerHTML = '';
  1351. if (isMainPage || isSubPage) {
  1352. if (currentConfig.mainAndSubPageKeywords) {
  1353. const mainPageKeywords = createArrayEditor(
  1354. setTextfromTemplate('keywords_config_keywords_list_title'),
  1355. currentConfig.mainAndSubPageKeywords.keywords || [],
  1356. (item) => {
  1357. if (!currentConfig.mainAndSubPageKeywords.keywords) {
  1358. currentConfig.mainAndSubPageKeywords.keywords = [];
  1359. }
  1360. currentConfig.mainAndSubPageKeywords.keywords.push(item);
  1361. saveUserConfig(userConfig);
  1362. debouncedHandleElements();
  1363. },
  1364. (index) => {
  1365. currentConfig.mainAndSubPageKeywords.keywords.splice(index, 1);
  1366. saveUserConfig(userConfig);
  1367. debouncedHandleElements();
  1368. },
  1369. 'array-editor-keywords-list'
  1370. );
  1371. keywordsContainer.appendChild(mainPageKeywords);
  1372. const mainPageKeywordsRegex = createArrayEditor(
  1373. setTextfromTemplate('keywords_config_keywords_regex_title'),
  1374. currentConfig.mainAndSubPageKeywords.regexPatterns || [],
  1375. (item) => {
  1376. if (!currentConfig.mainAndSubPageKeywords.regexPatterns) {
  1377. currentConfig.mainAndSubPageKeywords.regexPatterns = [];
  1378. }
  1379. currentConfig.mainAndSubPageKeywords.regexPatterns.push(item);
  1380. saveUserConfig(userConfig);
  1381. debouncedHandleElements();
  1382. },
  1383. (index) => {
  1384. currentConfig.mainAndSubPageKeywords.regexPatterns.splice(index, 1);
  1385. saveUserConfig(userConfig);
  1386. debouncedHandleElements();
  1387. },
  1388. 'array-editor-keywords-regex',
  1389. true
  1390. );
  1391. keywordsContainer.appendChild(mainPageKeywordsRegex);
  1392. }
  1393. if (currentConfig.mainAndSubPageUserKeywords) {
  1394. const mainPageUsernames = createArrayEditor(
  1395. setTextfromTemplate('usernames_config_usernames_list_title'),
  1396. currentConfig.mainAndSubPageUserKeywords.keywords || [],
  1397. (item) => {
  1398. if (!currentConfig.mainAndSubPageUserKeywords.keywords) {
  1399. currentConfig.mainAndSubPageUserKeywords.keywords = [];
  1400. }
  1401. currentConfig.mainAndSubPageUserKeywords.keywords.push(item);
  1402. saveUserConfig(userConfig);
  1403. debouncedHandleElements();
  1404. },
  1405. (index) => {
  1406. currentConfig.mainAndSubPageUserKeywords.keywords.splice(index, 1);
  1407. saveUserConfig(userConfig);
  1408. debouncedHandleElements();
  1409. },
  1410. 'array-editor-usernames-list'
  1411. );
  1412. usernamesContainer.appendChild(mainPageUsernames);
  1413. const mainPageUsernamesRegex = createArrayEditor(
  1414. setTextfromTemplate('usernames_config_usernames_regex_title'),
  1415. currentConfig.mainAndSubPageUserKeywords.regexPatterns || [],
  1416. (item) => {
  1417. if (!currentConfig.mainAndSubPageUserKeywords.regexPatterns) {
  1418. currentConfig.mainAndSubPageUserKeywords.regexPatterns = [];
  1419. }
  1420. currentConfig.mainAndSubPageUserKeywords.regexPatterns.push(item);
  1421. saveUserConfig(userConfig);
  1422. debouncedHandleElements();
  1423. },
  1424. (index) => {
  1425. currentConfig.mainAndSubPageUserKeywords.regexPatterns.splice(index, 1);
  1426. saveUserConfig(userConfig);
  1427. debouncedHandleElements();
  1428. },
  1429. 'array-editor-usernames-regex',
  1430. true
  1431. );
  1432. usernamesContainer.appendChild(mainPageUsernamesRegex);
  1433. }
  1434. } else if (isContentPage) {
  1435. if (currentConfig.contentPageKeywords) {
  1436. const contentPageKeywords = createArrayEditor(
  1437. setTextfromTemplate('keywords_config_keywords_list_title'),
  1438. currentConfig.contentPageKeywords.keywords || [],
  1439. (item) => {
  1440. if (!currentConfig.contentPageKeywords.keywords) {
  1441. currentConfig.contentPageKeywords.keywords = [];
  1442. }
  1443. currentConfig.contentPageKeywords.keywords.push(item);
  1444. saveUserConfig(userConfig);
  1445. debouncedHandleElements();
  1446. },
  1447. (index) => {
  1448. currentConfig.contentPageKeywords.keywords.splice(index, 1);
  1449. saveUserConfig(userConfig);
  1450. debouncedHandleElements();
  1451. },
  1452. 'array-editor-keywords-list'
  1453. );
  1454. keywordsContainer.appendChild(contentPageKeywords);
  1455. const contentPageKeywordsRegex = createArrayEditor(
  1456. setTextfromTemplate('keywords_config_keywords_regex_title'),
  1457. currentConfig.contentPageKeywords.regexPatterns || [],
  1458. (item) => {
  1459. if (!currentConfig.contentPageKeywords.regexPatterns) {
  1460. currentConfig.contentPageKeywords.regexPatterns = [];
  1461. }
  1462. currentConfig.contentPageKeywords.regexPatterns.push(item);
  1463. saveUserConfig(userConfig);
  1464. debouncedHandleElements();
  1465. },
  1466. (index) => {
  1467. currentConfig.contentPageKeywords.regexPatterns.splice(index, 1);
  1468. saveUserConfig(userConfig);
  1469. debouncedHandleElements();
  1470. },
  1471. 'array-editor-keywords-regex',
  1472. true
  1473. );
  1474. keywordsContainer.appendChild(contentPageKeywordsRegex);
  1475. }
  1476. if (currentConfig.contentPageUserKeywords) {
  1477. const contentPageUsernames = createArrayEditor(
  1478. setTextfromTemplate('usernames_config_usernames_list_title'),
  1479. currentConfig.contentPageUserKeywords.keywords || [],
  1480. (item) => {
  1481. if (!currentConfig.contentPageUserKeywords.keywords) {
  1482. currentConfig.contentPageUserKeywords.keywords = [];
  1483. }
  1484. currentConfig.contentPageUserKeywords.keywords.push(item);
  1485. saveUserConfig(userConfig);
  1486. debouncedHandleElements();
  1487. },
  1488. (index) => {
  1489. currentConfig.contentPageUserKeywords.keywords.splice(index, 1);
  1490. saveUserConfig(userConfig);
  1491. debouncedHandleElements();
  1492. },
  1493. 'array-editor-usernames-list'
  1494. );
  1495. usernamesContainer.appendChild(contentPageUsernames);
  1496. const contentPageUsernamesRegex = createArrayEditor(
  1497. setTextfromTemplate('usernames_config_usernames_regex_title'),
  1498. currentConfig.contentPageUserKeywords.regexPatterns || [],
  1499. (item) => {
  1500. if (!currentConfig.contentPageUserKeywords.regexPatterns) {
  1501. currentConfig.contentPageUserKeywords.regexPatterns = [];
  1502. }
  1503. currentConfig.contentPageUserKeywords.regexPatterns.push(item);
  1504. saveUserConfig(userConfig);
  1505. debouncedHandleElements();
  1506. },
  1507. (index) => {
  1508. currentConfig.contentPageUserKeywords.regexPatterns.splice(index, 1);
  1509. saveUserConfig(userConfig);
  1510. debouncedHandleElements();
  1511. },
  1512. 'array-editor-usernames-regex',
  1513. true
  1514. );
  1515. usernamesContainer.appendChild(contentPageUsernamesRegex);
  1516. }
  1517. } else {
  1518. keywordsContainer.innerHTML = '<div style="padding: 10px; color: #666;">请先配置并匹配页面类型</div>';
  1519. usernamesContainer.innerHTML = '<div style="padding: 10px; color: #666;">请先配置并匹配页面类型</div>';
  1520. }
  1521. ['global', 'keywords', 'usernames', 'url', 'xpath', 'sync'].forEach(section => {
  1522. const toggle = panel.querySelector(`[data-section="${section}"]`);
  1523. const isCollapsed = GLOBAL_CONFIG.CONFIG_SECTION_COLLAPSED[`${section}_SECTION_COLLAPSED`];
  1524. toggle.classList[isCollapsed ? 'add' : 'remove']('collapsed');
  1525. });
  1526. function updateGlobalUrlList() {
  1527. const listContainer = panel.querySelector('.global-url-list');
  1528. listContainer.innerHTML = '';
  1529. GLOBAL_CONFIG.GLOBAL_CONFIG_URL.forEach((url, index) => {
  1530. const item = document.createElement('div');
  1531. item.className = 'global-url-item';
  1532. item.innerHTML = `
  1533. <span>${url}</span>
  1534. <button title="删除">×</button>
  1535. `;
  1536. item.querySelector('button').addEventListener('click', () => {
  1537. GLOBAL_CONFIG.GLOBAL_CONFIG_URL.splice(index, 1);
  1538. updateGlobalUrlList();
  1539. });
  1540. listContainer.appendChild(item);
  1541. saveGlobalConfig();
  1542. });
  1543. }
  1544. panel.querySelector('#add-global-url').addEventListener('click', function() {
  1545. const input = panel.querySelector('#global-url-input');
  1546. const url = input.value.trim();
  1547. if (url) {
  1548. if (!GLOBAL_CONFIG.GLOBAL_CONFIG_URL.includes(url)) {
  1549. GLOBAL_CONFIG.GLOBAL_CONFIG_URL.push(url);
  1550. input.value = '';
  1551. updateGlobalUrlList();
  1552. } else {
  1553. alert(setTextfromTemplate('alert_url_exists'));
  1554. }
  1555. }
  1556. });
  1557. panel.querySelector('#apply-global-apply').addEventListener('click', function() {
  1558. downloadAndApplyConfig();
  1559. });
  1560. panel.querySelector('#global-url-input').addEventListener('keypress', function(e) {
  1561. if (e.key === 'Enter') {
  1562. panel.querySelector('#add-global-url').click();
  1563. }
  1564. });
  1565. updateGlobalUrlList();
  1566. const timeIntervalSelect = panel.querySelector('#time-interval');
  1567. timeIntervalSelect.value = GLOBAL_CONFIG.TIME_INTERVAL || '30';
  1568. timeIntervalSelect.addEventListener('change', function() {
  1569. const oldInterval = GLOBAL_CONFIG.TIME_INTERVAL;
  1570. GLOBAL_CONFIG.TIME_INTERVAL = parseInt(this.value);
  1571. GM_setValue('LAST_UPDATE_TIME', Date.now());
  1572. saveConfig();
  1573. });
  1574. }
  1575. function listenUrlChange(callback) {
  1576. let lastUrl = window.location.href;
  1577. const handleUrlChange = (type) => {
  1578. const currentUrl = window.location.href;
  1579. if (currentUrl !== lastUrl) {
  1580. lastUrl = currentUrl;
  1581. callback();
  1582. updatePanelContent();
  1583. }
  1584. };
  1585. window.addEventListener('popstate', () => {
  1586. handleUrlChange('popstate');
  1587. });
  1588. const originalPushState = history.pushState;
  1589. const originalReplaceState = history.replaceState;
  1590. history.pushState = function() {
  1591. originalPushState.apply(this, arguments);
  1592. handleUrlChange('pushState');
  1593. };
  1594. history.replaceState = function() {
  1595. originalReplaceState.apply(this, arguments);
  1596. handleUrlChange('replaceState');
  1597. };
  1598. }
  1599. if (document.readyState === 'loading') {
  1600. document.addEventListener('DOMContentLoaded', function() {
  1601. debouncedHandleElements();
  1602. listenUrlChange(debouncedHandleElements);
  1603. });
  1604. } else {
  1605. debouncedHandleElements();
  1606. listenUrlChange(debouncedHandleElements);
  1607. }
  1608. const observer = new MutationObserver((mutations) => {
  1609. debouncedHandleElements();
  1610. });
  1611. observer.observe(document.body, {
  1612. childList: true,
  1613. subtree: true
  1614. });
  1615. function exportDomainConfig(domain){
  1616. const configResult = getDomainConfig(domain);
  1617. if (!configResult.success) {
  1618. return configResult;
  1619. }
  1620. try {
  1621. const exportData = {
  1622. exportTime: new Date().toISOString(),
  1623. version: GM_info.script.version,
  1624. config: configResult.config
  1625. };
  1626. const jsonString = JSON.stringify(exportData, null, 2);
  1627. const blob = new Blob([jsonString], { type: 'application/json' });
  1628. const downloadUrl = URL.createObjectURL(blob);
  1629. const downloadLink = document.createElement('a');
  1630. downloadLink.href = downloadUrl;
  1631. downloadLink.download = `${domain}.json`;
  1632. document.body.appendChild(downloadLink);
  1633. downloadLink.click();
  1634. document.body.removeChild(downloadLink);
  1635. URL.revokeObjectURL(downloadUrl);
  1636. return {
  1637. success: true,
  1638. message: '配置导出成功',
  1639. config: configResult.config
  1640. };
  1641. } catch (error) {
  1642. console.error('导出配置失败:', error);
  1643. return {
  1644. success: false,
  1645. message: `导出配置失败: ${error.message}`,
  1646. config: null
  1647. };
  1648. }
  1649. }
  1650. function importDomainConfig(file) {
  1651. return new Promise((resolve, reject) => {
  1652. if (!file || !(file instanceof File)) {
  1653. resolve({
  1654. success: false,
  1655. message: '请选择有效的配置文件',
  1656. config: null
  1657. });
  1658. return;
  1659. }
  1660. const reader = new FileReader();
  1661. reader.onload = async (event) => {
  1662. try {
  1663. const importData = JSON.parse(event.target.result);
  1664. if (!importData.config || !importData.config.domain) {
  1665. resolve({
  1666. success: false,
  1667. message: '无效的配置文件格式',
  1668. config: null
  1669. });
  1670. return;
  1671. }
  1672. const existingConfig = getDomainConfig(importData.config.domain);
  1673. if (existingConfig) {
  1674. const updateResult = updateDomainConfig(importData.config.domain, importData.config);
  1675. resolve({
  1676. success: true,
  1677. message: '配置已更新',
  1678. config: updateResult.config
  1679. });
  1680. } else {
  1681. const addResult = addDomainConfig(importData.config);
  1682. resolve({
  1683. success: true,
  1684. message: '配置已导入',
  1685. config: addResult.config
  1686. });
  1687. }
  1688. } catch (error) {
  1689. console.error('导入配置失败:', error);
  1690. resolve({
  1691. success: false,
  1692. message: `导入配置失败: ${error.message}`,
  1693. config: null
  1694. });
  1695. }
  1696. };
  1697. reader.onerror = () => {
  1698. resolve({
  1699. success: false,
  1700. message: '读取文件失败',
  1701. config: null
  1702. });
  1703. };
  1704. reader.readAsText(file);
  1705. });
  1706. }
  1707. function importDomainConfigFromFile() {
  1708. return new Promise((resolve) => {
  1709. const input = document.createElement('input');
  1710. input.type = 'file';
  1711. input.accept = '.json';
  1712. input.onchange = async (event) => {
  1713. const file = event.target.files[0];
  1714. const result = await importDomainConfig(file);
  1715. resolve(result);
  1716. };
  1717. input.click();
  1718. });
  1719. }
  1720. function exportUserConfig() {
  1721. try {
  1722. const exportData = {
  1723. globalConfig: GLOBAL_CONFIG,
  1724. userConfig: userConfig
  1725. };
  1726. const jsonString = JSON.stringify(exportData, null, 2);
  1727. const blob = new Blob([jsonString], { type: 'application/json' });
  1728. const downloadUrl = URL.createObjectURL(blob);
  1729. const downloadLink = document.createElement('a');
  1730. downloadLink.href = downloadUrl;
  1731. downloadLink.download = `universal-forum-block-config-${new Date().toISOString().split('T')[0]}.json`;
  1732. document.body.appendChild(downloadLink);
  1733. downloadLink.click();
  1734. document.body.removeChild(downloadLink);
  1735. URL.revokeObjectURL(downloadUrl);
  1736. return {
  1737. success: true,
  1738. message: '配置导出成功',
  1739. config: exportData
  1740. };
  1741. } catch (error) {
  1742. console.error('导出配置失败:', error);
  1743. return {
  1744. success: false,
  1745. message: `导出配置失败: ${error.message}`,
  1746. config: null
  1747. };
  1748. }
  1749. }
  1750. function importUserConfig(file) {
  1751. return new Promise((resolve, reject) => {
  1752. if (!file || !(file instanceof File)) {
  1753. resolve({
  1754. success: false,
  1755. message: '请选择有效的配置文件',
  1756. config: null
  1757. });
  1758. return;
  1759. }
  1760. const reader = new FileReader();
  1761. reader.onload = async (event) => {
  1762. try {
  1763. const importData = JSON.parse(event.target.result);
  1764. const configCopy = JSON.parse(JSON.stringify(importData));
  1765. saveConfig(configCopy);
  1766. } catch (error) {
  1767. console.error('导入配置失败:', error);
  1768. resolve({
  1769. success: false,
  1770. message: `导入配置失败: ${error.message}`,
  1771. config: null
  1772. });
  1773. }
  1774. };
  1775. reader.onerror = () => {
  1776. resolve({
  1777. success: false,
  1778. message: '读取文件失败',
  1779. config: null
  1780. });
  1781. };
  1782. reader.readAsText(file);
  1783. });
  1784. }
  1785. function importUserConfigFromFile() {
  1786. return new Promise((resolve) => {
  1787. const input = document.createElement('input');
  1788. input.type = 'file';
  1789. input.accept = '.json';
  1790. input.onchange = async (event) => {
  1791. const file = event.target.files[0];
  1792. const result = await importUserConfig(file);
  1793. resolve(result);
  1794. };
  1795. input.click();
  1796. });
  1797. }
  1798. let PANEL_SETTINGS = GM_getValue('panelSettings', {
  1799. offset: 2,
  1800. expandMode: 'click',
  1801. collapsedWidth: 70,
  1802. expandedWidth: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 290 : 400,
  1803. showBlockButton: 'hover'
  1804. });
  1805. function savePanelSettings() {
  1806. GM_setValue('panelSettings', PANEL_SETTINGS);
  1807. applyPanelSettings();
  1808. debouncedHandleElements();
  1809. }
  1810. function applyPanelSettings() {
  1811. const panel = document.getElementById('forum-filter-panel');
  1812. if (!panel) return;
  1813. panel.style.left = PANEL_SETTINGS.offset + '%';
  1814. if (PANEL_SETTINGS.expandMode === 'click') {
  1815. panel.classList.add('click-mode');
  1816. } else {
  1817. panel.classList.remove('click-mode');
  1818. panel.classList.remove('expanded');
  1819. }
  1820. const currentConfig = getDomainConfig(getCurrentDomain());
  1821. const isContentPage = currentConfig?.contentPageUrlPatterns?.some(pattern =>
  1822. new RegExp(pattern).test(getSplitUrl())
  1823. );
  1824. const blockButtons = document.querySelectorAll('.block-user-btn');
  1825. blockButtons.forEach(button => {
  1826. const wrapper = button.parentElement;
  1827. const newWrapper = wrapper.cloneNode(true);
  1828. wrapper.parentNode.replaceChild(newWrapper, wrapper);
  1829. const newButton = newWrapper.querySelector('.block-user-btn');
  1830. newButton.style.display = PANEL_SETTINGS.showBlockButton === 'always' ? 'inline-flex' : 'none';
  1831. newButton.addEventListener('click', (e) => {
  1832. e.preventDefault();
  1833. e.stopPropagation();
  1834. const username = newButton.getAttribute('data-username');
  1835. const currentConfig = getDomainConfig(getCurrentDomain());
  1836. if (currentConfig) {
  1837. const configKey = isContentPage ? 'contentPageUserKeywords' : 'mainAndSubPageUserKeywords';
  1838. if (!currentConfig[configKey].keywords) {
  1839. currentConfig[configKey].keywords = [];
  1840. }
  1841. if (!currentConfig[configKey].keywords.includes(username)) {
  1842. currentConfig[configKey].keywords.push(username);
  1843. updateDomainConfig(getCurrentDomain(), currentConfig);
  1844. debouncedHandleElements();
  1845. updatePanelContent();
  1846. }
  1847. }
  1848. });
  1849. if (PANEL_SETTINGS.showBlockButton === 'hover') {
  1850. newWrapper.addEventListener('mouseenter', () => {
  1851. newButton.style.display = 'inline-flex';
  1852. });
  1853. newWrapper.addEventListener('mouseleave', () => {
  1854. newButton.style.display = 'none';
  1855. });
  1856. }
  1857. newButton.addEventListener('mouseenter', () => {
  1858. newButton.style.background = 'rgba(0, 0, 0, 0.8)';
  1859. });
  1860. newButton.addEventListener('mouseleave', () => {
  1861. newButton.style.background = 'rgba(0, 0, 0, 0.6)';
  1862. });
  1863. });
  1864. const style = document.createElement('style');
  1865. style.id = 'forum-filter-dynamic-style';
  1866. style.textContent = `
  1867. #forum-filter-panel.click-mode:not(.expanded),
  1868. #forum-filter-panel:not(.click-mode):not(:hover):not(:focus-within) {
  1869. width: ${PANEL_SETTINGS.collapsedWidth}px;
  1870. }
  1871. #forum-filter-panel:not(.click-mode):hover,
  1872. #forum-filter-panel:not(.click-mode):focus-within,
  1873. #forum-filter-panel.click-mode.expanded {
  1874. width: ${PANEL_SETTINGS.expandedWidth}px !important;
  1875. }
  1876. `;
  1877. const oldStyle = document.getElementById('forum-filter-dynamic-style');
  1878. if (oldStyle) {
  1879. oldStyle.remove();
  1880. }
  1881. document.head.appendChild(style);
  1882. }
  1883. function createSettingsPanel() {
  1884. const settingsPanel = document.createElement('div');
  1885. settingsPanel.id = 'forum-filter-settings';
  1886. settingsPanel.innerHTML = `
  1887. <h3 id="js-settings-title">面板设置</h3>
  1888. <!-- 添加语言选择下拉框 -->
  1889. <div class="setting-group">
  1890. <label for="language-select">语言</label>
  1891. <select id="language-select">
  1892. <option value="zh-CN">简体中文</option>
  1893. <option value="en-US">English</option>
  1894. <option value="ja-JP">日本語</option>
  1895. <option value="ko-KR">한국어</option>
  1896. <option value="ru-RU">Русский</option>
  1897. <option value="fr-FR">Français</option>
  1898. <option value="de-DE">Deutsch</option>
  1899. <option value="it-IT">Italiano</option>
  1900. <option value="hi-IN">हिन्दी</option>
  1901. <option value="id-ID">Bahasa Indonesia</option>
  1902. <option value="vi-VN">Tiếng Vit</option>
  1903. <option value="th-TH">ไทย</option>
  1904. <option value="es-ES">Español</option>
  1905. <option value="pt-PT">Português</option>
  1906. </select>
  1907. </div>
  1908. <div class="setting-group">
  1909. <label>展开方式</label>
  1910. <select id="expand-mode">
  1911. <option value="hover" ${PANEL_SETTINGS.expandMode === 'hover' ? 'selected' : ''}>悬停展开</option>
  1912. <option value="click" ${PANEL_SETTINGS.expandMode === 'click' ? 'selected' : ''}>点击展开</option>
  1913. </select>
  1914. </div>
  1915. <div class="setting-group">
  1916. <label>屏蔽按钮显示方式</label>
  1917. <select id="show-block-button">
  1918. <option value="hover" ${PANEL_SETTINGS.showBlockButton === 'hover' ? 'selected' : ''}>悬停显示</option>
  1919. <option value="always" ${PANEL_SETTINGS.showBlockButton === 'always' ? 'selected' : ''}>总是显示</option>
  1920. </select>
  1921. </div>
  1922. <div class="setting-group">
  1923. <label>水平位置</label>
  1924. <input type="range" id="position-offset" min="0" max="90" value="${PANEL_SETTINGS.offset}">
  1925. <div class="position-value">${PANEL_SETTINGS.offset}%</div>
  1926. </div>
  1927. <div class="setting-group">
  1928. <label>收起宽度</label>
  1929. <input type="range" id="collapsed-width" min="30" max="200" step="10" value="${PANEL_SETTINGS.collapsedWidth}">
  1930. <div class="collapsed-width-value">${PANEL_SETTINGS.collapsedWidth}px</div>
  1931. </div>
  1932. <div class="setting-group">
  1933. <label>展开宽度</label>
  1934. <input type="range" id="expanded-width" min="100" max="${/Mobile|Android|iPhone/i.test(navigator.userAgent) ? 400 : 1000}" step="10" value="${PANEL_SETTINGS.expandedWidth}">
  1935. <div class="expanded-width-value">${PANEL_SETTINGS.expandedWidth}px</div>
  1936. </div>
  1937. <div class="buttons">
  1938. <button id="settings-cancel">取消</button>
  1939. <button id="settings-save">保存</button>
  1940. </div>
  1941. `;
  1942. settingsPanel.querySelector('#js-settings-title').textContent = setTextfromTemplate('settings_title');
  1943. settingsPanel.querySelector('label[for="language-select"]').textContent = setTextfromTemplate('settings_language');
  1944. const expandModeLabel = settingsPanel.querySelector('#expand-mode').previousElementSibling;
  1945. expandModeLabel.textContent = setTextfromTemplate('settings_expand_mode');
  1946. const expandModeOptions = settingsPanel.querySelectorAll('#expand-mode option');
  1947. expandModeOptions[0].textContent = setTextfromTemplate('settings_expand_hover');
  1948. expandModeOptions[1].textContent = setTextfromTemplate('settings_expand_click');
  1949. const blockButtonLabel = settingsPanel.querySelector('#show-block-button').previousElementSibling;
  1950. blockButtonLabel.textContent = setTextfromTemplate('settings_block_button_mode');
  1951. const blockButtonOptions = settingsPanel.querySelectorAll('#show-block-button option');
  1952. blockButtonOptions[0].textContent = setTextfromTemplate('settings_block_hover');
  1953. blockButtonOptions[1].textContent = setTextfromTemplate('settings_block_always');
  1954. const horizontalPositionLabel = settingsPanel.querySelector('#position-offset').previousElementSibling;
  1955. horizontalPositionLabel.textContent = setTextfromTemplate('settings_horizontal_position');
  1956. const collapsedWidthLabel = settingsPanel.querySelector('#collapsed-width').previousElementSibling;
  1957. collapsedWidthLabel.textContent = setTextfromTemplate('settings_collapsed_width');
  1958. const expandedWidthLabel = settingsPanel.querySelector('#expanded-width').previousElementSibling;
  1959. expandedWidthLabel.textContent = setTextfromTemplate('settings_expanded_width');
  1960. settingsPanel.querySelector('#settings-cancel').textContent = setTextfromTemplate('settings_cancel');
  1961. settingsPanel.querySelector('#settings-save').textContent = setTextfromTemplate('settings_save');
  1962. const overlay = document.createElement('div');
  1963. overlay.id = 'settings-overlay';
  1964. overlay.style.cssText = `
  1965. position: fixed;
  1966. top: 0;
  1967. left: 0;
  1968. right: 0;
  1969. bottom: 0;
  1970. background: rgba(0, 0, 0, 0.5);
  1971. z-index: 9999;
  1972. display: none;
  1973. `;
  1974. document.body.appendChild(overlay);
  1975. overlay.addEventListener('click', function() {
  1976. settingsPanel.classList.remove('visible');
  1977. overlay.style.display = 'none';
  1978. });
  1979. document.body.appendChild(settingsPanel);
  1980. const previewSettings = () => {
  1981. const tempSettings = {
  1982. expandMode: document.getElementById('expand-mode').value,
  1983. showBlockButton: document.getElementById('show-block-button').value,
  1984. offset: parseInt(document.getElementById('position-offset').value),
  1985. collapsedWidth: parseInt(document.getElementById('collapsed-width').value),
  1986. expandedWidth: parseInt(document.getElementById('expanded-width').value)
  1987. };
  1988. Object.assign(PANEL_SETTINGS, tempSettings);
  1989. applyPanelSettings();
  1990. };
  1991. document.getElementById('expand-mode').addEventListener('change', previewSettings);
  1992. document.getElementById('show-block-button').addEventListener('change', previewSettings);
  1993. document.getElementById('position-offset').addEventListener('input', function(e) {
  1994. document.querySelector('.position-value').textContent = e.target.value + '%';
  1995. previewSettings();
  1996. });
  1997. document.getElementById('collapsed-width').addEventListener('input', function(e) {
  1998. document.querySelector('.collapsed-width-value').textContent = e.target.value + 'px';
  1999. previewSettings();
  2000. });
  2001. document.getElementById('expanded-width').addEventListener('input', function(e) {
  2002. document.querySelector('.expanded-width-value').textContent = e.target.value + 'px';
  2003. previewSettings();
  2004. });
  2005. document.getElementById('settings-save').addEventListener('click', function() {
  2006. savePanelSettings();
  2007. settingsPanel.classList.remove('visible');
  2008. overlay.style.display = 'none';
  2009. });
  2010. document.getElementById('settings-cancel').addEventListener('click', function() {
  2011. PANEL_SETTINGS = GM_getValue('panelSettings', {
  2012. offset: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 0 : 10,
  2013. expandMode: 'hover',
  2014. collapsedWidth: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 30 : 70,
  2015. expandedWidth: /Mobile|Android|iPhone/i.test(navigator.userAgent) ? 290 : 400
  2016. });
  2017. applyPanelSettings();
  2018. settingsPanel.classList.remove('visible');
  2019. overlay.style.display = 'none';
  2020. });
  2021. const languageSelect = document.getElementById('language-select');
  2022. languageSelect.value = GLOBAL_CONFIG.LANGUAGE || 'zh-CN';
  2023. languageSelect.addEventListener('change', function(e) {
  2024. const newLanguage = e.target.value;
  2025. setLanguage(newLanguage);
  2026. });
  2027. return settingsPanel;
  2028. }
  2029. function encodeHTML(str) {
  2030. if (!str) return '';
  2031. return str.replace(/&/g, '&amp;')
  2032. .replace(/</g, '&lt;')
  2033. .replace(/>/g, '&gt;')
  2034. .replace(/"/g, '&quot;')
  2035. .replace(/'/g, '&#39;');
  2036. }
  2037. function createControlPanel() {
  2038. const panel = document.createElement('div');
  2039. panel.id = 'forum-filter-panel';
  2040. panel.style.display = panelVisible ? 'block' : 'none';
  2041. panel.innerHTML = `
  2042. <div class="panel-tab">⚙</div>
  2043. <div class="panel-content">
  2044. <div class="external-links" style="position: absolute; top: 10px; left: 18px; display: flex; gap: 8px;">
  2045. </div>
  2046. <div class="external-links" style="position: absolute; top: 10px; right: 18px; display: flex; gap: 8px;">
  2047. <a href="https://ko-fi.com/0heavrnl" class="external-link" title="Ko-fi" target="_blank" style="color: #333 !important; text-decoration: none !important; display: flex; align-items: center;">
  2048. <svg width="14" height="14" viewBox="0 0 24 24" style="vertical-align: text-bottom;">
  2049. <path fill="currentColor" d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"/>
  2050. </svg>
  2051. </a>
  2052. <a href="https://github.com/Heavrnl/UniversalForumBlock" class="external-link" title="GitHub" target="_blank" style="color: #333 !important; text-decoration: none !important; display: flex; align-items: center;">
  2053. <svg height="14" width="14" viewBox="0 0 16 16" style="vertical-align: text-bottom;">
  2054. <path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
  2055. </svg>
  2056. </a>
  2057. <a href="https://greasyfork.org/scripts/522871-%E9%80%9A%E7%94%A8%E8%AE%BA%E5%9D%9B%E5%B1%8F%E8%94%BD%E6%8F%92%E4%BB%B6" class="external-link" title="GreasyFork" target="_blank" style="color: #333 !important; text-decoration: none !important; display: flex; align-items: center;">
  2058. <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOUlEQVR4AYTRAaaEUBgF4MmMCqVSiiAE2kO0sUCbCBiBdtAOWsAADFGqUCQlYdB5lSlv3qvm53Bzu191uuyMPsWd8pzyemdeu8vel7mbpgmapjGtj3LfPUkQxGO+wTAMZFmG2+12hjz+PXkKoigCRVHQdX0HOX4T3bIsaJoGRVEQx/GGpGn6DVk6cQVBQFVVUFUVsiwjSRJcr1c4joOiKECS5BHgXt4Ng2XZDRFFEXmeY50gCI6A+ezym7AiZVnCtm38Hs/zjoDXB7AibdtiHMcNmFGGYQ6B598NjuMQhiF830dd1wvWNA3mrvY+wT1pGTzPo+/7Bem6brkmCOKjRH0KziJJEoZh2JCfIXlHGSUh4cAgG8GGfP78GRwmxcXFWJP0BUzNmGFTVlYGSlwYSRndJURnJoqzMwAArDfg4/66PAAAAABJRU5ErkJggg==" height="14" width="14" style="vertical-align: text-bottom;">
  2059. </a>
  2060. <a class="external-link" style="color: #333 !important; text-decoration: none !important; display: flex; align-items: center;">
  2061. <span style="margin-left: 3px; font-size: 12px;">v1.1.1</span>
  2062. </a>
  2063. </div>
  2064. <div class="domain-info">
  2065. <h4><span id="domain-info-text">当前域名: </span><span id="domain-info-value"></span></h4>
  2066. <div class="page-type"><span id="page-type-text">当前页面类型: </span><span id="page-type-value"></span></div>
  2067. <button class="panel-settings-btn" title="面板设置">⚙ 设置</button>
  2068. <label class="domain-enabled-label">
  2069. <input type="checkbox" id="domain-enabled">
  2070. <span id="domain-enabled-text">启用此域名配置</span>
  2071. </label>
  2072. </div>
  2073. <div class="config-section" data-section="global">
  2074. <button class="config-section-toggle" data-section="global">
  2075. <span id="global-config-title">全局配置</span>
  2076. <span class="config-section-indicator">▼</span>
  2077. </button>
  2078. <div class="config-section-content">
  2079. <div class="config-group">
  2080. <div class="checkbox-row">
  2081. <label>
  2082. <input type="checkbox" id="global-keywords">
  2083. 全局关键词
  2084. </label>
  2085. <label>
  2086. <input type="checkbox" id="global-usernames">
  2087. 全局用户名
  2088. </label>
  2089. </div>
  2090. <div class="checkbox-row">
  2091. <label>
  2092. <input type="checkbox" id="share-keywords">
  2093. 主页/内容页共享关键词
  2094. </label>
  2095. <label>
  2096. <input type="checkbox" id="share-usernames">
  2097. 主页/内容页共享用户名
  2098. </label>
  2099. </div>
  2100. <div class="global-url-section">
  2101. <div class="global-url-input-row">
  2102. <select id="time-interval" style="margin-right: 8px;">
  2103. <option value="1">1m</option>
  2104. <option value="5">5m</option>
  2105. <option value="10">10m</option>
  2106. <option value="30">30m</option>
  2107. <option value="60">1h</option>
  2108. <option value="120">2h</option>
  2109. <option value="300">5h</option>
  2110. <option value="720">12h</option>
  2111. <option value="1440">24h</option>
  2112. </select>
  2113. <input type="text" id="global-url-input" placeholder="输入配置链接">
  2114. <button id="add-global-url">添加</button>
  2115. <button id="apply-global-apply">应用</button>
  2116. </div>
  2117. <div class="global-url-list"></div>
  2118. </div>
  2119. </div>
  2120. </div>
  2121. </div>
  2122. <div class="config-section" data-section="keywords">
  2123. <button class="config-section-toggle" data-section="keywords">
  2124. <span id="keywords-config-title">关键词配置</span>
  2125. <span class="config-section-indicator">▼</span>
  2126. </button>
  2127. <div class="config-section-content">
  2128. <div id="keywords-container"></div>
  2129. </div>
  2130. </div>
  2131. <div class="config-section" data-section="usernames">
  2132. <button class="config-section-toggle" data-section="usernames">
  2133. <span id="usernames-config-title">用户名配置</span>
  2134. <span class="config-section-indicator">▼</span>
  2135. </button>
  2136. <div class="config-section-content">
  2137. <div id="usernames-container"></div>
  2138. </div>
  2139. </div>
  2140. <div class="config-section" data-section="url">
  2141. <button class="config-section-toggle" data-section="url">
  2142. <span id="url-config-title">URL 匹配模式</span>
  2143. <span class="config-section-indicator">▼</span>
  2144. </button>
  2145. <div class="config-section-content">
  2146. <div class="pattern-group">
  2147. <div id="main-patterns-editor"></div>
  2148. </div>
  2149. <div class="pattern-group">
  2150. <div id="sub-patterns-editor"></div>
  2151. </div>
  2152. <div class="pattern-group">
  2153. <div id="content-patterns-editor"></div>
  2154. </div>
  2155. </div>
  2156. </div>
  2157. <div class="config-section" data-section="xpath">
  2158. <button class="config-section-toggle" data-section="xpath">
  2159. <span id="xpath-config-title">XPath 配置</span>
  2160. <span class="config-section-indicator">▼</span>
  2161. </button>
  2162. <div class="config-section-content">
  2163. <div class="xpath-group">
  2164. <div id="main-title-xpath-editor"></div>
  2165. </div>
  2166. <div class="xpath-group">
  2167. <div id="main-user-xpath-editor"></div>
  2168. </div>
  2169. <div class="xpath-group">
  2170. <div id="content-title-xpath-editor"></div>
  2171. </div>
  2172. <div class="xpath-group">
  2173. <div id="content-user-xpath-editor"></div>
  2174. </div>
  2175. </div>
  2176. </div>
  2177. <div class="config-section" data-section="sync">
  2178. <button class="config-section-toggle" data-section="sync">
  2179. <span id="sync-config-title">云端同步</span>
  2180. <span class="config-section-indicator">▼</span>
  2181. </button>
  2182. <div class="config-section-content">
  2183. <input type="text" id="sync-server-url" placeholder="输入服务器地址">
  2184. <input type="text" id="sync-user-key" placeholder="输入用户密钥">
  2185. <button id="sync-apply">同步</button>
  2186. <button id="sync-delete">删除云端配置</button>
  2187. <div id="sync-status"></div>
  2188. </div>
  2189. </div>
  2190. <div class="button-group">
  2191. <button id="export-config">导出配置</button>
  2192. <button id="import-domain-config" style="display: none;">导入当前域名配置</button>
  2193. <button id="import-config">导入配置</button>
  2194. <button id="delete-domain-config" style="background: #ff4444 !important; color: white !important;">删除当前域名配置</button>
  2195. <button id="save-domain-config">保存</button>
  2196. </div>
  2197. </div>
  2198. `;
  2199. panel.querySelector('#page-type-text').textContent = setTextfromTemplate('panel_top_page_type');
  2200. panel.querySelector('.panel-settings-btn').textContent = setTextfromTemplate('panel_top_settings_button');
  2201. panel.querySelector('.panel-settings-btn').title = setTextfromTemplate('panel_top_settings_title');
  2202. panel.querySelector('#domain-enabled-text').textContent = setTextfromTemplate('panel_top_enable_domain');
  2203. panel.querySelector('#global-keywords').nextSibling.textContent = setTextfromTemplate('global_config_keywords');
  2204. panel.querySelector('#global-usernames').nextSibling.textContent = setTextfromTemplate('global_config_usernames');
  2205. panel.querySelector('#share-keywords').nextSibling.textContent = setTextfromTemplate('global_config_share_keywords');
  2206. panel.querySelector('#share-usernames').nextSibling.textContent = setTextfromTemplate('global_config_share_usernames');
  2207. panel.querySelector('#global-url-input').placeholder = setTextfromTemplate('global_config_linkimport_input_placeholder');
  2208. panel.querySelector('#add-global-url').textContent = setTextfromTemplate('global_config_add_global_url');
  2209. panel.querySelector('#apply-global-apply').textContent = setTextfromTemplate('global_config_apply_global_apply');
  2210. panel.querySelector('#global-config-title').textContent = setTextfromTemplate('global_config_title');
  2211. panel.querySelector('#keywords-config-title').textContent = setTextfromTemplate('keywords_config_title');
  2212. panel.querySelector('#usernames-config-title').textContent = setTextfromTemplate('usernames_config_title');
  2213. panel.querySelector('#url-config-title').textContent = setTextfromTemplate('url_patterns_title');
  2214. panel.querySelector('#xpath-config-title').textContent = setTextfromTemplate('xpath_config_title');
  2215. panel.querySelector('#export-config').textContent = setTextfromTemplate('panel_bottom_export_button');
  2216. panel.querySelector('#import-config').textContent = setTextfromTemplate('panel_bottom_import_button');
  2217. panel.querySelector('#delete-domain-config').textContent = setTextfromTemplate('panel_bottom_delete_button');
  2218. panel.querySelector('#save-domain-config').textContent = setTextfromTemplate('panel_bottom_save_button');
  2219. panel.querySelector('#sync-config-title').textContent = setTextfromTemplate('sync_config_title');
  2220. panel.querySelector('#sync-server-url').placeholder = setTextfromTemplate('sync_config_server_url');
  2221. panel.querySelector('#sync-user-key').placeholder = setTextfromTemplate('sync_config_user_key');
  2222. panel.querySelector('#sync-apply').textContent = setTextfromTemplate('sync_config_apply');
  2223. panel.querySelector('#sync-delete').textContent = setTextfromTemplate('sync_config_delete');
  2224. ['global', 'keywords', 'usernames', 'url', 'xpath', 'sync'].forEach(section => {
  2225. const toggle = panel.querySelector(`[data-section="${section}"]`);
  2226. const isCollapsed = GLOBAL_CONFIG.CONFIG_SECTION_COLLAPSED[`${section}_SECTION_COLLAPSED`];
  2227. toggle.classList[isCollapsed ? 'add' : 'remove']('collapsed');
  2228. });
  2229. panel.querySelector('#export-config').addEventListener('click', exportUserConfig);
  2230. panel.querySelector('#import-config').addEventListener('click', importUserConfigFromFile);
  2231. panel.querySelector('#import-domain-config').addEventListener('click', importCurrentDomainConfigFromFile);
  2232. panel.querySelector('#sync-apply').addEventListener('click', handleSyncInput);
  2233. panel.querySelector('#sync-delete').addEventListener('click', deleteCloudConfig);
  2234. panel.querySelector('#sync-server-url').value = GLOBAL_CONFIG.SYNC_CONFIG.server_url || '';
  2235. panel.querySelector('#sync-user-key').value = GLOBAL_CONFIG.SYNC_CONFIG.user_key || '';
  2236. panel.querySelectorAll('.config-section-toggle').forEach(toggle => {
  2237. toggle.addEventListener('click', function() {
  2238. this.classList.toggle('collapsed');
  2239. const content = this.nextElementSibling;
  2240. if (content && content.classList.contains('config-section-content')) {
  2241. if (this.classList.contains('collapsed')) {
  2242. content.style.maxHeight = '0';
  2243. content.style.opacity = '0';
  2244. content.style.margin = '0';
  2245. content.style.padding = '0';
  2246. } else {
  2247. content.style.maxHeight = '500px';
  2248. content.style.opacity = '1';
  2249. content.style.margin = '';
  2250. content.style.padding = '';
  2251. }
  2252. }
  2253. const section = this.getAttribute('data-section');
  2254. GLOBAL_CONFIG.CONFIG_SECTION_COLLAPSED[`${section}_SECTION_COLLAPSED`] = this.classList.contains('collapsed');
  2255. saveGlobalConfig();
  2256. });
  2257. });
  2258. function restoreConfigSections() {
  2259. panel.querySelectorAll('.config-section-toggle').forEach(toggle => {
  2260. const section = toggle.getAttribute('data-section');
  2261. const isCollapsed = GLOBAL_CONFIG.CONFIG_SECTION_COLLAPSED[`${section}_SECTION_COLLAPSED`];
  2262. if (isCollapsed) {
  2263. toggle.classList.add('collapsed');
  2264. const content = toggle.nextElementSibling;
  2265. if (content && content.classList.contains('config-section-content')) {
  2266. content.style.maxHeight = '0';
  2267. content.style.opacity = '0';
  2268. content.style.margin = '0';
  2269. content.style.padding = '0';
  2270. }
  2271. }
  2272. });
  2273. }
  2274. document.body.appendChild(panel);
  2275. applyPanelSettings();
  2276. updatePanelContent();
  2277. restoreConfigSections();
  2278. panel.querySelector('.panel-settings-btn').addEventListener('click', function() {
  2279. const settingsPanel = document.getElementById('forum-filter-settings');
  2280. const overlay = document.getElementById('settings-overlay');
  2281. if (settingsPanel && overlay) {
  2282. settingsPanel.classList.add('visible');
  2283. overlay.style.display = 'block';
  2284. }
  2285. });
  2286. panel.querySelector('.panel-tab').addEventListener('click', function() {
  2287. if (panel.classList.contains('click-mode')) {
  2288. panel.classList.toggle('expanded');
  2289. }
  2290. });
  2291. panel.querySelector('#save-domain-config').addEventListener('click', function() {
  2292. saveConfig();
  2293. });
  2294. const globalConfigCheckboxes = [
  2295. '#global-keywords',
  2296. '#global-usernames',
  2297. '#share-keywords',
  2298. '#share-usernames',
  2299. '#domain-enabled'
  2300. ];
  2301. globalConfigCheckboxes.forEach(selector => {
  2302. panel.querySelector(selector).addEventListener('change', function() {
  2303. GLOBAL_CONFIG.GLOBAL_KEYWORDS = panel.querySelector('#global-keywords').checked;
  2304. GLOBAL_CONFIG.GLOBAL_USERNAMES = panel.querySelector('#global-usernames').checked;
  2305. const currentConfig = getDomainConfig(getCurrentDomain());
  2306. if (currentConfig) {
  2307. currentConfig.shareKeywordsAcrossPages = panel.querySelector('#share-keywords').checked;
  2308. currentConfig.shareUsernamesAcrossPages = panel.querySelector('#share-usernames').checked;
  2309. }
  2310. saveConfig();
  2311. });
  2312. });
  2313. panel.querySelector('#delete-domain-config').addEventListener('click', function() {
  2314. if (confirm(`确定要删除 ${getCurrentDomain()} 的配置吗?此操作不可恢复。`)) {
  2315. removeDomainConfig(getCurrentDomain());
  2316. updatePanelContent();
  2317. debouncedHandleElements();
  2318. alert('配置已删除!');
  2319. }
  2320. });
  2321. return panel;
  2322. }
  2323. if (document.readyState === 'loading') {
  2324. document.addEventListener('DOMContentLoaded', function() {
  2325. createSettingsPanel();
  2326. createControlPanel();
  2327. initCloudSync();
  2328. });
  2329. } else {
  2330. createSettingsPanel();
  2331. createControlPanel();
  2332. initCloudSync();
  2333. }
  2334. function getSplitUrl(){
  2335. const currentUrl = new URL(window.location.href);
  2336. return currentUrl.pathname + (currentUrl.search ? currentUrl.search : '');
  2337. }
  2338. function createArrayEditor(title, items, onAdd, onDelete,className = null,isRegex = false) {
  2339. const container = document.createElement('div');
  2340. container.className = 'array-editor';
  2341. if (!Array.isArray(items)) {
  2342. items = [];
  2343. }
  2344. const toggleButton = document.createElement('button');
  2345. toggleButton.className = 'array-editor-toggle';
  2346. const titleSpan = document.createElement('span');
  2347. titleSpan.textContent = title;
  2348. if(className){
  2349. titleSpan.className = className;
  2350. }
  2351. const countSpan = document.createElement('span');
  2352. countSpan.className = 'array-editor-count';
  2353. countSpan.textContent = `${items.length}`;
  2354. toggleButton.appendChild(titleSpan);
  2355. toggleButton.appendChild(countSpan);
  2356. const content = document.createElement('div');
  2357. content.className = 'array-editor-content';
  2358. let editorType;
  2359. const currentLanguage = GLOBAL_CONFIG.LANGUAGE || 'zh-CN';
  2360. const templates = LANGUAGE_TEMPLATES[currentLanguage];
  2361. if (title === templates.url_patterns_main_page_url_patterns_title) {
  2362. editorType = 'mainpage_url_patterns';
  2363. } else if (title === templates.url_patterns_sub_page_url_patterns_title) {
  2364. editorType = 'subpage_url_patterns';
  2365. } else if (title === templates.url_patterns_content_page_url_patterns_title) {
  2366. editorType = 'contentpage_url_patterns';
  2367. } else if (title === templates.xpath_config_main_and_sub_page_usernames_title) {
  2368. editorType = 'main_and_sub_page_user_xpath';
  2369. } else if (title === templates.xpath_config_main_and_sub_page_keywords_title) {
  2370. editorType = 'main_and_sub_page_title_xpath';
  2371. } else if (title === templates.xpath_config_content_page_usernames_title) {
  2372. editorType = 'contentpage_user_xpath';
  2373. } else if (title === templates.xpath_config_content_page_keywords_title) {
  2374. editorType = 'contentpage_title_xpath';
  2375. } else if (title === templates.keywords_config_keywords_regex_title) {
  2376. editorType = 'keywords_regex';
  2377. } else if (title === templates.usernames_config_usernames_regex_title) {
  2378. editorType = 'usernames_regex';
  2379. } else if (title === templates.usernames_config_usernames_list_title) {
  2380. editorType = 'usernames';
  2381. } else if (title === templates.keywords_config_keywords_list_title) {
  2382. editorType = 'keywords';
  2383. }
  2384. if (GLOBAL_CONFIG.EDITOR_STATES[editorType]) {
  2385. content.classList.add('expanded');
  2386. }
  2387. const header = document.createElement('div');
  2388. const header2 = document.createElement('div');
  2389. const header3 = document.createElement('div');
  2390. header.className = 'array-editor-header';
  2391. header2.className = 'array-editor-header';
  2392. header3.className = 'array-editor-header';
  2393. const buttonGroup1 = document.createElement('div');
  2394. buttonGroup1.className = 'button-group-inline';
  2395. const input = document.createElement('input');
  2396. input.type = 'text';
  2397. if(isRegex){
  2398. input.placeholder = setTextfromTemplate('array_editor_add_item_input_placeholder_regex');
  2399. input.className = 'array-editor-additem-input-regex';
  2400. }else{
  2401. input.placeholder = setTextfromTemplate('array_editor_add_item_input_placeholder');
  2402. input.className = 'array-editor-additem-input';
  2403. }
  2404. const addButton = document.createElement('button');
  2405. addButton.textContent = setTextfromTemplate('array_editor_add_item');
  2406. addButton.title = setTextfromTemplate('array_editor_add_item_title');
  2407. addButton.className = 'array-editor-add-button';
  2408. const deleteAllButton = document.createElement('button');
  2409. deleteAllButton.textContent = setTextfromTemplate('array_editor_clear_allitem');
  2410. deleteAllButton.title = setTextfromTemplate('array_editor_clear_allitem_title');
  2411. deleteAllButton.className = 'array-editor-delete-all-button';
  2412. const buttonGroup2 = document.createElement('div');
  2413. buttonGroup2.className = 'button-group-inline';
  2414. const exportButton = document.createElement('button');
  2415. exportButton.textContent = setTextfromTemplate('array_editor_export_button');
  2416. exportButton.title = setTextfromTemplate('array_editor_export_button_title');
  2417. exportButton.className = 'array-editor-export-button';
  2418. const importButton = document.createElement('button');
  2419. importButton.textContent = setTextfromTemplate('array_editor_fileimport_input_button');
  2420. importButton.title = setTextfromTemplate('array_editor_fileimport_input_button_title');
  2421. importButton.className = 'array-editor-import-button';
  2422. const linkimportbutton = document.createElement('button');
  2423. linkimportbutton.textContent = setTextfromTemplate('array_editor_linkimport_input_button');
  2424. linkimportbutton.title = setTextfromTemplate('array_editor_linkimport_input_button_title');
  2425. linkimportbutton.className = 'array-editor-linkimport-button';
  2426. const linkimportinput = document.createElement('input');
  2427. linkimportinput.type = 'text';
  2428. linkimportinput.placeholder = setTextfromTemplate('array_editor_linkimport_input_placeholder');
  2429. linkimportinput.className = 'array-editor-linkimport-input';
  2430. const fileInput = document.createElement('input');
  2431. fileInput.type = 'file';
  2432. fileInput.accept = '.txt';
  2433. fileInput.style.display = 'none';
  2434. const searchinput = document.createElement('input');
  2435. searchinput.type = 'text';
  2436. searchinput.placeholder = setTextfromTemplate('array_editor_search_input_placeholder');
  2437. searchinput.className = 'array-editor-search-input';
  2438. buttonGroup1.appendChild(addButton);
  2439. buttonGroup1.appendChild(deleteAllButton);
  2440. buttonGroup2.appendChild(linkimportbutton);
  2441. buttonGroup2.appendChild(importButton);
  2442. buttonGroup2.appendChild(exportButton);
  2443. header.appendChild(input);
  2444. header.appendChild(buttonGroup1);
  2445. header2.appendChild(linkimportinput);
  2446. header2.appendChild(buttonGroup2);
  2447. header2.appendChild(fileInput);
  2448. header3.appendChild(searchinput);
  2449. const list = document.createElement('div');
  2450. list.className = 'array-editor-list';
  2451. list.dataset.empty = setTextfromTemplate('array_editor_list_empty_placeholder');
  2452. const updateList = (searchText = '') => {
  2453. list.innerHTML = '';
  2454. const filteredItems = searchText.trim()
  2455. ? items.filter(item => item.toLowerCase().includes(searchText.toLowerCase()))
  2456. : items;
  2457. filteredItems.forEach((item, index) => {
  2458. const itemElement = document.createElement('div');
  2459. itemElement.className = 'array-item';
  2460. let displayText = item;
  2461. if (searchText.trim()) {
  2462. const regex = new RegExp(`(${searchText})`, 'gi');
  2463. displayText = item.replace(regex, '<mark>$1</mark>');
  2464. }
  2465. itemElement.innerHTML = `
  2466. <span>${displayText}</span>
  2467. <button>×</button>
  2468. `;
  2469. const originalIndex = items.indexOf(item);
  2470. itemElement.querySelector('button').onclick = () => {
  2471. onDelete(originalIndex);
  2472. updateList(searchText);
  2473. countSpan.textContent = `${items.length}`;
  2474. document.querySelector('#save-domain-config').click();
  2475. };
  2476. list.appendChild(itemElement);
  2477. });
  2478. countSpan.textContent = `${items.length} ${searchText ? ` (${filteredItems.length})` : ''}`;
  2479. };
  2480. searchinput.addEventListener('input', (e) => {
  2481. updateList(e.target.value);
  2482. });
  2483. searchinput.addEventListener('keydown', (e) => {
  2484. if (e.key === 'Escape') {
  2485. searchinput.value = '';
  2486. updateList('');
  2487. }
  2488. });
  2489. const addNewItem = (value) => {
  2490. if (value.trim()) {
  2491. const newItem = value.trim();
  2492. const isDuplicate = items.some(item =>
  2493. item.toLowerCase() === newItem.toLowerCase()
  2494. );
  2495. if (isDuplicate) {
  2496. return true;
  2497. }
  2498. onAdd(newItem);
  2499. updateList();
  2500. countSpan.textContent = `${items.length}`;
  2501. document.querySelector('#save-domain-config').click();
  2502. return true;
  2503. }
  2504. return false;
  2505. };
  2506. input.addEventListener('keypress', (e) => {
  2507. if (e.key === 'Enter' && input.value.trim()) {
  2508. if (addNewItem(input.value)) {
  2509. input.value = '';
  2510. }
  2511. }
  2512. });
  2513. addButton.onclick = () => {
  2514. if (input.value.trim()) {
  2515. if (addNewItem(input.value)) {
  2516. input.value = '';
  2517. }
  2518. }
  2519. };
  2520. deleteAllButton.onclick = () => {
  2521. if (items.length === 0) {
  2522. alert(setTextfromTemplate('alert_list_empty'));
  2523. return;
  2524. }
  2525. if (confirm(setTextfromTemplate('alert_clear_confirm'))) {
  2526. const currentConfig = getDomainConfig(getCurrentDomain());
  2527. const isMainOrSubPage = currentConfig.mainPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl())) ||
  2528. currentConfig.subPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  2529. const isContentPage = currentConfig.contentPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  2530. let targetConfig;
  2531. if (title === setTextfromTemplate('url_patterns_main_page_url_patterns_title')) {
  2532. currentConfig.mainPageUrlPatterns = [];
  2533. updateDomainConfig(getCurrentDomain(), currentConfig);
  2534. updatePanelContent();
  2535. debouncedHandleElements();
  2536. return;
  2537. } else if (title === setTextfromTemplate('url_patterns_sub_page_url_patterns_title')) {
  2538. currentConfig.subPageUrlPatterns = [];
  2539. updateDomainConfig(getCurrentDomain(), currentConfig);
  2540. updatePanelContent();
  2541. debouncedHandleElements();
  2542. return;
  2543. } else if (title === setTextfromTemplate('url_patterns_content_page_url_patterns_title')) {
  2544. currentConfig.contentPageUrlPatterns = [];
  2545. updateDomainConfig(getCurrentDomain(), currentConfig);
  2546. updatePanelContent();
  2547. debouncedHandleElements();
  2548. return;
  2549. }
  2550. if (title === setTextfromTemplate('xpath_config_main_and_sub_page_keywords_title')) {
  2551. if (!currentConfig.mainAndSubPageKeywords) {
  2552. currentConfig.mainAndSubPageKeywords = {};
  2553. }
  2554. currentConfig.mainAndSubPageKeywords.xpath = [];
  2555. updateDomainConfig(getCurrentDomain(), currentConfig);
  2556. updatePanelContent();
  2557. debouncedHandleElements();
  2558. return;
  2559. } else if (title === setTextfromTemplate('xpath_config_main_and_sub_page_usernames_title')) {
  2560. if (!currentConfig.mainAndSubPageUserKeywords) {
  2561. currentConfig.mainAndSubPageUserKeywords = {};
  2562. }
  2563. currentConfig.mainAndSubPageUserKeywords.xpath = [];
  2564. updateDomainConfig(getCurrentDomain(), currentConfig);
  2565. updatePanelContent();
  2566. debouncedHandleElements();
  2567. return;
  2568. } else if (title === setTextfromTemplate('xpath_config_content_page_keywords_title')) {
  2569. if (!currentConfig.contentPageKeywords) {
  2570. currentConfig.contentPageKeywords = {};
  2571. }
  2572. currentConfig.contentPageKeywords.xpath = [];
  2573. updateDomainConfig(getCurrentDomain(), currentConfig);
  2574. updatePanelContent();
  2575. debouncedHandleElements();
  2576. return;
  2577. } else if (title === setTextfromTemplate('xpath_config_content_page_usernames_title')) {
  2578. if (!currentConfig.contentPageUserKeywords) {
  2579. currentConfig.contentPageUserKeywords = {};
  2580. }
  2581. currentConfig.contentPageUserKeywords.xpath = [];
  2582. updateDomainConfig(getCurrentDomain(), currentConfig);
  2583. updatePanelContent();
  2584. debouncedHandleElements();
  2585. return;
  2586. }
  2587. if (title === setTextfromTemplate('keywords_config_keywords_list_title')) {
  2588. if (isMainOrSubPage) {
  2589. if (!currentConfig.mainAndSubPageKeywords) {
  2590. currentConfig.mainAndSubPageKeywords = { keywords: [], regexPatterns: [] };
  2591. }
  2592. currentConfig.mainAndSubPageKeywords.keywords = [];
  2593. } else if (isContentPage) {
  2594. if (!currentConfig.contentPageKeywords) {
  2595. currentConfig.contentPageKeywords = { keywords: [], regexPatterns: [] };
  2596. }
  2597. currentConfig.contentPageKeywords.keywords = [];
  2598. }
  2599. updateDomainConfig(getCurrentDomain(), currentConfig);
  2600. updatePanelContent();
  2601. debouncedHandleElements();
  2602. return;
  2603. } else if (title === setTextfromTemplate('keywords_config_keywords_regex_title')) {
  2604. if (isMainOrSubPage) {
  2605. if (!currentConfig.mainAndSubPageKeywords) {
  2606. currentConfig.mainAndSubPageKeywords = { keywords: [], regexPatterns: [] };
  2607. }
  2608. currentConfig.mainAndSubPageKeywords.regexPatterns = [];
  2609. } else if (isContentPage) {
  2610. if (!currentConfig.contentPageKeywords) {
  2611. currentConfig.contentPageKeywords = { keywords: [], regexPatterns: [] };
  2612. }
  2613. currentConfig.contentPageKeywords.regexPatterns = [];
  2614. }
  2615. updateDomainConfig(getCurrentDomain(), currentConfig);
  2616. updatePanelContent();
  2617. debouncedHandleElements();
  2618. return;
  2619. }
  2620. if (title === setTextfromTemplate('usernames_config_usernames_list_title')) {
  2621. if (isMainOrSubPage) {
  2622. if (!currentConfig.mainAndSubPageUserKeywords) {
  2623. currentConfig.mainAndSubPageUserKeywords = { keywords: [], regexPatterns: [] };
  2624. }
  2625. currentConfig.mainAndSubPageUserKeywords.keywords = [];
  2626. } else if (isContentPage) {
  2627. if (!currentConfig.contentPageUserKeywords) {
  2628. currentConfig.contentPageUserKeywords = { keywords: [], regexPatterns: [] };
  2629. }
  2630. currentConfig.contentPageUserKeywords.keywords = [];
  2631. }
  2632. updateDomainConfig(getCurrentDomain(), currentConfig);
  2633. updatePanelContent();
  2634. debouncedHandleElements();
  2635. return;
  2636. } else if (title === setTextfromTemplate('usernames_config_usernames_regex_title')) {
  2637. if (isMainOrSubPage) {
  2638. if (!currentConfig.mainAndSubPageUserKeywords) {
  2639. currentConfig.mainAndSubPageUserKeywords = { keywords: [], regexPatterns: [] };
  2640. }
  2641. currentConfig.mainAndSubPageUserKeywords.regexPatterns = [];
  2642. } else if (isContentPage) {
  2643. if (!currentConfig.contentPageUserKeywords) {
  2644. currentConfig.contentPageUserKeywords = { keywords: [], regexPatterns: [] };
  2645. }
  2646. currentConfig.contentPageUserKeywords.regexPatterns = [];
  2647. }
  2648. updateDomainConfig(getCurrentDomain(), currentConfig);
  2649. updatePanelContent();
  2650. debouncedHandleElements();
  2651. return;
  2652. }
  2653. }
  2654. };
  2655. exportButton.onclick = () => {
  2656. const blob = new Blob([items.join('\n')], { type: 'text/plain' });
  2657. const url = URL.createObjectURL(blob);
  2658. const a = document.createElement('a');
  2659. a.href = url;
  2660. a.download = `${title.replace(/[^a-zA-Z0-9]/g, '_')}_${new Date().toISOString().split('T')[0]}.txt`;
  2661. document.body.appendChild(a);
  2662. a.click();
  2663. document.body.removeChild(a);
  2664. URL.revokeObjectURL(url);
  2665. };
  2666. importButton.onclick = () => {
  2667. fileInput.click();
  2668. };
  2669. linkimportbutton.onclick = async () => {
  2670. const url = linkimportinput.value.trim();
  2671. if (!url) {
  2672. alert(setTextfromTemplate('alert_enter_url'));
  2673. return;
  2674. }
  2675. try {
  2676. const response = await fetch(url);
  2677. if (!response.ok) {
  2678. throw new Error(`HTTP error! status: ${response.status}`);
  2679. }
  2680. const text = await response.text();
  2681. const blob = new Blob([text], { type: 'text/plain' });
  2682. const file = new File([blob], 'imported.txt', { type: 'text/plain' });
  2683. const event = new Event('change');
  2684. Object.defineProperty(event, 'target', {
  2685. value: { files: [file] },
  2686. enumerable: true
  2687. });
  2688. fileInput.dispatchEvent(event);
  2689. linkimportinput.value = '';
  2690. } catch (error) {
  2691. console.error('导入失败:', error);
  2692. }
  2693. };
  2694. linkimportinput.addEventListener('keypress', (e) => {
  2695. if (e.key === 'Enter') {
  2696. linkimportbutton.click();
  2697. }
  2698. });
  2699. fileInput.onchange = (e) => {
  2700. const file = e.target.files[0];
  2701. if (file) {
  2702. const reader = new FileReader();
  2703. reader.onload = (event) => {
  2704. const content = event.target.result;
  2705. const newItems = content.split(/\r?\n/)
  2706. .map(item => item.trim())
  2707. .filter(item => item);
  2708. const currentConfig = getDomainConfig(getCurrentDomain());
  2709. let addedCount = 0;
  2710. let duplicateCount = 0;
  2711. if (title === setTextfromTemplate('url_patterns_main_page_url_patterns_title')) {
  2712. newItems.forEach(item => {
  2713. if (!currentConfig.mainPageUrlPatterns.includes(item)) {
  2714. currentConfig.mainPageUrlPatterns.push(item);
  2715. addedCount++;
  2716. } else {
  2717. duplicateCount++;
  2718. }
  2719. });
  2720. updateDomainConfig(getCurrentDomain(), currentConfig);
  2721. updatePanelContent();
  2722. debouncedHandleElements();
  2723. showImportResult(addedCount, duplicateCount);
  2724. return;
  2725. } else if (title === setTextfromTemplate('url_patterns_sub_page_url_patterns_title')) {
  2726. newItems.forEach(item => {
  2727. if (!currentConfig.subPageUrlPatterns.includes(item)) {
  2728. currentConfig.subPageUrlPatterns.push(item);
  2729. addedCount++;
  2730. } else {
  2731. duplicateCount++;
  2732. }
  2733. });
  2734. updateDomainConfig(getCurrentDomain(), currentConfig);
  2735. updatePanelContent();
  2736. debouncedHandleElements();
  2737. showImportResult(addedCount, duplicateCount);
  2738. return;
  2739. } else if (title === setTextfromTemplate('url_patterns_content_page_url_patterns_title')) {
  2740. newItems.forEach(item => {
  2741. if (!currentConfig.contentPageUrlPatterns.includes(item)) {
  2742. currentConfig.contentPageUrlPatterns.push(item);
  2743. addedCount++;
  2744. } else {
  2745. duplicateCount++;
  2746. }
  2747. });
  2748. updateDomainConfig(getCurrentDomain(), currentConfig);
  2749. updatePanelContent();
  2750. debouncedHandleElements();
  2751. showImportResult(addedCount, duplicateCount);
  2752. return;
  2753. }
  2754. if (title === setTextfromTemplate('xpath_config_main_and_sub_page_keywords_title')) {
  2755. if (!currentConfig.mainAndSubPageKeywords) {
  2756. currentConfig.mainAndSubPageKeywords = { xpath: [] };
  2757. }
  2758. newItems.forEach(item => {
  2759. if (!currentConfig.mainAndSubPageKeywords.xpath.includes(item)) {
  2760. currentConfig.mainAndSubPageKeywords.xpath.push(item);
  2761. addedCount++;
  2762. } else {
  2763. duplicateCount++;
  2764. }
  2765. });
  2766. updateDomainConfig(getCurrentDomain(), currentConfig);
  2767. updatePanelContent();
  2768. debouncedHandleElements();
  2769. showImportResult(addedCount, duplicateCount);
  2770. return;
  2771. } else if (title === setTextfromTemplate('xpath_config_main_and_sub_page_usernames_title')) {
  2772. if (!currentConfig.mainAndSubPageUserKeywords) {
  2773. currentConfig.mainAndSubPageUserKeywords = { xpath: [] };
  2774. }
  2775. newItems.forEach(item => {
  2776. if (!currentConfig.mainAndSubPageUserKeywords.xpath.includes(item)) {
  2777. currentConfig.mainAndSubPageUserKeywords.xpath.push(item);
  2778. addedCount++;
  2779. } else {
  2780. duplicateCount++;
  2781. }
  2782. });
  2783. updateDomainConfig(getCurrentDomain(), currentConfig);
  2784. updatePanelContent();
  2785. debouncedHandleElements();
  2786. showImportResult(addedCount, duplicateCount);
  2787. return;
  2788. } else if (title === setTextfromTemplate('xpath_config_content_page_keywords_title')) {
  2789. if (!currentConfig.contentPageKeywords) {
  2790. currentConfig.contentPageKeywords = { xpath: [] };
  2791. }
  2792. newItems.forEach(item => {
  2793. if (!currentConfig.contentPageKeywords.xpath.includes(item)) {
  2794. currentConfig.contentPageKeywords.xpath.push(item);
  2795. addedCount++;
  2796. } else {
  2797. duplicateCount++;
  2798. }
  2799. });
  2800. updateDomainConfig(getCurrentDomain(), currentConfig);
  2801. updatePanelContent();
  2802. debouncedHandleElements();
  2803. showImportResult(addedCount, duplicateCount);
  2804. return;
  2805. } else if (title === setTextfromTemplate('xpath_config_content_page_usernames_title')) {
  2806. if (!currentConfig.contentPageUserKeywords) {
  2807. currentConfig.contentPageUserKeywords = { xpath: [] };
  2808. }
  2809. newItems.forEach(item => {
  2810. if (!currentConfig.contentPageUserKeywords.xpath.includes(item)) {
  2811. currentConfig.contentPageUserKeywords.xpath.push(item);
  2812. addedCount++;
  2813. } else {
  2814. duplicateCount++;
  2815. }
  2816. });
  2817. updateDomainConfig(getCurrentDomain(), currentConfig);
  2818. updatePanelContent();
  2819. debouncedHandleElements();
  2820. showImportResult(addedCount, duplicateCount);
  2821. return;
  2822. }
  2823. const isMainOrSubPage = currentConfig.mainPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl())) ||
  2824. currentConfig.subPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  2825. const isContentPage = currentConfig.contentPageUrlPatterns?.some(pattern => new RegExp(pattern).test(getSplitUrl()));
  2826. if (title === setTextfromTemplate('keywords_config_keywords_list_title')) {
  2827. if (isMainOrSubPage) {
  2828. if (!currentConfig.mainAndSubPageKeywords) {
  2829. currentConfig.mainAndSubPageKeywords = { keywords: [], regexPatterns: [] };
  2830. }
  2831. newItems.forEach(item => {
  2832. if (!currentConfig.mainAndSubPageKeywords.keywords.includes(item)) {
  2833. currentConfig.mainAndSubPageKeywords.keywords.push(item);
  2834. addedCount++;
  2835. } else {
  2836. duplicateCount++;
  2837. }
  2838. });
  2839. } else if (isContentPage) {
  2840. if (!currentConfig.contentPageKeywords) {
  2841. currentConfig.contentPageKeywords = { keywords: [], regexPatterns: [] };
  2842. }
  2843. newItems.forEach(item => {
  2844. if (!currentConfig.contentPageKeywords.keywords.includes(item)) {
  2845. currentConfig.contentPageKeywords.keywords.push(item);
  2846. addedCount++;
  2847. } else {
  2848. duplicateCount++;
  2849. }
  2850. });
  2851. }
  2852. updateDomainConfig(getCurrentDomain(), currentConfig);
  2853. updatePanelContent();
  2854. debouncedHandleElements();
  2855. showImportResult(addedCount, duplicateCount);
  2856. return;
  2857. } else if (title === setTextfromTemplate('keywords_config_keywords_regex_title')) {
  2858. if (isMainOrSubPage) {
  2859. if (!currentConfig.mainAndSubPageKeywords) {
  2860. currentConfig.mainAndSubPageKeywords = { keywords: [], regexPatterns: [] };
  2861. }
  2862. newItems.forEach(item => {
  2863. if (!currentConfig.mainAndSubPageKeywords.regexPatterns.includes(item)) {
  2864. currentConfig.mainAndSubPageKeywords.regexPatterns.push(item);
  2865. addedCount++;
  2866. } else {
  2867. duplicateCount++;
  2868. }
  2869. });
  2870. } else if (isContentPage) {
  2871. if (!currentConfig.contentPageKeywords) {
  2872. currentConfig.contentPageKeywords = { keywords: [], regexPatterns: [] };
  2873. }
  2874. newItems.forEach(item => {
  2875. if (!currentConfig.contentPageKeywords.regexPatterns.includes(item)) {
  2876. currentConfig.contentPageKeywords.regexPatterns.push(item);
  2877. addedCount++;
  2878. } else {
  2879. duplicateCount++;
  2880. }
  2881. });
  2882. }
  2883. updateDomainConfig(getCurrentDomain(), currentConfig);
  2884. updatePanelContent();
  2885. debouncedHandleElements();
  2886. showImportResult(addedCount, duplicateCount);
  2887. return;
  2888. }
  2889. if (title === setTextfromTemplate('usernames_config_usernames_list_title')) {
  2890. if (isMainOrSubPage) {
  2891. if (!currentConfig.mainAndSubPageUserKeywords) {
  2892. currentConfig.mainAndSubPageUserKeywords = { keywords: [], regexPatterns: [] };
  2893. }
  2894. newItems.forEach(item => {
  2895. if (!currentConfig.mainAndSubPageUserKeywords.keywords.includes(item)) {
  2896. currentConfig.mainAndSubPageUserKeywords.keywords.push(item);
  2897. addedCount++;
  2898. } else {
  2899. duplicateCount++;
  2900. }
  2901. });
  2902. } else if (isContentPage) {
  2903. if (!currentConfig.contentPageUserKeywords) {
  2904. currentConfig.contentPageUserKeywords = { keywords: [], regexPatterns: [] };
  2905. }
  2906. newItems.forEach(item => {
  2907. if (!currentConfig.contentPageUserKeywords.keywords.includes(item)) {
  2908. currentConfig.contentPageUserKeywords.keywords.push(item);
  2909. addedCount++;
  2910. } else {
  2911. duplicateCount++;
  2912. }
  2913. });
  2914. }
  2915. updateDomainConfig(getCurrentDomain(), currentConfig);
  2916. updatePanelContent();
  2917. debouncedHandleElements();
  2918. showImportResult(addedCount, duplicateCount);
  2919. return;
  2920. } else if (title === setTextfromTemplate('usernames_config_usernames_regex_title')) {
  2921. if (isMainOrSubPage) {
  2922. if (!currentConfig.mainAndSubPageUserKeywords) {
  2923. currentConfig.mainAndSubPageUserKeywords = { keywords: [], regexPatterns: [] };
  2924. }
  2925. newItems.forEach(item => {
  2926. if (!currentConfig.mainAndSubPageUserKeywords.regexPatterns.includes(item)) {
  2927. currentConfig.mainAndSubPageUserKeywords.regexPatterns.push(item);
  2928. addedCount++;
  2929. } else {
  2930. duplicateCount++;
  2931. }
  2932. });
  2933. } else if (isContentPage) {
  2934. if (!currentConfig.contentPageUserKeywords) {
  2935. currentConfig.contentPageUserKeywords = { keywords: [], regexPatterns: [] };
  2936. }
  2937. newItems.forEach(item => {
  2938. if (!currentConfig.contentPageUserKeywords.regexPatterns.includes(item)) {
  2939. currentConfig.contentPageUserKeywords.regexPatterns.push(item);
  2940. addedCount++;
  2941. } else {
  2942. duplicateCount++;
  2943. }
  2944. });
  2945. }
  2946. updateDomainConfig(getCurrentDomain(), currentConfig);
  2947. updatePanelContent();
  2948. debouncedHandleElements();
  2949. showImportResult(addedCount, duplicateCount);
  2950. return;
  2951. }
  2952. };
  2953. reader.readAsText(file);
  2954. fileInput.value = '';
  2955. }
  2956. };
  2957. function showImportResult(addedCount, duplicateCount) {
  2958. const messages = {
  2959. 'zh-CN': `导入完成:\n成功导入 ${addedCount} \n重复项 ${duplicateCount} 项`,
  2960. 'en-US': `Import completed:\n${addedCount} items imported successfully\n${duplicateCount} duplicate items`,
  2961. 'ja-JP': `インポート完了:\n${addedCount} 件追加\n${duplicateCount} 件重複`,
  2962. 'ko-KR': `가져오기 완료:\n${addedCount}개 항목 추가됨\n${duplicateCount}개 중복 항목`,
  2963. 'ru-RU': `Импорт завершен:\n${addedCount} элементов импортировано\n${duplicateCount} повторяющихся элементов`,
  2964. 'fr-FR': `Importation terminée :\n${addedCount} éléments importés\n${duplicateCount} éléments en double`,
  2965. 'de-DE': `Import abgeschlossen:\n${addedCount} Elemente importiert\n${duplicateCount} doppelte Elemente`,
  2966. 'it-IT': `Importazione completata:\n${addedCount} elementi importati\n${duplicateCount} elementi duplicati`,
  2967. 'hi-IN': `आयात पूर्ण:\n${addedCount} आइटम सफलतापूर्वक आयात किए गए\n${duplicateCount} डुप्लिकेट आइटम`,
  2968. 'id-ID': `Impor selesai:\n${addedCount} item berhasil diimpor\n${duplicateCount} item duplikat`,
  2969. 'vi-VN': `Nhp hoàn tt:\n${addedCount} mc đã được nhp thành công\n${duplicateCount} mc trùng lp`,
  2970. 'th-TH': `การนำเข้าเสร็จสิ้น:\n${addedCount} รายการนำเข้าสำเร็จ\n${duplicateCount} รายการซ้ำ`,
  2971. 'es-ES': `Importación completada:\n${addedCount} elementos importados\n${duplicateCount} elementos duplicados`,
  2972. 'pt-PT': `Importação concluída:\n${addedCount} itens importados\n${duplicateCount} itens duplicados`
  2973. };
  2974. alert(messages[GLOBAL_CONFIG.LANGUAGE] || messages['zh-CN']);
  2975. }
  2976. toggleButton.onclick = () => {
  2977. content.classList.toggle('expanded');
  2978. GLOBAL_CONFIG.EDITOR_STATES[editorType] = content.classList.contains('expanded');
  2979. saveGlobalConfig();
  2980. };
  2981. content.appendChild(header);
  2982. content.appendChild(header3);
  2983. content.appendChild(list);
  2984. content.appendChild(header2);
  2985. container.appendChild(toggleButton);
  2986. container.appendChild(content);
  2987. updateList();
  2988. return container;
  2989. }
  2990. function importCurrentDomainConfig(file) {
  2991. return new Promise((resolve, reject) => {
  2992. if (!file || !(file instanceof File)) {
  2993. resolve({
  2994. success: false,
  2995. message: '请选择有效的配置文件',
  2996. config: null
  2997. });
  2998. return;
  2999. }
  3000. const reader = new FileReader();
  3001. reader.onload = async (event) => {
  3002. try {
  3003. const importData = JSON.parse(event.target.result);
  3004. const currentUrl = new URL(window.location.href);
  3005. const currentDomain = currentUrl.hostname
  3006. let domainConfig = null;
  3007. if (importData.userConfig) {
  3008. domainConfig = importData.userConfig.find(config => config.domain === currentDomain);
  3009. } else if (importData.config && importData.config.domain === currentDomain) {
  3010. domainConfig = importData.config;
  3011. }
  3012. if (!domainConfig) {
  3013. resolve({
  3014. success: false,
  3015. message: '配置文件中未找到当前域名的配置',
  3016. config: null
  3017. });
  3018. return;
  3019. }
  3020. const existingConfig = getDomainConfig(currentDomain);
  3021. if (existingConfig) {
  3022. const updateResult = updateDomainConfig(currentDomain, domainConfig);
  3023. resolve({
  3024. success: true,
  3025. message: '当前域名配置已更新',
  3026. config: updateResult.config
  3027. });
  3028. } else {
  3029. const addResult = addDomainConfig(domainConfig);
  3030. resolve({
  3031. success: true,
  3032. message: '当前域名配置已导入',
  3033. config: addResult.config
  3034. });
  3035. }
  3036. updatePanelContent();
  3037. } catch (error) {
  3038. console.error('导入配置失败:', error);
  3039. resolve({
  3040. success: false,
  3041. message: `导入配置失败: ${error.message}`,
  3042. config: null
  3043. });
  3044. }
  3045. };
  3046. reader.onerror = () => {
  3047. resolve({
  3048. success: false,
  3049. message: '读取文件失败',
  3050. config: null
  3051. });
  3052. };
  3053. reader.readAsText(file);
  3054. });
  3055. }
  3056. function importCurrentDomainConfigFromFile() {
  3057. return new Promise((resolve) => {
  3058. const input = document.createElement('input');
  3059. input.type = 'file';
  3060. input.accept = '.json';
  3061. input.onchange = async (event) => {
  3062. const file = event.target.files[0];
  3063. const result = await importCurrentDomainConfig(file);
  3064. if (!result.success) {
  3065. } else {
  3066. }
  3067. resolve(result);
  3068. };
  3069. input.click();
  3070. });
  3071. }
  3072. function checkUpdateTime() {
  3073. const now = Date.now();
  3074. const lastUpdate = GM_getValue('LAST_UPDATE_TIME', 0);
  3075. const interval = (GLOBAL_CONFIG.TIME_INTERVAL || 30) * 60 * 1000;
  3076. const timeSinceLastUpdate = now - lastUpdate;
  3077. const timeUntilNextUpdate = interval - timeSinceLastUpdate;
  3078. console.log(`
  3079. 当前时间: ${new Date(now).toLocaleString()}
  3080. 上次更新: ${new Date(lastUpdate).toLocaleString()}
  3081. 更新间隔: ${GLOBAL_CONFIG.TIME_INTERVAL} 分钟
  3082. 距离上次更新: ${Math.floor(timeSinceLastUpdate / 1000)}
  3083. 距离下次更新: ${Math.floor(timeUntilNextUpdate / 1000)}
  3084. `);
  3085. if (timeSinceLastUpdate >= interval) {
  3086. downloadAndApplyConfig();
  3087. GM_setValue('LAST_UPDATE_TIME', now);
  3088. }
  3089. }
  3090. function setTextfromTemplate(args){
  3091. const currentLanguage = GLOBAL_CONFIG.LANGUAGE || 'zh';
  3092. return LANGUAGE_TEMPLATES[currentLanguage][args] || args;
  3093. }
  3094. function setLanguage(language) {
  3095. if (!LANGUAGE_TEMPLATES[language]) {
  3096. console.error(`Language ${language} not found in templates`);
  3097. return;
  3098. }
  3099. GLOBAL_CONFIG.LANGUAGE = language;
  3100. saveGlobalConfig();
  3101. const templates = LANGUAGE_TEMPLATES[language];
  3102. document.querySelector('#domain-info-text').textContent = templates.panel_top_current_domain;
  3103. document.querySelector('#domain-info-value').textContent = getCurrentDomain();
  3104. document.querySelector('#page-type-text').textContent = templates.panel_top_page_type;
  3105. if(getPageType() === 'main'){
  3106. document.querySelector('#page-type-value').textContent = templates.panel_top_page_type_main;
  3107. }else if(getPageType() === 'sub'){
  3108. document.querySelector('#page-type-value').textContent = templates.panel_top_page_type_sub;
  3109. }else if(getPageType() === 'content'){
  3110. document.querySelector('#page-type-value').textContent = templates.panel_top_page_type_content;
  3111. }else{
  3112. document.querySelector('#page-type-value').textContent = templates.panel_top_page_type_unknown;
  3113. }
  3114. document.querySelector('.panel-settings-btn').title = templates.panel_top_settings_title;
  3115. document.querySelector('.panel-settings-btn').textContent = templates.panel_top_settings_button;
  3116. document.querySelector('#domain-enabled-text').textContent = templates.panel_top_enable_domain;
  3117. document.querySelectorAll('.config-section-toggle').forEach(toggle => {
  3118. const section = toggle.getAttribute('data-section');
  3119. const titleSpan = toggle.querySelector('span:first-child');
  3120. switch (section) {
  3121. case 'global':
  3122. titleSpan.textContent = templates.global_config_title;
  3123. break;
  3124. case 'keywords':
  3125. titleSpan.textContent = templates.keywords_config_title;
  3126. break;
  3127. case 'usernames':
  3128. titleSpan.textContent = templates.usernames_config_title;
  3129. break;
  3130. case 'url':
  3131. titleSpan.textContent = templates.url_patterns_title;
  3132. break;
  3133. case 'xpath':
  3134. titleSpan.textContent = templates.xpath_config_title;
  3135. break;
  3136. case 'sync':
  3137. titleSpan.textContent = templates.sync_config_title;
  3138. break;
  3139. }
  3140. });
  3141. const globalCheckboxes = document.querySelectorAll('.checkbox-row label');
  3142. globalCheckboxes[0].textContent = templates.global_config_keywords;
  3143. globalCheckboxes[1].textContent = templates.global_config_usernames;
  3144. globalCheckboxes[2].textContent = templates.global_config_share_keywords;
  3145. globalCheckboxes[3].textContent = templates.global_config_share_usernames;
  3146. document.querySelector('#global-url-input').placeholder = templates.global_config_linkimport_input_placeholder;
  3147. document.querySelector('#add-global-url').textContent = templates.global_config_add_global_url;
  3148. document.querySelector('#apply-global-apply').textContent = templates.global_config_apply_global_apply;
  3149. document.querySelectorAll('.array-editor').forEach(editor => {
  3150. const addItemInput = editor.querySelector('.array-editor-additem-input');
  3151. if (addItemInput) {
  3152. addItemInput.placeholder = templates.array_editor_add_item_input_placeholder;
  3153. }
  3154. const addItemInputRegex = editor.querySelector('.array-editor-additem-input-regex');
  3155. if (addItemInputRegex) {
  3156. addItemInputRegex.placeholder = templates.array_editor_add_item_input_placeholder_regex;
  3157. }
  3158. const searchInput = editor.querySelector('.array-editor-search-input');
  3159. if (searchInput) {
  3160. searchInput.placeholder = templates.array_editor_search_input_placeholder;
  3161. }
  3162. const linkImportInput = editor.querySelector('.array-editor-linkimport-input');
  3163. if (linkImportInput) {
  3164. linkImportInput.placeholder = templates.array_editor_linkimport_input_placeholder;
  3165. }
  3166. const list = editor.querySelector('.array-editor-list');
  3167. if (list) {
  3168. list.dataset.empty = templates.array_editor_list_empty_placeholder;
  3169. }
  3170. const keywords_config_keywords_list_title = editor.querySelector('.array-editor-keywords-list');
  3171. if (keywords_config_keywords_list_title) {
  3172. keywords_config_keywords_list_title.textContent = templates.keywords_config_keywords_list_title;
  3173. }
  3174. const keywords_config_keywords_regex_title = editor.querySelector('.array-editor-keywords-regex');
  3175. if (keywords_config_keywords_regex_title) {
  3176. keywords_config_keywords_regex_title.textContent = templates.keywords_config_keywords_regex_title;
  3177. }
  3178. const usernames_config_usernames_list_title = editor.querySelector('.array-editor-usernames-list');
  3179. if (usernames_config_usernames_list_title) {
  3180. usernames_config_usernames_list_title.textContent = templates.usernames_config_usernames_list_title;
  3181. }
  3182. const usernames_config_usernames_regex_title = editor.querySelector('.array-editor-usernames-regex');
  3183. if (usernames_config_usernames_regex_title) {
  3184. usernames_config_usernames_regex_title.textContent = templates.usernames_config_usernames_regex_title;
  3185. }
  3186. const url_patterns_main_page_url_patterns_title = editor.querySelector('.main-patterns-editor');
  3187. if (url_patterns_main_page_url_patterns_title) {
  3188. url_patterns_main_page_url_patterns_title.textContent = templates.url_patterns_main_page_url_patterns_title;
  3189. }
  3190. const url_patterns_sub_page_url_patterns_title = editor.querySelector('.sub-patterns-editor');
  3191. if (url_patterns_sub_page_url_patterns_title) {
  3192. url_patterns_sub_page_url_patterns_title.textContent = templates.url_patterns_sub_page_url_patterns_title;
  3193. }
  3194. const url_patterns_content_page_url_patterns_title = editor.querySelector('.content-patterns-editor');
  3195. if (url_patterns_content_page_url_patterns_title) {
  3196. url_patterns_content_page_url_patterns_title.textContent = templates.url_patterns_content_page_url_patterns_title;
  3197. }
  3198. const xpath_config_main_and_sub_page_keywords_title = editor.querySelector('.title-xpath-editor');
  3199. if (xpath_config_main_and_sub_page_keywords_title) {
  3200. xpath_config_main_and_sub_page_keywords_title.textContent = templates.xpath_config_main_and_sub_page_keywords_title;
  3201. }
  3202. const xpath_config_main_and_sub_page_usernames_title = editor.querySelector('.user-xpath-editor');
  3203. if (xpath_config_main_and_sub_page_usernames_title) {
  3204. xpath_config_main_and_sub_page_usernames_title.textContent = templates.xpath_config_main_and_sub_page_usernames_title;
  3205. }
  3206. const xpath_config_content_page_keywords_title = editor.querySelector('.content-title-xpath-editor');
  3207. if (xpath_config_content_page_keywords_title) {
  3208. xpath_config_content_page_keywords_title.textContent = templates.xpath_config_content_page_keywords_title;
  3209. }
  3210. const xpath_config_content_page_usernames_title = editor.querySelector('.content-user-xpath-editor');
  3211. if (xpath_config_content_page_usernames_title) {
  3212. xpath_config_content_page_usernames_title.textContent = templates.xpath_config_content_page_usernames_title;
  3213. }
  3214. const buttons = editor.querySelectorAll('.button-group-inline button');
  3215. buttons.forEach(button => {
  3216. if (button.className === 'array-editor-add-button') {
  3217. button.textContent = templates.array_editor_add_item;
  3218. } else if (button.className === 'array-editor-delete-all-button') {
  3219. button.textContent = templates.array_editor_clear_allitem;
  3220. } else if (button.className === 'array-editor-linkimport-button') {
  3221. button.textContent = templates.array_editor_linkimport_input_button;
  3222. } else if (button.className === 'array-editor-import-button') {
  3223. button.textContent = templates.array_editor_fileimport_input_button;
  3224. } else if (button.className === 'array-editor-export-button') {
  3225. button.textContent = templates.array_editor_export_button;
  3226. }
  3227. });
  3228. });
  3229. document.querySelector('#export-config').textContent = templates.panel_bottom_export_button;
  3230. document.querySelector('#import-config').textContent = templates.panel_bottom_import_button;
  3231. document.querySelector('#delete-domain-config').textContent = templates.panel_bottom_delete_button;
  3232. document.querySelector('#save-domain-config').textContent = templates.panel_bottom_save_button;
  3233. document.querySelector('#sync-server-url').placeholder = setTextfromTemplate('sync_config_server_url');
  3234. document.querySelector('#sync-user-key').placeholder = setTextfromTemplate('sync_config_user_key');
  3235. document.querySelector('#sync-apply').textContent = setTextfromTemplate('sync_config_apply');
  3236. document.querySelector('#sync-delete').textContent = setTextfromTemplate('sync_config_delete');
  3237.  
  3238. document.querySelector('#js-settings-title').textContent = setTextfromTemplate('settings_title');
  3239. document.querySelector('label[for="language-select"]').textContent = setTextfromTemplate('settings_language');
  3240. const expandModeLabel = document.querySelector('#expand-mode').previousElementSibling;
  3241. expandModeLabel.textContent = setTextfromTemplate('settings_expand_mode');
  3242. const expandModeOptions = document.querySelectorAll('#expand-mode option');
  3243. expandModeOptions[0].textContent = setTextfromTemplate('settings_expand_hover');
  3244. expandModeOptions[1].textContent = setTextfromTemplate('settings_expand_click');
  3245. const blockButtonLabel = document.querySelector('#show-block-button').previousElementSibling;
  3246. blockButtonLabel.textContent = setTextfromTemplate('settings_block_button_mode');
  3247. const blockButtonOptions = document.querySelectorAll('#show-block-button option');
  3248. blockButtonOptions[0].textContent = setTextfromTemplate('settings_block_hover');
  3249. blockButtonOptions[1].textContent = setTextfromTemplate('settings_block_always');
  3250. const horizontalPositionLabel = document.querySelector('#position-offset').previousElementSibling;
  3251. horizontalPositionLabel.textContent = setTextfromTemplate('settings_horizontal_position');
  3252. const collapsedWidthLabel = document.querySelector('#collapsed-width').previousElementSibling;
  3253. collapsedWidthLabel.textContent = setTextfromTemplate('settings_collapsed_width');
  3254. const expandedWidthLabel = document.querySelector('#expanded-width').previousElementSibling;
  3255. expandedWidthLabel.textContent = setTextfromTemplate('settings_expanded_width');
  3256. document.querySelector('#settings-cancel').textContent = setTextfromTemplate('settings_cancel');
  3257. document.querySelector('#settings-save').textContent = setTextfromTemplate('settings_save');
  3258. }
  3259. function gmFetch(url, options = {}) {
  3260. return new Promise((resolve, reject) => {
  3261. GM_xmlhttpRequest({
  3262. url,
  3263. method: options.method || 'GET',
  3264. headers: options.headers || {},
  3265. data: options.body,
  3266. responseType: 'json',
  3267. onload: function(response) {
  3268. resolve({
  3269. ok: response.status >= 200 && response.status < 300,
  3270. status: response.status,
  3271. statusText: response.statusText,
  3272. json: () => Promise.resolve(response.response),
  3273. text: () => Promise.resolve(response.responseText)
  3274. });
  3275. },
  3276. onerror: function(error) {
  3277. reject(new Error('Network error'));
  3278. }
  3279. });
  3280. });
  3281. }
  3282. function checkSyncInput(){
  3283. const sync_server_url = document.getElementById('sync-server-url').value;
  3284. const sync_user_key = document.getElementById('sync-user-key').value;
  3285. if(!sync_server_url || !sync_user_key){
  3286. updateSyncStatus(setTextfromTemplate('sync_panel_status_input_error'), 'error');
  3287. return false;
  3288. }
  3289. GLOBAL_CONFIG.SYNC_CONFIG.server_url = sync_server_url;
  3290. GLOBAL_CONFIG.SYNC_CONFIG.user_key = sync_user_key;
  3291. saveGlobalConfig();
  3292. return true;
  3293. }
  3294. async function getConfigFromServer() {
  3295. const response = await gmFetch(`${GLOBAL_CONFIG.SYNC_CONFIG.server_url}/config`, {
  3296. method: 'GET',
  3297. headers: {
  3298. 'X-API-Key': GLOBAL_CONFIG.SYNC_CONFIG.user_key
  3299. }
  3300. });
  3301. if (!response.ok) {
  3302. throw new Error(await response.text());
  3303. }
  3304. return await response.json();
  3305. }
  3306. async function handleSyncInput() {
  3307. let sync_server_url = document.querySelector('#sync-server-url').value
  3308. if(sync_server_url.startsWith('http')){
  3309. sync_server_url = sync_server_url.replace(/^https?/, 'wss');
  3310. }else{
  3311. sync_server_url = 'wss://' + sync_server_url;
  3312. }
  3313. const sync_user_key = document.querySelector('#sync-user-key').value;
  3314. if(!sync_server_url || !sync_user_key){
  3315. updateSyncStatus(setTextfromTemplate('sync_panel_status_input_error'), 'error');
  3316. return false;
  3317. }
  3318. const response = await testConnection(sync_server_url , sync_user_key);
  3319. if (response.success) {
  3320. GLOBAL_CONFIG.SYNC_CONFIG.server_url = sync_server_url;
  3321. GLOBAL_CONFIG.SYNC_CONFIG.user_key = sync_user_key;
  3322. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_success'), 'success');
  3323. initWebSocket();
  3324. return true;
  3325. } else {
  3326. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_failed') + response.message, 'error');
  3327. return false;
  3328. }
  3329. }
  3330. async function deleteCloudConfig() {
  3331. try {
  3332. if (!confirm(setTextfromTemplate('sync_panel_status_delete_confirm'))) {
  3333. return;
  3334. }
  3335. if (!wsConnection || wsConnection.readyState !== WebSocket.OPEN) {
  3336. initWebSocket();
  3337. if(wsConnection.readyState !== WebSocket.OPEN){
  3338. throw new Error(setTextfromTemplate('sync_panel_status_connect_error'));
  3339. }
  3340. }
  3341. wsConnection.send(JSON.stringify({
  3342. type: 'delete',
  3343. userKey: GLOBAL_CONFIG.SYNC_CONFIG.user_key
  3344. }));
  3345. const originalOnMessage = wsConnection.onmessage;
  3346. wsConnection.onmessage = (event) => {
  3347. const data = JSON.parse(event.data);
  3348. if (data.type === 'delete') {
  3349. if (data.success) {
  3350. updateSyncStatus(setTextfromTemplate('sync_panel_status_delete_success'), 'success');
  3351. } else {
  3352. updateSyncStatus(setTextfromTemplate('sync_panel_status_delete_failed') + (data.message || ''), 'error');
  3353. }
  3354. wsConnection.onmessage = originalOnMessage;
  3355. } else {
  3356. originalOnMessage(event);
  3357. }
  3358. };
  3359. } catch (error) {
  3360. updateSyncStatus(setTextfromTemplate('sync_panel_status_delete_failed') + error.message, 'error');
  3361. }
  3362. }
  3363. function updateSyncStatus(message, type = 'info') {
  3364. const statusDiv = document.getElementById('sync-status');
  3365. statusDiv.textContent = message;
  3366. statusDiv.className = `sync-status ${type}`;
  3367. switch(type) {
  3368. case 'success':
  3369. statusDiv.style.color = '#4caf50';
  3370. break;
  3371. case 'error':
  3372. statusDiv.style.color = '#f44336';
  3373. break;
  3374. case 'warning':
  3375. statusDiv.style.color = '#ff9800';
  3376. break;
  3377. case 'info':
  3378. default:
  3379. statusDiv.style.color = '#2196f3';
  3380. break;
  3381. }
  3382. }
  3383. function initWebSocket() {
  3384. closeWebSocket();
  3385. const wsUrl = GLOBAL_CONFIG.SYNC_CONFIG.server_url.startsWith('wss') ?
  3386. GLOBAL_CONFIG.SYNC_CONFIG.server_url :
  3387. GLOBAL_CONFIG.SYNC_CONFIG.server_url.startsWith('http') ?
  3388. GLOBAL_CONFIG.SYNC_CONFIG.server_url.replace(/^https?/, 'wss') :
  3389. 'wss://' + GLOBAL_CONFIG.SYNC_CONFIG.server_url;
  3390. wsConnection = new WebSocket(`${wsUrl}/ws/config/${GLOBAL_CONFIG.SYNC_CONFIG.user_key}`);
  3391. const isFirstSync = GM_getValue('isFirstSync_' + GLOBAL_CONFIG.SYNC_CONFIG.user_key+'_'+GLOBAL_CONFIG.SYNC_CONFIG.server_url, true);
  3392. wsConnection.onopen = () => {
  3393. if(isFirstSync){
  3394. GM_setValue('isFirstSync_' + GLOBAL_CONFIG.SYNC_CONFIG.user_key+'_'+GLOBAL_CONFIG.SYNC_CONFIG.server_url, false);
  3395. GLOBAL_CONFIG.SYNC_CONFIG.lastSyncTime = Date.now();
  3396. wsConnection.send(JSON.stringify({
  3397. type: 'firstSync',
  3398. globalConfig: GLOBAL_CONFIG,
  3399. userConfig: userConfig
  3400. }));
  3401. }else{
  3402. wsConnection.send(JSON.stringify({
  3403. type: 'update',
  3404. globalConfig: GLOBAL_CONFIG,
  3405. userConfig: userConfig
  3406. }));
  3407. }
  3408. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_server_success'), 'success');
  3409. };
  3410. wsConnection.onmessage = (event) => {
  3411. const data = JSON.parse(event.data);
  3412. switch(data.type) {
  3413. case 'firstSync':
  3414. if(data.message === 'firstSync_success'){
  3415. updateSyncStatus(setTextfromTemplate('sync_panel_status_client_to_server_success'), 'success');
  3416. }
  3417. break;
  3418. case 'update':
  3419. if(data){
  3420. saveConfig(data,false,true);
  3421. GLOBAL_CONFIG.SYNC_CONFIG.lastSyncTime = Date.now();
  3422. if(data.message === 'config_updated'){
  3423. updateSyncStatus(setTextfromTemplate('sync_panel_status_config_updated'), 'success');
  3424. }
  3425. }
  3426. break;
  3427. case 'configConflict':
  3428. const useCloud = confirm(
  3429. setTextfromTemplate('sync_panel_status_config_conflict_1') + '\n\n' +
  3430. setTextfromTemplate('sync_panel_status_config_conflict_2') + data.cloudTime + '\n' +
  3431. setTextfromTemplate('sync_panel_status_config_conflict_3') + data.localTime + '\n\n' +
  3432. setTextfromTemplate('sync_panel_status_config_conflict_4') + '\n\n' +
  3433. (data.cloudTime > data.localTime ? setTextfromTemplate('sync_panel_status_config_conflict_cloud_newer') : setTextfromTemplate('sync_panel_status_config_conflict_local_newer'))
  3434. );
  3435. wsConnection.send(JSON.stringify({
  3436. type: 'resolveConflict',
  3437. choice: useCloud ? 'useCloud' : 'useLocal'
  3438. }));
  3439. break;
  3440. case 'delete':
  3441. if (data.success) {
  3442. updateSyncStatus(setTextfromTemplate('sync_panel_status_delete_success'), 'success');
  3443. } else {
  3444. updateSyncStatus(setTextfromTemplate('sync_panel_status_delete_failed') + (data.message || ''), 'error');
  3445. }
  3446. break;
  3447. }
  3448. };
  3449. wsConnection.onclose = () => {
  3450. updateSyncStatus(setTextfromTemplate('sync_panel_status_disconnect'), 'warning');
  3451. };
  3452. wsConnection.onerror = (error) => {
  3453. console.error('WebSocket 错误:', error);
  3454. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_error'), 'error');
  3455. };
  3456. }
  3457. function closeWebSocket() {
  3458. if (wsConnection) {
  3459. wsConnection.close();
  3460. wsConnection = null;
  3461. }
  3462. }
  3463. async function testConnection(serverUrl, userKey) {
  3464. try {
  3465. const wsUrl = serverUrl + '/ws/config/' + userKey;
  3466. const ws = new WebSocket(wsUrl);
  3467. return new Promise((resolve) => {
  3468. ws.onopen = () => {
  3469. ws.close();
  3470. resolve({ success: true });
  3471. };
  3472. ws.onerror = (error) => {
  3473. ws.close();
  3474. resolve({
  3475. success: false,
  3476. message: 'WebSocket连接失败'
  3477. });
  3478. };
  3479. });
  3480. } catch (error) {
  3481. return {
  3482. success: false,
  3483. message: error.message
  3484. };
  3485. }
  3486. }
  3487. async function initCloudSync() {
  3488. if(GLOBAL_CONFIG.SYNC_CONFIG.server_url && GLOBAL_CONFIG.SYNC_CONFIG.user_key){
  3489. try {
  3490. const response = await testConnection(GLOBAL_CONFIG.SYNC_CONFIG.server_url , GLOBAL_CONFIG.SYNC_CONFIG.user_key);
  3491. if (response.success) {
  3492. initWebSocket();
  3493. } else {
  3494. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_failed') + response.message, 'error');
  3495. }
  3496. } catch (error) {
  3497. updateSyncStatus(setTextfromTemplate('sync_panel_status_connect_error') + error.message, 'error');
  3498. }
  3499. if (GLOBAL_CONFIG.SYNC_CONFIG.server_url && GLOBAL_CONFIG.SYNC_CONFIG.user_key) {
  3500. initWebSocket();
  3501. }
  3502. }
  3503. }
  3504. async function pushConfigUpdate() {
  3505. try {
  3506. if (wsConnection && wsConnection.readyState === WebSocket.OPEN) {
  3507. GLOBAL_CONFIG.SYNC_CONFIG.lastSyncTime = Date.now();
  3508. const configData = {
  3509. type: 'update',
  3510. globalConfig: GLOBAL_CONFIG,
  3511. userConfig: userConfig
  3512. };
  3513. await wsConnection.send(JSON.stringify(configData));
  3514. } else {
  3515. console.warn('WebSocket未连接');
  3516. initWebSocket();
  3517. }
  3518. } catch (error) {
  3519. console.error('同步失败:', error);
  3520. updateSyncStatus(setTextfromTemplate('sync_panel_status_sync_failed') + error.message, 'error');
  3521. }
  3522. }
  3523. function downloadAndApplyConfig(){
  3524. const urls = GLOBAL_CONFIG.GLOBAL_CONFIG_URL;
  3525. urls.forEach(async (url) => {
  3526. try {
  3527. const response = await fetch(url);
  3528. if (!response.ok) {
  3529. console.error(`从 ${url} 下载配置失败:`, response.statusText);
  3530. return;
  3531. }
  3532. const configData = await response.json();
  3533. saveConfig(configData, true);
  3534. } catch (error) {
  3535. console.error(`处理URL ${url} 时出错:`, error);
  3536. }
  3537. });
  3538. GM_setValue('LAST_UPDATE_TIME', Date.now());
  3539. }
  3540. if (document.readyState === 'loading') {
  3541. document.addEventListener('DOMContentLoaded', function() {
  3542. debouncedHandleElements();
  3543. listenUrlChange(debouncedHandleElements);
  3544. setInterval(checkUpdateTime, 60000);
  3545. });
  3546. } else {
  3547. debouncedHandleElements();
  3548. listenUrlChange(debouncedHandleElements);
  3549. setInterval(checkUpdateTime, 60000);
  3550. }
  3551. function saveConfig(args_config = null,isglobalurl = false,isPushConfigUpdate = false) {
  3552. const panel = document.getElementById('forum-filter-panel');
  3553. if (!panel) return;
  3554. if (args_config) {
  3555. if(args_config.globalConfig){
  3556. for (const key in args_config.globalConfig) {
  3557. if (args_config.globalConfig.hasOwnProperty(key)) {
  3558. if (isglobalurl && key === 'GLOBAL_CONFIG_URL') continue;
  3559. GLOBAL_CONFIG[key] = args_config.globalConfig[key];
  3560. }
  3561. }
  3562. saveGlobalConfig();
  3563. }
  3564. if(args_config.userConfig && args_config.userConfig.length > 0){
  3565. args_config.userConfig.forEach(config => {
  3566. const existingConfig = getDomainConfig(config.domain);
  3567. if (existingConfig) {
  3568. if(isPushConfigUpdate){
  3569. updateDomainConfigOverride(config.domain, config);
  3570. }else{
  3571. updateDomainConfig(config.domain, config);
  3572. }
  3573. } else {
  3574. addDomainConfig(config);
  3575. }
  3576. });
  3577. }
  3578. if (GLOBAL_CONFIG.SYNC_CONFIG.server_url && GLOBAL_CONFIG.SYNC_CONFIG.user_key) {
  3579. if(!isPushConfigUpdate){
  3580. pushConfigUpdate()
  3581. }
  3582. }
  3583. saveUserConfig(userConfig);
  3584. debouncedHandleElements();
  3585. updatePanelContent();
  3586. return;
  3587. }
  3588. const currentConfig = getDomainConfig(getCurrentDomain()) || SAMPLE_TEMPLATE;
  3589. const config = {
  3590. domain: getCurrentDomain(),
  3591. enabled: panel.querySelector('#domain-enabled').checked,
  3592. shareKeywordsAcrossPages: panel.querySelector('#share-keywords').checked,
  3593. shareUsernamesAcrossPages: panel.querySelector('#share-usernames').checked,
  3594. mainPageUrlPatterns: currentConfig.mainPageUrlPatterns || [],
  3595. subPageUrlPatterns: currentConfig.subPageUrlPatterns || [],
  3596. contentPageUrlPatterns: currentConfig.contentPageUrlPatterns || [],
  3597. mainAndSubPageKeywords: {
  3598. ...currentConfig.mainAndSubPageKeywords,
  3599. xpath: currentConfig.mainAndSubPageKeywords?.xpath || []
  3600. },
  3601. mainAndSubPageUserKeywords: {
  3602. ...currentConfig.mainAndSubPageUserKeywords,
  3603. xpath: currentConfig.mainAndSubPageUserKeywords?.xpath || []
  3604. },
  3605. contentPageKeywords: {
  3606. ...currentConfig.contentPageKeywords,
  3607. xpath: currentConfig.contentPageKeywords?.xpath || []
  3608. },
  3609. contentPageUserKeywords: {
  3610. ...currentConfig.contentPageUserKeywords,
  3611. xpath: currentConfig.contentPageUserKeywords?.xpath || []
  3612. }
  3613. };
  3614. saveGlobalConfig();
  3615. const existingIndex = userConfig.findIndex(c => c.domain === getCurrentDomain());
  3616. if (existingIndex !== -1) {
  3617. userConfig[existingIndex] = config;
  3618. } else {
  3619. userConfig.push(config);
  3620. }
  3621. if (GLOBAL_CONFIG.SYNC_CONFIG.server_url && GLOBAL_CONFIG.SYNC_CONFIG.user_key) {
  3622. if(!isPushConfigUpdate){
  3623. pushConfigUpdate()
  3624. }
  3625. }
  3626. saveUserConfig(userConfig);
  3627. debouncedHandleElements();
  3628. updatePanelContent();
  3629. return config;
  3630. }
  3631. })();