// ==UserScript== // @name Advanced Search for X (Twitter) 🔍 // @name:ja Advanced Search for X(Twitter)🔍 // @name:en Advanced Search for X (Twitter) 🔍 // @name:zh-CN Advanced Search for X(Twitter)🔍 // @name:zh-TW Advanced Search for X(Twitter)🔍 // @name:ko Advanced Search for X (Twitter) 🔍 // @name:fr Advanced Search for X (Twitter) 🔍 // @name:es Advanced Search for X (Twitter) 🔍 // @name:de Advanced Search for X (Twitter) 🔍 // @name:pt-BR Advanced Search for X (Twitter) 🔍 // @name:ru Advanced Search for X (Twitter) 🔍 // @version 6.3.0 // @description Adds a floating modal for advanced search on X.com (Twitter). Syncs with search box and remembers position/display state. The top-right search icon is now draggable and its position persists. // @description:ja X.com(Twitter)に高度な検索機能を呼び出せるフローティング・モーダルを追加します。検索ボックスと双方向で同期し、位置や表示状態も記憶します。右上の検索アイコンはドラッグで移動でき、位置は保存されます。 // @description:en Adds a floating modal for advanced search on X.com (formerly Twitter). Syncs with search box and remembers position/display state. The top-right search icon is draggable with persistent position. // @description:zh-CN 为X.com(Twitter)添加高级搜索浮动模态框,支持与搜索框双向同步并记住位置与显示状态。右上角的搜索图标可拖动,并会记住位置。 // @description:zh-TW 為 X.com(Twitter)增加高級搜尋模態框,支援與搜尋框雙向同步並記住位置與顯示狀態。右上角搜尋圖示可拖曳,位置會被保存。 // @description:ko X.com(Twitter)에 고급 검색 모달을 추가합니다. 검색창과 양방향 동기화하며 위치와 표시 상태를 기억합니다. 우상단 검색 아이콘은 드래그 이동 및 위치 저장이 가능합니다. // @description:fr Ajoute une fenêtre modale de recherche avancée à X.com (Twitter), synchronisée avec la barre de recherche et mémorise de l’état d’affichage. L’icône de recherche en haut à droite est déplaçable. // @description:es Agrega un modal flotante de búsqueda avanzada en X.com (Twitter), sincronizado con la caja de búsqueda y con estado persistente. // @description:de Fügt X.com (Twitter) ein modales Fenster für erweiterte Suche hinzu, synchronisiert mit der Suchleiste und speichert Position/Zustand. Das Suchsymbol oben rechts ist per Drag & Drop verschiebbar und bleibt gespeichert. // @description:pt-BR Adiciona um modal de busca avançada flutuante no X.com (Twitter), sincronizado com a caixa de busca e com estado salvo. O ícone de busca no canto superior direito é arrastável com posição persistente. // @description:ru Добавляет модальное окно расширенного поиска на X.com (Twitter). Синхронизируется с поисковой строкой и запоминает состояние. Кнопку поиска в правом верхнем углу можно перетаскивать; её положение сохраняется. // @namespace https://github.com/koyasi777/advanced-search-for-x-twitter // @author koyasi777 // @match https://x.com/* // @match https://twitter.com/* // @exclude https://x.com/i/tweetdeck* // @exclude https://twitter.com/i/tweetdeck* // @icon https://raw.githubusercontent.com/koyasi777/advanced-search-for-x-twitter/refs/heads/main/extension/icons/icon-128.png // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_info // @run-at document-idle // @license MIT // @homepageURL https://github.com/koyasi777/advanced-search-for-x-twitter // @supportURL https://github.com/koyasi777/advanced-search-for-x-twitter/issues // @downloadURL none // ==/UserScript== const __X_ADV_SEARCH_MAIN_LOGIC__ = function() { 'use strict'; if (window.__X_ADV_SEARCH_INITED__) return; window.__X_ADV_SEARCH_INITED__ = true; const i18n = { translations: { 'en': { modalTitle: "Advanced Search", tooltipClose: "Close", labelAllWords: "All of these words", placeholderAllWords: "e.g., AI news", labelExactPhrase: "This exact phrase", placeholderExactPhrase: 'e.g., "ChatGPT 4o"', labelAnyWords: "Any of these words (OR)", placeholderAnyWords: "e.g., iPhone Android", labelNotWords: "None of these words (-)", placeholderNotWords: "e.g., -sale -ads", labelHashtag: "Hashtags (#)", placeholderHashtag: "e.g., #TechEvent", labelLang: "Language (lang:)", optLangDefault: "Any language", optLangJa: "Japanese (ja)", optLangEn: "English (en)", optLangId: "Indonesian (id)", optLangHi: "Hindi (hi)", optLangDe: "German (de)", optLangTr: "Turkish (tr)", optLangEs: "Spanish (es)", optLangPt: "Portuguese (pt)", optLangAr: "Arabic (ar)", optLangFr: "French (fr)", optLangKo: "Korean (ko)", optLangRu: "Russian (ru)", optLangZhHans: "Chinese Simplified (zh-cn)", optLangZhHant: "Chinese Traditional (zh-tw)", hrSeparator: " ", labelFilters: "Filters", labelVerified: "Verified accounts", labelLinks: "Links", labelImages: "Images", labelVideos: "Videos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Include", checkExclude: "Exclude", labelReplies: "Replies", optRepliesDefault: "Default (Show all)", optRepliesInclude: "Include replies", optRepliesOnly: "Replies only", optRepliesExclude: "Exclude replies", labelEngagement: "Engagement", placeholderMinReplies: "Min replies", placeholderMinLikes: "Min likes", placeholderMinRetweets: "Min reposts", labelDateRange: "Date range", labelDateShortcut: "Quick Range", optDate1Day: "Past 24h", optDate1Week: "Past week", optDate1Month: "Past month", optDate3Months: "Past 3 months", optDate6Months: "Past 6 months", optDate1Year: "Past year", optDate2Years: "Past 2 years", optDate3Years: "Past 3 years", optDate5Years: "Past 5 years", optDateClear: "Clear dates", tooltipSince: "From this date", tooltipUntil: "Until this date", labelFromUser: "From these accounts (from:)", placeholderFromUser: "e.g., @X", labelToUser: "To these accounts (to:)", placeholderToUser: "e.g., @google", labelMentioning: "Mentioning these accounts (@)", placeholderMentioning: "e.g., @OpenAI", buttonClear: "Clear", buttonApply: "Search", tooltipTrigger: "Open Advanced Search", buttonOpen: "Open", tabSearch: "Search", tabHistory: "History", tabSaved: "Saved", buttonSave: "Save", buttonSaved: "Saved", secretMode: "Secret", secretOn: "Secret mode ON (No history)", secretOff: "Secret mode OFF", toastSaved: "Saved.", toastDeleted: "Deleted.", toastReordered: "Order updated.", emptyHistory: "No history yet.", emptySaved: "No saved searches. Add from the Save button at the bottom left of the Search tab.", run: "Run", delete: "Delete", updated: "Updated.", tooltipSecret: "Toggle Secret Mode (no history will be recorded)", historyClearAll: "Clear All", confirmClearHistory: "Clear all history?", labelAccountScope: "Accounts", optAccountAll: "All accounts", optAccountFollowing: "Accounts you follow", labelLocationScope: "Location", optLocationAll: "All locations", optLocationNearby: "Near you", chipFollowing: "Following", chipNearby: "Nearby", labelSearchTarget: "Search target", labelHitName: "Exclude matches only in display name", labelHitHandle: "Exclude matches only in username (@handle)", hintSearchTarget: "Hide posts that only match in name or handle (not in body).", hintName: "If a keyword appears only in the display name, hide it.", hintHandle: "If a keyword appears only in @username, hide it. Exception: when the query explicitly uses from:/to:/@ with the same word.", tabMute: "Mute", labelMuteWord: "Add mute word", placeholderMuteWord: "e.g., spoiler", labelCaseSensitive: "Case sensitive", labelWordBoundary: "Whole word", labelEnabled: "Enabled", labelEnableAll: "Enable all", buttonAdd: "Add", emptyMuted: "No muted words.", mutedListTitle: "Muted words", mutedListHeading: "Muted items", optMuteHidden: "Hidden", optMuteCollapsed: "Collapsed", placeholderFilterMute: "Filter muted words...", muteLabel: "Muted: ", buttonShow: "Show", muteHit: "Mute hits in body", buttonRemute: "Re-mute", buttonImport: "Import", buttonExport: "Export", /* Accounts tab */ tabAccounts: "Accounts", emptyAccounts: "No accounts yet. Open a profile and click the Add button to save it.", buttonAddAccount: "Add account", toastAccountAdded: "Account added.", toastAccountExists: "Already added.", buttonConfirm: "Confirm", /* Lists tab */ tabLists: "Lists", emptyLists: "No lists yet. Open a List and click the + button in the top-right to add it.", buttonAddList: "Add list", toastListAdded: "List added.", toastListExists: "Already added.", /* History tab */ placeholderSearchHistory: "Search history (query)", labelSortBy: "Sort by:", placeholderSearchSaved: "Search saved (query)", sortNewest: "Newest first", sortOldest: "Oldest first", sortNameAsc: "Query (A-Z)", sortNameDesc: "Query (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filter accounts (@, name)", placeholderFilterLists: "Filter lists (name, url)", buttonAddFolder: "+Folder", folderFilterAll: "ALL", folderFilterUnassigned: "Unassigned", folderRename: "Rename", folderRenameTitle: "Rename folder", folderDelete: "Delete", folderDeleteTitle: "Delete folder", promptNewFolder: "New folder name", confirmDeleteFolder: "Delete this folder and all items inside it? This cannot be undone.", optListsAll: "Lists", defaultSavedFolders: "Saved Searches", /* Favorites */ tabFavorites: "Favorites", emptyFavorites: "No favorite tweets yet. Click the ★ icon on tweets to save them.", optFavoritesAll: "All Favorites", toastFavorited: "Added to favorites.", toastUnfavorited: "Removed from favorites.", /* Settings */ settingsTitle: "Settings", settingsTitleGeneral: "General", settingsTitleFeatures: "Tab Visibility", settingsTitleData: "Data", buttonClose: "Close", labelUILang: "Interface language", optUILangAuto: "Auto", labelInitialTab: "Startup tab", optInitialTabLast: "Last opened (Default)", labelImportExport: "Import / Export", placeholderSettingsJSON: "Paste backup JSON here...", tooltipSettings: "Open settings", toastImported: "Imported.", alertInvalidJSON: "Invalid JSON file.", alertInvalidData: "Invalid data format.", alertInvalidApp: 'This file is not a valid backup for "Advanced Search for X".', toastExported: "Exported to file.", buttonReset: "Reset all data", confirmResetAll: "Reset all data? This cannot be undone.", toastReset: "All data has been reset.", buttonImportSuccess: "Imported successfully 👍️", /* Favorites Sort */ sortSavedNewest: "Saved date (Newest)", sortSavedOldest: "Saved date (Oldest)", sortPostedNewest: "Posted date (Newest)", sortPostedOldest: "Posted date (Oldest)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Uncategorized', FT_DROPDOWN_TITLE: 'Favorite Tags', FT_DROPDOWN_SETTINGS_TITLE: 'Favorite Tag Settings', FT_DROPDOWN_NEW_TAG: 'New tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Tag name', FT_DROPDOWN_NEW_TAG_ADD: 'Add', FT_FILTER_ALL: 'All', FT_SETTINGS_TITLE: 'Favorite Tag Settings', FT_SETTINGS_EMPTY_TAG_LIST: 'No tags yet. You can add one from "New tag".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Uncategorized', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'The name of "Uncategorized" cannot be changed.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Uncategorized" cannot be deleted.', FT_SETTINGS_CLOSE: 'Close', FT_SETTINGS_DELETE_BUTTON: 'Delete', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Display', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Tag label format', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Tag name only', FT_SETTINGS_DISPLAY_MODE_FULL: 'Full path', FT_CONFIRM_DELETE_TAG_MSG: 'Delete tag "{tagName}"?\nFavorites with this tag will become "Uncategorized".', FT_SETTINGS_BUTTON_TITLE: 'Favorite Tag Settings', }, 'ja': { modalTitle: "高度な検索", tooltipClose: "閉じる", labelAllWords: "すべての語句を含む", placeholderAllWords: "例: AI ニュース", labelExactPhrase: "この語句を完全に含む", placeholderExactPhrase: '例: "ChatGPT 4o"', labelAnyWords: "いずれかの語句を含む (OR)", placeholderAnyWords: "例: iPhone Android", labelNotWords: "含まない語句 (-)", placeholderNotWords: "例: -セール -広告", labelHashtag: "ハッシュタグ (#)", placeholderHashtag: "例: #技術書典", labelLang: "言語 (lang:)", optLangDefault: "指定しない", optLangJa: "日本語 (ja)", optLangEn: "英語 (en)", optLangId: "インドネシア語 (id)", optLangHi: "ヒンディー語 (hi)", optLangDe: "ドイツ語 (de)", optLangTr: "トルコ語 (tr)", optLangEs: "スペイン語 (es)", optLangPt: "ポルトガル語 (pt)", optLangAr: "アラビア語 (ar)", optLangFr: "フランス語 (fr)", optLangKo: "韓国語 (ko)", optLangRu: "ロシア語 (ru)", optLangZhHans: "中国語(簡体字)(zh-cn)", optLangZhHant: "中国語(繁体字)(zh-tw)", hrSeparator: " ", labelFilters: "フィルター", labelVerified: "認証済みアカウント", labelLinks: "リンク", labelImages: "画像", labelVideos: "動画", labelReposts: "リポスト", labelTimelineHashtags: "ハッシュタグ (#)", checkInclude: "含む", checkExclude: "含まない", labelReplies: "返信", optRepliesDefault: "指定しない", optRepliesInclude: "返信を含める", optRepliesOnly: "返信のみ", optRepliesExclude: "返信を除外", labelEngagement: "エンゲージメント", placeholderMinReplies: "最小返信数", placeholderMinLikes: "最小いいね数", placeholderMinRetweets: "最小リポスト数", labelDateRange: "期間指定", labelDateShortcut: "期間ショートカット", optDate1Day: "過去24時間", optDate1Week: "過去1週間", optDate1Month: "過去1ヶ月", optDate3Months: "過去3ヶ月", optDate6Months: "過去6ヶ月", optDate1Year: "過去1年", optDate2Years: "過去2年", optDate3Years: "過去3年", optDate5Years: "過去5年", optDateClear: "日付クリア", tooltipSince: "この日以降", tooltipUntil: "この日以前", labelFromUser: "このアカウントから (from:)", placeholderFromUser: "例: @X", labelToUser: "このアカウントへ (to:)", placeholderToUser: "例: @google", labelMentioning: "このアカウントへのメンション (@)", placeholderMentioning: "例: @OpenAI", buttonClear: "クリア", buttonApply: "検索実行", tooltipTrigger: "高度な検索を開く", buttonOpen: "開く", tabSearch: "検索", tabHistory: "履歴", tabSaved: "保存", buttonSave: "保存", buttonSaved: "保存済み", secretMode: "シークレット", secretOn: "シークレットモード ON(履歴は記録しません)", secretOff: "シークレットモード OFF", toastSaved: "保存しました。", toastDeleted: "削除しました。", toastReordered: "並び順を更新しました。", emptyHistory: "履歴はまだありません。", emptySaved: "保存済みの検索はありません。検索タブの左下の保存から追加してください。", run: "実行", delete: "削除", updated: "更新しました。", tooltipSecret: "シークレットモードを切り替え(履歴を記録しません)", historyClearAll: "すべて削除", confirmClearHistory: "履歴をすべて削除しますか?", labelAccountScope: "アカウント", optAccountAll: "すべてのアカウント", optAccountFollowing: "フォローしているアカウント", labelLocationScope: "場所", optLocationAll: "すべての場所", optLocationNearby: "近くの場所", chipFollowing: "フォロー中", chipNearby: "近く", labelSearchTarget: "検索対象", labelHitName: "表示名(名前)のみのヒットは除外", labelHitHandle: "ユーザー名(@)のみのヒットは除外", hintSearchTarget: "本文ではなく、名前/ユーザー名のみに一致した投稿を非表示にします。", hintName: "キーワードが表示名のみに含まれる場合は非表示にします。", hintHandle: "キーワードが @ユーザー名のみに含まれる場合は非表示にします。例外: 同じ語を from:/to:/@ で明示しているときは表示します。", tabMute: "ミュート", labelMuteWord: "ミュート語句の追加", placeholderMuteWord: "例: ネタバレ", labelCaseSensitive: "大文字小文字を区別", labelWordBoundary: "完全一致(単語)", labelEnabled: "有効", labelEnableAll: "すべて有効", buttonAdd: "追加", emptyMuted: "ミュート語句はまだありません。", mutedListTitle: "ミュート語句", mutedListHeading: "ミュート一覧", optMuteHidden: "非表示", optMuteCollapsed: "折りたたみ", placeholderFilterMute: "ミュートを検索...", muteLabel: "ミュート: ", buttonShow: "表示する", muteHit: "本文でのヒットをミュート", buttonRemute: "再ミュート", buttonImport: "インポート", buttonExport: "エクスポート", /* Accounts tab */ tabAccounts: "アカウント", emptyAccounts: "アカウントはまだありません。アカウントページの追加ボタンから追加してください。", buttonAddAccount: "アカウントを追加", toastAccountAdded: "アカウントを追加しました。", toastAccountExists: "すでに追加済みです。", buttonConfirm: "確認", /* Lists tab */ tabLists: "リスト", emptyLists: "リストはまだありません。リストを開き右上の+ボタンから追加してください。", buttonAddList: "リストを追加", toastListAdded: "リストを追加しました。", toastListExists: "すでに追加済みです。", /* History tab */ placeholderSearchHistory: "履歴を検索(クエリ)", labelSortBy: "並び順:", placeholderSearchSaved: "保存済みを検索(クエリ)", sortNewest: "新しい順", sortOldest: "古い順", sortNameAsc: "クエリ (昇順)", sortNameDesc: "クエリ (降順)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "アカウントを検索 (@, 名前)", placeholderFilterLists: "リストを検索 (名前, URL)", buttonAddFolder: "+フォルダー", folderFilterAll: "すべて", folderFilterUnassigned: "未分類", folderRename: "名前変更", folderRenameTitle: "フォルダー名を変更", folderDelete: "削除", folderDeleteTitle: "フォルダーを削除", promptNewFolder: "新しいフォルダー名", confirmDeleteFolder: "このフォルダーと中のすべてのアイテムを完全に削除しますか?この操作は元に戻せません。", optListsAll: "リスト", defaultSavedFolders: "保存済み検索", /* Favorites */ tabFavorites: "お気に入り", emptyFavorites: "お気に入りはまだありません。ツイートの★ボタンをクリックして保存できます。", optFavoritesAll: "すべてのお気に入り", toastFavorited: "お気に入りに追加しました。", toastUnfavorited: "お気に入りから削除しました。", /* Settings */ settingsTitle: "設定", settingsTitleGeneral: "一般設定", settingsTitleFeatures: "タブ表示設定", settingsTitleData: "データ管理", buttonClose: "閉じる", labelUILang: "UI 言語", optUILangAuto: "自動判定", labelInitialTab: "起動時に開くタブ", optInitialTabLast: "前回のタブ (デフォルト)", labelImportExport: "インポート / エクスポート", placeholderSettingsJSON: "ここにバックアップ JSON を貼り付けてください...", tooltipSettings: "設定を開く", toastImported: "インポートしました。", toastExported: "ファイルにエクスポートしました。", alertInvalidJSON: "無効なJSONファイルです。", alertInvalidData: "無効なデータ形式です。", alertInvalidApp: "このファイルは「Advanced Search for X」のバックアップデータではありません。", buttonReset: "すべて初期化", confirmResetAll: "すべてのデータを初期化しますか?この操作は元に戻せません。", toastReset: "すべてのデータを初期化しました。", buttonImportSuccess: "インポートに成功しました👍️", /* Favorites Sort */ sortSavedNewest: "追加日 (新しい順)", sortSavedOldest: "追加日 (古い順)", sortPostedNewest: "投稿日 (新しい順)", sortPostedOldest: "投稿日 (古い順)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: '未分類', FT_DROPDOWN_TITLE: 'お気に入りタグ', FT_DROPDOWN_SETTINGS_TITLE: 'お気に入りタグ設定', FT_DROPDOWN_NEW_TAG: '新しいタグ', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'タグ名', FT_DROPDOWN_NEW_TAG_ADD: '追加', FT_FILTER_ALL: 'すべて', FT_SETTINGS_TITLE: 'お気に入りタグ設定', FT_SETTINGS_EMPTY_TAG_LIST: 'タグはまだありません。「新しいタグ」から追加できます。', FT_SETTINGS_UNCATEGORIZED_NAME: '未分類', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: '未分類の名前は変更できません', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '未分類は削除できません', FT_SETTINGS_CLOSE: '閉じる', FT_SETTINGS_DELETE_BUTTON: '削除', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: '表示設定', FT_SETTINGS_DISPLAY_MODE_LABEL: 'タグの表示形式', FT_SETTINGS_DISPLAY_MODE_LEAF: '末尾のみ (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'フルパス (full)', FT_CONFIRM_DELETE_TAG_MSG: 'タグ「{tagName}」を削除しますか?\nこのタグが付いていたお気に入りは未分類になります。', FT_SETTINGS_BUTTON_TITLE: 'お気に入りタグ設定', }, 'zh-CN': { modalTitle: "高级搜索", tooltipClose: "关闭", labelAllWords: "包含全部词语", placeholderAllWords: "例如:AI 新闻", labelExactPhrase: "包含确切短语", placeholderExactPhrase: '例如:"ChatGPT 4o"', labelAnyWords: "包含任意词语 (OR)", placeholderAnyWords: "例如:iPhone Android", labelNotWords: "不包含词语 (-)", placeholderNotWords: "例如:-促销 -广告", labelHashtag: "话题标签 (#)", placeholderHashtag: "例如:#科技大会", labelLang: "语言 (lang:)", optLangDefault: "所有语言", optLangJa: "日语 (ja)", optLangEn: "英语 (en)", optLangId: "印尼语 (id)", optLangHi: "印地语 (hi)", optLangDe: "德语 (de)", optLangTr: "土耳其语 (tr)", optLangEs: "西班牙语 (es)", optLangPt: "葡萄牙语 (pt)", optLangAr: "阿拉伯语 (ar)", optLangFr: "法语 (fr)", optLangKo: "韩语 (ko)", optLangRu: "俄语 (ru)", optLangZhHans: "简体中文 (zh-cn)", optLangZhHant: "繁体中文 (zh-tw)", hrSeparator: " ", labelFilters: "筛选", labelVerified: "认证账号", labelLinks: "链接", labelImages: "图片", labelVideos: "视频", labelReposts: "转发", labelTimelineHashtags: "话题标签 (#)", checkInclude: "包含", checkExclude: "排除", labelReplies: "回复", optRepliesDefault: "默认 (显示全部)", optRepliesInclude: "包含回复", optRepliesOnly: "仅回复", optRepliesExclude: "排除回复", labelEngagement: "互动量", placeholderMinReplies: "最少回复", placeholderMinLikes: "最少喜欢", placeholderMinRetweets: "最少转发", labelDateRange: "日期范围", labelDateShortcut: "快速选择", optDate1Day: "过去 24 小时", optDate1Week: "过去 1 周", optDate1Month: "过去 1 个月", optDate3Months: "过去 3 个月", optDate6Months: "过去 6 个月", optDate1Year: "过去 1 年", optDate2Years: "过去 2 年", optDate3Years: "过去 3 年", optDate5Years: "过去 5 年", optDateClear: "清除日期", tooltipSince: "起始日期", tooltipUntil: "结束日期", labelFromUser: "来自这些账号 (from:)", placeholderFromUser: "例如:@X", labelToUser: "发送给这些账号 (to:)", placeholderToUser: "例如:@google", labelMentioning: "提及这些账号 (@)", placeholderMentioning: "例如:@OpenAI", buttonClear: "清除", buttonApply: "搜索", tooltipTrigger: "打开高级搜索", buttonOpen: "打开", tabSearch: "搜索", tabHistory: "历史", tabSaved: "已保存", buttonSave: "保存", buttonSaved: "已保存", secretMode: "无痕模式", secretOn: "无痕模式已开启 (不记录历史)", secretOff: "无痕模式已关闭", toastSaved: "已保存。", toastDeleted: "已删除。", toastReordered: "顺序已更新。", emptyHistory: "暂无历史记录。", emptySaved: "暂无保存的搜索。请在搜索标签页左下角点击保存按钮添加。", run: "运行", delete: "删除", updated: "已更新。", tooltipSecret: "切换无痕模式 (不记录搜索历史)", historyClearAll: "全部清除", confirmClearHistory: "确定要清除所有历史记录吗?", labelAccountScope: "账号范围", optAccountAll: "所有账号", optAccountFollowing: "关注的账号", labelLocationScope: "位置范围", optLocationAll: "所有位置", optLocationNearby: "附近", chipFollowing: "已关注", chipNearby: "附近", labelSearchTarget: "搜索目标", labelHitName: "排除仅在显示名称中的匹配", labelHitHandle: "排除仅在用户名 (@handle) 中的匹配", hintSearchTarget: "隐藏仅在名称或用户名中匹配(而非正文)的帖子。", hintName: "如果关键词仅出现在显示名称中,则隐藏。", hintHandle: "如果关键词仅出现在 @用户名 中,则隐藏。例外:当查询中明确使用了 from:/to:/@ 时除外。", tabMute: "屏蔽", labelMuteWord: "添加屏蔽词", placeholderMuteWord: "例如:剧透", labelCaseSensitive: "区分大小写", labelWordBoundary: "全字匹配", labelEnabled: "已启用", labelEnableAll: "全部启用", buttonAdd: "添加", emptyMuted: "暂无屏蔽词。", mutedListTitle: "屏蔽词", mutedListHeading: "屏蔽列表", optMuteHidden: "隐藏", optMuteCollapsed: "折叠", placeholderFilterMute: "筛选屏蔽词...", muteLabel: "已屏蔽: ", buttonShow: "显示", muteHit: "屏蔽正文匹配项", buttonRemute: "重新屏蔽", buttonImport: "导入", buttonExport: "导出", /* Accounts tab */ tabAccounts: "账号", emptyAccounts: "暂无账号。请打开个人资料页并点击添加按钮进行保存。", buttonAddAccount: "添加账号", toastAccountAdded: "账号已添加。", toastAccountExists: "已存在。", buttonConfirm: "确认", /* Lists tab */ tabLists: "列表", emptyLists: "暂无列表。请打开列表页并点击右上角的 + 按钮添加。", buttonAddList: "添加列表", toastListAdded: "列表已添加。", toastListExists: "已存在。", /* History tab */ placeholderSearchHistory: "搜索历史 (查询词)", labelSortBy: "排序:", placeholderSearchSaved: "搜索已保存 (查询词)", sortNewest: "最新", sortOldest: "最旧", sortNameAsc: "查询词 (A-Z)", sortNameDesc: "查询词 (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "筛选账号 (@, 名称)", placeholderFilterLists: "筛选列表 (名称, URL)", buttonAddFolder: "+文件夹", folderFilterAll: "全部", folderFilterUnassigned: "未分类", folderRename: "重命名", folderRenameTitle: "重命名文件夹", folderDelete: "删除", folderDeleteTitle: "删除文件夹", promptNewFolder: "新文件夹名称", confirmDeleteFolder: "确定要删除此文件夹及其内部所有项目吗?此操作无法撤销。", optListsAll: "列表", defaultSavedFolders: "已保存搜索", /* Favorites */ tabFavorites: "收藏", emptyFavorites: "暂无收藏的帖子。点击帖子上的 ★ 按钮进行保存。", optFavoritesAll: "所有收藏", toastFavorited: "已添加到收藏。", toastUnfavorited: "已从收藏中移除。", /* Settings */ settingsTitle: "设置", settingsTitleGeneral: "通用", settingsTitleFeatures: "标签显示", settingsTitleData: "数据管理", buttonClose: "关闭", labelUILang: "界面语言", optUILangAuto: "自动", labelInitialTab: "启动时打开的标签页", optInitialTabLast: "上次打开的标签页 (默认)", labelImportExport: "导入 / 导出", placeholderSettingsJSON: "请在此粘贴备份 JSON...", tooltipSettings: "打开设置", toastImported: "已导入。", toastExported: "已导出到文件。", alertInvalidJSON: "无效的 JSON 文件。", alertInvalidData: "无效的数据格式。", alertInvalidApp: '此文件不是 "Advanced Search for X" 的备份数据。', buttonReset: "重置所有数据", confirmResetAll: "确定要重置所有数据吗?此操作无法撤销。", toastReset: "所有数据已重置。", buttonImportSuccess: "导入成功 👍️", /* Favorites Sort */ sortSavedNewest: "保存日期 (最新)", sortSavedOldest: "保存日期 (最旧)", sortPostedNewest: "发布日期 (最新)", sortPostedOldest: "发布日期 (最旧)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: '未分类', FT_DROPDOWN_TITLE: '收藏标签', FT_DROPDOWN_SETTINGS_TITLE: '收藏标签设置', FT_DROPDOWN_NEW_TAG: '新建标签', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: '标签名称', FT_DROPDOWN_NEW_TAG_ADD: '添加', FT_FILTER_ALL: '全部', FT_SETTINGS_TITLE: '收藏标签设置', FT_SETTINGS_EMPTY_TAG_LIST: '暂无标签。您可以从“新建标签”添加。', FT_SETTINGS_UNCATEGORIZED_NAME: '未分类', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: '“未分类”的名称无法更改。', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '“未分类”无法被删除。', FT_SETTINGS_CLOSE: '关闭', FT_SETTINGS_DELETE_BUTTON: '删除', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: '显示设置', FT_SETTINGS_DISPLAY_MODE_LABEL: '标签显示格式', FT_SETTINGS_DISPLAY_MODE_LEAF: '仅末级 (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: '完整路径 (full)', FT_CONFIRM_DELETE_TAG_MSG: '确定要删除标签“{tagName}”吗?\n带有此标签的收藏将变为“未分类”。', FT_SETTINGS_BUTTON_TITLE: '收藏标签设置', }, 'zh-TW': { modalTitle: "進階搜尋", tooltipClose: "關閉", labelAllWords: "包含所有詞語", placeholderAllWords: "例如:AI 新聞", labelExactPhrase: "包含精確詞組", placeholderExactPhrase: '例如:"ChatGPT 4o"', labelAnyWords: "包含任一詞語 (OR)", placeholderAnyWords: "例如:iPhone Android", labelNotWords: "排除詞語 (-)", placeholderNotWords: "例如:-促銷 -廣告", labelHashtag: "主題標籤 (#)", placeholderHashtag: "例如:#科技大會", labelLang: "語言 (lang:)", optLangDefault: "所有語言", optLangJa: "日語 (ja)", optLangEn: "英語 (en)", optLangId: "印尼語 (id)", optLangHi: "印地語 (hi)", optLangDe: "德語 (de)", optLangTr: "土耳其語 (tr)", optLangEs: "西班牙語 (es)", optLangPt: "葡萄牙語 (pt)", optLangAr: "阿拉伯語 (ar)", optLangFr: "法語 (fr)", optLangKo: "韓語 (ko)", optLangRu: "俄語 (ru)", optLangZhHans: "簡體中文 (zh-cn)", optLangZhHant: "繁體中文 (zh-tw)", hrSeparator: " ", labelFilters: "篩選", labelVerified: "已認證帳號", labelLinks: "連結", labelImages: "圖片", labelVideos: "影片", labelReposts: "轉發", labelTimelineHashtags: "主題標籤 (#)", checkInclude: "包含", checkExclude: "排除", labelReplies: "回覆", optRepliesDefault: "預設 (顯示全部)", optRepliesInclude: "包含回覆", optRepliesOnly: "僅限回覆", optRepliesExclude: "排除回覆", labelEngagement: "互動量", placeholderMinReplies: "最少回覆", placeholderMinLikes: "最少喜歡", placeholderMinRetweets: "最少轉發", labelDateRange: "日期範圍", labelDateShortcut: "快速範圍", optDate1Day: "過去 24 小時", optDate1Week: "過去 1 週", optDate1Month: "過去 1 個月", optDate3Months: "過去 3 個月", optDate6Months: "過去 6 個月", optDate1Year: "過去 1 年", optDate2Years: "過去 2 年", optDate3Years: "過去 3 年", optDate5Years: "過去 5 年", optDateClear: "清除日期", tooltipSince: "開始日期", tooltipUntil: "結束日期", labelFromUser: "來自這些帳號 (from:)", placeholderFromUser: "例如:@X", labelToUser: "發送給這些帳號 (to:)", placeholderToUser: "例如:@google", labelMentioning: "提及這些帳號 (@)", placeholderMentioning: "例如:@OpenAI", buttonClear: "清除", buttonApply: "搜尋", tooltipTrigger: "打開進階搜尋", buttonOpen: "打開", tabSearch: "搜尋", tabHistory: "紀錄", tabSaved: "已儲存", buttonSave: "儲存", buttonSaved: "已儲存", secretMode: "無痕模式", secretOn: "無痕模式已開啟 (不記錄歷史)", secretOff: "無痕模式已關閉", toastSaved: "已儲存。", toastDeleted: "已刪除。", toastReordered: "順序已更新。", emptyHistory: "暫無搜尋紀錄。", emptySaved: "暫無儲存的搜尋。請在搜尋分頁左下角點擊儲存按鈕添加。", run: "執行", delete: "刪除", updated: "已更新。", tooltipSecret: "切換無痕模式 (不記錄搜尋歷史)", historyClearAll: "全部清除", confirmClearHistory: "確定要清除所有搜尋紀錄嗎?", labelAccountScope: "帳號範圍", optAccountAll: "所有帳號", optAccountFollowing: "跟隨的帳號", labelLocationScope: "位置範圍", optLocationAll: "所有位置", optLocationNearby: "附近", chipFollowing: "正在跟隨", chipNearby: "附近", labelSearchTarget: "搜尋目標", labelHitName: "排除僅在顯示名稱中的相符項目", labelHitHandle: "排除僅在使用者名稱 (@handle) 中的相符項目", hintSearchTarget: "隱藏僅在名稱或使用者名稱中相符(而非內文)的貼文。", hintName: "如果關鍵字僅出現在顯示名稱中,則隱藏。", hintHandle: "如果關鍵字僅出現在 @使用者名稱 中,則隱藏。例外:當查詢中明確使用了 from:/to:/@ 時除外。", tabMute: "靜音", labelMuteWord: "新增靜音詞彙", placeholderMuteWord: "例如:劇透", labelCaseSensitive: "區分大小寫", labelWordBoundary: "全字匹配", labelEnabled: "已啟用", labelEnableAll: "全部啟用", buttonAdd: "新增", emptyMuted: "暫無靜音詞彙。", mutedListTitle: "靜音詞彙", mutedListHeading: "靜音清單", optMuteHidden: "隱藏", optMuteCollapsed: "收合", placeholderFilterMute: "篩選靜音詞彙...", muteLabel: "已靜音: ", buttonShow: "顯示", muteHit: "靜音內文相符項目", buttonRemute: "重新靜音", buttonImport: "匯入", buttonExport: "匯出", /* Accounts tab */ tabAccounts: "帳號", emptyAccounts: "暫無帳號。請打開個人檔案頁面並點擊新增按鈕進行儲存。", buttonAddAccount: "新增帳號", toastAccountAdded: "帳號已新增。", toastAccountExists: "已存在。", buttonConfirm: "確認", /* Lists tab */ tabLists: "列表", emptyLists: "暫無列表。請打開列表頁並點擊右上角的 + 按鈕新增。", buttonAddList: "新增列表", toastListAdded: "列表已新增。", toastListExists: "已存在。", /* History tab */ placeholderSearchHistory: "搜尋紀錄 (關鍵字)", labelSortBy: "排序:", placeholderSearchSaved: "搜尋已儲存 (關鍵字)", sortNewest: "最新", sortOldest: "最舊", sortNameAsc: "關鍵字 (A-Z)", sortNameDesc: "關鍵字 (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "篩選帳號 (@, 名稱)", placeholderFilterLists: "篩選列表 (名稱, URL)", buttonAddFolder: "+資料夾", folderFilterAll: "全部", folderFilterUnassigned: "未分類", folderRename: "重新命名", folderRenameTitle: "重新命名資料夾", folderDelete: "刪除", folderDeleteTitle: "刪除資料夾", promptNewFolder: "新資料夾名稱", confirmDeleteFolder: "確定要刪除此資料夾及其內部所有項目嗎?此操作無法復原。", optListsAll: "列表", defaultSavedFolders: "已儲存的搜尋", /* Favorites */ tabFavorites: "收藏", emptyFavorites: "暫無收藏的貼文。點擊貼文上的 ★ 按鈕進行儲存。", optFavoritesAll: "所有收藏", toastFavorited: "已加入收藏。", toastUnfavorited: "已從收藏中移除。", /* Settings */ settingsTitle: "設定", settingsTitleGeneral: "一般", settingsTitleFeatures: "標籤顯示", settingsTitleData: "資料管理", buttonClose: "關閉", labelUILang: "介面語言", optUILangAuto: "自動", labelInitialTab: "啟動時開啟的分頁", optInitialTabLast: "上次開啟的分頁 (預設)", labelImportExport: "匯入 / 匯出", placeholderSettingsJSON: "請在此貼上備份 JSON...", tooltipSettings: "打開設定", toastImported: "已匯入。", toastExported: "已匯出至檔案。", alertInvalidJSON: "無效的 JSON 檔案。", alertInvalidData: "無效的資料格式。", alertInvalidApp: '此檔案不是 "Advanced Search for X" 的備份資料。', buttonReset: "重設所有資料", confirmResetAll: "確定要重設所有資料嗎?此操作無法復原。", toastReset: "所有資料已重設。", buttonImportSuccess: "匯入成功 👍️", /* Favorites Sort */ sortSavedNewest: "儲存日期 (最新)", sortSavedOldest: "儲存日期 (最舊)", sortPostedNewest: "發布日期 (最新)", sortPostedOldest: "發布日期 (最舊)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: '未分類', FT_DROPDOWN_TITLE: '收藏標籤', FT_DROPDOWN_SETTINGS_TITLE: '收藏標籤設定', FT_DROPDOWN_NEW_TAG: '新建標籤', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: '標籤名稱', FT_DROPDOWN_NEW_TAG_ADD: '新增', FT_FILTER_ALL: '全部', FT_SETTINGS_TITLE: '收藏標籤設定', FT_SETTINGS_EMPTY_TAG_LIST: '暫無標籤。您可以從「新建標籤」新增。', FT_SETTINGS_UNCATEGORIZED_NAME: '未分類', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: '「未分類」的名稱無法更改。', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '「未分類」無法被刪除。', FT_SETTINGS_CLOSE: '關閉', FT_SETTINGS_DELETE_BUTTON: '刪除', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: '顯示設定', FT_SETTINGS_DISPLAY_MODE_LABEL: '標籤顯示格式', FT_SETTINGS_DISPLAY_MODE_LEAF: '僅末級 (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: '完整路徑 (full)', FT_CONFIRM_DELETE_TAG_MSG: '確定要刪除標籤「{tagName}」嗎?\n帶有此標籤的收藏將變為「未分類」。', FT_SETTINGS_BUTTON_TITLE: '收藏標籤設定', }, 'ko': { modalTitle: "고급 검색", tooltipClose: "닫기", labelAllWords: "다음 단어 모두 포함", placeholderAllWords: "예: AI 뉴스", labelExactPhrase: "정확하게 일치하는 문구", placeholderExactPhrase: '예: "ChatGPT 4o"', labelAnyWords: "다음 단어 중 하나라도 포함 (OR)", placeholderAnyWords: "예: iPhone Android", labelNotWords: "다음 단어 제외 (-)", placeholderNotWords: "예: -세일 -광고", labelHashtag: "해시태그 (#)", placeholderHashtag: "예: #개발자", labelLang: "언어 (lang:)", optLangDefault: "모든 언어", optLangJa: "일본어 (ja)", optLangEn: "영어 (en)", optLangId: "인도네시아어 (id)", optLangHi: "힌디어 (hi)", optLangDe: "독일어 (de)", optLangTr: "튀르키예어 (tr)", optLangEs: "스페인어 (es)", optLangPt: "포르투갈어 (pt)", optLangAr: "아랍어 (ar)", optLangFr: "프랑스어 (fr)", optLangKo: "한국어 (ko)", optLangRu: "러시아어 (ru)", optLangZhHans: "중국어 간체 (zh-cn)", optLangZhHant: "중국어 번체 (zh-tw)", hrSeparator: " ", labelFilters: "필터", labelVerified: "인증된 계정", labelLinks: "링크", labelImages: "이미지", labelVideos: "동영상", labelReposts: "재게시", labelTimelineHashtags: "해시태그 (#)", checkInclude: "포함", checkExclude: "제외", labelReplies: "답글", optRepliesDefault: "기본 (모두 표시)", optRepliesInclude: "답글 포함", optRepliesOnly: "답글만", optRepliesExclude: "답글 제외", labelEngagement: "참여", placeholderMinReplies: "최소 답글 수", placeholderMinLikes: "최소 마음에 들어요 수", placeholderMinRetweets: "최소 재게시 수", labelDateRange: "날짜 범위", labelDateShortcut: "빠른 범위 설정", optDate1Day: "지난 24시간", optDate1Week: "지난 1주", optDate1Month: "지난 1개월", optDate3Months: "지난 3개월", optDate6Months: "지난 6개월", optDate1Year: "지난 1년", optDate2Years: "지난 2년", optDate3Years: "지난 3년", optDate5Years: "지난 5년", optDateClear: "날짜 초기화", tooltipSince: "시작일", tooltipUntil: "종료일", labelFromUser: "다음 계정에서 (from:)", placeholderFromUser: "예: @X", labelToUser: "다음 계정으로 (to:)", placeholderToUser: "예: @google", labelMentioning: "다음 계정 언급 (@)", placeholderMentioning: "예: @OpenAI", buttonClear: "지우기", buttonApply: "검색", tooltipTrigger: "고급 검색 열기", buttonOpen: "열기", tabSearch: "검색", tabHistory: "기록", tabSaved: "저장됨", buttonSave: "저장", buttonSaved: "저장됨", secretMode: "시크릿 모드", secretOn: "시크릿 모드 켜짐 (기록되지 않음)", secretOff: "시크릿 모드 꺼짐", toastSaved: "저장되었습니다.", toastDeleted: "삭제되었습니다.", toastReordered: "순서가 업데이트되었습니다.", emptyHistory: "기록이 없습니다.", emptySaved: "저장된 검색이 없습니다. 검색 탭 왼쪽 하단의 저장 버튼으로 추가하세요.", run: "실행", delete: "삭제", updated: "업데이트됨.", tooltipSecret: "시크릿 모드 전환 (검색 기록을 저장하지 않음)", historyClearAll: "모두 지우기", confirmClearHistory: "모든 기록을 삭제하시겠습니까?", labelAccountScope: "계정 범위", optAccountAll: "모든 계정", optAccountFollowing: "팔로우 중인 계정", labelLocationScope: "위치 범위", optLocationAll: "모든 위치", optLocationNearby: "내 주변", chipFollowing: "팔로잉", chipNearby: "주변", labelSearchTarget: "검색 대상", labelHitName: "표시 이름(닉네임)만 일치하는 결과 제외", labelHitHandle: "사용자 아이디(@handle)만 일치하는 결과 제외", hintSearchTarget: "본문이 아닌 이름/아이디만 일치하는 게시물을 숨깁니다.", hintName: "키워드가 표시 이름에만 포함된 경우 숨깁니다.", hintHandle: "키워드가 @아이디에만 포함된 경우 숨깁니다. 예외: 검색어에 from:/to:/@ 등으로 명시한 경우는 표시합니다.", tabMute: "뮤트", labelMuteWord: "뮤트 단어 추가", placeholderMuteWord: "예: 스포일러", labelCaseSensitive: "대소문자 구분", labelWordBoundary: "단어 단위", labelEnabled: "활성화", labelEnableAll: "모두 활성화", buttonAdd: "추가", emptyMuted: "뮤트된 단어가 없습니다.", mutedListTitle: "뮤트 단어", mutedListHeading: "뮤트 목록", optMuteHidden: "숨기기", optMuteCollapsed: "접기", placeholderFilterMute: "뮤트 단어 검색...", muteLabel: "뮤트됨: ", buttonShow: "표시", muteHit: "본문 일치 항목 뮤트", buttonRemute: "다시 뮤트", buttonImport: "가져오기", buttonExport: "내보내기", /* Accounts tab */ tabAccounts: "계정", emptyAccounts: "저장된 계정이 없습니다. 프로필 페이지를 열고 추가 버튼을 눌러 저장하세요.", buttonAddAccount: "계정 추가", toastAccountAdded: "계정이 추가되었습니다.", toastAccountExists: "이미 추가되었습니다.", buttonConfirm: "확인", /* Lists tab */ tabLists: "리스트", emptyLists: "저장된 리스트가 없습니다. 리스트를 열고 우측 상단의 + 버튼을 눌러 추가하세요.", buttonAddList: "리스트 추가", toastListAdded: "리스트가 추가되었습니다.", toastListExists: "이미 추가되었습니다.", /* History tab */ placeholderSearchHistory: "기록 검색 (검색어)", labelSortBy: "정렬:", placeholderSearchSaved: "저장된 항목 검색 (검색어)", sortNewest: "최신순", sortOldest: "오래된순", sortNameAsc: "이름순 (ㄱ-ㅎ)", sortNameDesc: "이름순 (ㅎ-ㄱ)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "계정 필터링 (@, 이름)", placeholderFilterLists: "리스트 필터링 (이름, URL)", buttonAddFolder: "+폴더", folderFilterAll: "전체", folderFilterUnassigned: "미분류", folderRename: "이름 변경", folderRenameTitle: "폴더 이름 변경", folderDelete: "삭제", folderDeleteTitle: "폴더 삭제", promptNewFolder: "새 폴더 이름", confirmDeleteFolder: "이 폴더와 내부의 모든 항목을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", optListsAll: "리스트", defaultSavedFolders: "저장된 검색", /* Favorites */ tabFavorites: "즐겨찾기", emptyFavorites: "즐겨찾기에 추가한 게시물이 없습니다. 게시물의 ★ 버튼을 눌러 저장하세요.", optFavoritesAll: "모든 즐겨찾기", toastFavorited: "즐겨찾기에 추가했습니다.", toastUnfavorited: "즐겨찾기에서 삭제했습니다.", /* Settings */ settingsTitle: "설정", settingsTitleGeneral: "일반", settingsTitleFeatures: "탭 표시 설정", settingsTitleData: "데이터 관리", buttonClose: "닫기", labelUILang: "UI 언어", optUILangAuto: "자동", labelInitialTab: "시작 시 열 탭", optInitialTabLast: "마지막에 연 탭 (기본)", labelImportExport: "가져오기 / 내보내기", placeholderSettingsJSON: "백업 JSON을 여기에 붙여넣으세요...", tooltipSettings: "설정 열기", toastImported: "가져오기가 완료되었습니다.", alertInvalidJSON: "유효하지 않은 JSON 파일입니다.", alertInvalidData: "유효하지 않은 데이터 형식입니다.", alertInvalidApp: '"Advanced Search for X"의 백업 파일이 아닙니다.', toastExported: "파일로 내보냈습니다.", buttonReset: "모든 데이터 초기화", confirmResetAll: "모든 데이터를 초기화하시겠습니까? 이 작업은 되돌릴 수 없습니다.", toastReset: "모든 데이터가 초기화되었습니다.", buttonImportSuccess: "가져오기 성공 👍️", /* Favorites Sort */ sortSavedNewest: "저장일 (최신순)", sortSavedOldest: "저장일 (오래된순)", sortPostedNewest: "게시일 (최신순)", sortPostedOldest: "게시일 (오래된순)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: '미분류', FT_DROPDOWN_TITLE: '태그', FT_DROPDOWN_SETTINGS_TITLE: '태그 설정', FT_DROPDOWN_NEW_TAG: '새 태그', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: '태그 이름', FT_DROPDOWN_NEW_TAG_ADD: '추가', FT_FILTER_ALL: '전체', FT_SETTINGS_TITLE: '태그 설정', FT_SETTINGS_EMPTY_TAG_LIST: '태그가 없습니다. "새 태그"에서 추가할 수 있습니다.', FT_SETTINGS_UNCATEGORIZED_NAME: '미분류', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: '"미분류" 이름은 변경할 수 없습니다.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"미분류"는 삭제할 수 없습니다.', FT_SETTINGS_CLOSE: '닫기', FT_SETTINGS_DELETE_BUTTON: '삭제', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: '표시', FT_SETTINGS_DISPLAY_MODE_LABEL: '태그 표시 형식', FT_SETTINGS_DISPLAY_MODE_LEAF: '말단만 (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: '전체 경로 (full)', FT_CONFIRM_DELETE_TAG_MSG: '태그 "{tagName}"을(를) 삭제하시겠습니까?\n이 태그가 지정된 항목은 "미분류"가 됩니다.', FT_SETTINGS_BUTTON_TITLE: '태그 설정', }, 'fr': { modalTitle: "Recherche avancée", tooltipClose: "Fermer", labelAllWords: "Tous ces mots", placeholderAllWords: "ex: AI actualités", labelExactPhrase: "Cette phrase exacte", placeholderExactPhrase: 'ex: "ChatGPT 4o"', labelAnyWords: "L'un de ces mots (OR)", placeholderAnyWords: "ex: iPhone Android", labelNotWords: "Aucun de ces mots (-)", placeholderNotWords: "ex: -soldes -pub", labelHashtag: "Hashtags (#)", placeholderHashtag: "ex: #Paris2024", labelLang: "Langue (lang:)", optLangDefault: "Toutes les langues", optLangJa: "Japonais (ja)", optLangEn: "Anglais (en)", optLangId: "Indonésien (id)", optLangHi: "Hindi (hi)", optLangDe: "Allemand (de)", optLangTr: "Turc (tr)", optLangEs: "Espagnol (es)", optLangPt: "Portugais (pt)", optLangAr: "Arabe (ar)", optLangFr: "Français (fr)", optLangKo: "Coréen (ko)", optLangRu: "Russe (ru)", optLangZhHans: "Chinois simplifié (zh-cn)", optLangZhHant: "Chinois traditionnel (zh-tw)", hrSeparator: " ", labelFilters: "Filtres", labelVerified: "Comptes certifiés", labelLinks: "Liens", labelImages: "Images", labelVideos: "Vidéos", labelReposts: "Republications", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Inclure", checkExclude: "Exclure", labelReplies: "Réponses", optRepliesDefault: "Par défaut (Tout)", optRepliesInclude: "Inclure les réponses", optRepliesOnly: "Réponses uniquement", optRepliesExclude: "Exclure les réponses", labelEngagement: "Engagement", placeholderMinReplies: "Min réponses", placeholderMinLikes: "Min J'aime", placeholderMinRetweets: "Min republications", labelDateRange: "Période", labelDateShortcut: "Plage rapide", optDate1Day: "Dernières 24h", optDate1Week: "Semaine dernière", optDate1Month: "Mois dernier", optDate3Months: "3 derniers mois", optDate6Months: "6 derniers mois", optDate1Year: "Année dernière", optDate2Years: "2 dernières années", optDate3Years: "3 dernières années", optDate5Years: "5 dernières années", optDateClear: "Effacer les dates", tooltipSince: "Depuis cette date", tooltipUntil: "Jusqu'à cette date", labelFromUser: "De ces comptes (from:)", placeholderFromUser: "ex: @X", labelToUser: "À ces comptes (to:)", placeholderToUser: "ex: @google", labelMentioning: "Mentionnant ces comptes (@)", placeholderMentioning: "ex: @OpenAI", buttonClear: "Effacer", buttonApply: "Rechercher", tooltipTrigger: "Ouvrir la recherche avancée", buttonOpen: "Ouvrir", tabSearch: "Recherche", tabHistory: "Historique", tabSaved: "Enregistré", buttonSave: "Enregistrer", buttonSaved: "Enregistré", secretMode: "Mode privé", secretOn: "Mode privé activé (Pas d'historique)", secretOff: "Mode privé désactivé", toastSaved: "Enregistré.", toastDeleted: "Supprimé.", toastReordered: "Ordre mis à jour.", emptyHistory: "Aucun historique.", emptySaved: "Aucune recherche enregistrée. Ajoutez-en via le bouton Enregistrer en bas à gauche de l'onglet Recherche.", run: "Lancer", delete: "Supprimer", updated: "Mis à jour.", tooltipSecret: "Basculer le mode privé (aucun historique ne sera enregistré)", historyClearAll: "Tout effacer", confirmClearHistory: "Effacer tout l'historique ?", labelAccountScope: "Comptes", optAccountAll: "Tous les comptes", optAccountFollowing: "Comptes suivis", labelLocationScope: "Lieu", optLocationAll: "Tous les lieux", optLocationNearby: "Proche de vous", chipFollowing: "Abonnements", chipNearby: "À proximité", labelSearchTarget: "Cible de la recherche", labelHitName: "Exclure les résultats dans le nom d'affichage", labelHitHandle: "Exclure les résultats dans le nom d'utilisateur (@)", hintSearchTarget: "Masquer les posts qui ne correspondent que par le nom ou l'identifiant (pas dans le texte).", hintName: "Si un mot-clé n'apparaît que dans le nom d'affichage, le masquer.", hintHandle: "Si un mot-clé n'apparaît que dans le @nom_utilisateur, le masquer. Exception : si la requête utilise explicitement from:/to:/@.", tabMute: "Masquer", labelMuteWord: "Ajouter un mot masqué", placeholderMuteWord: "ex: spoiler", labelCaseSensitive: "Sensible à la casse", labelWordBoundary: "Mot entier", labelEnabled: "Activé", labelEnableAll: "Tout activer", buttonAdd: "Ajouter", emptyMuted: "Aucun mot masqué.", mutedListTitle: "Mots masqués", mutedListHeading: "Liste masquée", optMuteHidden: "Masqué", optMuteCollapsed: "Réduit", placeholderFilterMute: "Filtrer les mots masqués...", muteLabel: "Masqué : ", buttonShow: "Afficher", muteHit: "Masquer les résultats dans le texte", buttonRemute: "Masquer à nouveau", buttonImport: "Importer", buttonExport: "Exporter", /* Accounts tab */ tabAccounts: "Comptes", emptyAccounts: "Aucun compte. Ouvrez un profil et cliquez sur le bouton Ajouter pour l'enregistrer.", buttonAddAccount: "Ajouter compte", toastAccountAdded: "Compte ajouté.", toastAccountExists: "Déjà ajouté.", buttonConfirm: "Confirmer", /* Lists tab */ tabLists: "Listes", emptyLists: "Aucune liste. Ouvrez une liste et cliquez sur le bouton + en haut à droite pour l'ajouter.", buttonAddList: "Ajouter liste", toastListAdded: "Liste ajoutée.", toastListExists: "Déjà ajoutée.", /* History tab */ placeholderSearchHistory: "Historique (requête)", labelSortBy: "Trier par :", placeholderSearchSaved: "Recherches enregistrées (requête)", sortNewest: "Plus récent", sortOldest: "Plus ancien", sortNameAsc: "Nom (A-Z)", sortNameDesc: "Nom (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrer comptes (@, nom)", placeholderFilterLists: "Filtrer listes (nom, url)", buttonAddFolder: "+Dossier", folderFilterAll: "TOUT", folderFilterUnassigned: "Non classé", folderRename: "Renommer", folderRenameTitle: "Renommer le dossier", folderDelete: "Supprimer", folderDeleteTitle: "Supprimer le dossier", promptNewFolder: "Nom du dossier", confirmDeleteFolder: "Supprimer ce dossier et tout son contenu ? Cette action est irréversible.", optListsAll: "Listes", defaultSavedFolders: "Recherches enregistrées", /* Favorites */ tabFavorites: "Favoris", emptyFavorites: "Aucun favori. Cliquez sur l'icône ★ d'un tweet pour l'enregistrer.", optFavoritesAll: "Tous les favoris", toastFavorited: "Ajouté aux favoris.", toastUnfavorited: "Retiré des favoris.", /* Settings */ settingsTitle: "Paramètres", settingsTitleGeneral: "Général", settingsTitleFeatures: "Affichage onglets", settingsTitleData: "Données", buttonClose: "Fermer", labelUILang: "Langue de l'interface", optUILangAuto: "Auto", labelInitialTab: "Onglet au démarrage", optInitialTabLast: "Dernier ouvert (Défaut)", labelImportExport: "Importer / Exporter", placeholderSettingsJSON: "Collez le JSON de sauvegarde ici...", tooltipSettings: "Ouvrir les paramètres", toastImported: "Importé.", toastExported: "Exporté vers un fichier.", alertInvalidJSON: "Fichier JSON invalide.", alertInvalidData: "Format de données invalide.", alertInvalidApp: 'Ce fichier n\'est pas une sauvegarde valide pour "Advanced Search for X".', buttonReset: "Réinitialiser tout", confirmResetAll: "Tout réinitialiser ? Cette action est irréversible.", toastReset: "Toutes les données ont été réinitialisées.", buttonImportSuccess: "Importation réussie 👍️", /* Favorites Sort */ sortSavedNewest: "Date d'ajout (Récent)", sortSavedOldest: "Date d'ajout (Ancien)", sortPostedNewest: "Date de publication (Récent)", sortPostedOldest: "Date de publication (Ancien)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Non classé', FT_DROPDOWN_TITLE: 'Tags favoris', FT_DROPDOWN_SETTINGS_TITLE: 'Réglages des tags', FT_DROPDOWN_NEW_TAG: 'Nouveau tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nom du tag', FT_DROPDOWN_NEW_TAG_ADD: 'Ajouter', FT_FILTER_ALL: 'Tout', FT_SETTINGS_TITLE: 'Réglages des tags favoris', FT_SETTINGS_EMPTY_TAG_LIST: 'Aucun tag. Ajoutez-en un depuis "Nouveau tag".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Non classé', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'Le nom "Non classé" ne peut pas être modifié.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Non classé" ne peut pas être supprimé.', FT_SETTINGS_CLOSE: 'Fermer', FT_SETTINGS_DELETE_BUTTON: 'Supprimer', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Affichage', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Format du tag', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Libellé seul (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Chemin complet', FT_CONFIRM_DELETE_TAG_MSG: 'Supprimer le tag "{tagName}" ?\nLes favoris associés deviendront "Non classé".', FT_SETTINGS_BUTTON_TITLE: 'Réglages des tags', }, 'es': { modalTitle: "Búsqueda avanzada", tooltipClose: "Cerrar", labelAllWords: "Todas estas palabras", placeholderAllWords: "ej. AI noticias", labelExactPhrase: "Esta frase exacta", placeholderExactPhrase: 'ej. "ChatGPT 4o"', labelAnyWords: "Cualquiera de estas palabras (OR)", placeholderAnyWords: "ej. iPhone Android", labelNotWords: "Ninguna de estas palabras (-)", placeholderNotWords: "ej. -oferta -anuncio", labelHashtag: "Hashtags (#)", placeholderHashtag: "ej. #Tecnología", labelLang: "Idioma (lang:)", optLangDefault: "Cualquier idioma", optLangJa: "Japonés (ja)", optLangEn: "Inglés (en)", optLangId: "Indonesio (id)", optLangHi: "Hindi (hi)", optLangDe: "Alemán (de)", optLangTr: "Turco (tr)", optLangEs: "Español (es)", optLangPt: "Portugués (pt)", optLangAr: "Árabe (ar)", optLangFr: "Francés (fr)", optLangKo: "Coreano (ko)", optLangRu: "Ruso (ru)", optLangZhHans: "Chino simplificado (zh-cn)", optLangZhHant: "Chino tradicional (zh-tw)", hrSeparator: " ", labelFilters: "Filtros", labelVerified: "Cuentas verificadas", labelLinks: "Enlaces", labelImages: "Imágenes", labelVideos: "Vídeos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Incluir", checkExclude: "Excluir", labelReplies: "Respuestas", optRepliesDefault: "Por defecto (Todo)", optRepliesInclude: "Incluir respuestas", optRepliesOnly: "Solo respuestas", optRepliesExclude: "Excluir respuestas", labelEngagement: "Interacciones", placeholderMinReplies: "Mín. respuestas", placeholderMinLikes: "Mín. Me gusta", placeholderMinRetweets: "Mín. reposts", labelDateRange: "Rango de fechas", labelDateShortcut: "Rango rápido", optDate1Day: "Últimas 24 horas", optDate1Week: "Última semana", optDate1Month: "Último mes", optDate3Months: "Últimos 3 meses", optDate6Months: "Últimos 6 meses", optDate1Year: "Último año", optDate2Years: "Últimos 2 años", optDate3Years: "Últimos 3 años", optDate5Years: "Últimos 5 años", optDateClear: "Borrar fechas", tooltipSince: "Desde esta fecha", tooltipUntil: "Hasta esta fecha", labelFromUser: "De estas cuentas (from:)", placeholderFromUser: "ej. @X", labelToUser: "Para estas cuentas (to:)", placeholderToUser: "ej. @google", labelMentioning: "Mencionando a estas cuentas (@)", placeholderMentioning: "ej. @OpenAI", buttonClear: "Borrar", buttonApply: "Buscar", tooltipTrigger: "Abrir búsqueda avanzada", buttonOpen: "Abrir", tabSearch: "Búsqueda", tabHistory: "Historial", tabSaved: "Guardado", buttonSave: "Guardar", buttonSaved: "Guardado", secretMode: "Secreto", secretOn: "Modo secreto ACTIVADO (sin historial)", secretOff: "Modo secreto DESACTIVADO", toastSaved: "Guardado.", toastDeleted: "Eliminado.", toastReordered: "Orden actualizado.", emptyHistory: "Aún no hay historial.", emptySaved: "No hay búsquedas guardadas. Añade una desde el botón Guardar abajo a la izquierda en la pestaña Búsqueda.", run: "Ejecutar", delete: "Eliminar", updated: "Actualizado.", tooltipSecret: "Alternar modo secreto (no se guardará historial)", historyClearAll: "Borrar todo", confirmClearHistory: "¿Borrar todo el historial?", labelAccountScope: "Cuentas", optAccountAll: "Todas las cuentas", optAccountFollowing: "Cuentas que sigues", labelLocationScope: "Ubicación", optLocationAll: "Todas las ubicaciones", optLocationNearby: "Cerca de ti", chipFollowing: "Siguiendo", chipNearby: "Cerca", labelSearchTarget: "Ámbito de búsqueda", labelHitName: "Excluir coincidencias solo en nombre", labelHitHandle: "Excluir coincidencias solo en usuario (@)", hintSearchTarget: "Ocultar publicaciones que solo coincidan en el nombre o usuario (no en el cuerpo).", hintName: "Si la palabra clave aparece solo en el nombre mostrado, ocultarla.", hintHandle: "Si la palabra clave aparece solo en el @usuario, ocultarla. Excepción: si la consulta usa explícitamente from:/to:/@.", tabMute: "Silenciar", labelMuteWord: "Añadir palabra silenciada", placeholderMuteWord: "ej. spoiler", labelCaseSensitive: "Distinguir mayúsculas", labelWordBoundary: "Palabra completa", labelEnabled: "Habilitado", labelEnableAll: "Habilitar todo", buttonAdd: "Añadir", emptyMuted: "No hay palabras silenciadas.", mutedListTitle: "Palabras silenciadas", mutedListHeading: "Lista de silenciados", optMuteHidden: "Oculto", optMuteCollapsed: "Colapsado", placeholderFilterMute: "Filtrar palabras silenciadas...", muteLabel: "Silenciado: ", buttonShow: "Mostrar", muteHit: "Silenciar coincidencias en cuerpo", buttonRemute: "Volver a silenciar", buttonImport: "Importar", buttonExport: "Exportar", /* Accounts tab */ tabAccounts: "Cuentas", emptyAccounts: "Aún no hay cuentas. Abre un perfil y haz clic en el botón Añadir para guardarlo.", buttonAddAccount: "Añadir cuenta", toastAccountAdded: "Cuenta añadida.", toastAccountExists: "Ya existe.", buttonConfirm: "Confirmar", /* Lists tab */ tabLists: "Listas", emptyLists: "Aún no hay listas. Abre una lista y haz clic en el botón + arriba a la derecha para añadirla.", buttonAddList: "Añadir lista", toastListAdded: "Lista añadida.", toastListExists: "Ya existe.", /* History tab */ placeholderSearchHistory: "Historial de búsqueda (consulta)", labelSortBy: "Ordenar por:", placeholderSearchSaved: "Búsquedas guardadas (consulta)", sortNewest: "Más reciente", sortOldest: "Más antiguo", sortNameAsc: "Nombre (A-Z)", sortNameDesc: "Nombre (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrar cuentas (@, nombre)", placeholderFilterLists: "Filtrar listas (nombre, url)", buttonAddFolder: "+Carpeta", folderFilterAll: "TODO", folderFilterUnassigned: "Sin asignar", folderRename: "Renombrar", folderRenameTitle: "Renombrar carpeta", folderDelete: "Eliminar", folderDeleteTitle: "Eliminar carpeta", promptNewFolder: "Nombre de nueva carpeta", confirmDeleteFolder: "¿Eliminar esta carpeta y todo su contenido? Esto no se puede deshacer.", optListsAll: "Listas", defaultSavedFolders: "Búsquedas guardadas", /* Favorites */ tabFavorites: "Favoritos", emptyFavorites: "No hay favoritos. Haz clic en el icono ★ de los tweets para guardarlos.", optFavoritesAll: "Todos los favoritos", toastFavorited: "Añadido a favoritos.", toastUnfavorited: "Eliminado de favoritos.", /* Settings */ settingsTitle: "Configuración", settingsTitleGeneral: "General", settingsTitleFeatures: "Visibilidad de pestañas", settingsTitleData: "Datos", buttonClose: "Cerrar", labelUILang: "Idioma de interfaz", optUILangAuto: "Automático", labelInitialTab: "Pestaña de inicio", optInitialTabLast: "Última abierta (Predeterminado)", labelImportExport: "Importar / Exportar", placeholderSettingsJSON: "Pega el JSON de respaldo aquí...", tooltipSettings: "Abrir configuración", toastImported: "Importado.", toastExported: "Exportado a archivo.", alertInvalidJSON: "Archivo JSON inválido.", alertInvalidData: "Formato de datos inválido.", alertInvalidApp: 'Este archivo no es un respaldo válido para "Advanced Search for X".', buttonReset: "Restablecer todo", confirmResetAll: "¿Restablecer todos los datos? Esto no se puede deshacer.", toastReset: "Todos los datos han sido restablecidos.", buttonImportSuccess: "Importado con éxito 👍️", /* Favorites Sort */ sortSavedNewest: "Fecha de guardado (Reciente)", sortSavedOldest: "Fecha de guardado (Antigua)", sortPostedNewest: "Fecha de publicación (Reciente)", sortPostedOldest: "Fecha de publicación (Antigua)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Sin categoría', FT_DROPDOWN_TITLE: 'Etiquetas de favoritos', FT_DROPDOWN_SETTINGS_TITLE: 'Configuración de etiquetas', FT_DROPDOWN_NEW_TAG: 'Nueva etiqueta', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nombre de etiqueta', FT_DROPDOWN_NEW_TAG_ADD: 'Añadir', FT_FILTER_ALL: 'Todo', FT_SETTINGS_TITLE: 'Configuración de etiquetas', FT_SETTINGS_EMPTY_TAG_LIST: 'No hay etiquetas. Añade una desde "Nueva etiqueta".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Sin categoría', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'El nombre "Sin categoría" no se puede cambiar.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Sin categoría" no se puede eliminar.', FT_SETTINGS_CLOSE: 'Cerrar', FT_SETTINGS_DELETE_BUTTON: 'Eliminar', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Visualización', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Formato de etiqueta', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Solo etiqueta (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Ruta completa (full)', FT_CONFIRM_DELETE_TAG_MSG: '¿Eliminar la etiqueta "{tagName}"?\nLos favoritos con esta etiqueta pasarán a "Sin categoría".', FT_SETTINGS_BUTTON_TITLE: 'Configuración de etiquetas', }, 'de': { modalTitle: "Erweiterte Suche", tooltipClose: "Schließen", labelAllWords: "All diese Wörter", placeholderAllWords: "z.B. AI Nachrichten", labelExactPhrase: "Genau dieser Ausdruck", placeholderExactPhrase: 'z.B. "ChatGPT 4o"', labelAnyWords: "Beliebige dieser Wörter (OR)", placeholderAnyWords: "z.B. iPhone Android", labelNotWords: "Keines dieser Wörter (-)", placeholderNotWords: "z.B. -Verkauf -Werbung", labelHashtag: "Hashtags (#)", placeholderHashtag: "z.B. #TechEvent", labelLang: "Sprache (lang:)", optLangDefault: "Beliebige Sprache", optLangJa: "Japanisch (ja)", optLangEn: "Englisch (en)", optLangId: "Indonesisch (id)", optLangHi: "Hindi (hi)", optLangDe: "Deutsch (de)", optLangTr: "Türkisch (tr)", optLangEs: "Spanisch (es)", optLangPt: "Portugiesisch (pt)", optLangAr: "Arabisch (ar)", optLangFr: "Französisch (fr)", optLangKo: "Koreanisch (ko)", optLangRu: "Russisch (ru)", optLangZhHans: "Chinesisch vereinfacht (zh-cn)", optLangZhHant: "Chinesisch traditionell (zh-tw)", hrSeparator: " ", labelFilters: "Filter", labelVerified: "Verifizierte Konten", labelLinks: "Links", labelImages: "Bilder", labelVideos: "Videos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Einschl.", checkExclude: "Ausschl.", labelReplies: "Antworten", optRepliesDefault: "Standard (Alle)", optRepliesInclude: "Antworten einschließen", optRepliesOnly: "Nur Antworten", optRepliesExclude: "Antworten ausschließen", labelEngagement: "Interaktionen", placeholderMinReplies: "Min. Antworten", placeholderMinLikes: "Min. Gefällt mir", placeholderMinRetweets: "Min. Reposts", labelDateRange: "Zeitraum", labelDateShortcut: "Schnellauswahl", optDate1Day: "Letzte 24 Std.", optDate1Week: "Letzte Woche", optDate1Month: "Letzter Monat", optDate3Months: "Letzte 3 Monate", optDate6Months: "Letzte 6 Monate", optDate1Year: "Letztes Jahr", optDate2Years: "Letzte 2 Jahre", optDate3Years: "Letzte 3 Jahre", optDate5Years: "Letzte 5 Jahre", optDateClear: "Datum löschen", tooltipSince: "Seit diesem Datum", tooltipUntil: "Bis zu diesem Datum", labelFromUser: "Von diesen Konten (from:)", placeholderFromUser: "z.B. @X", labelToUser: "An diese Konten (to:)", placeholderToUser: "z.B. @google", labelMentioning: "Erwähnung dieser Konten (@)", placeholderMentioning: "z.B. @OpenAI", buttonClear: "Löschen", buttonApply: "Suchen", tooltipTrigger: "Erweiterte Suche öffnen", buttonOpen: "Öffnen", tabSearch: "Suche", tabHistory: "Verlauf", tabSaved: "Gespeichert", buttonSave: "Speichern", buttonSaved: "Gespeichert", secretMode: "Inkognito", secretOn: "Inkognito-Modus AN (Kein Verlauf)", secretOff: "Inkognito-Modus AUS", toastSaved: "Gespeichert.", toastDeleted: "Gelöscht.", toastReordered: "Reihenfolge aktualisiert.", emptyHistory: "Noch kein Verlauf.", emptySaved: "Keine gespeicherten Suchen. Fügen Sie welche über den Speichern-Button unten links im Suche-Tab hinzu.", run: "Ausführen", delete: "Löschen", updated: "Aktualisiert.", tooltipSecret: "Inkognito-Modus umschalten (kein Verlauf wird gespeichert)", historyClearAll: "Alle löschen", confirmClearHistory: "Gesamten Verlauf löschen?", labelAccountScope: "Konten", optAccountAll: "Alle Konten", optAccountFollowing: "Konten, denen du folgst", labelLocationScope: "Standort", optLocationAll: "Alle Standorte", optLocationNearby: "In deiner Nähe", chipFollowing: "Folge ich", chipNearby: "In der Nähe", labelSearchTarget: "Suchziel", labelHitName: "Treffer nur im Anzeigenamen ausschließen", labelHitHandle: "Treffer nur im Benutzernamen (@) ausschließen", hintSearchTarget: "Beiträge ausblenden, die nur im Namen oder Handle übereinstimmen (nicht im Text).", hintName: "Wenn ein Stichwort nur im Anzeigenamen vorkommt, ausblenden.", hintHandle: "Wenn ein Stichwort nur im @Benutzernamen vorkommt, ausblenden. Ausnahme: wenn die Anfrage explizit from:/to:/@ verwendet.", tabMute: "Stummschalten", labelMuteWord: "Stummes Wort hinzufügen", placeholderMuteWord: "z.B. Spoiler", labelCaseSensitive: "Groß-/Kleinschreibung", labelWordBoundary: "Ganzes Wort", labelEnabled: "Aktiviert", labelEnableAll: "Alle aktivieren", buttonAdd: "Hinzufügen", emptyMuted: "Keine stummgeschalteten Wörter.", mutedListTitle: "Stummgeschaltete Wörter", mutedListHeading: "Stummgeschaltete Liste", optMuteHidden: "Verborgen", optMuteCollapsed: "Eingeklappt", placeholderFilterMute: "Stummgeschaltete Wörter filtern...", muteLabel: "Stummgeschaltet: ", buttonShow: "Anzeigen", muteHit: "Treffer im Text stummschalten", buttonRemute: "Erneut stummschalten", buttonImport: "Importieren", buttonExport: "Exportieren", /* Accounts tab */ tabAccounts: "Konten", emptyAccounts: "Noch keine Konten. Öffnen Sie ein Profil und klicken Sie auf Hinzufügen, um es zu speichern.", buttonAddAccount: "Konto hinzufügen", toastAccountAdded: "Konto hinzugefügt.", toastAccountExists: "Bereits vorhanden.", buttonConfirm: "Bestätigen", /* Lists tab */ tabLists: "Listen", emptyLists: "Noch keine Listen. Öffnen Sie eine Liste und klicken Sie oben rechts auf +, um sie hinzuzufügen.", buttonAddList: "Liste hinzufügen", toastListAdded: "Liste hinzugefügt.", toastListExists: "Bereits vorhanden.", /* History tab */ placeholderSearchHistory: "Suchverlauf (Query)", labelSortBy: "Sortieren nach:", placeholderSearchSaved: "Gespeicherte Suchen (Query)", sortNewest: "Neueste zuerst", sortOldest: "Älteste zuerst", sortNameAsc: "Name (A-Z)", sortNameDesc: "Name (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Konten filtern (@, Name)", placeholderFilterLists: "Listen filtern (Name, URL)", buttonAddFolder: "+Ordner", folderFilterAll: "ALLE", folderFilterUnassigned: "Nicht zugewiesen", folderRename: "Umbenennen", folderRenameTitle: "Ordner umbenennen", folderDelete: "Löschen", folderDeleteTitle: "Ordner löschen", promptNewFolder: "Neuer Ordnername", confirmDeleteFolder: "Diesen Ordner und alle Elemente darin löschen? Dies kann nicht rückgängig gemacht werden.", optListsAll: "Listen", defaultSavedFolders: "Gespeicherte Suchen", /* Favorites */ tabFavorites: "Favoriten", emptyFavorites: "Keine Favoriten. Klicken Sie auf das ★-Symbol bei Tweets, um sie zu speichern.", optFavoritesAll: "Alle Favoriten", toastFavorited: "Zu Favoriten hinzugefügt.", toastUnfavorited: "Aus Favoriten entfernt.", /* Settings */ settingsTitle: "Einstellungen", settingsTitleGeneral: "Allgemein", settingsTitleFeatures: "Tab-Sichtbarkeit", settingsTitleData: "Daten", buttonClose: "Schließen", labelUILang: "Oberflächensprache", optUILangAuto: "Automatisch", labelInitialTab: "Start-Tab", optInitialTabLast: "Zuletzt geöffnet (Standard)", labelImportExport: "Import / Export", placeholderSettingsJSON: "Backup-JSON hier einfügen...", tooltipSettings: "Einstellungen öffnen", toastImported: "Importiert.", toastExported: "In Datei exportiert.", alertInvalidJSON: "Ungültige JSON-Datei.", alertInvalidData: "Ungültiges Datenformat.", alertInvalidApp: 'Diese Datei ist kein gültiges Backup für "Advanced Search for X".', buttonReset: "Alle Daten zurücksetzen", confirmResetAll: "Alle Daten zurücksetzen? Dies kann nicht rückgängig gemacht werden.", toastReset: "Alle Daten wurden zurückgesetzt.", buttonImportSuccess: "Erfolgreich importiert 👍️", /* Favorites Sort */ sortSavedNewest: "Speicherdatum (Neu)", sortSavedOldest: "Speicherdatum (Alt)", sortPostedNewest: "Veröffentlichungsdatum (Neu)", sortPostedOldest: "Veröffentlichungsdatum (Alt)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Unkategorisiert', FT_DROPDOWN_TITLE: 'Favoriten-Tags', FT_DROPDOWN_SETTINGS_TITLE: 'Tag-Einstellungen', FT_DROPDOWN_NEW_TAG: 'Neuer Tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Tag-Name', FT_DROPDOWN_NEW_TAG_ADD: 'Hinzufügen', FT_FILTER_ALL: 'Alle', FT_SETTINGS_TITLE: 'Favoriten-Tag-Einstellungen', FT_SETTINGS_EMPTY_TAG_LIST: 'Keine Tags. Fügen Sie einen über "Neuer Tag" hinzu.', FT_SETTINGS_UNCATEGORIZED_NAME: 'Unkategorisiert', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'Der Name "Unkategorisiert" kann nicht geändert werden.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Unkategorisiert" kann nicht gelöscht werden.', FT_SETTINGS_CLOSE: 'Schließen', FT_SETTINGS_DELETE_BUTTON: 'Löschen', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Anzeige', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Tag-Format', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Nur Label (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Voller Pfad (full)', FT_CONFIRM_DELETE_TAG_MSG: 'Tag "{tagName}" löschen?\nFavoriten mit diesem Tag werden "Unkategorisiert".', FT_SETTINGS_BUTTON_TITLE: 'Tag-Einstellungen', }, 'pt-BR': { modalTitle: "Busca avançada", tooltipClose: "Fechar", labelAllWords: "Todas estas palavras", placeholderAllWords: "ex: AI notícias", labelExactPhrase: "Esta frase exata", placeholderExactPhrase: 'ex: "ChatGPT 4o"', labelAnyWords: "Qualquer destas palavras (OR)", placeholderAnyWords: "ex: iPhone Android", labelNotWords: "Nenhuma destas palavras (-)", placeholderNotWords: "ex: -promoção -ads", labelHashtag: "Hashtags (#)", placeholderHashtag: "ex: #Tecnologia", labelLang: "Idioma (lang:)", optLangDefault: "Qualquer idioma", optLangJa: "Japonês (ja)", optLangEn: "Inglês (en)", optLangId: "Indonésio (id)", optLangHi: "Hindi (hi)", optLangDe: "Alemão (de)", optLangTr: "Turco (tr)", optLangEs: "Espanhol (es)", optLangPt: "Português (pt)", optLangAr: "Árabe (ar)", optLangFr: "Francês (fr)", optLangKo: "Coreano (ko)", optLangRu: "Russo (ru)", optLangZhHans: "Chinês Simplificado (zh-cn)", optLangZhHant: "Chinês Tradicional (zh-tw)", hrSeparator: " ", labelFilters: "Filtros", labelVerified: "Contas verificadas", labelLinks: "Links", labelImages: "Imagens", labelVideos: "Vídeos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Incluir", checkExclude: "Excluir", labelReplies: "Respostas", optRepliesDefault: "Padrão (Tudo)", optRepliesInclude: "Incluir respostas", optRepliesOnly: "Apenas respostas", optRepliesExclude: "Excluir respostas", labelEngagement: "Engajamento", placeholderMinReplies: "Mín respostas", placeholderMinLikes: "Mín curtidas", placeholderMinRetweets: "Mín reposts", labelDateRange: "Período", labelDateShortcut: "Intervalo rápido", optDate1Day: "Últimas 24h", optDate1Week: "Última semana", optDate1Month: "Último mês", optDate3Months: "Últimos 3 meses", optDate6Months: "Últimos 6 meses", optDate1Year: "Último ano", optDate2Years: "Últimos 2 anos", optDate3Years: "Últimos 3 anos", optDate5Years: "Últimos 5 anos", optDateClear: "Limpar datas", tooltipSince: "A partir desta data", tooltipUntil: "Até esta data", labelFromUser: "Destas contas (from:)", placeholderFromUser: "ex: @X", labelToUser: "Para estas contas (to:)", placeholderToUser: "ex: @google", labelMentioning: "Mencionando estas contas (@)", placeholderMentioning: "ex: @OpenAI", buttonClear: "Limpar", buttonApply: "Buscar", tooltipTrigger: "Abrir busca avançada", buttonOpen: "Abrir", tabSearch: "Busca", tabHistory: "Histórico", tabSaved: "Salvos", buttonSave: "Salvar", buttonSaved: "Salvo", secretMode: "Secreto", secretOn: "Modo secreto ON (Sem histórico)", secretOff: "Modo secreto OFF", toastSaved: "Salvo.", toastDeleted: "Excluído.", toastReordered: "Ordem atualizada.", emptyHistory: "Sem histórico ainda.", emptySaved: "Nenhuma busca salva. Adicione pelo botão Salvar no canto inferior esquerdo da aba Busca.", run: "Executar", delete: "Excluir", updated: "Atualizado.", tooltipSecret: "Alternar Modo Secreto (histórico não será gravado)", historyClearAll: "Limpar tudo", confirmClearHistory: "Limpar todo o histórico?", labelAccountScope: "Contas", optAccountAll: "Todas as contas", optAccountFollowing: "Contas que você segue", labelLocationScope: "Localização", optLocationAll: "Todas as localizações", optLocationNearby: "Perto de você", chipFollowing: "Seguindo", chipNearby: "Próximo", labelSearchTarget: "Alvo da busca", labelHitName: "Excluir resultados apenas no nome", labelHitHandle: "Excluir resultados apenas no usuário (@)", hintSearchTarget: "Ocultar posts que correspondem apenas ao nome ou usuário (não no corpo).", hintName: "Se a palavra-chave aparecer apenas no nome de exibição, ocultar.", hintHandle: "Se a palavra-chave aparecer apenas no @usuario, ocultar. Exceção: quando a consulta usar explicitamente from:/to:/@.", tabMute: "Silenciar", labelMuteWord: "Adicionar palavra silenciada", placeholderMuteWord: "ex: spoiler", labelCaseSensitive: "Diferenciar maiúsculas", labelWordBoundary: "Palavra inteira", labelEnabled: "Ativado", labelEnableAll: "Ativar tudo", buttonAdd: "Adicionar", emptyMuted: "Nenhuma palavra silenciada.", mutedListTitle: "Palavras silenciadas", mutedListHeading: "Lista de silenciados", optMuteHidden: "Oculto", optMuteCollapsed: "Colapsado", placeholderFilterMute: "Filtrar palavras silenciadas...", muteLabel: "Silenciado: ", buttonShow: "Mostrar", muteHit: "Silenciar resultados no corpo", buttonRemute: "Silenciar novamente", buttonImport: "Importar", buttonExport: "Exportar", /* Accounts tab */ tabAccounts: "Contas", emptyAccounts: "Nenhuma conta ainda. Abra um perfil e clique no botão Adicionar para salvar.", buttonAddAccount: "Adicionar conta", toastAccountAdded: "Conta adicionada.", toastAccountExists: "Já adicionada.", buttonConfirm: "Confirmar", /* Lists tab */ tabLists: "Listas", emptyLists: "Nenhuma lista ainda. Abra uma Lista e clique no botão + no canto superior direito para adicionar.", buttonAddList: "Adicionar lista", toastListAdded: "Lista adicionada.", toastListExists: "Já adicionada.", /* History tab */ placeholderSearchHistory: "Histórico de busca (query)", labelSortBy: "Ordenar por:", placeholderSearchSaved: "Buscas salvas (query)", sortNewest: "Mais recente", sortOldest: "Mais antigo", sortNameAsc: "Nome (A-Z)", sortNameDesc: "Nome (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrar contas (@, nome)", placeholderFilterLists: "Filtrar listas (nome, url)", buttonAddFolder: "+Pasta", folderFilterAll: "TUDO", folderFilterUnassigned: "Não atribuído", folderRename: "Renomear", folderRenameTitle: "Renomear pasta", folderDelete: "Excluir", folderDeleteTitle: "Excluir pasta", promptNewFolder: "Nome da nova pasta", confirmDeleteFolder: "Excluir esta pasta e todos os itens dentro dela? Isso não pode ser desfeito.", optListsAll: "Listas", defaultSavedFolders: "Buscas Salvas", /* Favorites */ tabFavorites: "Favoritos", emptyFavorites: "Nenhum favorito ainda. Clique no ícone ★ nos tweets para salvar.", optFavoritesAll: "Todos os favoritos", toastFavorited: "Adicionado aos favoritos.", toastUnfavorited: "Removido dos favoritos.", /* Settings */ settingsTitle: "Configurações", settingsTitleGeneral: "Geral", settingsTitleFeatures: "Visibilidade de abas", settingsTitleData: "Dados", buttonClose: "Fechar", labelUILang: "Idioma da interface", optUILangAuto: "Automático", labelInitialTab: "Aba inicial", optInitialTabLast: "Última aberta (Padrão)", labelImportExport: "Importar / Exportar", placeholderSettingsJSON: "Cole o JSON de backup aqui...", tooltipSettings: "Abrir configurações", toastImported: "Importado.", toastExported: "Exportado para arquivo.", alertInvalidJSON: "Arquivo JSON inválido.", alertInvalidData: "Formato de dados inválido.", alertInvalidApp: 'Este arquivo não é um backup válido para "Advanced Search for X".', buttonReset: "Redefinir tudo", confirmResetAll: "Redefinir todos os dados? Isso não pode ser desfeito.", toastReset: "Todos os dados foram redefinidos.", buttonImportSuccess: "Importado com sucesso 👍️", /* Favorites Sort */ sortSavedNewest: "Data (Mais recente)", sortSavedOldest: "Data (Mais antigo)", sortPostedNewest: "Postado (Mais recente)", sortPostedOldest: "Postado (Mais antigo)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Sem categoria', FT_DROPDOWN_TITLE: 'Tags favoritas', FT_DROPDOWN_SETTINGS_TITLE: 'Configurações de tags', FT_DROPDOWN_NEW_TAG: 'Nova tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nome da tag', FT_DROPDOWN_NEW_TAG_ADD: 'Adicionar', FT_FILTER_ALL: 'Tudo', FT_SETTINGS_TITLE: 'Configurações de tags favoritas', FT_SETTINGS_EMPTY_TAG_LIST: 'Sem tags. Adicione em "Nova tag".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Sem categoria', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'O nome "Sem categoria" não pode ser alterado.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Sem categoria" não pode ser excluída.', FT_SETTINGS_CLOSE: 'Fechar', FT_SETTINGS_DELETE_BUTTON: 'Excluir', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Exibição', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Formato da tag', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Apenas etiqueta (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Caminho completo (full)', FT_CONFIRM_DELETE_TAG_MSG: 'Excluir tag "{tagName}"?\nFavoritos com esta tag ficarão "Sem categoria".', FT_SETTINGS_BUTTON_TITLE: 'Configurações de tags', }, 'ru': { modalTitle: "Расширенный поиск", tooltipClose: "Закрыть", labelAllWords: "Все эти слова", placeholderAllWords: "напр., AI новости", labelExactPhrase: "Точная фраза", placeholderExactPhrase: 'напр., "ChatGPT 4o"', labelAnyWords: "Любое из этих слов (OR)", placeholderAnyWords: "напр., iPhone Android", labelNotWords: "Исключить слова (-)", placeholderNotWords: "напр., -распродажа -реклама", labelHashtag: "Хэштеги (#)", placeholderHashtag: "напр., #TechEvent", labelLang: "Язык (lang:)", optLangDefault: "Любой язык", optLangJa: "Японский (ja)", optLangEn: "Английский (en)", optLangId: "Индонезийский (id)", optLangHi: "Хинди (hi)", optLangDe: "Немецкий (de)", optLangTr: "Турецкий (tr)", optLangEs: "Испанский (es)", optLangPt: "Португальский (pt)", optLangAr: "Арабский (ar)", optLangFr: "Французский (fr)", optLangKo: "Корейский (ko)", optLangRu: "Русский (ru)", optLangZhHans: "Китайский упр. (zh-cn)", optLangZhHant: "Китайский трад. (zh-tw)", hrSeparator: " ", labelFilters: "Фильтры", labelVerified: "Подтвержденные аккаунты", labelLinks: "Ссылки", labelImages: "Изображения", labelVideos: "Видео", labelReposts: "Репосты", labelTimelineHashtags: "Хэштеги (#)", checkInclude: "Вкл", checkExclude: "Искл", labelReplies: "Ответы", optRepliesDefault: "По умолчанию (Все)", optRepliesInclude: "Включая ответы", optRepliesOnly: "Только ответы", optRepliesExclude: "Исключить ответы", labelEngagement: "Вовлеченность", placeholderMinReplies: "Мин. ответов", placeholderMinLikes: "Мин. лайков", placeholderMinRetweets: "Мин. репостов", labelDateRange: "Диапазон дат", labelDateShortcut: "Быстрый выбор", optDate1Day: "За 24 часа", optDate1Week: "За неделю", optDate1Month: "За месяц", optDate3Months: "За 3 месяца", optDate6Months: "За 6 месяцев", optDate1Year: "За год", optDate2Years: "За 2 года", optDate3Years: "За 3 года", optDate5Years: "За 5 лет", optDateClear: "Очистить даты", tooltipSince: "С этой даты", tooltipUntil: "По эту дату", labelFromUser: "От этих аккаунтов (from:)", placeholderFromUser: "напр., @X", labelToUser: "Этим аккаунтам (to:)", placeholderToUser: "напр., @google", labelMentioning: "Упоминание этих аккаунтов (@)", placeholderMentioning: "напр., @OpenAI", buttonClear: "Очистить", buttonApply: "Поиск", tooltipTrigger: "Открыть расширенный поиск", buttonOpen: "Открыть", tabSearch: "Поиск", tabHistory: "История", tabSaved: "Сохраненное", buttonSave: "Сохранить", buttonSaved: "Сохранено", secretMode: "Секретный", secretOn: "Секретный режим ВКЛ (без истории)", secretOff: "Секретный режим ВЫКЛ", toastSaved: "Сохранено.", toastDeleted: "Удалено.", toastReordered: "Порядок обновлен.", emptyHistory: "Истории пока нет.", emptySaved: "Нет сохраненных поисков. Добавьте их кнопкой Сохранить внизу вкладки Поиск.", run: "Выполнить", delete: "Удалить", updated: "Обновлено.", tooltipSecret: "Переключить секретный режим (история не будет записана)", historyClearAll: "Очистить всё", confirmClearHistory: "Очистить всю историю?", labelAccountScope: "Аккаунты", optAccountAll: "Все аккаунты", optAccountFollowing: "Читаемые вами", labelLocationScope: "Местоположение", optLocationAll: "Везде", optLocationNearby: "Рядом с вами", chipFollowing: "Читаемые", chipNearby: "Рядом", labelSearchTarget: "Цель поиска", labelHitName: "Исключить совпадения только в имени", labelHitHandle: "Исключить совпадения только в юзернейме (@)", hintSearchTarget: "Скрыть посты, совпадающие только по имени/юзернейму (но не в тексте).", hintName: "Если ключевое слово только в отображаемом имени — скрыть.", hintHandle: "Если ключевое слово только в @юзернейме — скрыть. Искл: если запрос явно использует from:/to:/@.", tabMute: "Скрыть", labelMuteWord: "Добавить скрытое слово", placeholderMuteWord: "напр., спойлер", labelCaseSensitive: "Учитывать регистр", labelWordBoundary: "Слово целиком", labelEnabled: "Включено", labelEnableAll: "Включить все", buttonAdd: "Добавить", emptyMuted: "Нет скрытых слов.", mutedListTitle: "Скрытые слова", mutedListHeading: "Список скрытого", optMuteHidden: "Скрыто", optMuteCollapsed: "Свернуто", placeholderFilterMute: "Фильтр скрытых слов...", muteLabel: "Скрыто: ", buttonShow: "Показать", muteHit: "Скрывать совпадения в тексте", buttonRemute: "Скрыть снова", buttonImport: "Импорт", buttonExport: "Экспорт", /* Accounts tab */ tabAccounts: "Аккаунты", emptyAccounts: "Аккаунтов нет. Откройте профиль и нажмите Добавить, чтобы сохранить.", buttonAddAccount: "Добавить аккаунт", toastAccountAdded: "Аккаунт добавлен.", toastAccountExists: "Уже добавлен.", buttonConfirm: "Подтвердить", /* Lists tab */ tabLists: "Списки", emptyLists: "Списков нет. Откройте список и нажмите + в углу для добавления.", buttonAddList: "Добавить список", toastListAdded: "Список добавлен.", toastListExists: "Уже добавлен.", /* History tab */ placeholderSearchHistory: "История поиска (запрос)", labelSortBy: "Сортировка:", placeholderSearchSaved: "Сохраненный поиск (запрос)", sortNewest: "Сначала новые", sortOldest: "Сначала старые", sortNameAsc: "Имя (А-Я)", sortNameDesc: "Имя (Я-А)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Фильтр аккаунтов (@, имя)", placeholderFilterLists: "Фильтр списков (имя, url)", buttonAddFolder: "+Папка", folderFilterAll: "ВСЕ", folderFilterUnassigned: "Без папки", folderRename: "Переименовать", folderRenameTitle: "Переименовать папку", folderDelete: "Удалить", folderDeleteTitle: "Удалить папку", promptNewFolder: "Имя новой папки", confirmDeleteFolder: "Удалить эту папку и всё содержимое? Это нельзя отменить.", optListsAll: "Списки", defaultSavedFolders: "Сохраненные поиски", /* Favorites */ tabFavorites: "Избранное", emptyFavorites: "В избранном пусто. Нажмите ★ на твите, чтобы сохранить.", optFavoritesAll: "Всё избранное", toastFavorited: "Добавлено в избранное.", toastUnfavorited: "Удалено из избранного.", /* Settings */ settingsTitle: "Настройки", settingsTitleGeneral: "Общие", settingsTitleFeatures: "Вкладки", settingsTitleData: "Данные", buttonClose: "Закрыть", labelUILang: "Язык интерфейса", optUILangAuto: "Авто", labelInitialTab: "Вкладка при запуске", optInitialTabLast: "Последняя открытая (По умолч.)", labelImportExport: "Импорт / Экспорт", placeholderSettingsJSON: "Вставьте JSON резервной копии...", tooltipSettings: "Открыть настройки", toastImported: "Импортировано.", toastExported: "Экспортировано в файл.", alertInvalidJSON: "Неверный файл JSON.", alertInvalidData: "Неверный формат данных.", alertInvalidApp: 'Файл не является копией "Advanced Search for X".', buttonReset: "Сбросить всё", confirmResetAll: "Сбросить все данные? Это нельзя отменить.", toastReset: "Все данные сброшены.", buttonImportSuccess: "Успешный импорт 👍️", /* Favorites Sort */ sortSavedNewest: "Дата сохр. (Новые)", sortSavedOldest: "Дата сохр. (Старые)", sortPostedNewest: "Дата публ. (Новые)", sortPostedOldest: "Дата публ. (Старые)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Без категории', FT_DROPDOWN_TITLE: 'Теги избранного', FT_DROPDOWN_SETTINGS_TITLE: 'Настройка тегов', FT_DROPDOWN_NEW_TAG: 'Новый тег', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Имя тега', FT_DROPDOWN_NEW_TAG_ADD: 'Добавить', FT_FILTER_ALL: 'Все', FT_SETTINGS_TITLE: 'Настройка тегов избранного', FT_SETTINGS_EMPTY_TAG_LIST: 'Тегов нет. Добавьте через "Новый тег".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Без категории', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'Имя "Без категории" нельзя изменить.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Без категории" нельзя удалить.', FT_SETTINGS_CLOSE: 'Закрыть', FT_SETTINGS_DELETE_BUTTON: 'Удалить', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Отображение', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Формат тега', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Только имя (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Полный путь (full)', FT_CONFIRM_DELETE_TAG_MSG: 'Удалить тег "{tagName}"?\nЭлементы с этим тегом станут "Без категории".', FT_SETTINGS_BUTTON_TITLE: 'Настройка тегов', } }, lang: 'en', init: function() { const supportedLangs = Object.keys(this.translations); let detectedLang = document.documentElement.lang || navigator.language || 'en'; if (supportedLangs.includes(detectedLang)) { this.lang = detectedLang; return; } const baseLang = detectedLang.split('-')[0]; if (supportedLangs.includes(baseLang)) { this.lang = baseLang; return; } this.lang = 'en'; }, t: function(key) { return this.translations[this.lang]?.[key] || this.translations['en'][key] || `[${key}]`; }, apply: function(container) { container.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = this.t(el.dataset.i18n); }); container.querySelectorAll('[data-i18n-placeholder]').forEach(el => { el.placeholder = this.t(el.dataset.i18nPlaceholder); }); container.querySelectorAll('[data-i18n-title]').forEach(el => { el.title = this.t(el.dataset.i18nTitle); }); } }; const SEARCH_SVG = ` `; const SETTINGS_SVG = ` `; const FOLDER_TOGGLE_OPEN_SVG = ` `; const FOLDER_TOGGLE_CLOSED_SVG = ` `; // トグルボタンの小ユーティリティ function renderFolderToggleButton(collapsed) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'adv-folder-toggle-btn'; btn.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse'); btn.setAttribute('title', collapsed ? 'Expand' : 'Collapse'); btn.setAttribute('aria-expanded', (!collapsed).toString()); btn.style.cssText = ` appearance:none;border:none;background:transparent;cursor:pointer; width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center; margin-right:8px;color:inherit;flex:0 0 auto; `; btn.innerHTML = collapsed ? FOLDER_TOGGLE_CLOSED_SVG : FOLDER_TOGGLE_OPEN_SVG; return btn; } function updateFolderToggleButton(btn, collapsed) { if (!btn) return; btn.innerHTML = collapsed ? FOLDER_TOGGLE_CLOSED_SVG : FOLDER_TOGGLE_OPEN_SVG; btn.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse'); btn.setAttribute('title', collapsed ? 'Expand' : 'Collapse'); btn.setAttribute('aria-expanded', (!collapsed).toString()); } const themeManager = { colors: { light: { '--modal-bg': '#ffffff', '--modal-text-primary': '#0f1419', '--modal-text-secondary': '#536471', '--modal-border': '#d9e1e8', '--modal-input-bg': '#eff3f4', '--modal-input-border': '#cfd9de', '--modal-button-hover-bg': 'rgba(15, 20, 25, 0.1)', '--modal-scrollbar-thumb': '#aab8c2', '--modal-scrollbar-track': '#eff3f4', '--modal-close-color': '#0f1419', '--modal-close-hover-bg': 'rgba(15, 20, 25, 0.1)', '--hr-color': '#eff3f4', '--modal-tabs-shadow': '0 1px 12px rgba(0, 0, 0, 0.22)', }, dim: { '--modal-bg': '#15202b', '--modal-text-primary': '#f7f9f9', '--modal-text-secondary': '#8899a6', '--modal-border': '#38444d', '--modal-input-bg': '#192734', '--modal-input-border': '#38444d', '--modal-button-hover-bg': 'rgba(247, 249, 249, 0.1)', '--modal-scrollbar-thumb': '#536471', '--modal-scrollbar-track': '#192734', '--modal-close-color': '#f7f9f9', '--modal-close-hover-bg': 'rgba(247, 249, 249, 0.1)', '--hr-color': '#38444d', '--modal-tabs-shadow': '0 5px 12px rgba(0, 0, 0, 0.27)', }, dark: { '--modal-bg': '#000000', '--modal-text-primary': '#e7e9ea', '--modal-text-secondary': '#71767b', '--modal-border': '#2f3336', '--modal-input-bg': '#16181c', '--modal-input-border': '#54595d', '--modal-button-hover-bg': 'rgba(231, 233, 234, 0.1)', '--modal-scrollbar-thumb': '#536471', '--modal-scrollbar-track': '#16181c', '--modal-close-color': '#e7e9ea', '--modal-close-hover-bg': 'rgba(231, 233, 234, 0.1)', '--hr-color': '#2f3336', '--modal-tabs-shadow': '0 5px 12px rgba(0, 0, 0, 0.27)', } }, applyTheme: function(modalElement, triggerEl) { if (!modalElement) return; const bodyBg = getComputedStyle(document.body).backgroundColor; let theme = 'dark'; if (bodyBg === 'rgb(21, 32, 43)') theme = 'dim'; else if (bodyBg === 'rgb(255, 255, 255)') theme = 'light'; // ▼ ブックマークUIのテーマ切替用にクラスを付与 try { document.documentElement.classList.remove('x-theme-light', 'x-theme-dim', 'x-theme-dark'); if (theme === 'light') { document.documentElement.classList.add('x-theme-light'); } else if (theme === 'dim') { document.documentElement.classList.add('x-theme-dim'); } else { document.documentElement.classList.add('x-theme-dark'); } } catch (e) {} const themeColors = this.colors[theme] || this.colors.dark; const targets = [modalElement, document.documentElement]; if (triggerEl) targets.push(triggerEl); for (const t of targets) { for (const [key, value] of Object.entries(themeColors)) { t.style.setProperty(key, value); } } }, observeChanges: function(modalElement, triggerEl) { const observer = new MutationObserver(() => this.applyTheme(modalElement, triggerEl)); observer.observe(document.body, { attributes: true, attributeFilter: ['style'] }); this.applyTheme(modalElement, triggerEl); } }; /** * Mobile Drag & Drop Shim * タッチイベントを検知し、HTML5 Drag & Dropイベント(dragstart, dragover, drop等)を発火させる */ function enableMobileDragSupport() { let dragSource = null; let lastTarget = null; // DataTransferのデータを保持する擬似ストア let dataTransferStore = {}; // 擬似的な DragEvent を作成するヘルパー const createEvent = (type, touch, target) => { const event = new CustomEvent(type, { bubbles: true, cancelable: true }); // dataTransfer オブジェクトを擬似的に再現 event.dataTransfer = { effectAllowed: 'move', dropEffect: 'move', types: Object.keys(dataTransferStore), setData: (format, data) => { dataTransferStore[format] = data; }, getData: (format) => dataTransferStore[format], clearData: () => { dataTransferStore = {}; } }; // 座標情報を付与 (getDragAfterElement 等の計算に必要) event.clientX = touch.clientX; event.clientY = touch.clientY; event.pageX = touch.pageX; event.pageY = touch.pageY; // ターゲット要素を上書き設定 (CustomEventの制約回避) Object.defineProperty(event, 'target', { value: target, enumerable: true }); return event; }; const onTouchStart = (e) => { // ハンドル、またはドラッグ可能な要素自体へのタッチか判定 const handle = e.target.closest('.adv-item-handle, .adv-folder-header, .adv-tab-btn, .ft-modal-tag-drag-handle'); if (!handle) return; const draggable = handle.closest('[draggable="true"]'); if (!draggable) return; dragSource = draggable; dataTransferStore = {}; // データ初期化 const touch = e.changedTouches[0]; const evt = createEvent('dragstart', touch, dragSource); dragSource.dispatchEvent(evt); }; const onTouchMove = (e) => { if (!dragSource) return; // スクロール防止(CSSのtouch-actionで防げない場合用) if (e.cancelable) e.preventDefault(); const touch = e.changedTouches[0]; // 指の下にある要素を取得 const element = document.elementFromPoint(touch.clientX, touch.clientY); if (!element) return; // dragover は頻繁に発火させる必要がある // ターゲットが変わった場合は dragenter/dragleave も検討すべきだが、 // このアプリのロジック(並び替え)では dragover がメインのため、そこに集中する // 既存ロジックが .closest('.adv-item') 等を使っているため、適切なターゲットに対して発火 // ここでは elementFromPoint で取れた要素に対して dragover を投げる const evt = createEvent('dragover', touch, element); element.dispatchEvent(evt); lastTarget = element; }; const onTouchEnd = (e) => { if (!dragSource) return; const touch = e.changedTouches[0]; // 最後に指があった要素に対して drop を発火 if (lastTarget) { const evtDrop = createEvent('drop', touch, lastTarget); lastTarget.dispatchEvent(evtDrop); } const evtEnd = createEvent('dragend', touch, dragSource); dragSource.dispatchEvent(evtEnd); // クリーンアップ dragSource = null; lastTarget = null; dataTransferStore = {}; }; document.addEventListener('touchstart', onTouchStart, { passive: false }); document.addEventListener('touchmove', onTouchMove, { passive: false }); document.addEventListener('touchend', onTouchEnd); } function decodeURIComponentSafe(s) { try { return decodeURIComponent(s); } catch { return s; } } // 正規表現の特殊文字をエスケープする function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // “ ” 『』などのスマート引用を ASCII の " に寄せる function normalizeQuotes(s) { return String(s).replace(/[\u201C\u201D\u300C\u300D\uFF02]/g, '"'); } // 解析前に軽く正規化(URL から来る %22..., 連続空白など) function normalizeForParse(s) { if (!s) return ''; let out = String(s); // URL っぽいエンコードだけ軽く剥がす(%22 等) if (/%[0-9A-Fa-f]{2}/.test(out)) out = decodeURIComponentSafe(out); out = normalizeQuotes(out); // 制御文字を潰し、空白を整形 out = out.replace(/\s+/g, ' ').trim(); return out; } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ── OR/引用のための簡易トークナイザ function tokenizeQuotedWords(s) { const out = []; let cur = ''; let inQ = false; for (let i = 0; i < s.length; i++) { const c = s[i]; if (c === '"') { inQ = !inQ; cur += c; continue; } if (!inQ && /\s/.test(c)) { if (cur) { out.push(cur); cur=''; } } else { cur += c; } } if (cur) out.push(cur); return out.filter(Boolean); } // トップレベルの OR で文字列を分割(引用/括弧を考慮) function splitTopLevelOR(str) { const parts = []; let cur = ''; let inQ = false, depth = 0; for (let i = 0; i < str.length; ) { const c = str[i]; if (c === '"') { inQ = !inQ; cur += c; i++; continue; } if (!inQ && (c === '(' || c === ')')) { depth += (c === '(' ? 1 : -1); cur += c; i++; continue; } if (!inQ && depth === 0) { // 単語境界の "or" / "OR" if ((str.slice(i, i+2).toLowerCase() === 'or') && (i === 0 || /\s|\(/.test(str[i-1] || '')) && (i+2 >= str.length || /\s|\)/.test(str[i+2] || ''))) { parts.push(cur.trim()); cur = ''; i += 2; continue; } } cur += c; i++; } if (cur.trim()) parts.push(cur.trim()); return parts.length > 1 ? parts : null; } // OR 専用判定(演算子/否定/括弧が無い素の OR 群なら true) function isPureORQuery(q) { const hasOps = /(?:^|\s)(?:from:|to:|lang:|filter:|is:|min_replies:|min_faves:|min_retweets:|since:|until:)\b/i.test(q); const hasNeg = /(^|\s)-\S/.test(q); const hasPar = /[()]/.test(q); return !hasOps && !hasNeg && !hasPar; } function waitForElement(selector, timeout = 10000, checkProperty = null) { return new Promise((resolve) => { const checkInterval = 100; let elapsedTime = 0; const intervalId = setInterval(() => { const element = document.querySelector(selector); if (element) { if (checkProperty) { if (element[checkProperty]) { clearInterval(intervalId); resolve(element); return; } } else { clearInterval(intervalId); resolve(element); return; } } elapsedTime += checkInterval; if (elapsedTime >= timeout) { clearInterval(intervalId); resolve(null); } }, checkInterval); }); } function hideUIImmediately(modal, trigger) { if (modal) modal.style.display = 'none'; if (trigger) trigger.style.display = 'none'; } // ▼ ルート適用を軽く検証(URL一致 + プロフィール系DOMが現れたか) function waitForRouteApply(path, timeoutMs = 2000) { const goal = new URL(path, location.origin).pathname; // ルート毎の判定を用意(必要に応じて拡張) const perRouteProbes = [ // 検索ページ:検索結果タイムライン or 検索ボックス or 何かしらのツイート { test: p => p.startsWith('/search'), sels: [ '[aria-label*="Search results"], [aria-label*="検索結果"]', 'div[data-testid="primaryColumn"] input[data-testid="SearchBox_Search_Input"]', 'div[data-testid="primaryColumn"] article[data-testid="tweet"]' ] }, // プロフィール { test: p => /^\/[A-Za-z0-9_]{1,50}\/?$/.test(p), sels: [ '[data-testid="UserName"]', 'div[data-testid="UserProfileHeader_Items"]', 'div[data-testid="UserDescription"]' ] }, // デフォルト(保険):主要カラムに何かレンダされたらOK { test: _ => true, sels: [ 'div[data-testid="primaryColumn"]', 'main[role="main"]' ] } ]; const probes = (perRouteProbes.find(x => x.test(goal)) || perRouteProbes.at(-1)).sels; return new Promise(resolve => { const t0 = performance.now(); (function tick() { const elapsed = performance.now() - t0; const urlOk = location.pathname === goal; const domOk = probes.some(sel => document.querySelector(sel)); if (urlOk && domOk) return resolve(true); if (elapsed >= timeoutMs) return resolve(false); // 立ち上がりは速く、以後はやや疎にポーリング setTimeout(tick, elapsed < 300 ? 60 : elapsed < 700 ? 120 : 180); })(); }); } // ▼ SPA 遷移の核。pushState → 合成 popstate → DOM適用待ち → 失敗ならフォールバック async function spaNavigate(path, { ctrlMeta = false, timeoutMs = 1200 } = {}) { try { const to = new URL(path, location.origin); if (to.origin !== location.origin) throw new Error('cross-origin'); history.pushState(history.state, '', to.pathname + to.search + to.hash); // X のルーターは popstate を購読している想定 window.dispatchEvent(new PopStateEvent('popstate', { state: history.state })); const ok = await waitForRouteApply(to.pathname, timeoutMs); if (ok) return; // 成功 } catch (e) { // fall through to fallback } // フォールバック:修飾キーありなら新規タブ、なければ通常遷移 if (ctrlMeta) window.open(path, '_blank', 'noopener'); else location.assign(path); } const uid = () => Math.random().toString(36).slice(2) + Date.now().toString(36); let isUpdating = false; let manualOverrideOpen = false; const lastHistory = { q: null, pf: null, lf: null, ts: 0 }; // ▼ パース結果をキャッシュ(スクロール時の再パース防止) let __cachedSearchTokens = null; let __cachedSearchQuery = null; // このクエリ文字列で __cachedSearchTokens が生成された // ▼ 入力中ガード(IME合成を含めてカバー) let __typingGuardUntil = 0; const TYPING_GRACE_MS = 600; // 入力終了からこのmsはスキャン停止 const markTyping = () => { __typingGuardUntil = Date.now() + TYPING_GRACE_MS; }; const isTyping = () => Date.now() < __typingGuardUntil; const isMediaViewPath = (pathname) => /\/status\/\d+\/(?:photo|video|media|analytics)(?:\/\d+)?\/?$/.test(pathname); const isComposePath = (pathname) => /^\/compose\/post(?:\/|$)/.test(pathname); const isProfileMediaPath = (pathname) => /^\/[A-Za-z0-9_]{1,50}\/(?:photo|header_photo)\/?$/.test(pathname); const isBroadcastPath = (pathname) => /^\/i\/broadcasts\//.test(pathname); const isBlockedPath = (pathname) => isMediaViewPath(pathname) || isComposePath(pathname) || isProfileMediaPath(pathname) || isBroadcastPath(pathname); GM_addStyle(` :root { --modal-primary-color:#1d9bf0; --modal-primary-color-hover:#1a8cd8; --modal-primary-text-color:#fff; } #advanced-search-trigger { position:fixed; top:18px; right:20px; z-index:9999; background-color:var(--modal-primary-color); color:var(--modal-primary-text-color); border:none; border-radius:50%; width:50px; height:50px; font-size:24px; cursor:pointer; box-shadow:0 4px 12px rgba(0,0,0,0.15); display:flex; align-items:center; justify-content:center; transition:transform .2s, background-color .2s; } #advanced-search-trigger:hover { transform:scale(1.1); background-color:var(--modal-primary-color-hover); } #advanced-search-modal { position:fixed; z-index:10000; width:450px; display:none; flex-direction:column; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background-color:var(--modal-bg, #000); color:var(--modal-text-primary, #e7e9ea); border:1px solid var(--modal-border, #333); border-radius:16px; box-shadow:0 8px 24px rgba(29,155,240,.2); transition:background-color .2s,color .2s,border-color .2s; } .adv-modal-header{padding:12px 16px;border-bottom:1px solid var(--modal-border,#333);cursor:move;display:flex;justify-content:space-between;align-items:center} .adv-modal-title-left{display:flex;align-items:center;gap:8px;} .adv-modal-header h2{margin:0;font-size:18px;font-weight:700} .adv-settings-btn{ margin-left:6px; width:26px;height:26px; border-radius:9999px; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; padding:0; } .adv-settings-btn:hover{ background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1)); } .adv-settings-btn svg{ width:14px; height:14px; } .adv-modal-close{background:0 0;border:none;color:var(--modal-close-color,#e7e9ea);font-size:24px;cursor:pointer;width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color .2s} .adv-modal-close:hover{background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1))} .adv-modal-body{flex:1;overflow-y:auto;padding:0} .adv-form-group{margin-bottom:16px} .adv-form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:700;color:var(--modal-text-secondary,#8b98a5)} .adv-form-group input[type=text],.adv-form-group input[type=number],.adv-form-group input[type=date],.adv-form-group select{width:100%;background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:4px;padding:8px 12px;color:var(--modal-text-primary,#e7e9ea);font-size:15px;box-sizing:border-box} .adv-form-group input:focus,.adv-form-group select:focus{outline:0;border-color:var(--modal-primary-color)} .adv-form-group input::placeholder{color:var(--modal-text-secondary,#536471)} .adv-form-group-date-container {display:flex;gap:8px;align-items: center;} .adv-form-group-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; gap: 12px; } .adv-form-group-header label { margin-bottom: 0; white-space: nowrap; flex-shrink: 0; } .adv-select-mini { background-color: var(--modal-input-bg, #202327); color: var(--modal-text-primary, #e7e9ea); border: 1px solid var(--modal-input-border, #38444d); border-radius: 18px !important; font-size: 13px !important; height: 34px; line-height: normal; padding: 0 8px; width: auto; min-width: 90px; max-width: 200px; text-overflow: ellipsis; cursor: pointer; outline: none; } .adv-select-mini:hover { border-color: var(--modal-text-secondary, #8b98a5); } .adv-select-mini:focus { border-color: var(--modal-primary-color); } .adv-form-group-date-container input[type=date] {flex:1;min-width: 0;width: auto !important;} .adv-date-separator {color:var(--modal-text-secondary, #8b98a5);font-weight:700;user-select:none;flex-shrink:0;padding: 0 2px;} .adv-filter-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px} .adv-checkbox-group{background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:8px;padding:10px;display:flex;flex-direction:column;gap:8px} .adv-checkbox-group span{font-weight:700;font-size:14px;color:var(--modal-text-primary,#e7e9ea)} .adv-checkbox-item{display:flex;align-items:center} .adv-checkbox-item input{margin-right:8px; accent-color:var(--modal-primary-color);} .adv-checkbox-item label{color:var(--modal-text-secondary,#8b98a5);margin-bottom:0} .adv-checkbox-item input[type="checkbox"]:disabled {opacity:0.5; cursor:not-allowed;} .adv-checkbox-item input[type="checkbox"]:disabled + label {opacity:0.5;cursor:not-allowed;text-decoration:line-through;} .adv-modal-footer{padding:12px 16px;border-top:1px solid var(--modal-border,#333);display:flex;justify-content:flex-end;gap:12px} .adv-modal-button{padding:5px 16px;border-radius:9999px;border:1px solid var(--modal-text-secondary,#536471);background-color:transparent;color:var(--modal-text-primary,#e7e9ea);font-weight:700;cursor:pointer;transition:background-color .2s} .adv-modal-button:hover{background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1))} .adv-modal-button.primary, .adv-chip.primary { background-color:var(--modal-primary-color); border-color:var(--modal-primary-color); color:var(--modal-primary-text-color); } .adv-modal-button.primary:hover{background-color:var(--modal-primary-color-hover)} .adv-modal-button[disabled]{opacity:.5; cursor:not-allowed;} #adv-settings-import.adv-modal-button[disabled]{opacity:1;} .adv-modal-body::-webkit-scrollbar{width:8px} .adv-modal-body::-webkit-scrollbar-track{background:var(--modal-scrollbar-track,#202327)} .adv-modal-body::-webkit-scrollbar-thumb{background:var(--modal-scrollbar-thumb,#536471);border-radius:4px} body.adv-dragging{user-select:none} .adv-account-label-group{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px} .adv-exclude-toggle{display:flex;align-items:center} .adv-exclude-toggle input{margin-right:4px} .adv-exclude-toggle label{font-size:13px;font-weight:normal;color:var(--modal-text-secondary,#8b98a5);cursor:pointer} hr.adv-separator{border:none;height:1px;background-color:var(--hr-color,#333);margin:20px 0;transition:background-color .2s} /* ★全タブ共通のズーム対象に拡張(検索タブの既存idにも適用維持) */ .adv-zoom-root, #adv-zoom-root{ transform-origin: top left; will-change: transform; padding:12px 11.6px 10px 11px; } #adv-zoom-root { padding-top: 16px; /* 検索タブの上余白だけを 16px に上書き */ padding-left:16px; padding-right:20px; } .adv-modal-body{ overflow:auto; } .adv-form-row.two-cols { display:grid; grid-template-columns:1fr 1fr; gap:10px; } @media (max-width: 480px) { .adv-form-row.two-cols { grid-template-columns:1fr; } } .adv-tabs { display: flex; border-bottom: 1px solid var(--modal-border, #333); padding: 0 8px 0 6px; gap: 4px; align-items: stretch; flex-wrap: wrap; container-type: inline-size; /* ▼ 固定表示設定 */ position: sticky; top: 0; z-index: 10; background-color: var(--modal-bg, #000); box-shadow: var(--modal-tabs-shadow); } .adv-tab-btn { appearance: none; border: none; background: transparent; color: var(--modal-text-secondary, #8b98a5); padding: 10px 8px; cursor: pointer; font-weight: 700; border-radius: 8px 8px 0 0; font-size: 0.78rem; /* ボタン内のテキストは折り返さない */ white-space: nowrap; /* 余ったスペースを全員で分け合う(均等配置・最大化) */ flex: 1 1 auto; text-align: center; /* なめらかな変化 */ transition: font-size 0.1s, padding 0.1s, background-color 0.2s; } .adv-tab-btn.active { color: var(--modal-text-primary, #e7e9ea); background-color: var(--modal-input-bg, #202327); border: 1px solid var(--modal-input-border, #38444d); border-bottom: none; /* アクティブタブは少し強調 */ z-index: 1; } /* ▼▼▼ コンテナクエリ: 幅に応じて最適化 ▼▼▼ */ /* 幅 480px 以下: フォントを少し小さくし、1行収まりを狙う */ @container (max-width: 480px) { .adv-tab-btn { font-size: 12px; padding: 8px 4px; } } /* 幅 380px 以下: さらにフォントを詰め、もし2行になっても違和感ないサイズに */ @container (max-width: 380px) { .adv-tab-btn { font-size: 11px; padding: 6px 2px; border-radius: 6px; /* 角丸も少し小さく */ } /* 2行になった際に上下の列がくっつきすぎないようにする */ .adv-tabs { row-gap: 2px; } /* 2行目のボーダー処理(見た目を整える) */ .adv-tab-btn.active { border-bottom: 1px solid var(--modal-input-bg, #202327); margin-bottom: -1px; } } .adv-tab-content { display:none; } .adv-tab-content.active { display:block; } .adv-secret-wrap { display:flex; align-items:center; gap:8px; } .adv-secret-btn { cursor:pointer; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-weight:700; user-select:none; display:flex; align-items:center; gap:6px; font-size:12px; } .adv-secret-btn .dot { width:7px; height:7px; border-radius:50%; background:#777; box-shadow:0 0 0px #0000; transition:all .2s; } .adv-secret-btn.off { opacity:0.9; } .adv-secret-btn.on { background-color:var(--modal-primary-color); border-color:var(--modal-primary-color); color:var(--modal-primary-text-color); } .adv-secret-btn.on .dot { background:#fff; box-shadow:0 0 8px rgba(255,255,255,.9); } .adv-list { display:flex; flex-direction:column; gap:8px; } .adv-item { position: relative; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); border-radius:8px; padding:8px; display:flex; gap:8px; align-items:flex-start; } .adv-item.dragging { opacity:.6; } .adv-item-handle { cursor:grab; user-select:none; padding:4px 6px; border-radius:6px; border:1px dashed var(--modal-border,#333); touch-action: none; } .adv-item-avatar { width:36px; height:36px; border-radius:9999px; object-fit:cover; flex:0 0 auto; background:var(--modal-border,#333); } a.adv-link { color: inherit; text-decoration: none; } a.adv-link:hover { text-decoration: underline; cursor: pointer; } .adv-item-avatar-link { display:inline-block; border-radius:9999px; } .adv-item-main { flex:1; min-width:0; } .adv-item-title { font-size:14px; font-weight:700; color:var(--modal-text-primary,#e7e9ea); word-break:break-word; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; } .adv-item-sub { font-size:12px; color:var(--modal-text-secondary,#8b98a5); margin-top:2px; display:flex; gap:6px; flex-wrap:wrap; align-items:center; } .adv-item-actions { display:flex; gap:6px; align-items:center; align-self:center; } .adv-chip { border:1px solid var(--modal-input-border,#38444d); background:transparent; color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-size:12px; cursor:pointer; } .adv-fav-btn-pos { position: absolute; right: 8px; } .adv-fav-btn-top { top: 8px; } .adv-fav-btn-bottom { bottom: 8px; } .adv-chip.danger { border-color:#8b0000; color:#ffb3b3; } .adv-modal-button.danger { border-color:#8b0000; color:#ffb3b3; } .adv-modal-button.danger:hover{ background-color:rgba(139,0,0,0.2); } .adv-chip.scope { padding:2px 6px; font-size:11px; line-height:1.2; opacity:0.95; } .adv-toast { position:fixed; z-index:10001; left:50%; transform:translateX(-50%); bottom:24px; background:#111a; color:#fff; backdrop-filter: blur(6px); border:1px solid #fff3; padding:8px 12px; border-radius:8px; font-weight:700; opacity:0; pointer-events:none; transition:opacity .2s, transform .2s; } .adv-toast.show { opacity:1; transform:translateX(-50%) translateY(-6px); } .adv-modal-footer { justify-content:flex-end; } .adv-modal-footer .adv-modal-button#adv-save-button { margin-right:auto; } .adv-tab-toolbar { display:flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom:12px; padding: 0 2px; } /* ツールバーの左側(検索・ソート) */ .adv-tab-toolbar-left { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 150px; } /* ツールバーの右側(すべて削除ボタン) */ .adv-tab-toolbar-right { display: flex; flex: 0 0 auto; } /* ツールバー入力欄の共通スタイル */ .adv-select, .adv-input { background-color:var(--modal-input-bg,#202327); border:1px solid var(--modal-input-border,#38444d); border-radius:8px; padding:6px 10px; color:var(--modal-text-primary,#e7e9ea); } /* 検索ボックスとセレクトボックスのスタイル(.adv-folder-toolbar内と共通化) */ /* 共通スタイルは .adv-input, .adv-select が担当 */ .adv-tab-toolbar .adv-input { flex: 1; min-width: 80px; } .adv-tab-toolbar .adv-select { flex: 0 1 auto; } [data-testid="cellInnerDiv"][data-adv-hidden], article[data-adv-hidden] { display:none !important; content-visibility: hidden; contain: strict; } #advanced-search-modal { max-height:none; } .adv-resizer { position:absolute; z-index:10002; background:transparent; } .adv-resizer.e, .adv-resizer.w { top:-3px; bottom:-3px; width:8px; } .adv-resizer.e { right:-3px; cursor: ew-resize; } .adv-resizer.w { left:-3px; cursor: ew-resize; } .adv-resizer.n, .adv-resizer.s { left:-3px; right:-3px; height:8px; } .adv-resizer.n { top:-3px; cursor: ns-resize; } .adv-resizer.s { bottom:-3px; cursor: ns-resize; } .adv-resizer.se, .adv-resizer.ne, .adv-resizer.sw, .adv-resizer.nw { width:12px; height:12px; } .adv-resizer.se { right:-4px; bottom:-4px; cursor:nwse-resize; } .adv-resizer.ne { right:-4px; top:-4px; cursor:nesw-resize; } .adv-resizer.sw { left:-4px; bottom:-4px; cursor:nesw-resize; } .adv-resizer.nw { left:-4px; top:-4px; cursor:nwse-resize; } /* ▶ Mute タブ */ .adv-mute-add { display:flex; gap:8px; align-items:center; margin-bottom:10px; } .adv-mute-add input[type=text]{ flex:1; border-radius:8px; padding: 6px 10px; font-size: 14px; } .adv-mute-list { display:flex; flex-direction:column; gap:8px; } /* ▼ グローバル無効(マスターOFF)のとき:リスト全体を淡く */ .adv-mute-list.disabled { opacity: .6; filter: grayscale(35%); } /* ▼ 個別無効(enabled=false)の行だけ淡く+打ち消し等の視覚 */ .adv-mute-item { border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); border-radius:8px; padding:8px 10px; display:flex; gap:10px; justify-content: space-between; align-items:center; transition: opacity .15s ease, filter .15s ease, border-color .15s ease; } .adv-mute-item.disabled { opacity: .55; filter: grayscale(25%); border-color: color-mix(in oklab, var(--modal-input-border,#38444d), transparent 20%); } .adv-mute-item.disabled .adv-mute-word { color: var(--modal-text-secondary,#8b98a5); text-decoration: line-through; } /* 左側のコンテナ(単語+オプション) */ .adv-mute-content-left { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } .adv-mute-word { font-weight:700; color:var(--modal-text-primary,#e7e9ea); word-break:break-word; font-size: 14px; } /* 左下のオプション群 */ .adv-mute-options-row { display: flex; gap: 12px; align-items: center; } /* 右側のコンテナ(削除ボタンのみ) */ .adv-mute-actions-right { display:flex; align-items:center; justify-content:center; flex: 0 0 auto; white-space: nowrap; padding-left: 4px; } @media (max-width: 480px) { .adv-mute-actions { margin-top: 4px; } } .adv-toggle { display: inline-flex; gap: 6px; align-items: center; color: var(--modal-text-secondary,#8b98a5); line-height: 1; margin-bottom:0!important; } .adv-toggle input[type="checkbox"] { width: 14px; height: 14px; margin: 0; flex: 0 0 auto; vertical-align: middle; } .adv-toggle span { font-size: 11px; line-height: 1; } /* ▼▼▼ Mute Header Fix ▼▼▼ */ .adv-mute-header { display:flex; justify-content:space-between; align-items:center; margin: 4px 0 12px; gap: 10px; flex-wrap: nowrap; /* 折り返しを禁止して1行に強制 */ } .adv-mute-header input[type="text"] { flex: 1; min-width: 0; border-radius: 8px; padding: 6px 10px; font-size: 14px; background-color: var(--modal-input-bg,#202327); border: 1px solid var(--modal-input-border,#38444d); color: var(--modal-text-primary,#e7e9ea); } .adv-mute-header input[type="text"]:focus { outline: 0; border-color: var(--modal-primary-color); } .adv-mute-title { font-weight:700; color: var(--modal-text-primary,#e7e9ea); white-space: nowrap; /* テキスト折り返し禁止 */ overflow: hidden; text-overflow: ellipsis; /* 溢れたら...にする */ flex-shrink: 1; /* 幅不足時はタイトル側を縮める */ min-width: 0; } .adv-mute-header-controls { display: flex; align-items: center; gap: 8px; /* 余白を少し詰める */ flex-shrink: 0; /* 操作パネルは縮めない */ } #adv-mute-mode { padding: 3px 24px 3px 8px; /* 矢印スペース考慮 */ font-size: 12px; height: 28px; cursor: pointer; width: auto; } /* マスター切替の一瞬だけ付けるガードクラス */ .adv-no-anim, .adv-no-anim * { transition: none !important; } #adv-history-empty:not(:empty), #adv-saved-empty:not(:empty), #adv-favorites-empty:not(:empty), #adv-accounts-empty:not(:empty), #adv-lists-empty:not(:empty) { padding-inline: 7px; } #adv-mute-empty:not(:empty) { padding-top: 6px; } /* ▼ マスターOFF中は、個別無効の“さらに薄く”を抑制(親の薄さのみ適用) */ .adv-mute-list.disabled .adv-mute-item.disabled { opacity: 1; /* 子の追加の薄さを無効化(親のopacityのみが効く) */ filter: none; /* 子の追加グレースケールも無効化(親側のfilterのみ適用) */ /* ボーダーだけ通常色に戻す */ /* border-color: var(--modal-input-border,#38444d); */ } /* === Trigger: モーダルと同質の見た目に合わせる === */ #advanced-search-trigger.adv-trigger-search { width: 49px; height: 49px; border-radius: 9999px; background-color: var(--modal-bg, #000); color: var(--modal-text-primary, #e7e9ea); border: 2px solid var(--modal-border, #2f3336); /* ← モーダルと同じ枠色 */ box-shadow: 0 8px 24px rgba(29,155,240,.2); /* ← モーダルと同じshadow */ display:flex; align-items:center; justify-content:center; transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; } #advanced-search-trigger.adv-trigger-search:hover { /* 背景は変えず、浮かせる表現だけ強化 */ transform: translateZ(0) scale(1.04); box-shadow: 0 12px 36px rgba(29,155,240,.28); border-color: var(--modal-border, #2f3336); } #advanced-search-trigger.adv-trigger-search:active { transform: translateZ(0) scale(0.98); box-shadow: 0 6px 18px rgba(29,155,240,.22); } #advanced-search-trigger.adv-trigger-search:focus-visible { outline: none; box-shadow: 0 8px 24px rgba(29,155,240,.2), 0 0 0 3px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 45%, transparent); } #advanced-search-trigger.adv-trigger-search svg { width: 22px; height: 22px; display:block; /* 検索アイコンは stroke="currentColor" を使っているので配色は自動追従 */ } /* リストコンテナ自体に十分な高さを確保し、下部にドロップ用の余白を強制的に広げる */ #adv-accounts-list, #adv-lists-list, #adv-saved-list { min-height: 200px; /* アイテムが空でもドロップできるようにする */ padding-bottom: 20px; box-sizing: content-box; /* padding分を確実に高さに加える */ } /* 未分類セクションが空の時も、ドラッグ中は少し広げて受け入れやすくする */ body.adv-dragging .adv-unassigned { min-height: 60px; background-color: rgba(128, 128, 128, 0.05); /* 視覚的にエリアを暗示 */ border-radius: 8px; transition: min-height 0.2s ease, background-color 0.2s; } /* === Folders === */ .adv-folder { border:1px solid var(--modal-input-border,#38444d); border-radius:10px; margin-bottom:10px; } .adv-folder-header { display:flex; justify-content:space-between; align-items:center; padding:8px 10px; background:var(--modal-input-bg,#202327); border-bottom:1px solid var(--modal-input-border,#38444d); } .adv-folder[data-drop="1"] { outline:2px dashed var(--modal-primary-color); outline-offset:-2px; } .adv-folder-title { display:flex; gap:8px; align-items:baseline; } .adv-folder-actions { display:flex; gap:6px; } .adv-folder-toolbar { display:flex; gap:8px; align-items:center; margin:0 0 12px; padding:0 2px; } .adv-folder-toolbar input[type="text"] { flex:1; min-width:80px; } .adv-folder-collapsed .adv-list { display:none; } /* ▶ Folder headers: show grab cursor except on action buttons */ .adv-folder-header { cursor: grab; touch-action: none; } .adv-folder-header:active { cursor: grabbing; } /* ボタン上では通常のポインタ(=ドラッグ開始させない見た目) */ .adv-folder-header .adv-folder-actions, .adv-folder-header .adv-folder-actions * { cursor: pointer; } /* ▼ トグルボタン(左端) */ .adv-folder-toggle { appearance: none; border: none; background: transparent; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; margin-right: 6px; } .adv-folder-toggle:focus-visible { outline: none; box-shadow: 0 0 0 2px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 60%, transparent); } /* ▼ アイコン(chevron) */ .adv-folder-toggle svg { width: 16px; height: 16px; transition: transform .15s ease; } /* ▼ 開閉で向きを変える(右▶ → 下▼) */ .adv-folder:not(.adv-folder-collapsed) .adv-folder-toggle svg { transform: rotate(90deg); } /* ▼ 開いているヘッダーはわずかに背景強調 */ .adv-folder:not(.adv-folder-collapsed) .adv-folder-header { background: color-mix(in oklab, var(--modal-input-bg,#202327) 92%, var(--modal-primary-color,#1d9bf0)); } /* ▼ ドラッグハンドルは“掴める”見た目を強調 */ .adv-folder-drag-handle { cursor: grab; user-select: none; padding: 4px 6px; border-radius: 6px; border: 1px dashed var(--modal-border,#38444d); } .adv-folder-drag-handle:active { cursor: grabbing; } /* ▼ Unassigned セクション(見出しなし・枠なし) */ .adv-unassigned { margin-bottom: 10px; min-height: 30px; /* ★ 空の時でもドロップできるように最小高さを確保 */ } .adv-unassigned .adv-list { display: flex; flex-direction: column; gap: 8px; } /* フォルダー並び替え用のドラッグ時の視覚(Unassigned も対象) */ .adv-unassigned.dragging-folder { opacity: .6; } /* タブ背景およびリストコンテナ背景へのドロップハイライト */ #adv-tab-accounts.adv-bg-drop-active, #adv-tab-lists.adv-bg-drop-active, #adv-tab-saved.adv-bg-drop-active, #adv-accounts-list.adv-bg-drop-active, #adv-lists-list.adv-bg-drop-active, #adv-saved-list.adv-bg-drop-active { outline: 2px dashed var(--modal-primary-color, #1d9bf0); /* リストコンテナ側はパディングが無いためオフセットを小さく */ outline-offset: -4px; } /* タブパネル(上部余白)側は既存のオフセットを維持 */ #adv-tab-accounts.adv-bg-drop-active, #adv-tab-lists.adv-bg-drop-active, #adv-tab-saved.adv-bg-drop-active { outline-offset: -8px; } /* 背景(Unassigned 宛て)をドロップ中は、フォルダー内の“薄い残像”を消す */ #adv-tab-accounts.adv-bg-drop-active .adv-list .adv-item.dragging, #adv-accounts-list.adv-bg-drop-active .adv-list .adv-item.dragging, #adv-tab-lists.adv-bg-drop-active .adv-list .adv-item.dragging, #adv-lists-list.adv-bg-drop-active .adv-list .adv-item.dragging, #adv-tab-saved.adv-bg-drop-active .adv-list .adv-item.dragging, #adv-saved-list.adv-bg-drop-active .adv-list .adv-item.dragging { display: none !important; } /* === Settings modal === */ #adv-settings-modal.adv-settings-modal{ position:fixed; inset:0; z-index:10001; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,.5); } .adv-settings-dialog{ width:420px; max-width:90vw; max-height:80vh; background-color:var(--modal-bg,#000); color:var(--modal-text-primary,#e7e9ea); border-radius:16px; border:1px solid var(--modal-border,#333); box-shadow:0 8px 24px rgba(0,0,0,.3); display:flex; flex-direction:column; overflow:hidden; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; } .adv-settings-header{ padding:12px 16px; border-bottom:1px solid var(--modal-border,#333); display:flex; align-items:center; justify-content:space-between; } .adv-settings-title{ margin:0; font-size:16px; font-weight:700; } .adv-settings-close{ border:none; background:transparent; color:var(--modal-close-color,#e7e9ea); font-size:20px; width:32px; height:32px; border-radius:50%; display:flex; align-items:center; justify-content:center; cursor:pointer; } .adv-settings-close:hover{ background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1)); } .adv-settings-body{ padding:12px 16px 23px 16px; overflow-y:auto; display:flex; flex-direction:column; gap:16px; } .adv-settings-group label{ display:block; margin-bottom:4px; font-size:14px; font-weight:700; color:var(--modal-text-secondary,#8b98a5); } .adv-settings-group select, .adv-settings-group textarea{ width:100%; background-color:var(--modal-input-bg,#202327); border:1px solid var(--modal-input-border,#38444d); border-radius:8px; padding:8px 10px; color:var(--modal-text-primary,#e7e9ea); font-size:14px; box-sizing:border-box; } .adv-settings-group textarea{ resize:vertical; min-height:80px; } .adv-settings-section-header { margin: 12px 0 2px 0; padding-bottom: 4px; border-bottom: 1px solid var(--modal-border,#333); font-size: 13px; font-weight: 700; color: var(--modal-text-primary,#e7e9ea); } .adv-settings-toggle-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; } .adv-settings-toggle-row .adv-toggle { font-size: 14px; color: var(--modal-text-primary,#e7e9ea); user-select: none; cursor: pointer; } .adv-settings-toggle-row .adv-toggle span { font-size: 14px; } /* Simple toggle switch CSS */ .adv-switch { position: relative; display: inline-block; width: 40px; height: 22px; } .adv-switch input { opacity: 0; width: 0; height: 0; } .adv-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--modal-input-border,#38444d); transition: .2s; border-radius: 22px; } .adv-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: var(--modal-bg, #000); transition: .2s; border-radius: 50%; } .adv-switch input:checked + .adv-slider { background-color: var(--modal-primary-color); } .adv-switch input:checked + .adv-slider:before { transform: translateX(18px); } .adv-settings-actions-inline{ display:flex; gap:8px; margin-top:6px; flex-wrap:wrap; } .adv-settings-footer{ padding:10px 16px; border-top:1px solid var(--modal-border,#333); display:flex; justify-content:flex-end; gap:8px; } /* === Tab Drag & Drop === */ .adv-tab-btn { user-select: none; } .adv-tab-btn:active { cursor: grabbing; } .adv-tab-btn.dragging { opacity: .5; } /* --- Favorite Tags CSS --- */ /* ▼ ブックマークUI専用の配色変数を定義 */ :root { /* デフォルト (Dim / Dark) は静的なダークテーマ */ --ft-bg: rgb(21, 24, 28); --ft-border-light: rgba(239, 243, 244, 0.24); --ft-border-dim: rgba(239, 243, 244, 0.15); --ft-border-strong: rgba(239, 243, 244, 0.3); --ft-border-accent: rgba(239, 243, 244, 0.8); --ft-text-primary: rgb(239, 243, 244); --ft-text-secondary: rgba(239, 243, 244, 0.7); --ft-input-bg: rgba(0,0,0,0.2); --ft-input-border: rgba(239,243,244,0.2); --ft-hover-bg: rgba(255, 255, 255, 0.06); --ft-hover-bg-strong: rgba(255, 255, 255, 0.08); --ft-accent-color: #1d9bf0; } :root.x-theme-light { /* Lightテーマの時だけ、X本体の動的変数を参照する */ --ft-bg: var(--modal-bg); --ft-border-light: var(--modal-border); --ft-border-dim: var(--modal-border); --ft-border-strong: var(--modal-text-secondary); --ft-border-accent: var(--modal-text-primary); --ft-text-primary: var(--modal-text-primary); --ft-text-secondary: var(--modal-text-secondary); --ft-input-bg: var(--modal-input-bg); --ft-input-border: var(--modal-input-border); --ft-hover-bg: var(--modal-button-hover-bg); --ft-hover-bg-strong: var(--modal-button-hover-bg); --ft-accent-color: var(--modal-primary-color); } /* Tag chip on tweet header */ .ft-tag-chip { display: inline-flex; align-items: center; margin-left: 4px; /* JS (ft_attachTagChipToArticle) 側の gap: 4px と連動 */ padding: 1px 8px; border-radius: 9999px; border: 1px solid currentColor; font-size: 11px; line-height: 1.4; cursor: pointer; user-select: none; white-space: nowrap; background: rgba(255, 255, 255, 0.03); /* これは静的なまま (ほぼ透明なので) */ flex: 0 0 auto; order: 9999; } .ft-tag-chip-label { max-width: 150px; overflow: hidden; text-overflow: ellipsis; } .ft-tag-chip-uncategorized { opacity: 0.7; } /* Dropdown for selecting tag / filter */ .ft-tag-dropdown { position: fixed; z-index: 2147482000; min-width: 220px; max-width: 260px; max-height: 60vh; overflow-y: auto; padding: 8px; border-radius: 12px; border: 1px solid var(--ft-border-light); background: var(--ft-bg); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.7); font-size: 13px; color: var(--ft-text-primary); } .ft-tag-dropdown-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-weight: 600; } .ft-tag-dropdown-close { border: none; background: transparent; color: inherit; cursor: pointer; padding: 2px 4px; } .ft-tag-dropdown-tags { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; } .ft-tag-dropdown-tag-item { display: flex; align-items: center; padding: 4px 6px; border-radius: 6px; cursor: pointer; } .ft-tag-dropdown-tag-item:hover { background: var(--ft-hover-bg); } .ft-tag-dropdown-tag-color { width: 10px; height: 10px; border-radius: 9999px; margin-right: 6px; } .ft-tag-dropdown-tag-label { flex: 1; } .ft-tag-dropdown-tag-selected::after { content: '✓'; margin-left: 6px; font-size: 11px; } /* New tag row in dropdown */ .ft-tag-dropdown-new { border-top: 1px solid var(--ft-border-dim); padding-top: 6px; display: flex; flex-direction: column; gap: 4px; } .ft-tag-dropdown-new-row { display: flex; gap: 4px; } .ft-tag-dropdown-new-input { flex: 1; background: var(--ft-input-bg); border: 1px solid var(--ft-input-border); border-radius: 6px; padding: 3px 6px; color: inherit; } .ft-tag-dropdown-new-color { width: 36px; padding: 0; border-radius: 6px; border: 1px solid var(--ft-input-border); background: transparent; } .ft-tag-dropdown-new-button { border-radius: 6px; border: 1px solid var(--ft-border-strong); background: transparent; color: inherit; padding: 2px 6px; font-size: 12px; cursor: pointer; } .ft-tag-dropdown-new-button:hover { background: var(--ft-hover-bg); } /* Bookmark header controls (テーマ変数適用) */ .ft-filter-button { border-radius: 8px; border: 1px solid var(--modal-border, rgba(239,243,244,0.3)); background: var(--modal-input-bg, rgba(0,0,0,0.2)); color: var(--modal-text-primary, rgb(239,243,244)); font-size: 14px; padding: 4px 10px; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; } .ft-filter-button-label { max-width: 140px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ft-filter-button-caret { font-size: 10px; opacity: 0.8; } .ft-filter-button[disabled] { opacity: 0.4; cursor: default; } .ft-filter-button:not([disabled]):hover { background: var(--modal-button-hover-bg, rgba(255,255,255,0.06)); border-color: var(--modal-text-secondary, rgba(239,243,244,0.6)); } .ft-settings-button { border-radius: 9999px; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--modal-border, rgba(239,243,244,0.3)); background: var(--modal-input-bg, rgba(0,0,0,0.2)); color: var(--modal-text-primary, rgb(239,243,244)); cursor: pointer; } .ft-settings-button:hover { background: var(--modal-button-hover-bg, rgba(255,255,255,0.06)); } /* Settings modal */ .ft-modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 2147483000; display: flex; align-items: center; justify-content: center; } .ft-modal { width: min(380px, 100vw - 32px); max-height: 80vh; border-radius: 16px; background: var(--ft-bg); border: 1px solid var(--ft-border-light); box-shadow: 0 20px 40px rgba(0,0,0,0.75); display: flex; flex-direction: column; color: var(--ft-text-primary); } .ft-modal-header { padding: 10px 14px; border-bottom: 1px solid var(--ft-border-dim); display: flex; align-items: center; justify-content: space-between; gap: 8px; } .ft-modal-title { font-size: 14px; font-weight: 600; } .ft-modal-toggle { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; } .ft-modal-toggle input[type="checkbox"] { accent-color: var(--ft-accent-color); } .ft-modal-body { padding: 10px 14px 12px; overflow-y: auto; font-size: 13px; } .ft-modal-footer { padding: 8px 14px 10px; border-top: 1px solid var(--ft-border-dim); display: flex; justify-content: flex-end; gap: 8px; } .ft-modal-button { border-radius: 9999px; border: 1px solid var(--ft-border-strong); background: transparent; color: inherit; font-size: 12px; padding: 4px 10px; cursor: pointer; } .ft-modal-button:hover { background: var(--ft-hover-bg); } /* Display settings section */ .ft-modal-display-settings { margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--ft-border-dim); font-size: 12px; } .ft-modal-display-settings-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 4px; } .ft-modal-display-radio { display: inline-flex; align-items: center; gap: 4px; } /* Tag list in modal */ .ft-modal-tag-list { display: flex; flex-direction: column; gap: 6px; padding-bottom: 30px; /* 余白を大きく取る */ min-height: 100px; /* 空っぽでもドロップできるように */ position: relative; /* ルートドロップの枠線表示用 */ box-sizing: content-box; /* paddingを含めない高さ計算 */ } /* 一番下の余白にドラッグした時に、リスト全体の下に枠線を出すクラス */ .ft-modal-tag-list.ft-drag-to-root::after { content: ''; position: absolute; bottom: 20px; /* 余白の中ほどに線を引く */ left: 0; right: 0; height: 2px; background-color: var(--modal-primary-color, #1d9bf0); box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0); } .ft-modal-tag-item { position: relative; display: grid; /* [mainCell] [dragHandle] [orderButtons] [deleteBtn] */ grid-template-columns: minmax(0, 1fr) auto auto auto; align-items: center; gap: 6px; /* cursor: grab; を削除 (ハンドルが担当) */ } .ft-modal-tag-main { display: flex; align-items: center; gap: 6px; } .ft-modal-tag-item-dragging { opacity: 0.6; } .ft-modal-tag-item-drop-before::before, .ft-modal-tag-item-drop-after::after { content: ''; position: absolute; left: 0; right: 0; height: 1px; /* 従来の色(白っぽいグレー) */ background-color: var(--ft-border-accent, rgba(239, 243, 244, 0.8)); border: none; /* border-top/bottom を background-color に変更して統一 */ pointer-events: none; } .ft-modal-tag-item-drop-before::before { top: -3.5px; } .ft-modal-tag-item-drop-after::after { bottom: -3.5px; } /* ルート階層用(青い線) */ .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before, .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after { background-color: var(--modal-primary-color, #1d9bf0); box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0); /* 発光させて強調 */ height: 2px; z-index: 10; } /* 青い線の位置(太くなった分、あるいは強調のため少し外側に広げる) */ .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before { top: -4.2px; } .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after { bottom: -4.2px; } .ft-modal-tag-item-drop-child { background: var(--ft-hover-bg-strong); } .ft-modal-tag-name { background: var(--ft-input-bg); border-radius: 6px; border: 1px solid var(--ft-input-border); padding: 3px 6px; color: inherit; font-size: 12px; } .ft-modal-tag-color { width: 40px; padding: 0; border-radius: 6px; border: 1px solid var(--ft-input-border); background: transparent; } .ft-modal-tag-order, .ft-modal-tag-delete { border-radius: 6px; border: 1px solid var(--ft-border-strong); background: transparent; color: inherit; padding: 2px 4px; cursor: pointer; font-size: 11px; } .ft-modal-tag-order:hover, .ft-modal-tag-delete:hover { background: var(--ft-hover-bg-strong); } /* --- Drag handle for tag settings --- */ .ft-modal-tag-drag-handle { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 4px; cursor: grab; color: var(--ft-text-secondary); user-select: none; touch-action: none; } .ft-modal-tag-drag-handle:hover { background: var(--ft-hover-bg-strong); color: var(--ft-text-primary); } /* Uncategorized: disable drag */ .ft-modal-tag-item[data-kind="uncat"] .ft-modal-tag-drag-handle { cursor: not-allowed; opacity: 0.5; } /* New tag row */ .ft-modal-new-tag { border-top: 1px solid var(--ft-border-dim); padding-top: 8px; display: flex; flex-direction: column; gap: 6px; } .ft-modal-new-tag-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px; } /* 未分類は名前変更不可&削除不可の視覚表現 */ .ft-modal-tag-name[readonly] { cursor: not-allowed; opacity: 0.8; } .ft-modal-tag-delete:disabled { cursor: not-allowed; opacity: 0.4; } /* Hidden helper */ .ft-hidden { display: none !important; content-visibility: hidden; contain: strict; } /* --- End Favorite Tags CSS --- */ /* --- Favorites Feature --- */ .adv-fav-btn { display: inline-flex; align-items: center; justify-content: center; background: transparent; border: none; cursor: pointer; color: rgb(83, 100, 113); /* Default grey */ padding: 0; margin: 0; width: 34.75px; height: 34.75px; /* X standard icon size touch target */ border-radius: 50%; transition: background-color 0.2s, color 0.2s; } /* ネイティブのクラスを借用した時は固定サイズを無効化する */ .adv-fav-btn.adv-native-style { width: auto; height: auto; min-width: 34.75px; /* 最低限の大きさは確保 */ min-height: 34.75px; } .adv-fav-btn:hover { background-color: rgba(29, 155, 240, 0.1); color: rgb(29, 155, 240); } .adv-fav-btn.active { color: rgb(249, 24, 128); /* Pink/Red like Like, or Gold? Let's use Gold for Star */ color: rgb(255, 215, 0); } .adv-fav-btn.active:hover { background-color: rgba(255, 215, 0, 0.1); } .adv-fav-btn svg { width: 20px; height: 20px; fill: currentColor; } .adv-item-body-text { font-size: 13px; color: var(--modal-text-primary); margin-top: 4px; white-space: pre-wrap; /* 改行を維持 */ word-break: break-word; /* 長い単語を折り返し */ } /* Favorites Media */ .adv-item-media-row { display: flex; gap: 4px; margin-top: 6px; overflow-x: auto; padding-bottom: 2px; } .adv-item-media-row::-webkit-scrollbar { height: 4px; } .adv-item-media-row::-webkit-scrollbar-thumb { background: var(--modal-border); border-radius: 2px; } .adv-media-thumb { height: 60px; min-width: 60px; border-radius: 6px; border: 1px solid var(--modal-border); object-fit: cover; cursor: pointer; } /* Favorites Quote */ .adv-quote-box { margin-top: 8px; border: 1px solid var(--modal-border); border-radius: 12px; padding: 8px 12px; background-color: rgba(0, 0, 0, 0.03); } .adv-quote-header { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; font-size: 12px; } .adv-quote-avatar { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; } .adv-quote-name { font-weight: 700; color: var(--modal-text-primary); } .adv-quote-handle { color: var(--modal-text-secondary); } .adv-quote-text { font-size: 13px; color: var(--modal-text-primary); white-space: pre-wrap; word-break: break-word; } /* Content Link */ .adv-content-link { color: var(--modal-primary-color); text-decoration: none; } .adv-content-link:hover { text-decoration: underline; } /* Media Play Icon */ .adv-media-wrap { position: relative; display: inline-flex; } .adv-media-play-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 24px; height: 24px; background-color: rgba(0, 0, 0, 0.6); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; pointer-events: none; /* クリックを下の画像(リンク)に透過させる */ backdrop-filter: blur(1px); z-index: 1; } .adv-media-play-icon svg { width: 14px; height: 14px; fill: currentColor; display: block; margin-left: 2px; } /* Favorites Item Tag Container */ .adv-fav-tag-container { margin-top:0.7px; margin-left: 2px; display: inline-flex; align-items: center; } /* --- Mute Collapse Styles --- */ /* Hard Mute: data-adv-hidden */ [data-testid="cellInnerDiv"][data-adv-hidden], article[data-adv-hidden] { display: none !important; content-visibility: hidden; contain: strict; } /* Soft Mute: data-adv-collapsed */ /* 1. Hide original content */ [data-testid="cellInnerDiv"][data-adv-collapsed] > div:not(.adv-collapsed-placeholder), article[data-adv-collapsed] > div:not(.adv-collapsed-placeholder) { display: none !important; } /* 2. Show placeholder */ .adv-collapsed-placeholder { display: none; align-items: center; justify-content: space-between; padding: 12px 16px; background-color: var(--modal-input-bg, #202327); border-bottom: 1px solid var(--modal-border, #38444d); cursor: pointer; user-select: none; } .adv-collapsed-placeholder:hover { background-color: color-mix(in srgb, var(--modal-input-bg, #202327) 85%, var(--modal-text-primary, #e7e9ea)); } [data-testid="cellInnerDiv"][data-adv-collapsed] .adv-collapsed-placeholder, article[data-adv-collapsed] .adv-collapsed-placeholder { display: flex !important; } .adv-collapsed-label { flex: 1; font-size: 13px; color: var(--modal-text-secondary, #8b98a5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 12px; } .adv-btn-show { background: transparent; border: 1px solid var(--modal-primary-color, #1d9bf0); color: var(--modal-primary-color, #1d9bf0); border-radius: 9999px; padding: 4px 16px; font-size: 12px; font-weight: 700; cursor: pointer; transition: background-color 0.2s; } .adv-btn-show:hover { background-color: rgba(29, 155, 240, 0.1); } /* タグチップのサイズ微調整 */ .adv-item-sub .ft-tag-chip { margin-left: 8px; font-size: 10px; padding: 0 6px; height: 18px; } /* ▼▼▼ 再ミュートボタンのスタイル ▼▼▼ */ .adv-btn-remute { margin-right: 12px; /* Caret(…)との間隔を確保 */ padding: 4px 12px; /* クリックしやすいよう少し拡大 */ font-size: 12px; font-weight: 700; border-radius: 9999px; border: 1px solid var(--modal-border, #38444d); color: var(--modal-text-secondary, #8b98a5); background: transparent; cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; height: 28px; /* ヘッダーのアクションボタンの高さに合わせる */ line-height: 1; transition: all 0.2s; } .adv-btn-remute:hover { background: rgba(244, 33, 46, 0.1); /* Red tint */ color: rgb(244, 33, 46); border-color: rgb(244, 33, 46); } /* 検索入力中、モーダルを背景レベルまで下げる */ #advanced-search-modal.adv-z-lower { z-index: 0 !important; } /* 検索入力中、Xのアプリ全体をモーダルの上に持ち上げる */ /* #react-root は body 直下の X アプリケーションのルート要素 */ #react-root.adv-app-lifted { z-index: 1 !important; position: relative !important; /* z-indexを効かせるために必須 */ } /* === Native Search Resizer === */ form[role="search"] { position: relative !important; /* リサイザーの基準点 */ max-width: none !important; /* 幅制限の解除 */ } .adv-native-search-resizer { position: absolute; right: -8px; top: 0; bottom: 0; width: 16px; cursor: col-resize; z-index: 9999; background: transparent; touch-action: none; /* スマホでのスクロール干渉防止 */ } .adv-native-search-resizer:hover { background: rgba(29,155,240,0.15); /* ホバー時に薄く青色を表示 */ } `); const modalHTML = `


`; const initialize = async () => { i18n.init(); const kv = { get(key, def) { try { return GM_getValue(key, def); } catch (_) { return def; } }, set(key, val) { try { GM_setValue(key, val); } catch (_) {} }, del(key) { try { GM_deleteValue(key); } catch (_) {} }, }; const loadJSON = (key, def) => { try { const raw = kv.get(key, JSON.stringify(def)); return JSON.parse(raw); } catch(_) { return def; } }; const saveJSON = (key, value) => { try { kv.set(key, JSON.stringify(value)); } catch(_) {} }; const DEFAULT_TABS = ['search', 'history', 'saved', 'favorites', 'mute', 'lists', 'accounts']; const DEFAULT_TABS_VISIBILITY = { search: true, history: true, saved: true, favorites: true, mute: true, lists: true, accounts: true, }; const loadTabsVisibility = () => { const stored = loadJSON(TABS_VISIBILITY_KEY, DEFAULT_TABS_VISIBILITY); const normalized = { ...DEFAULT_TABS_VISIBILITY }; for (const key of DEFAULT_TABS) { normalized[key] = stored[key] === false ? false : true; // false のみ明示的に引き継ぐ } return normalized; }; const saveTabsVisibility = (state) => { saveJSON(TABS_VISIBILITY_KEY, state); }; /* --- Favorite Tags: Code Block --- */ // ------------- 定数 & 状態 ------------- // const FT_STATE_KEY = 'ftTagState_v1'; const FT_FILTER_ALL = 'all'; const FT_FILTER_UNCATEGORIZED = 'uncategorized'; const FT_TWEET_ID_REGEX = /\/status\/(\d+)/; let ft_state = null; let ft_initialized = false; let ft_currentFilter = FT_FILTER_ALL; let ft_currentDropdown = null; let ft_settingsModalBackdrop = null; let ft_dragSrcEntry = null; // ------------- State 管理 ------------- // function ft_createDefaultState() { return { enabled: true, tags: [], tweetTags: {}, uncategorized: { color: '#8899A6', order: 0 }, display: { mode: 'leaf' }, }; } function ft_normalizeTagOrdersFor(stateObj) { if (!stateObj || !Array.isArray(stateObj.tags)) return; const groups = new Map(); for (const tag of stateObj.tags) { if (!tag || typeof tag !== 'object') continue; const pid = tag.parentId || null; if (!groups.has(pid)) groups.set(pid, []); groups.get(pid).push(tag); } for (const arr of groups.values()) { arr.sort((a, b) => (typeof a.order === 'number' ? a.order : 0) - (typeof b.order === 'number' ? b.order : 0)); arr.forEach((tag, i) => { tag.order = i; }); } } function ft_countRootTagsFor(stateObj) { if (!stateObj || !Array.isArray(stateObj.tags)) return 0; return stateObj.tags.filter((t) => !t.parentId).length; } function ft_clampUncategorizedOrderFor(stateObj) { if (!stateObj) return; if (!stateObj.uncategorized || typeof stateObj.uncategorized !== 'object') { stateObj.uncategorized = { color: '#8899A6', order: 0 }; } const rootCount = ft_countRootTagsFor(stateObj); let pos = typeof stateObj.uncategorized.order === 'number' ? stateObj.uncategorized.order : 0; if (pos < 0) pos = 0; if (pos > rootCount) pos = rootCount; stateObj.uncategorized.order = pos; } function ft_normalizeTagOrders() { if (ft_state) ft_normalizeTagOrdersFor(ft_state); } function ft_clampUncategorizedOrder() { if (ft_state) ft_clampUncategorizedOrderFor(ft_state); } function ft_loadState() { try { const parsed = loadJSON(FT_STATE_KEY, null); if (!parsed || typeof parsed !== 'object') return ft_createDefaultState(); if (!Array.isArray(parsed.tags)) parsed.tags = []; if (!parsed.tweetTags || typeof parsed.tweetTags !== 'object') parsed.tweetTags = {}; parsed.enabled = true; if (!parsed.uncategorized || typeof parsed.uncategorized !== 'object') { parsed.uncategorized = { color: '#8899A6', order: 0 }; } else { if (!parsed.uncategorized.color) parsed.uncategorized.color = '#8899A6'; if (typeof parsed.uncategorized.order !== 'number') parsed.uncategorized.order = 0; } if (!parsed.display || typeof parsed.display !== 'object') { parsed.display = { mode: 'leaf' }; } else if (parsed.display.mode !== 'leaf' && parsed.display.mode !== 'full') { parsed.display.mode = 'leaf'; } ft_normalizeTagOrdersFor(parsed); ft_clampUncategorizedOrderFor(parsed); return parsed; } catch (e) { return ft_createDefaultState(); } } function ft_saveState(newState) { if (newState) ft_state = newState; try { if (ft_state) { ft_normalizeTagOrdersFor(ft_state); ft_clampUncategorizedOrderFor(ft_state); saveJSON(FT_STATE_KEY, ft_state); } } catch (e) {} requestAnimationFrame(() => { ft_refreshAllTagChips(); // お気に入りタブが開いていれば再描画してタグ変更/絞り込みを反映 if (getActiveTabName() === 'favorites') { renderFavorites(); } }); } function ft_generateTagId() { return 'tag_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8); } function ft_getTagById(tagId) { return ft_state.tags.find((t) => t.id === tagId) || null; } function ft_getAllTags() { return ft_state.tags.slice(); } function ft_getTagColor(tagId) { const tag = ft_getTagById(tagId); return tag ? tag.color || '#1d9bf0' : '#8899A6'; } function ft_getUncategorizedColor() { return ft_state?.uncategorized?.color || '#8899A6'; } function ft_createNewTag(name, color, parentId) { const pid = parentId || null; const siblingsCount = ft_state.tags.filter((t) => (t.parentId || null) === pid).length; const tag = { id: ft_generateTagId(), name, color, parentId: pid, order: siblingsCount, }; ft_state.tags.push(tag); return tag; } function ft_countRootTags() { return ft_countRootTagsFor(ft_state); } function ft_getTagAncestors(tag) { const result = []; if (!tag) return result; const seen = new Set(); let current = tag; while (current) { if (seen.has(current.id)) break; seen.add(current.id); result.unshift(current); if (!current.parentId) break; current = ft_getTagById(current.parentId); } return result; } function ft_getTagFullPath(tag) { const ancestors = ft_getTagAncestors(tag); if (!ancestors.length) return tag ? tag.name || '' : ''; return ancestors.map((t) => t.name || '').join(' / '); } function ft_getTagDisplayLabelFromTag(tag) { if (!tag) return ''; const mode = ft_state?.display?.mode; if (mode === 'full') return ft_getTagFullPath(tag); return tag.name; } function ft_getTagListWithUncategorized() { const result = []; if (!ft_state || !Array.isArray(ft_state.tags)) return result; const byParent = new Map(); for (const tag of ft_state.tags) { if (!tag || typeof tag !== 'object') continue; const pid = tag.parentId || null; if (!byParent.has(pid)) byParent.set(pid, []); byParent.get(pid).push(tag); } for (const arr of byParent.values()) { arr.sort((a, b) => (typeof a.order === 'number' ? a.order : 0) - (typeof b.order === 'number' ? b.order : 0)); } function dfs(parentId, depth) { const arr = byParent.get(parentId || null); if (!arr) return; for (const tag of arr) { result.push({ tag, depth }); dfs(tag.id, depth + 1); } } dfs(null, 0); const entries = []; const rootCount = result.filter((e) => e.depth === 0).length; let uncatPos = ft_state.uncategorized.order || 0; if (uncatPos < 0) uncatPos = 0; if (uncatPos > rootCount) uncatPos = rootCount; let rootIndex = 0; for (const item of result) { if (item.depth === 0 && rootIndex === uncatPos) { entries.push({ kind: 'uncat', depth: 0 }); } entries.push({ kind: 'tag', tag: item.tag, depth: item.depth }); if (item.depth === 0) rootIndex++; } if (rootCount === 0 || uncatPos === rootCount) { entries.push({ kind: 'uncat', depth: 0 }); } return entries; } function ft_isTagInSubtree(tagId, rootTagId) { // ft_state が存在しない場合は即座に false を返す if (!ft_state || !tagId || !rootTagId) return false; if (tagId === rootTagId) return true; let current = ft_getTagById(tagId); const visited = new Set(); while (current && current.parentId) { if (visited.has(current.id)) break; visited.add(current.id); if (current.parentId === rootTagId) return true; current = ft_getTagById(current.parentId); } return false; } function ft_wouldCreateCycle(newParentId, childId) { if (!newParentId || !childId) return false; if (newParentId === childId) return true; let current = ft_getTagById(newParentId); const visited = new Set(); while (current && current.parentId) { if (visited.has(current.id)) break; visited.add(current.id); if (current.parentId === childId) return true; current = ft_getTagById(current.parentId); } return false; } // ------------- ルート & ユーティリティ ------------- // // ツイートのDOMからIDを抽出 function ft_extractTweetId(article) { if (article.dataset.ftTweetId) return article.dataset.ftTweetId; // 引用ツイート(カード部分)の中にあるリンクを除外するための判定関数 // div[role="link"] は引用カードのコンテナに付与される属性です const isInsideQuote = (el) => { return !!el.closest('div[role="link"]'); }; // 1. 最も確実な方法: