No need to memorize search commands anymore. Adds a feature-rich floating window to X.com (Twitter) that combines an easy-to-use advanced search UI, search history, saved searches, local post (tweet) bookmarks with tags, regex-based muting, and folder-based account and list management.
// ==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.6.2
// @description No need to memorize search commands anymore. Adds a feature-rich floating window to X.com (Twitter) that combines an easy-to-use advanced search UI, search history, saved searches, local post (tweet) bookmarks with tags, regex-based muting, and folder-based account and list management.
// @description:ja æ€çŽ¢ã³ãã³ãã¯ããèŠããå¿
èŠãªãã誰ã«ã§ã䜿ããããé«åºŠãªæ€çŽ¢UIãæ€çŽ¢å±¥æŽãæ€çŽ¢æ¡ä»¶ã®ä¿åãæçš¿ïŒãã€ãŒãïŒãã¿ã°ã§ç®¡çã§ããããŒã«ã«ãæ°ã«å
¥ãæ©èœãæ£èŠè¡šçŸå¯Ÿå¿ã®ãã¥ãŒãããã©ã«ããŒåã察å¿ã®ã¢ã«ãŠã³ãïŒãªã¹ãç®¡çæ©èœãªã©ãçµ±åããè¶
倿©èœãããŒãã£ã³ã°ãŠã£ã³ããŠã X.comïŒTwitterïŒã«è¿œå ããŸãã
// @description:en No need to memorize search commands anymore. Adds a feature-rich floating window to X.com (Twitter) that combines an easy-to-use advanced search UI, search history, saved searches, local post (tweet) bookmarks with tags, regex-based muting, and folder-based account and list management.
// @description:zh-CN æ éåæ»è®°ç¡¬èæçŽ¢åœä»€ã䞺 X.comïŒTwitterïŒæ·»å äžäžªè¶
å€åèœæµ®åšçªå£ïŒéææçšçé«çº§æçŽ¢çé¢ãæçŽ¢åå²ãå·²ä¿åçæçŽ¢æ¡ä»¶ãæ¯æäžºåžåïŒæšæïŒæ·»å æ çŸçæ¬å°æ¶èãåºäºæ£å衚蟟åŒçå±èœïŒä»¥åæ¯æææä»¶å€¹ç®¡çç莊å·åå衚åèœã
// @description:zh-TW ç¡éåæ»èšç¡¬èæå°æä»€ãçº X.comïŒTwitterïŒå¢å äžåè¶
å€åèœæžæµ®èŠçªïŒæŽåæçšçé«çŽæå°ä»é¢ãæå°çŽéãå·²ä¿åçæå°æ¢ä»¶ãå¯çšæšç±€ç®¡ç貌æïŒæšæïŒçæ¬å°æ¶èãæ£å衚瀺åŒé濟ïŒä»¥åæ¯æŽä»¥è³æå€Ÿåé¡çåž³èåå衚管çåèœã
// @description:ko ë ìŽì ê²ì ëª
ë ¹ìŽë¥Œ ìžìž íì ììµëë€. X.com(Twitter)ì ë구ë ìœê² ì¬ì©í ì ìë ê³ êž ê²ì UI, ê²ì êž°ë¡, ê²ì 조걎 ì ì¥, ê²ìêž(ížì)ì íê·žë¡ êŽëЬí ì ìë ë¡ì»¬ ìŠê²šì°Ÿêž° êž°ë¥, ì ê·ì ììê±°, íŽë ë¶ë¥ê° ê°ë¥í ê³ì ë° ëŠ¬ì€íž êŽëЬ êž°ë¥ ë±ì íµí©í ë€êž°ë¥ íë¡í
ì°œì ì¶ê°í©ëë€.
// @description:fr Plus besoin de mémoriser les commandes de recherche. Ajoute à X.com (Twitter) une fenêtre flottante trÚs complÚte regroupant une interface de recherche avancée et facile à utiliser, lâhistorique, les recherches enregistrées, des favoris locaux pour les publications (tweets) avec tags, un masquage par expressions réguliÚres (regex) et une gestion des comptes et listes avec classement par dossiers.
// @description:es ¡OlvÃdate de memorizar comandos de búsqueda! Añade a X.com (Twitter) una ventana flotante multifuncional con una interfaz de búsqueda avanzada y fácil de usar, historial, búsquedas guardadas, favoritos locales de publicaciones (tuits) con etiquetas, silenciado mediante expresiones regulares (regex) y gestión de cuentas y listas con organización por carpetas.
// @description:de Kein Auswendiglernen von Suchbefehlen mehr! FÌgt X.com (Twitter) ein multifunktionales schwebendes Fenster hinzu, das eine leicht zu bedienende erweiterte SuchoberflÀche, Suchverlauf, gespeicherte Suchanfragen, lokale Lesezeichen fÌr Posts (Tweets) mit Tags, Stummschaltung per regulÀren AusdrÌcken (Regex) und eine ordnerbasierte Konten- und Listenverwaltung vereint.
// @description:pt-BR Não precisa mais decorar comandos de busca! Adiciona ao X.com (Twitter) uma janela flutuante multifuncional com uma interface de busca avançada e fácil de usar, histórico, buscas salvas, favoritos locais de posts (tweets) com tags, silenciamento por expressões regulares (regex) e gestão de contas e listas com organização em pastas.
// @description:ru ÐПлÑÑе Ме МÑжМП запПЌОМаÑÑ Ð¿ÐŸÐžÑкПвÑе кПЌаМЎÑ! ÐПбавлÑÐµÑ ÐœÐ° X.com (Twitter) ЌМПгПÑÑМкÑОПМалÑМПе плаваÑÑее ПкМП Ñ ÑЎПбМÑÐŒ ОМÑеÑÑейÑПЌ ÑаÑÑОÑеММПгП пПОÑка, ОÑÑПÑОей, ÑПÑ
ÑаМÑММÑЌО запÑПÑаЌО, лПкалÑМÑЌО заклаЎкаЌО пПÑÑПв (ÑвОÑПв) Ñ ÑегаЌО, ÑОлÑÑÑаÑОей пП ÑегÑлÑÑМÑÐŒ вÑÑажеМОÑÐŒ (regex) О ÑпÑавлеМОеЌ аккаÑМÑаЌО О ÑпОÑкаЌО Ñ ÐŸÑгаМОзаÑОей пП папкаЌ.
// @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
// @grant unsafeWindow
// @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
// ==/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 = `
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" fill="none"></circle>
<line x1="16.65" y1="16.65" x2="22" y2="22"
stroke="currentColor" stroke-width="2" stroke-linecap="round"></line>
</svg>`;
const SETTINGS_SVG = `
<svg
viewBox="0 0 24 24"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 00-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 00-2.282.819l-.922 1.597a1.875 1.875 0 00.432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 000 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 00-.432 2.385l.922 1.597a1.875 1.875 0 002.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 002.28-.819l.923-1.597a1.875 1.875 0 00-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 000-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 00-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 00-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 00-1.85-1.567h-1.843zM12 15.75a3.75 3.75 0 100-7.5 3.75 3.75 0 000 7.5z"
/>
</svg>
`;
const FOLDER_TOGGLE_OPEN_SVG = `
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
const FOLDER_TOGGLE_CLOSED_SVG = `
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</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);
})();
});
}
// ⌠StateçæïŒReact Routerã®ä»æ§ã«æºæ ããã¯ãªãŒã³ãªStateïŒ
function createCleanState(currentPath) {
// 6æåã®ã©ã³ãã ãªè±æ°å (React Routeræšæºã®keyçæããžãã¯æš¡å£)
const key = Math.random().toString(36).slice(2, 8);
return {
key: key,
state: {
fromApp: true,
previousPath: currentPath || location.pathname,
// ããã« focalTweetId ã context ãªã©ã®ãåã®æèããå«ããªãããšã§
// React Router ã«ãæ°ãããã¥ãŒãšããŠã¬ã³ããªã³ã°ãããã
}
};
}
// ⌠SPA é·ç§»é¢æ°ïŒChrome/Firefox/Safariå
šå¯Ÿå¿ã»Sandboxçªç ŽçïŒ
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');
// 1. ã¯ãªãŒã³ãªStateãäœæ
let nextState = createCleanState(location.pathname);
// 2.ãFirefox察çãç¹æš©é å(UserScript)ã®ãªããžã§ã¯ããããŒãžé åãžè€è£œ
// ãããããªããš Firefox ã§ "Permission denied to access property" ãšã©ãŒã«ãªã
if (typeof cloneInto === 'function') {
nextState = cloneInto(nextState, document.defaultView || window);
}
// 3. ç°å¢ã«å¿ããã°ããŒãã«ãªããžã§ã¯ãã®ååŸ
// unsafeWindow ã䜿ãããªããããŒãžæ¬æ¥ã® window (å®äœ) ã䜿ã
const targetWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
// 4. History API æŽæ°
// ããŒãžå®äœã® history ãæäœãã
targetWindow.history.pushState(nextState, '', to.pathname + to.search + to.hash);
// 5. ã€ãã³ãçºç« (PopStateEvent)
// React Router 㯠window.addEventListener('popstate', ...) ã§åŸ
æ©ããŠãããã
// ããŒãžå®äœã®ã³ã³ã¹ãã©ã¯ã¿ã§ã€ãã³ããäœããããŒãžå®äœã® window ã§çºç«ãã
const PopStateEventClass = targetWindow.PopStateEvent || PopStateEvent;
const evt = new PopStateEventClass('popstate', { state: nextState });
targetWindow.dispatchEvent(evt);
// 6. é©çšåŸ
ã¡ (DOMå€åã®ç£èŠ)
const ok = await waitForRouteApply(to.pathname, timeoutMs);
if (ok) return;
} catch (e) {
console.error('[spaNavigate] Fallback triggered due to:', e);
// ãšã©ãŒæã¯ãã©ãŒã«ããã¯ãžé²ã
}
// ãã©ãŒã«ããã¯ïŒéåžžé·ç§»: ããŒãžãªããŒãçºçïŒ
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);
// ⌠èªåçã«éãããã¹ãã©ãããå€å®ãã颿°
const isAutoClosePath = (pathname) => {
const targets = ['/messages', '/i/grok', '/settings', '/i/chat', '/i/spaces', '/i/monetization'];
return targets.some(t => pathname.startsWith(t));
};
GM_addStyle(`
:root { --modal-primary-color:#1d9bf0; --modal-primary-color-hover:#1a8cd8; --modal-primary-text-color:#fff; }
#layers { z-index: 6000 !important; } /* #layers (Grok/DM) ãã¢ãŒãã«ããæåã«è¡šç€ºããèšå® */
#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: 5000; 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{transform-origin:top left; 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{transform-origin:top left; 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;
transform-origin: top left;
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: block; line-height: 1.5; }
.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; /* ãã¿ã³å
ã®æåã瞊äžå€®ã« */
justify-content: center; /* ãã¿ã³å
ã®æåãæšªäžå€®ã« */
margin-left: 7px;
padding: 0 8px;
height: 20px;
border-radius: 9999px;
border: 1px solid currentColor;
font-size: 11px;
line-height: 1;
cursor: pointer;
user-select: none;
white-space: nowrap;
background: rgba(255, 255, 255, 0.03);
flex: 0 0 auto;
order: 9999;
align-self: center;
vertical-align: middle;
}
.ft-tag-chip-label {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 0.4px;
}
.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;
min-width: 0;
box-sizing: border-box;
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;
box-sizing: border-box;
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;
white-space: nowrap;
}
.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;
}
/* --- Link Card (OGP) --- */
.adv-card-box {
margin-top: 8px;
border: 1px solid var(--modal-border);
border-radius: 12px;
overflow: hidden;
text-decoration: none;
display: flex; /* Flexã«å€æŽïŒSmall Card察å¿ïŒ */
flex-direction: column;
transition: background-color 0.2s;
}
.adv-card-box:hover {
background-color: rgba(255, 255, 255, 0.03);
}
/* éåžžã®ç»å */
.adv-card-image {
width: 100%;
height: auto;
aspect-ratio: 1.91 / 1;
object-fit: cover;
display: block;
border-bottom: 1px solid var(--modal-border);
}
/* Small Cardçšã¬ã€ã¢ãŠã */
.adv-card-box.small-card {
flex-direction: row; /* 暪䞊㳠*/
align-items: center;
height: 100px; /* é«ããåºå® */
}
.adv-card-box.small-card .adv-card-image {
width: 100px;
height: 100px;
aspect-ratio: 1 / 1;
border-bottom: none;
border-right: 1px solid var(--modal-border);
flex-shrink: 0;
}
/* SVGã¢ã€ã³ã³çšã³ã³ãã */
.adv-card-icon-container {
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--modal-input-bg);
border-right: 1px solid var(--modal-border);
color: var(--modal-text-secondary);
flex-shrink: 0;
}
.adv-card-icon-container svg {
width: 24px;
height: 24px;
fill: currentColor;
}
.adv-card-content {
padding: 8px 12px;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.adv-card-domain {
font-size: 13px;
color: var(--modal-text-secondary);
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.adv-card-title {
font-size: 14px;
font-weight: 700;
color: var(--modal-text-primary);
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Favorites Item Tag Container */
.adv-fav-tag-container {
display: inline-block;
margin-left: -2px;
margin-bottom: 1px;
vertical-align: middle;
transform: translateY(-2.5px);
}
/* --- 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);
}
/* âŒâŒâŒ æ€çŽ¢å
¥åäžã®ãã©ãŒã«ã¹å¶åŸ¡ (Focus Mode) âŒâŒâŒ */
/* 1. ã¢ãŒãã«ãèæ¯ã¬ãã«ãŸã§äžãã */
#advanced-search-modal.adv-z-lower {
z-index: 0 !important;
}
/* 2. Xã®ã¢ããªå
šäœãã¢ãŒãã«ã®äžã«æã¡äžããïŒãµãžã§ã¹ãæåºã®ããïŒ */
/* #react-root 㯠body çŽäžã® X ã¢ããªã±ãŒã·ã§ã³ã®ã«ãŒãèŠçŽ */
#react-root.adv-app-lifted {
z-index: 1 !important;
position: relative !important; /* z-indexãå¹ãããããã«å¿
é */
}
/* 3. ãµã€ãããŒå
šäœããäžå¯èŠãã«ãã
ããã«ããããã¬ã³ãã»ãããããŠãŒã¶ãŒã»ããã¿ãŒã»æ ç·ãªã©ãå
šãŠæ¶ãã
èåŸã«ããã¢ãŒãã«ãèŠããããã«ã
(opacityã§ã¯ãªãvisibilityã䜿ãããšã§ãæ ç·ãå€å®ãå®å
šã«æ¶ã)
*/
#react-root.adv-app-lifted [data-testid="sidebarColumn"] {
visibility: hidden !important;
}
/* 4. æ€çŽ¢ãã©ãŒã ãšãã®äžèº«ã ãããå¯èŠåãããŠæåºãã
ïŒvisibilityã¯èŠªãhiddenã§ãèªåãvisibleã«ããã°è¡šç€ºãããïŒ
*/
#react-root.adv-app-lifted [data-testid="sidebarColumn"] form[role="search"],
#react-root.adv-app-lifted [data-testid="sidebarColumn"] form[role="search"] * {
visibility: visible !important;
opacity: 1 !important;
}
/* 5. ãµãžã§ã¹ãïŒå
¥ååè£ïŒãåæ§ã«æåºãã */
#react-root.adv-app-lifted [data-testid="sidebarColumn"] [role="listbox"],
#react-root.adv-app-lifted [data-testid="sidebarColumn"] [role="listbox"] * {
visibility: visible !important;
opacity: 1 !important;
}
/* ã¢ãŒãã«ãå·ŠåŽã«ãã£ãŠè¢«ãå Žåã®ã¿ãå·Šã¡ãã¥ãŒãé ã */
#react-root.adv-app-lifted.adv-overlap-left-menu header[role="banner"] {
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
/* === 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); /* ãããŒæã«èãéè²ã衚瀺 */
}
/* âŒSPæ (å¹
700px以äž) ã¯ã¬ã€ã€ãŒèª¿æŽãç¡èŠããŠåŒ·å¶æåé¢ã«ãã */
@media screen and (max-width: 700px) {
/* èšå®ã¢ãŒãã«çãã¡ã€ã³ã¢ãŒãã«ãšåãæåé¢ã¬ã€ã€ãŒã«æã¡äžãã */
/* ããã«ããDOMé åºãåŸã®èšå®ã¢ãŒãã«ãæåã«è¡šç€ºããã */
#adv-settings-modal.adv-settings-modal,
.ft-modal-backdrop {
z-index: 2147483647 !important;
}
/* ã¢ãŒãã«æ¬äœ: ç»é¢äžå€®ã«åºå®ãããµã€ãºã匷å¶é©çš */
#advanced-search-modal {
z-index: 2147483647 !important; /* æåé¢ */
/* äœçœ®ã®åŒ·å¶åºå® (JSã«ããstyle屿§ãç¡èŠããã) */
position: fixed !important;
top: 50% !important;
left: 50% !important;
right: auto !important;
bottom: auto !important;
transform: translate(-50%, -50%) !important;
/* ãµã€ãºã®åŒ·å¶ (ç»é¢ã«åããã) */
width: 96vw !important;
height: 85.5vh !important;
max-width: none !important;
max-height: none !important;
border-radius: 16px !important;
}
/* ããããŒ: ãã©ãã°ã§ãããšæãããªãã«ãŒãœã«ã«ãã */
.adv-modal-header {
cursor: default !important;
}
/* ãªãµã€ã¶ãŒ: é衚瀺ã«ããŠæäœäžèœã«ãã */
.adv-resizer {
display: none !important;
}
/* ã¢ãŒãã«ãéããŠãã(bodyã«ã¯ã©ã¹ããã)æã¯ããªã¬ãŒãæ¶ã */
body.adv-modal-active #advanced-search-trigger {
display: none !important;
}
}
`);
const modalHTML = `
<div id="advanced-search-modal">
<div class="adv-modal-header">
<div class="adv-modal-title-left">
<h2 data-i18n="modalTitle"></h2>
<button id="adv-settings-button" class="adv-settings-btn" type="button" data-i18n-title="tooltipSettings">
${SETTINGS_SVG}
</button>
</div>
<div class="adv-secret-wrap">
<button id="adv-secret-btn" class="adv-secret-btn off" data-i18n-title="tooltipSecret" title="">
<span class="dot" aria-hidden="true"></span>
<span id="adv-secret-label" data-i18n="secretMode"></span>
<span id="adv-secret-state" style="font-weight:700;"></span>
</button>
<button class="adv-modal-close" data-i18n-title="tooltipClose">×</button>
</div>
</div>
<div class="adv-modal-body">
<div class="adv-tabs">
<button class="adv-tab-btn active" data-tab="search" data-i18n="tabSearch"></button>
<button class="adv-tab-btn" data-tab="history" data-i18n="tabHistory"></button>
<button class="adv-tab-btn" data-tab="saved" data-i18n="tabSaved"></button>
<button class="adv-tab-btn" data-tab="favorites" data-i18n="tabFavorites"></button>
<button class="adv-tab-btn" data-tab="mute" data-i18n="tabMute"></button>
<button class="adv-tab-btn" data-tab="lists" data-i18n="tabLists"></button>
<button class="adv-tab-btn" data-tab="accounts" data-i18n="tabAccounts"></button>
</div>
<div class="adv-tab-content active" id="adv-tab-search">
<div id="adv-zoom-root" class="adv-zoom-root">
<form id="advanced-search-form">
<div class="adv-form-group"><label for="adv-all-words" data-i18n="labelAllWords"></label><input type="text" id="adv-all-words" data-i18n-placeholder="placeholderAllWords"></div>
<div class="adv-form-group"><label for="adv-exact-phrase" data-i18n="labelExactPhrase"></label><input type="text" id="adv-exact-phrase" data-i18n-placeholder="placeholderExactPhrase"></div>
<div class="adv-form-group"><label for="adv-any-words" data-i18n="labelAnyWords"></label><input type="text" id="adv-any-words" data-i18n-placeholder="placeholderAnyWords"></div>
<div class="adv-form-group"><label for="adv-not-words" data-i18n="labelNotWords"></label><input type="text" id="adv-not-words" data-i18n-placeholder="placeholderNotWords"></div>
<div class="adv-form-group"><label for="adv-hashtag" data-i18n="labelHashtag"></label><input type="text" id="adv-hashtag" data-i18n-placeholder="placeholderHashtag"></div>
<div class="adv-form-group">
<label for="adv-lang" data-i18n="labelLang"></label>
<select id="adv-lang">
<option value="" data-i18n="optLangDefault"></option>
<option value="ja" data-i18n="optLangJa"></option>
<option value="en" data-i18n="optLangEn"></option>
<option value="id" data-i18n="optLangId"></option> <!-- ã€ã³ããã·ã¢ -->
<option value="hi" data-i18n="optLangHi"></option> <!-- ãã³ãã£ãŒïŒã€ã³ãïŒ -->
<option value="de" data-i18n="optLangDe"></option> <!-- ãã€ã -->
<option value="tr" data-i18n="optLangTr"></option> <!-- ãã«ã³ -->
<option value="es" data-i18n="optLangEs"></option> <!-- ã¹ãã€ã³èªïŒã¡ãã·ã³å«ãïŒ -->
<option value="pt" data-i18n="optLangPt"></option> <!-- ãã«ãã¬ã«èªïŒãã©ãžã«ïŒ-->
<option value="ar" data-i18n="optLangAr"></option> <!-- ã¢ã©ãã¢èªïŒãµãŠãžçïŒ -->
<option value="fr" data-i18n="optLangFr"></option>
<option value="ko" data-i18n="optLangKo"></option>
<option value="ru" data-i18n="optLangRu"></option>
<option value="zh-cn" data-i18n="optLangZhHans"></option> <!-- ç°¡äœäžæ -->
<option value="zh-tw" data-i18n="optLangZhHant"></option> <!-- ç¹é«äžæ -->
</select>
</div>
<hr class="adv-separator">
<div class="adv-form-group">
<label data-i18n="labelFilters"></label>
<div class="adv-filter-grid">
<div class="adv-checkbox-group"><span data-i18n="labelVerified"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-verified-include"><label for="adv-filter-verified-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-verified-exclude"><label for="adv-filter-verified-exclude" data-i18n="checkExclude"></label></div></div>
<div class="adv-checkbox-group"><span data-i18n="labelLinks"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-links-include"><label for="adv-filter-links-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-links-exclude"><label for="adv-filter-links-exclude" data-i18n="checkExclude"></label></div></div>
<div class="adv-checkbox-group"><span data-i18n="labelImages"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-images-include"><label for="adv-filter-images-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-images-exclude"><label for="adv-filter-images-exclude" data-i18n="checkExclude"></label></div></div>
<div class="adv-checkbox-group"><span data-i18n="labelVideos"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-videos-include"><label for="adv-filter-videos-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-videos-exclude"><label for="adv-filter-videos-exclude" data-i18n="checkExclude"></label></div></div>
<div class="adv-checkbox-group"><span data-i18n="labelReposts"></span><div class="adv-checkbox-item" style="display: none;"><input type="checkbox" id="adv-filter-reposts-include" disabled><label for="adv-filter-reposts-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-reposts-exclude"><label for="adv-filter-reposts-exclude" data-i18n="checkExclude"></label></div></div>
<div class="adv-checkbox-group"><span data-i18n="labelTimelineHashtags"></span><div class="adv-checkbox-item" style="display: none;"><input type="checkbox" id="adv-filter-hashtags-include" disabled><label for="adv-filter-hashtags-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-hashtags-exclude"><label for="adv-filter-hashtags-exclude" data-i18n="checkExclude"></label></div></div>
</div>
</div>
<div class="adv-form-group" title="" data-i18n-title="hintSearchTarget">
<label data-i18n="labelSearchTarget"></label>
<div class="adv-checkbox-group">
<div class="adv-checkbox-item">
<input type="checkbox" id="adv-exclude-hit-name" checked>
<label for="adv-exclude-hit-name" data-i18n="labelHitName" title="" data-i18n-title="hintName"></label>
</div>
<div class="adv-checkbox-item">
<input type="checkbox" id="adv-exclude-hit-handle" checked>
<label for="adv-exclude-hit-handle" data-i18n="labelHitHandle" title="" data-i18n-title="hintHandle"></label>
</div>
</div>
</div>
<div class="adv-form-row two-cols">
<div class="adv-form-group">
<label for="adv-account-scope" data-i18n="labelAccountScope"></label>
<select id="adv-account-scope">
<option value="" data-i18n="optAccountAll"></option>
<option value="following" data-i18n="optAccountFollowing"></option>
</select>
</div>
<div class="adv-form-group">
<label for="adv-location-scope" data-i18n="labelLocationScope"></label>
<select id="adv-location-scope">
<option value="" data-i18n="optLocationAll"></option>
<option value="nearby" data-i18n="optLocationNearby"></option>
</select>
</div>
</div>
<div class="adv-form-group"><label data-i18n="labelReplies"></label><select id="adv-replies"><option value="" data-i18n="optRepliesDefault"></option><option value="include" data-i18n="optRepliesInclude"></option><option value="only" data-i18n="optRepliesOnly"></option><option value="exclude" data-i18n="optRepliesExclude"></option></select></div>
<hr class="adv-separator">
<div class="adv-form-group">
<label data-i18n="labelEngagement"></label>
<div class="adv-filter-grid">
<input type="number" id="adv-min-replies" data-i18n-placeholder="placeholderMinReplies" min="0">
<input type="number" id="adv-min-faves" data-i18n-placeholder="placeholderMinLikes" min="0">
<input type="number" id="adv-min-retweets" data-i18n-placeholder="placeholderMinRetweets" min="0">
</div>
</div>
<div class="adv-form-group">
<div class="adv-form-group-header">
<label data-i18n="labelDateRange"></label>
<select id="adv-date-shortcut" class="adv-select-mini" data-i18n-title="labelDateShortcut">
<option value="" data-i18n="labelDateShortcut" selected disabled style="display:none">Quick...</option>
<option value="1d" data-i18n="optDate1Day">Past 24h</option>
<option value="1w" data-i18n="optDate1Week">Past week</option>
<option value="1m" data-i18n="optDate1Month">Past month</option>
<option value="3m" data-i18n="optDate3Months">Past 3 months</option>
<option value="6m" data-i18n="optDate6Months">Past 6 months</option>
<option value="1y" data-i18n="optDate1Year">Past year</option>
<option value="2y" data-i18n="optDate2Years">Past 2 years</option>
<option value="3y" data-i18n="optDate3Years">Past 3 years</option>
<option value="5y" data-i18n="optDate5Years">Past 5 years</option>
<option disabled>ââââââââââ</option>
<option value="clear" data-i18n="optDateClear">Clear</option>
</select>
</div>
<div class="adv-form-group-date-container">
<input type="date" id="adv-since" data-i18n-title="tooltipSince">
<span class="adv-date-separator">~</span>
<input type="date" id="adv-until" data-i18n-title="tooltipUntil">
</div>
</div>
<hr class="adv-separator">
<div class="adv-form-group">
<div class="adv-account-label-group">
<label for="adv-from-user" data-i18n="labelFromUser"></label>
<div class="adv-exclude-toggle"><input type="checkbox" id="adv-from-user-exclude"><label for="adv-from-user-exclude" data-i18n="checkExclude"></label></div>
</div>
<input type="text" id="adv-from-user" data-i18n-placeholder="placeholderFromUser">
</div>
<div class="adv-form-group">
<div class="adv-account-label-group">
<label for="adv-to-user" data-i18n="labelToUser"></label>
<div class="adv-exclude-toggle"><input type="checkbox" id="adv-to-user-exclude"><label for="adv-to-user-exclude" data-i18n="checkExclude"></label></div>
</div>
<input type="text" id="adv-to-user" data-i18n-placeholder="placeholderToUser">
</div>
<div class="adv-form-group">
<div class="adv-account-label-group">
<label for="adv-mentioning" data-i18n="labelMentioning"></label>
<div class="adv-exclude-toggle"><input type="checkbox" id="adv-mentioning-exclude"><label for="adv-mentioning-exclude" data-i18n="checkExclude"></label></div>
</div>
<input type="text" id="adv-mentioning" data-i18n-placeholder="placeholderMentioning">
</div>
</form>
</div>
</div>
<div class="adv-tab-content" id="adv-tab-history">
<div class="adv-zoom-root">
<div class="adv-tab-toolbar">
<div class="adv-tab-toolbar-left">
<input id="adv-history-search" class="adv-input" type="text" data-i18n-placeholder="placeholderSearchHistory">
<select id="adv-history-sort" class="adv-select" data-i18n-title="labelSortBy" title="Sort by:">
<option value="newest" data-i18n="sortNewest"></option>
<option value="oldest" data-i18n="sortOldest"></option>
<option value="name_asc" data-i18n="sortNameAsc"></option>
<option value="name_desc" data-i18n="sortNameDesc"></option>
</select>
</div>
<div class="adv-tab-toolbar-right">
<button id="adv-history-clear-all" class="adv-chip danger"></button>
</div>
</div>
<div id="adv-history-empty" class="adv-item-sub"></div>
<div id="adv-history-list" class="adv-list"></div>
</div>
</div>
<div class="adv-tab-content" id="adv-tab-saved">
<div class="adv-zoom-root">
<div id="adv-saved-empty" class="adv-item-sub"></div>
<div id="adv-saved-list" class="adv-list"></div>
</div>
</div>
<div class="adv-tab-content" id="adv-tab-favorites">
<div class="adv-zoom-root">
<div id="adv-favorites-list" class="adv-list"></div>
<div id="adv-favorites-empty" class="adv-item-sub"></div>
</div>
</div>
<div class="adv-tab-content" id="adv-tab-lists">
<div class="adv-zoom-root">
<div id="adv-lists-empty" class="adv-item-sub"></div>
<div id="adv-lists-list" class="adv-list"></div>
</div>
</div>
<div class="adv-tab-content" id="adv-tab-accounts">
<div class="adv-zoom-root">
<div id="adv-accounts-empty" class="adv-item-sub"></div>
<div id="adv-accounts-list" class="adv-list"></div>
</div>
</div>
<!-- â¶ Mute ã¿ã -->
<div class="adv-tab-content" id="adv-tab-mute">
<div class="adv-zoom-root">
<div class="adv-form-group">
<!-- 远å ãã䞊ã³ïŒãŸãâ远å âUI -->
<div class="adv-mute-add">
<input type="text" id="adv-mute-input" data-i18n-placeholder="placeholderMuteWord">
<div style="display:flex; flex-direction:column; gap:2px; margin-left:4px; margin-right:4px;">
<label class="adv-toggle" title="">
<input type="checkbox" id="adv-mute-wb">
<span data-i18n="labelWordBoundary"></span>
</label>
<label class="adv-toggle" title="">
<input type="checkbox" id="adv-mute-cs">
<span data-i18n="labelCaseSensitive"></span>
</label>
</div>
<button id="adv-mute-add" class="adv-modal-button" data-i18n="buttonAdd"></button>
</div>
<hr class="adv-separator" style="margin-top:12px; margin-bottom:12px;">
<!-- ⌠æ°ããèŠåºããããã¯ïŒãã¥ãŒãäžèЧ + ãã¹ãŠæå¹/ç¡å¹ïŒ -->
<div class="adv-mute-header">
<input type="text" id="adv-mute-filter" data-i18n-placeholder="placeholderFilterMute">
<div class="adv-mute-header-controls">
<select id="adv-mute-mode" class="adv-select">
<option value="hidden" data-i18n="optMuteHidden">Hidden</option>
<option value="collapsed" data-i18n="optMuteCollapsed">Collapsed</option>
</select>
<label class="adv-toggle">
<input type="checkbox" id="adv-mute-enable-all" checked>
<span data-i18n="labelEnableAll"></span>
</label>
</div>
</div>
<div id="adv-mute-empty" class="adv-item-sub"></div>
<div id="adv-mute-list" class="adv-mute-list"></div>
</div>
</div>
</div>
</div>
<div class="adv-modal-footer">
<button id="adv-save-button" class="adv-modal-button" data-i18n="buttonSave"></button>
<button id="adv-clear-button" class="adv-modal-button" data-i18n="buttonClear"></button>
<button id="adv-apply-button" class="adv-modal-button primary" data-i18n="buttonApply"></button>
</div>
</div>
<div id="adv-toast" class="adv-toast" role="status" aria-live="polite"></div>
<div id="adv-settings-modal" class="adv-settings-modal">
<div class="adv-settings-dialog">
<div class="adv-settings-header">
<div style="display:flex; align-items:center; gap:15px;">
<h3 class="adv-settings-title" data-i18n="settingsTitle"></h3>
<button id="adv-settings-reset" type="button" class="adv-chip danger" data-i18n="buttonReset"></button>
</div>
<button id="adv-settings-close" type="button" class="adv-settings-close" data-i18n-title="tooltipClose">×</button>
</div>
<div class="adv-settings-body">
<div class="adv-settings-section-header" data-i18n="settingsTitleGeneral"></div>
<div class="adv-settings-group">
<label for="adv-settings-lang" data-i18n="labelUILang"></label>
<select id="adv-settings-lang">
<option value="" data-i18n="optUILangAuto"></option>
<option value="en">English</option>
<option value="ja">æ¥æ¬èª</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="de">Deutsch</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="ru">Ð ÑÑÑкОй</option>
<option value="ko">íêµìŽ</option>
<option value="zh-CN">ç®äœäžæ</option>
<option value="zh-TW">ç¹é«äžæ</option>
</select>
</div>
<div class="adv-settings-group">
<label for="adv-settings-initial-tab" data-i18n="labelInitialTab"></label>
<select id="adv-settings-initial-tab">
<option value="last" data-i18n="optInitialTabLast"></option>
<option value="search" data-i18n="tabSearch"></option>
<option value="history" data-i18n="tabHistory"></option>
<option value="saved" data-i18n="tabSaved"></option>
<option value="favorites" data-i18n="tabFavorites"></option>
<option value="mute" data-i18n="tabMute"></option>
<option value="lists" data-i18n="tabLists"></option>
<option value="accounts" data-i18n="tabAccounts"></option>
</select>
</div>
<div class="adv-settings-section-header" data-i18n="settingsTitleFeatures"></div>
<div class="adv-settings-group">
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-search">
<span data-i18n="tabSearch"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-search" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-history">
<span data-i18n="tabHistory"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-history" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-saved">
<span data-i18n="tabSaved"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-saved" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-favorites">
<span data-i18n="tabFavorites"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-favorites" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-mute">
<span data-i18n="tabMute"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-mute" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-lists">
<span data-i18n="tabLists"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-lists" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
<div class="adv-settings-toggle-row">
<label class="adv-toggle" for="adv-settings-tab-toggle-accounts">
<span data-i18n="tabAccounts"></span>
</label>
<label class="adv-switch">
<input id="adv-settings-tab-toggle-accounts" type="checkbox">
<span class="adv-slider"></span>
</label>
</div>
</div>
<div class="adv-settings-section-header" data-i18n="settingsTitleData"></div>
<div class="adv-settings-group">
<div class="adv-settings-actions-inline">
<button id="adv-settings-export" type="button" class="adv-modal-button" data-i18n="buttonExport"></button>
<button id="adv-settings-import" type="button" class="adv-modal-button primary" data-i18n="buttonImport"></button>
<input id="adv-settings-file-input" type="file" accept="application/json" style="display:none">
</div>
</div>
</div>
<div class="adv-settings-footer">
<button id="adv-settings-close-footer" type="button" class="adv-modal-button" data-i18n="buttonClose"></button>
</div>
</div>
</div>
`;
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. æã確å®ãªæ¹æ³: <time>ã¿ã°ã®èŠªã¢ã³ã«ãŒãæ¢ã
const timeEls = Array.from(article.querySelectorAll('time'));
for (const timeEl of timeEls) {
const timeAnchor = timeEl.closest('a');
if (timeAnchor) {
// â
远å : åŒçšã«ãŒãã®äžã«ããæå»ãªã³ã¯ãªãã¹ããããã
if (isInsideQuote(timeAnchor)) continue;
const href = timeAnchor.getAttribute('href');
const m = href.match(/\/status\/(\d+)/);
if (m) return m[1];
}
}
// 2. ãã©ãŒã«ããã¯: åŸæ¥ã®æ€çŽ¢æ¹æ³
try {
const anchors = Array.from(article.querySelectorAll('a[href*="/status/"]'));
for (const a of anchors) {
if (a.dataset.testid === 'tweet-text-show-more-link') continue;
// åŒçšã«ãŒãã®äžã«ãããªã³ã¯ãªãã¹ããããã
if (isInsideQuote(a)) continue;
const href = a.getAttribute('href') || '';
const m = href.match(/\/status\/(\d+)/);
if (m) return m[1];
}
} catch (e) {}
return null;
}
// ã¿ã°ãããã®æ¿å
¥å ŽæïŒããããŒã¡ã¿æ
å ±è¡ïŒãç¹å®ãã颿°
function ft_findHeaderMetaContainer(article) {
// 1. èšäºå
ã®ã¡ã€ã³ãšãªã time èŠçŽ ãæ¢ãïŒåŒçšãã€ãŒãå
ã® time ã¯é€å€ïŒ
const allTimes = Array.from(article.querySelectorAll('time'));
const mainTime = allTimes.find(t => !t.closest('div[role="link"]'));
// 2. èšäºå
ã® User-Name èŠçŽ ãæ¢ã
const userName = article.querySelector('[data-testid="User-Name"]');
// --- ãã¿ãŒã³A: ã¿ã€ã ã©ã€ã³è¡šç€º ---
// ãUser-NameããšãTimeããåãè¡ã³ã³ããã«åå±
ããŠããå ŽåããããããããŒè¡ã
if (userName && mainTime) {
let p = userName.parentElement;
while (p && p !== article) {
// flex-row (r-18u37iz) ã§ãããã〠mainTime ãå«ãã§ããã確èª
if (p.classList.contains('r-18u37iz') && p.contains(mainTime)) {
return p;
}
p = p.parentElement;
}
}
// --- ãã¿ãŒã³B: 詳现ããŒãž (åç¬è¡šç€º) ---
// User-Name ãš Time ãé¢ããŠããå Žåã詳现ããŒãžãšã¿ãªããŠãTimeããããè¡ãæ¢ãã
// (ããã«ãããä»¶ã®è¡šç€ºããå«ãŸããŠããŸã)
if (mainTime) {
let p = mainTime.parentElement;
while (p && p !== article) {
// r-18u37iz (flex-row) ã§ããããã€åèŠçŽ ãè€æ°ããïŒæ¥ä» + äžé» + Views ãªã©ïŒ
if (p.classList.contains('r-18u37iz') && p.childElementCount > 1) {
return p;
}
p = p.parentElement;
}
}
// --- ãã¿ãŒã³C: ãã©ãŒã«ãã㯠---
// äžèšã§èŠã€ãããªãå ŽåïŒTimeããªãããã¢ãŒã·ã§ã³ãªã©ïŒãUser-Name ã®æšªã« @handle ãããè¡ãæ¢ã
if (userName) {
let p = userName.parentElement;
while (p && p !== article) {
const hasHandleSibling = Array.from(p.children).some(sib => {
if (sib.contains(userName)) return false;
return sib.innerText && sib.innerText.trim().startsWith('@');
});
if (hasHandleSibling) return p;
p = p.parentElement;
}
}
return null;
}
// ------------- ã¿ã°ãããæç»ïŒã€ãã³ãå§è²å¯Ÿå¿ïŒ ------------- //
function ft_buildTagChip(tweetId) {
const currentTagId = ft_state.tweetTags[tweetId];
const isUncategorized = !currentTagId;
const tag = currentTagId ? ft_getTagById(currentTagId) : null;
const label = isUncategorized
? i18n.t('FT_UNCATEGORIZED')
: ft_getTagDisplayLabelFromTag(tag) || i18n.t('FT_UNCATEGORIZED');
const color = isUncategorized ? ft_getUncategorizedColor() : ft_getTagColor(currentTagId);
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'ft-tag-chip' + (isUncategorized ? ' ft-tag-chip-uncategorized' : '');
btn.style.color = color;
btn.style.borderColor = color;
btn.dataset.tweetId = tweetId;
const span = document.createElement('span');
span.className = 'ft-tag-chip-label';
span.textContent = label;
span.style.pointerEvents = 'none'; // ã¯ãªãã¯ããã¿ã³ã«éé
btn.appendChild(span);
return btn;
}
function ft_attachTagChipToArticle(article, tweetId) {
if (!ft_state.enabled) return;
const headerRow = ft_findHeaderMetaContainer(article);
if (!headerRow) return;
// æ¢åã®ã¿ã°ãããã°ååŸããªããã°æ°èŠäœæ
let existing = headerRow.querySelector('.ft-tag-chip');
const chip = ft_buildTagChip(tweetId);
// 念ã®ãã CSS order ãæå€§ã«ããŠãã
chip.style.order = "9999999";
if (existing) {
existing.replaceWith(chip);
} else {
headerRow.appendChild(chip);
}
article.classList.add('ft-chip-attached');
// ç£èŠããžãã¯
// ãã®è¡(headerRow)ã«ä»ã®æ¡åŒµæ©èœãèŠçŽ ã远å ããŠããããã¿ã°ãåã³æåŸå°Ÿãžç§»åããã
if (!headerRow.dataset.ftObserverAttached) {
headerRow.dataset.ftObserverAttached = '1';
const observer = new MutationObserver((mutations) => {
// ã¿ã°ããããååŸïŒã¯ããŒãžã£å
ã® chip 倿°ã ãšå€ãå¯èœæ§ãããããDOMããåãïŒ
const currentChip = headerRow.querySelector('.ft-tag-chip');
// ã¿ã°ãååšãããã€ãæåŸã®èŠçŽ ãã§ã¯ãªããªã£ãŠããå Žå
if (currentChip && headerRow.lastElementChild !== currentChip) {
// èªåèªèº«(ã¿ã°)ãå远å ããããšã§ãDOMé åºã®äžçªåŸããžç§»åãã
headerRow.appendChild(currentChip);
}
});
// åèŠçŽ ã®è¿œå ã»åé€ãç£èŠãã
observer.observe(headerRow, { childList: true });
}
}
function ft_removeTagChipFromArticle(article) {
const chip = article.querySelector('.ft-tag-chip');
if (chip) chip.remove();
article.classList.remove('ft-chip-attached');
}
function ft_refreshAllTagChips() {
const articles = document.querySelectorAll('article[data-testid="tweet"]');
for (const article of articles) {
ft_processTweetArticle(article);
}
}
// ------------- ã¿ã° / ãã£ã«ã¿çšããããããŠã³ ------------- //
function ft_closeTagDropdown() {
if (ft_currentDropdown) {
ft_currentDropdown.remove();
ft_currentDropdown = null;
}
}
// ã¿ã°ããšã®ä»¶æ°ãéèšãããã«ããŒ
function ft_countTagUsage() {
const counts = { uncat: 0, total: 0 };
// å
šã¿ã°ã®åæå€ã0ã«ãã
if (ft_state && ft_state.tags) {
ft_state.tags.forEach(t => counts[t.id] = 0);
}
// çŸåšã®ãæ°ã«å
¥ããªã¹ããããŒãããŠéèš
const favs = loadFavorites();
counts.total = favs.length;
favs.forEach(item => {
const tagId = ft_state.tweetTags[item.id];
if (tagId && counts[tagId] !== undefined) {
counts[tagId]++;
} else {
counts.uncat++;
}
});
return counts;
}
function ft_buildTagDropdownContent(tweetId) {
const wrapper = document.createElement('div');
wrapper.className = 'ft-tag-dropdown';
wrapper.dataset.tweetId = tweetId;
const header = document.createElement('div');
header.className = 'ft-tag-dropdown-header';
const headerLeft = document.createElement('div');
headerLeft.style.display = 'flex'; headerLeft.style.alignItems = 'center'; headerLeft.style.gap = '4px';
const title = document.createElement('div');
title.textContent = i18n.t('FT_DROPDOWN_TITLE');
const settingsBtn = document.createElement('button');
settingsBtn.type = 'button'; settingsBtn.className = 'ft-settings-button'; settingsBtn.textContent = 'â';
settingsBtn.title = i18n.t('FT_SETTINGS_BUTTON_TITLE');
settingsBtn.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); ft_closeTagDropdown(); ft_openSettingsModal(); });
headerLeft.appendChild(title); headerLeft.appendChild(settingsBtn);
const closeBtn = document.createElement('button');
closeBtn.className = 'ft-tag-dropdown-close'; closeBtn.type = 'button'; closeBtn.textContent = 'Ã';
closeBtn.addEventListener('click', () => ft_closeTagDropdown());
header.appendChild(headerLeft); header.appendChild(closeBtn);
const tagList = document.createElement('div');
tagList.className = 'ft-tag-dropdown-tags';
const currentTagId = ft_state.tweetTags[tweetId] || null;
const entries = ft_getTagListWithUncategorized();
const counts = ft_countTagUsage();
for (const entry of entries) {
const item = document.createElement('div');
const colorDot = document.createElement('div');
const label = document.createElement('div');
label.className = 'ft-tag-dropdown-tag-label';
if (entry.kind === 'uncat') {
item.className = 'ft-tag-dropdown-tag-item' + (currentTagId ? '' : ' ft-tag-dropdown-tag-selected');
colorDot.className = 'ft-tag-dropdown-tag-color';
colorDot.style.backgroundColor = ft_getUncategorizedColor();
label.textContent = i18n.t('FT_UNCATEGORIZED') + ` (${counts.uncat})`;
item.addEventListener('click', () => { delete ft_state.tweetTags[tweetId]; ft_saveState(); ft_closeTagDropdown(); });
} else {
const tag = entry.tag;
item.className = 'ft-tag-dropdown-tag-item' + (tag.id === currentTagId ? ' ft-tag-dropdown-tag-selected' : '');
colorDot.className = 'ft-tag-dropdown-tag-color';
colorDot.style.backgroundColor = tag.color || '#1d9bf0';
if (entry.depth > 0) colorDot.style.marginLeft = `${entry.depth * 12}px`;
const c = counts[entry.tag.id] || 0;
label.textContent = (entry.tag.name || '') + ` (${c})`;
item.addEventListener('click', () => { ft_state.tweetTags[tweetId] = tag.id; ft_saveState(); ft_closeTagDropdown(); });
}
item.appendChild(colorDot); item.appendChild(label);
tagList.appendChild(item);
}
const newSection = document.createElement('div');
newSection.className = 'ft-tag-dropdown-new';
const newLabel = document.createElement('div');
newLabel.textContent = i18n.t('FT_DROPDOWN_NEW_TAG');
const newRow = document.createElement('div');
newRow.className = 'ft-tag-dropdown-new-row';
const newInput = document.createElement('input');
newInput.type = 'text'; newInput.placeholder = i18n.t('FT_DROPDOWN_NEW_TAG_PLACEHOLDER'); newInput.className = 'ft-tag-dropdown-new-input';
const newColor = document.createElement('input');
newColor.type = 'color'; newColor.value = '#1d9bf0'; newColor.className = 'ft-tag-dropdown-new-color';
const addBtn = document.createElement('button');
addBtn.type = 'button'; addBtn.className = 'ft-tag-dropdown-new-button'; addBtn.textContent = i18n.t('FT_DROPDOWN_NEW_TAG_ADD');
function doAddTag() {
const name = newInput.value.trim();
if (!name) return;
const tag = ft_createNewTag(name, newColor.value || '#1d9bf0', null);
ft_state.tweetTags[tweetId] = tag.id;
ft_saveState();
ft_closeTagDropdown();
}
addBtn.addEventListener('click', doAddTag);
newInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') { ev.preventDefault(); doAddTag(); } });
newRow.appendChild(newColor); newRow.appendChild(newInput); newRow.appendChild(addBtn);
newSection.appendChild(newLabel); newSection.appendChild(newRow);
wrapper.appendChild(header); wrapper.appendChild(tagList); wrapper.appendChild(newSection);
return wrapper;
}
function ft_openTagDropdown(chipEl, tweetId) {
ft_closeTagDropdown();
const dropdown = ft_buildTagDropdownContent(tweetId);
ft_currentDropdown = dropdown;
document.body.appendChild(dropdown);
const rect = chipEl.getBoundingClientRect();
const margin = 8;
const width = dropdown.offsetWidth || 240;
const height = dropdown.offsetHeight || 200;
let left = rect.left;
if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
if (left < margin) left = margin;
let top = rect.bottom + 4;
if (top + height + margin > window.innerHeight) top = rect.top - height - 4;
if (top < margin) top = margin;
dropdown.style.left = `${left}px`; dropdown.style.top = `${top}px`;
}
// ---- ããã¯ããŒã¯ãã£ã«ã¿çšããããããŠã³ ---- //
function ft_buildFilterDropdownContent(targetValue, onSelectCallback) {
const currentValue = targetValue;
const wrapper = document.createElement('div');
wrapper.className = 'ft-tag-dropdown ft-filter-dropdown';
const header = document.createElement('div'); header.className = 'ft-tag-dropdown-header';
const headerLeft = document.createElement('div'); headerLeft.style.display = 'flex'; headerLeft.style.alignItems = 'center'; headerLeft.style.gap = '4px';
const title = document.createElement('div'); title.textContent = i18n.t('FT_DROPDOWN_TITLE');
const settingsBtn = document.createElement('button'); settingsBtn.type = 'button'; settingsBtn.className = 'ft-settings-button'; settingsBtn.textContent = 'â'; settingsBtn.title = i18n.t('FT_SETTINGS_BUTTON_TITLE');
settingsBtn.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); ft_closeTagDropdown(); ft_openSettingsModal(); });
headerLeft.appendChild(title); headerLeft.appendChild(settingsBtn);
const closeBtn = document.createElement('button'); closeBtn.className = 'ft-tag-dropdown-close'; closeBtn.type = 'button'; closeBtn.textContent = 'Ã';
closeBtn.addEventListener('click', () => ft_closeTagDropdown());
header.appendChild(headerLeft); header.appendChild(closeBtn);
const tagList = document.createElement('div'); tagList.className = 'ft-tag-dropdown-tags';
function addItem(value, label, color, depth) {
const item = document.createElement('div');
item.className = 'ft-tag-dropdown-tag-item' + (value === currentValue ? ' ft-tag-dropdown-tag-selected' : '');
const colorDot = document.createElement('div');
colorDot.className = 'ft-tag-dropdown-tag-color'; colorDot.style.backgroundColor = color || 'rgba(239,243,244,0.6)';
if (depth > 0) colorDot.style.marginLeft = `${depth * 12}px`;
const text = document.createElement('div');
text.className = 'ft-tag-dropdown-tag-label'; text.textContent = label;
item.appendChild(colorDot); item.appendChild(text);
item.addEventListener('click', () => {
if (typeof onSelectCallback === 'function') {
onSelectCallback(value);
}
ft_closeTagDropdown();
});
tagList.appendChild(item);
}
const counts = ft_countTagUsage();
addItem(FT_FILTER_ALL, i18n.t('FT_FILTER_ALL') + ` (${counts.total})`, 'rgba(239,243,244,0.7)', 0);
const entries = ft_getTagListWithUncategorized();
for (const entry of entries) {
if (entry.kind === 'uncat') {
addItem(FT_FILTER_UNCATEGORIZED, i18n.t('FT_UNCATEGORIZED') + ` (${counts.uncat})`, ft_getUncategorizedColor(), 0);
} else {
// ãã£ã«ã¿æã¯ããã®ã¿ã° + åå«ã¿ã°ãã®åèšä»¶æ°ãèšç®ããŠè¡šç€ºãã
// (çµãèŸŒã¿æ©èœããµãããªãŒæ€çŽ¢ã§ãããããä»¶æ°ãåãããã®ãèªç¶)
let subTreeCount = 0;
if (ft_state && ft_state.tags) {
ft_state.tags.forEach(t => {
if (ft_isTagInSubtree(t.id, entry.tag.id)) {
subTreeCount += (counts[t.id] || 0);
}
});
}
// åèšä»¶æ°ã衚瀺
addItem(entry.tag.id, (entry.tag.name || '') + ` (${subTreeCount})`, entry.tag.color, entry.depth);
}
}
wrapper.appendChild(header); wrapper.appendChild(tagList);
return wrapper;
}
function ft_openFilterDropdown(buttonEl, targetValue, onSelectCallback) {
ft_closeTagDropdown();
const dropdown = ft_buildFilterDropdownContent(targetValue, onSelectCallback);
ft_currentDropdown = dropdown;
document.body.appendChild(dropdown);
const rect = buttonEl.getBoundingClientRect();
const margin = 8;
const width = dropdown.offsetWidth || 240;
const height = dropdown.offsetHeight || 200;
let left = rect.left;
if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
if (left < margin) left = margin;
let top = rect.bottom + 4;
if (top + height + margin > window.innerHeight) top = rect.top - height - 4;
if (top < margin) top = margin;
dropdown.style.left = `${left}px`; dropdown.style.top = `${top}px`;
}
// ------------- èšå®ã¢ãŒãã« ------------- //
function ft_closeSettingsModal() {
if (ft_settingsModalBackdrop) { ft_settingsModalBackdrop.remove(); ft_settingsModalBackdrop = null; }
ft_dragSrcEntry = null;
}
function ft_openSettingsModal() {
ft_closeSettingsModal();
const backdrop = document.createElement('div'); backdrop.className = 'ft-modal-backdrop';
const modal = document.createElement('div'); modal.className = 'ft-modal';
const header = document.createElement('div'); header.className = 'ft-modal-header';
const title = document.createElement('div'); title.className = 'ft-modal-title'; title.textContent = i18n.t('FT_SETTINGS_TITLE');
header.appendChild(title);
const body = document.createElement('div'); body.className = 'ft-modal-body';
const displaySection = document.createElement('div'); displaySection.className = 'ft-modal-display-settings';
const displayTitle = document.createElement('div'); displayTitle.textContent = i18n.t('FT_SETTINGS_DISPLAY_SECTION_TITLE');
const modeRow = document.createElement('div'); modeRow.className = 'ft-modal-display-settings-row';
const modeLabel = document.createElement('span'); modeLabel.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_LABEL');
const radioLeafLabel = document.createElement('label'); radioLeafLabel.className = 'ft-modal-display-radio';
const radioLeaf = document.createElement('input'); radioLeaf.type = 'radio'; radioLeaf.name = 'ft-display-mode'; radioLeaf.value = 'leaf'; radioLeaf.checked = ft_state.display.mode === 'leaf';
const radioLeafText = document.createElement('span'); radioLeafText.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_LEAF');
radioLeafLabel.appendChild(radioLeaf); radioLeafLabel.appendChild(radioLeafText);
const radioFullLabel = document.createElement('label'); radioFullLabel.className = 'ft-modal-display-radio';
const radioFull = document.createElement('input'); radioFull.type = 'radio'; radioFull.name = 'ft-display-mode'; radioFull.value = 'full'; radioFull.checked = ft_state.display.mode === 'full';
const radioFullText = document.createElement('span'); radioFullText.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_FULL');
radioFullLabel.appendChild(radioFull); radioFullLabel.appendChild(radioFullText);
modeRow.appendChild(modeLabel); modeRow.appendChild(radioLeafLabel); modeRow.appendChild(radioFullLabel);
radioLeaf.addEventListener('change', () => { if (radioLeaf.checked) { ft_state.display.mode = 'leaf'; ft_saveState(); } });
radioFull.addEventListener('change', () => { if (radioFull.checked) { ft_state.display.mode = 'full'; ft_saveState(); } });
displaySection.appendChild(displayTitle); displaySection.appendChild(modeRow);
const tagListEl = document.createElement('div'); tagListEl.className = 'ft-modal-tag-list';
function clearDropClasses() {
tagListEl.querySelectorAll('.ft-modal-tag-item').forEach(el => el.classList.remove('ft-modal-tag-item-drop-before', 'ft-modal-tag-item-drop-after', 'ft-modal-tag-item-drop-child'));
}
function deleteTagAndReparentChildren(tag) {
ft_state.tags.forEach(child => { if (child.parentId === tag.id) child.parentId = tag.parentId || null; });
ft_state.tags = ft_state.tags.filter(t => t.id !== tag.id);
Object.keys(ft_state.tweetTags).forEach(tid => { if (ft_state.tweetTags[tid] === tag.id) delete ft_state.tweetTags[tid]; });
ft_normalizeTagOrders(); ft_clampUncategorizedOrder(); ft_saveState();
}
function moveTagInSiblings(tag, direction) {
const siblings = ft_state.tags.filter(t => (t.parentId || null) === (tag.parentId || null)).sort((a, b) => a.order - b.order);
const idx = siblings.findIndex(t => t.id === tag.id);
if (idx < 0 || idx + direction < 0 || idx + direction >= siblings.length) return;
const other = siblings[idx + direction];
[tag.order, other.order] = [other.order, tag.order];
ft_normalizeTagOrders(); ft_saveState();
}
function moveTagAsChild(srcTag, targetTag) {
if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.id, srcTag.id)) return;
srcTag.parentId = targetTag.id;
const children = ft_state.tags.filter(t => (t.parentId || null) === targetTag.id);
srcTag.order = (children.length ? Math.max(...children.map(t => t.order)) : -1) + 1;
ft_normalizeTagOrders(); ft_saveState();
}
function moveTagBefore(srcTag, targetTag) {
if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.parentId, srcTag.id)) return;
srcTag.parentId = targetTag.parentId; srcTag.order = targetTag.order - 0.5; ft_normalizeTagOrders(); ft_saveState();
}
function moveTagAfter(srcTag, targetTag) {
if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.parentId, srcTag.id)) return;
srcTag.parentId = targetTag.parentId; srcTag.order = targetTag.order + 0.5; ft_normalizeTagOrders(); ft_saveState();
}
function moveTagToRootRelativeToUncat(srcTag, mode) {
if (!srcTag) return;
srcTag.parentId = null;
let uncatPos = ft_state.uncategorized.order;
const insertIndex = mode === 'before' ? uncatPos : uncatPos + 1;
const rootTags = ft_state.tags.filter(t => !t.parentId && t.id !== srcTag.id).sort((a, b) => a.order - b.order);
rootTags.splice(insertIndex, 0, srcTag);
rootTags.forEach((t, i) => t.order = i);
ft_normalizeTagOrders(); ft_clampUncategorizedOrder(); ft_saveState();
}
function getDropTargetInfoFromY(y) {
const items = Array.from(tagListEl.querySelectorAll('.ft-modal-tag-item'));
if (!items.length) return { row: null, mode: 'root-end' }; // 空ãªãã«ãŒã远å
const rects = items.map(row => row.getBoundingClientRect());
// äžçªäžã®èŠçŽ ã®ãåºèŸºãããäžãªããç¡æ¡ä»¶ã§ã«ãŒãæ«å°Ÿç§»åãšãã
const lastRect = rects[rects.length - 1];
// å°ãã§ãäž(0px以äž)ã«ããã°ã«ãŒãæ±ãã«ããïŒCSSã§äœçœãäœã£ãããããã§OKïŒ
if (y > lastRect.bottom) {
return { row: null, mode: 'root-end' };
}
const boundaries = [rects[0].top];
for (let i = 1; i < items.length; i++) boundaries.push((rects[i - 1].bottom + rects[i].top) / 2);
boundaries.push(rects[items.length - 1].bottom);
let idx = 0; let min = Infinity;
for (let i = 0; i < boundaries.length; i++) {
const d = Math.abs(y - boundaries[i]);
if (d < min) { min = d; idx = i; }
}
if (idx === 0) return { row: items[0], mode: 'before' };
if (idx === items.length) return { row: items[items.length - 1], mode: 'after' };
return { row: items[idx], mode: 'before' };
}
function rebuildTagList() {
tagListEl.innerHTML = '';
const entries = ft_getTagListWithUncategorized();
if (entries.length === 1 && entries[0].kind === 'uncat') {
const empty = document.createElement('div'); empty.style.opacity = '0.7'; empty.style.fontSize = '12px'; empty.textContent = i18n.t('FT_SETTINGS_EMPTY_TAG_LIST'); tagListEl.appendChild(empty);
}
const rootCount = ft_countRootTags(); ft_clampUncategorizedOrder();
entries.forEach(entry => {
const row = document.createElement('div'); row.className = 'ft-modal-tag-item'; row.dataset.kind = entry.kind;
const mainCell = document.createElement('div'); mainCell.className = 'ft-modal-tag-main'; if (entry.depth > 0) mainCell.style.paddingLeft = `${entry.depth * 16}px`;
const nameInput = document.createElement('input'); nameInput.className = 'ft-modal-tag-name'; nameInput.type = 'text';
const colorInput = document.createElement('input'); colorInput.className = 'ft-modal-tag-color'; colorInput.type = 'color';
const orderDiv = document.createElement('div');
const upBtn = document.createElement('button'); upBtn.className = 'ft-modal-tag-order'; upBtn.textContent = i18n.t('FT_SETTINGS_UP'); upBtn.type='button';
const downBtn = document.createElement('button'); downBtn.className = 'ft-modal-tag-order'; downBtn.textContent = i18n.t('FT_SETTINGS_DOWN'); downBtn.type='button';
orderDiv.appendChild(upBtn); orderDiv.appendChild(downBtn);
const delBtn = document.createElement('button'); delBtn.className = 'ft-modal-tag-delete'; delBtn.textContent = i18n.t('FT_SETTINGS_DELETE_BUTTON'); delBtn.type='button';
mainCell.appendChild(colorInput); mainCell.appendChild(nameInput);
const dragHandle = document.createElement('div'); dragHandle.className = 'ft-modal-tag-drag-handle'; dragHandle.innerHTML = 'â¡';
if (entry.kind === 'uncat') {
row.draggable = false; dragHandle.draggable = false; dragHandle.title = i18n.t('FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP');
nameInput.value = i18n.t('FT_SETTINGS_UNCATEGORIZED_NAME'); nameInput.readOnly = true; nameInput.title = i18n.t('FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP');
colorInput.value = ft_getUncategorizedColor();
colorInput.addEventListener('change', () => { ft_state.uncategorized.color = colorInput.value; ft_saveState(); });
delBtn.disabled = true;
upBtn.disabled = ft_state.uncategorized.order <= 0;
downBtn.disabled = ft_state.uncategorized.order >= rootCount;
upBtn.addEventListener('click', () => { ft_state.uncategorized.order--; ft_saveState(); rebuildTagList(); });
downBtn.addEventListener('click', () => { ft_state.uncategorized.order++; ft_saveState(); rebuildTagList(); });
} else {
const tag = entry.tag; row.dataset.tagId = tag.id;
dragHandle.draggable = true;
nameInput.value = tag.name; colorInput.value = tag.color || '#1d9bf0';
nameInput.addEventListener('change', () => { if(nameInput.value.trim()) { tag.name = nameInput.value.trim(); ft_saveState(); rebuildTagList(); } });
colorInput.addEventListener('change', () => { tag.color = colorInput.value; ft_saveState(); });
delBtn.addEventListener('click', () => { if(confirm(i18n.t('FT_CONFIRM_DELETE_TAG_MSG').replace('{tagName}', tag.name))) { deleteTagAndReparentChildren(tag); rebuildTagList(); } });
const siblings = ft_state.tags.filter(t => (t.parentId || null) === (tag.parentId || null)).sort((a, b) => a.order - b.order);
const idx = siblings.findIndex(t => t.id === tag.id);
upBtn.disabled = idx <= 0; downBtn.disabled = idx >= siblings.length - 1;
upBtn.addEventListener('click', () => { moveTagInSiblings(tag, -1); rebuildTagList(); });
downBtn.addEventListener('click', () => { moveTagInSiblings(tag, 1); rebuildTagList(); });
dragHandle.addEventListener('dragstart', (ev) => { ev.stopPropagation(); ft_dragSrcEntry = { kind: 'tag', tagId: tag.id }; row.classList.add('ft-modal-tag-item-dragging'); ev.dataTransfer.setData('text/plain', tag.id); ev.dataTransfer.effectAllowed='move'; });
dragHandle.addEventListener('dragend', (ev) => { ev.stopPropagation(); ft_dragSrcEntry = null; row.classList.remove('ft-modal-tag-item-dragging'); clearDropClasses(); });
row.addEventListener('dragover', (ev) => {
if (!ft_dragSrcEntry) return;
const rect = row.getBoundingClientRect(); const ratio = (ev.clientY - rect.top) / rect.height;
clearDropClasses();
if (ratio > 0.3 && ratio < 0.7) { ev.preventDefault(); row.classList.add('ft-modal-tag-item-drop-child'); }
});
row.addEventListener('drop', (ev) => {
if (!ft_dragSrcEntry) return;
const rect = row.getBoundingClientRect(); const ratio = (ev.clientY - rect.top) / rect.height;
if (ratio > 0.3 && ratio < 0.7) { ev.preventDefault(); ev.stopPropagation(); moveTagAsChild(ft_getTagById(ft_dragSrcEntry.tagId), tag); rebuildTagList(); }
});
}
row.appendChild(mainCell); row.appendChild(dragHandle); row.appendChild(orderDiv); row.appendChild(delBtn); tagListEl.appendChild(row);
});
}
tagListEl.ondragover = (ev) => {
if (!ft_dragSrcEntry) return;
if (ev.defaultPrevented) return;
ev.preventDefault();
clearDropClasses();
tagListEl.classList.remove('ft-drag-to-root');
// ã¯ã©ã¹ãªã»ãã: ãã¹ãŠã®è¡ããã«ãŒãå€å®ã¯ã©ã¹ãäžæŠæ¶ã
tagListEl.querySelectorAll('.ft-is-root-ref').forEach(el => el.classList.remove('ft-is-root-ref'));
const info = getDropTargetInfoFromY(ev.clientY);
// 1. äžçªäžã®äœçœãžã®ããããïŒã«ãŒãæ«å°ŸïŒ
if (info.mode === 'root-end') {
tagListEl.classList.add('ft-drag-to-root');
}
// 2. è¡ãžã®ããããïŒéé or 芪ååïŒ
else if (info.row) {
// èªåèªèº«ãžã®ããããã§ãªãå Žå
if (info.row.dataset.tagId !== ft_dragSrcEntry.tagId) {
const targetId = info.row.dataset.tagId;
const targetKind = info.row.dataset.kind;
// âŒâŒâŒ ã«ãŒãéå±€ãã©ããã®å€å® âŒâŒâŒ
let isRoot = false;
if (targetKind === 'uncat') {
// ãæªåé¡ãã¯åžžã«ã«ãŒã
isRoot = true;
} else if (targetId) {
// ã¿ã°ã®å Žåã芪ID(parentId)ãç¡ããã°ã«ãŒã
const tTag = ft_getTagById(targetId);
if (tTag && !tTag.parentId) {
isRoot = true;
}
}
// ã«ãŒãéå±€ãªãå°çšã¯ã©ã¹ãä»äžïŒâ CSSã§éç·ã«ãªãïŒ
if (isRoot) {
info.row.classList.add('ft-is-root-ref');
}
// ååŸ(before/after) ãŸã㯠芪å(child) ã®ã¯ã©ã¹ãä»äž
info.row.classList.add(
info.mode === 'before' ? 'ft-modal-tag-item-drop-before' : 'ft-modal-tag-item-drop-after'
);
// 芪ååïŒè¡ã®äžå€®ããããïŒã®å€å®ãããå Žåã¯äžæžã
const rect = info.row.getBoundingClientRect();
const ratio = (ev.clientY - rect.top) / rect.height;
if (ratio > 0.3 && ratio < 0.7) {
// äžå€®ããããã¯ãå
¥ãåããªã®ã§ãç·ã®ã¯ã©ã¹ãæ¶ããŠèæ¯ã¯ã©ã¹ãä»ãã
info.row.classList.remove('ft-modal-tag-item-drop-before', 'ft-modal-tag-item-drop-after');
info.row.classList.add('ft-modal-tag-item-drop-child');
}
}
}
};
tagListEl.ondrop = (ev) => {
if (!ft_dragSrcEntry) return;
if (ev.defaultPrevented) return;
ev.preventDefault();
clearDropClasses();
tagListEl.classList.remove('ft-drag-to-root'); // ã¯ã©ã¹åé€
const info = getDropTargetInfoFromY(ev.clientY);
const srcTag = ft_getTagById(ft_dragSrcEntry.tagId);
if (!srcTag) return;
// root-end ãªãã芪ãªããã«ããŠãäžçªäžããž
if (info.mode === 'root-end') {
srcTag.parentId = null; // 芪ãè§£é€
// ã«ãŒãèŠçŽ ã®äžã§ã®æå€§order + 1 ãèšå®ããŠæ«å°Ÿãž
const rootTags = ft_state.tags.filter(t => !t.parentId);
const maxOrder = rootTags.length ? Math.max(...rootTags.map(t => t.order || 0)) : 0;
srcTag.order = maxOrder + 1;
ft_normalizeTagOrders();
ft_clampUncategorizedOrder();
ft_saveState();
rebuildTagList();
return;
}
// æ¢åã®åŠçïŒè¡éãžã®ããããïŒ
if (info.row && info.row.dataset.kind === 'tag') {
const targetId = info.row.dataset.tagId;
// èªåèªèº«ãžã®ããããã¯ç¡èŠ
if (targetId === srcTag.id) return;
const tTag = ft_getTagById(targetId);
if (info.mode === 'before') moveTagBefore(srcTag, tTag);
else moveTagAfter(srcTag, tTag);
} else {
// æªåé¡(Uncategorized)ãšã®äœçœ®é¢ä¿åŠç
moveTagToRootRelativeToUncat(srcTag, info.mode);
}
rebuildTagList();
};
rebuildTagList();
const newTagSection = document.createElement('div'); newTagSection.className = 'ft-modal-new-tag';
const newTagLabel = document.createElement('div'); newTagLabel.textContent = i18n.t('FT_DROPDOWN_NEW_TAG');
const newTagRow = document.createElement('div'); newTagRow.className = 'ft-modal-new-tag-row';
const newNameInput = document.createElement('input'); newNameInput.className = 'ft-modal-tag-name'; newNameInput.type = 'text'; newNameInput.placeholder = i18n.t('FT_DROPDOWN_NEW_TAG_PLACEHOLDER');
const newColorInput = document.createElement('input'); newColorInput.className = 'ft-modal-tag-color'; newColorInput.type = 'color'; newColorInput.value = '#1d9bf0';
const newAddBtn = document.createElement('button'); newAddBtn.className = 'ft-modal-button'; newAddBtn.textContent = i18n.t('FT_DROPDOWN_NEW_TAG_ADD'); newAddBtn.type='button';
function doAddNew() {
const name = newNameInput.value.trim();
if (!name) return;
ft_createNewTag(name, newColorInput.value, null); ft_saveState();
newNameInput.value = ''; rebuildTagList();
}
newAddBtn.addEventListener('click', doAddNew);
newNameInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') { ev.preventDefault(); doAddNew(); } });
newTagRow.appendChild(newColorInput); newTagRow.appendChild(newNameInput); newTagRow.appendChild(newAddBtn);
newTagSection.appendChild(newTagLabel); newTagSection.appendChild(newTagRow);
body.appendChild(displaySection); body.appendChild(tagListEl); body.appendChild(newTagSection);
const footer = document.createElement('div'); footer.className = 'ft-modal-footer';
const closeBtn = document.createElement('button'); closeBtn.className = 'ft-modal-button'; closeBtn.textContent = i18n.t('FT_SETTINGS_CLOSE'); closeBtn.type='button';
closeBtn.addEventListener('click', ft_closeSettingsModal);
footer.appendChild(closeBtn);
modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer);
backdrop.appendChild(modal);
backdrop.addEventListener('mousedown', (ev) => { if (ev.target === backdrop) ft_closeSettingsModal(); });
ft_settingsModalBackdrop = backdrop;
document.body.appendChild(backdrop);
}
// ------------- ã€ãã³ãå§è² & å
ç¢åæå (Optimized) ------------- //
function ft_installGlobalListeners() {
// Document level Delegation
document.addEventListener('click', (ev) => {
const target = ev.target;
if (!(target instanceof Element)) return;
// 1. Tag Chip Click
const chipBtn = target.closest('.ft-tag-chip');
if (chipBtn) {
ev.stopPropagation(); ev.preventDefault();
const tweetId = chipBtn.dataset.tweetId;
if (tweetId && ft_state.enabled) {
ft_openTagDropdown(chipBtn, tweetId);
}
return;
}
// 3. Close Dropdown on outside click
if (ft_currentDropdown && !ft_currentDropdown.contains(target)) {
ft_closeTagDropdown();
}
}, true); // Use capture for better delegation reliability
document.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape') { ft_closeTagDropdown(); ft_closeSettingsModal(); }
});
}
function ft_startRobustPolling() {
let count = 0;
const maxChecks = 10;
const intervalId = setInterval(() => {
count++;
processNewTweets(true);
if (count >= maxChecks) clearInterval(intervalId);
}, 500);
}
// ------------- ãã€ãŒãåŠç (åäžèŠçŽ ) ------------- //
function ft_processTweetArticle(article) {
const tweetId = article.dataset.ftTweetId || ft_extractTweetId(article);
if (!tweetId) return;
article.dataset.ftTweetId = tweetId;
if (!ft_state.enabled) {
ft_removeTagChipFromArticle(article);
return;
}
// ãã€ãã£ãããã¯ããŒã¯ãåãªãã¿ã°æç¡ã®ãã§ãã¯ãåé€
// ããæ°ã«å
¥ãæžã¿ (isFav)ãã®å Žåã®ã¿ã¿ã°ãããã衚瀺ãã
const isFav = (typeof isFavorited === 'function') && isFavorited(tweetId);
if (isFav) {
ft_attachTagChipToArticle(article, tweetId);
} else {
ft_removeTagChipFromArticle(article);
}
}
// ------------- åæå ------------- //
function ft_init() {
if (ft_initialized) return;
ft_initialized = true;
ft_state = ft_loadState();
ft_installGlobalListeners();
processNewTweets(true);
ft_startRobustPolling();
}
/* --- End Favorite Tags: Code Block --- */
const LANG_OVERRIDE_KEY = 'advUILang_v1';
// Settings ã§æå®ããã UI èšèªãããã°ãæ€åºçµæããåªå
ããŠé©çš
try {
const overrideLang = kv.get(LANG_OVERRIDE_KEY, '');
if (overrideLang && i18n.translations[overrideLang]) {
i18n.lang = overrideLang;
}
} catch (_) {}
const trigger = document.createElement('button');
const HISTORY_SORT_KEY = 'advHistorySort_v1';
trigger.id = 'advanced-search-trigger';
trigger.type = 'button';
trigger.innerHTML = SEARCH_SVG;
trigger.classList.add('adv-trigger-search');
trigger.setAttribute('aria-label', i18n.t('tooltipTrigger'));
trigger.setAttribute('aria-haspopup', 'dialog');
trigger.setAttribute('aria-expanded', 'false');
document.body.appendChild(trigger);
const modalContainer = document.createElement('div');
modalContainer.innerHTML = modalHTML;
// bodyãžã®åçŽè¿œå ãããã#layers ãšåãéå±€ã«æ¿å
¥
const mountModal = () => {
const layers = document.getElementById('layers');
// #layers ãèŠã€ããã°ãã®èŠªèŠçŽ ã«è¿œå ïŒlayersã®çŽåïŒè£åŽã«æ¿å
¥ïŒ
if (layers && layers.parentNode) {
// ãŸã æ¿å
¥ãããŠããªãããŸãã¯å Žæãéãå Žåã®ã¿ç§»å
if (modalContainer.nextSibling !== layers) {
layers.parentNode.insertBefore(modalContainer, layers);
}
} else {
// #layers ããŸã ãªãå Žå㯠body ã«ä»®çœ®ã
if (modalContainer.parentNode !== document.body) {
document.body.appendChild(modalContainer);
}
}
};
// ååé
眮
mountModal();
// X (React) ãç»é¢é·ç§»ã§DOMãæžãæããŠã¢ãŒãã«ãæ¶ãããã®ãé²ãããã宿çã«é
眮ãä¿®æ£
setInterval(mountModal, 500);
i18n.apply(modalContainer);
const modal = document.getElementById('advanced-search-modal');
const form = document.getElementById('advanced-search-form');
const closeButton = modal.querySelector('.adv-modal-close');
const clearButton = document.getElementById('adv-clear-button');
const applyButton = document.getElementById('adv-apply-button');
const saveButton = document.getElementById('adv-save-button');
const footerEl = modal.querySelector('.adv-modal-footer');
const toastEl = document.getElementById('adv-toast');
const secretBtn = document.getElementById('adv-secret-btn');
const secretStateEl = document.getElementById('adv-secret-state');
const settingsModal = document.getElementById('adv-settings-modal');
const settingsLangSel = document.getElementById('adv-settings-lang');
const settingsInitialTabSel = document.getElementById('adv-settings-initial-tab');
const settingsFileInput = document.getElementById('adv-settings-file-input');
const settingsOpenBtn = document.getElementById('adv-settings-button');
const settingsCloseBtn = document.getElementById('adv-settings-close');
const settingsCloseFooterBtn = document.getElementById('adv-settings-close-footer');
const settingsExportBtn = document.getElementById('adv-settings-export');
const settingsImportBtn = document.getElementById('adv-settings-import');
const settingsResetBtn = document.getElementById('adv-settings-reset');
const historyClearAllBtn = document.getElementById('adv-history-clear-all');
historyClearAllBtn.textContent = i18n.t('historyClearAll');
const accountScopeSel = document.getElementById('adv-account-scope');
const locationScopeSel = document.getElementById('adv-location-scope');
['n','e','s','w','ne','nw','se','sw'].forEach(dir => {
const h = document.createElement('div');
h.className = `adv-resizer ${dir}`;
h.dataset.dir = dir;
modal.appendChild(h);
});
const EXC_NAME_KEY = 'advExcludeHitName_v1';
const EXC_HANDLE_KEY = 'advExcludeHitHandle_v1';
const EXC_REPOSTS_KEY = 'advExcludeReposts_v1';
const EXC_HASHTAGS_KEY = 'advExcludeTimelineHashtags_v1';
const excNameEl = document.getElementById('adv-exclude-hit-name');
const excHandleEl = document.getElementById('adv-exclude-hit-handle');
const excRepostsEl = document.getElementById('adv-filter-reposts-exclude');
const excHashtagsEl = document.getElementById('adv-filter-hashtags-exclude');
const loadExcludeFlags = () => ({
name: kv.get(EXC_NAME_KEY, '1') === '1',
handle: kv.get(EXC_HANDLE_KEY, '1') === '1',
reposts: kv.get(EXC_REPOSTS_KEY, '0') === '1',
hashtags: kv.get(EXC_HASHTAGS_KEY, '0') === '1',
});
const saveExcludeFlags = (v) => {
kv.set(EXC_NAME_KEY, v.name ? '1':'0');
kv.set(EXC_HANDLE_KEY, v.handle ? '1':'0');
kv.set(EXC_REPOSTS_KEY, v.reposts ? '1':'0');
kv.set(EXC_HASHTAGS_KEY, v.hashtags ? '1':'0');
};
{
const st = loadExcludeFlags();
if (excNameEl) excNameEl.checked = st.name;
if (excHandleEl) excHandleEl.checked = st.handle;
if (excRepostsEl) excRepostsEl.checked = st.reposts;
if (excHashtagsEl) excHashtagsEl.checked = st.hashtags;
}
[excNameEl, excHandleEl, excRepostsEl, excHashtagsEl].forEach(el=>{
if (!el) return;
el.addEventListener('change', ()=>{
saveExcludeFlags({
name: excNameEl?.checked ?? false,
handle: excHandleEl?.checked ?? false,
reposts: excRepostsEl?.checked ?? false,
hashtags: excHashtagsEl?.checked ?? false,
});
rescanAllTweetsForFilter();
});
});
themeManager.observeChanges(modal, trigger);
// Accounts/Listsã¿ãã®èæ¯ãããããã¿ãŒã²ããã«ããããã®ãã«ããŒ
const setupBackgroundDrop = (panel, host, unassignFunction) => {
const feedbackClass = 'adv-bg-drop-active';
const SECT_MIME = 'adv/folder'; // ãã©ã«ãäžŠã³æ¿ãD&Dã®MIME
// panel å
ã® .adv-zoom-root ãã€ãã³ãã®å¯Ÿè±¡ã«è¿œå
const zoomRoot = panel?.querySelector('.adv-zoom-root');
const eventTargets = [panel, host, zoomRoot].filter(Boolean); // ã€ãã³ãããªãã¹ã³ãã察象
// ç Žç·ã衚瀺ãã察象㯠panel ã®ã¿ãšãã
const feedbackTargets = [panel].filter(Boolean); // ç Žç·ã衚瀺ãã察象
const onDragEnter = (ev) => {
// ã¢ã€ãã ïŒtext/plainïŒã§ãããã»ã¯ã·ã§ã³ïŒadv/folderïŒã§ã¯ãªã
if (ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
// ã¿ãŒã²ããã panel, host, zoomRoot ã®ãããã
if (eventTargets.includes(ev.target)) {
// ç Žç·ã¯ feedbackTargets ã«ä»ãã (ä»å㯠panel ã®ã¿)
feedbackTargets.forEach(t => t.classList.add(feedbackClass));
}
}
};
const onDragLeave = (ev) => {
// ã¿ãŒã²ããèªèº«ããé¢ããæã ããã£ãŒãããã¯ãæ¶ã
if (eventTargets.includes(ev.target)) {
// ç Žç·ã¯ feedbackTargets ããæ¶ã
feedbackTargets.forEach(t => t.classList.remove(feedbackClass));
}
};
const onDragOver = (ev) => {
// ã«ãŒãœã«ã .adv-folder (ãã©ã«ããŒ) ã®äžã«ããå Žåã¯ãåçç¡çšã§èæ¯ãã€ã©ã€ããæ¶ããŠçµãã
if (ev.target.closest('.adv-folder')) {
feedbackTargets.forEach(t => t.classList.remove(feedbackClass));
return;
}
// dropã€ãã³ããçºç«ãããããã«ãdragoverã§preventDefaultãå¿
èŠ
// ã¢ã€ãã ã§ãããã¿ãŒã²ããã panel/host/zoomRoot èªèº«ã®å Žåã®ã¿èš±å¯
if (eventTargets.includes(ev.target) && ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
ev.preventDefault();
ev.stopPropagation();
/* âŒâŒâŒ èæ¯ïŒééïŒã«ãããªãããã©ã«ããŒã®ãã€ã©ã€ãã¯åŒ·å¶çã«æ¶ã âŒâŒâŒ */
document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => delete el.dataset.drop);
// ç Žç·ã¯ feedbackTargets ã«ä»ãç¶ãã
feedbackTargets.forEach(t => t.classList.add(feedbackClass));
} else {
// åèŠçŽ ïŒãã©ã«ããªã©ïŒã®äžã«æ¥ããèæ¯ãã€ã©ã€ãã¯æ¶ã
feedbackTargets.forEach(t => t.classList.remove(feedbackClass));
// æ®ã£ãŠãããã©ã«ããŒèŠåºãã®ç Žç·ã確å®ã«è§£é€
document.querySelectorAll('.adv-folder-header[data-drop="1"]').forEach(el => { delete el.dataset.drop; });
}
};
const onDrop = (ev) => {
feedbackTargets.forEach(t => t.classList.remove(feedbackClass)); // ããããæã¯åžžã«ãã€ã©ã€ãè§£é€
// æçµãã§ãã¯ïŒã¢ã€ãã ã§ãããããã«/ãã¹ã/zoomRoot èªèº«ãžã®ãããã
if (eventTargets.includes(ev.target) && ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
ev.preventDefault();
ev.stopPropagation();
const draggedId = ev.dataTransfer.getData('text/plain');
if (draggedId) {
unassignFunction(draggedId); // (unassignAccount ãŸã㯠unassignList ãå®è¡)
}
}
};
// ã€ãã³ã㯠eventTargets ã«ç»é²ãã
eventTargets.forEach(target => {
if (!target) return; // hostããŸã ååšããªãå Žåãªã©
target.addEventListener('dragenter', onDragEnter);
target.addEventListener('dragleave', onDragLeave);
target.addEventListener('dragover', onDragOver);
target.addEventListener('drop', onDrop);
});
};
// ãã©ãã°çµäºæïŒæåã»ãã£ã³ã»ã«åããïŒã«ã匷å¶çã«ãã¹ãŠã®ãããããã€ã©ã€ããè§£é€ãã
document.addEventListener('dragend', () => {
// èæ¯ã®ç Žç·ãæ¶ã
document.querySelectorAll('.adv-bg-drop-active').forEach(el => {
el.classList.remove('adv-bg-drop-active');
});
// ãã©ã«ããŒããããŒçã®ãã€ã©ã€ãã念ã®ããæ¶ã
document.querySelectorAll('[data-drop="1"]').forEach(el => {
delete el.dataset.drop;
});
// ãã©ãã°äžã®ã¯ã©ã¹ã念ã®ããæ¶ã
document.querySelectorAll('.adv-item.dragging').forEach(el => {
el.classList.remove('dragging');
});
document.body.classList.remove('adv-dragging');
});
// --- generic unassign helper (de-duplicate) ---
// Remove an item from all folders under FOLDERS_KEY,
// then move the item to the top of the master list (Unassigned head).
function unassignItemGeneric({ FOLDERS_KEY, loadItems, saveItems, itemId }) {
// 1) remove from every folder
const folders = loadFolders(FOLDERS_KEY, '');
let changed = false;
for (const f of folders) {
const before = f.order.length;
f.order = f.order.filter(id => id !== itemId);
if (f.order.length !== before) { f.ts = Date.now(); changed = true; }
}
if (changed) saveFolders(FOLDERS_KEY, folders);
// 2) bump the item to the head of the master list (Unassigned first)
const all = loadItems();
const hit = all.find(x => x.id === itemId);
if (hit) {
const next = [hit, ...all.filter(x => x.id !== itemId)];
saveItems(next);
}
}
// --- generic "move item to a folder" helper ---
function moveItemToFolderGeneric({ FOLDERS_KEY, itemId, folderId }) {
const fArr = loadFolders(FOLDERS_KEY, '');
// remove from every folder
for (const f of fArr) {
const before = f.order.length;
f.order = f.order.filter(id => id !== itemId);
if (f.order.length !== before) f.ts = Date.now();
}
// add to head of the target folder
const target = fArr.find(f => f.id === folderId);
if (target) {
target.order = [itemId, ...target.order.filter(id => id !== itemId)];
target.ts = Date.now();
}
saveFolders(FOLDERS_KEY, fArr);
}
// === [ADD] ç¹å move 颿°ïŒããŒã¹ãïŒåæç»ãŸã§å«ãïŒ ===
function moveAccountToFolder(accountId, folderId) {
moveItemToFolderGeneric({
FOLDERS_KEY: ACCOUNTS_FOLDERS_KEY,
itemId: accountId,
folderId
});
showToast(i18n.t('toastReordered'));
try { renderAccounts(); } catch(_) {}
}
function moveSavedToFolder(savedId, folderId) {
moveItemToFolderGeneric({
FOLDERS_KEY: SAVED_FOLDERS_KEY,
itemId: savedId,
folderId
});
showToast(i18n.t('toastReordered'));
try { renderSaved(); } catch(_) {}
}
function moveListToFolder(listId, targetFolderId) {
moveItemToFolderGeneric({
FOLDERS_KEY: LISTS_FOLDERS_KEY,
itemId: listId,
folderId: targetFolderId
});
showToast(i18n.t('toastReordered'));
try { renderLists(); } catch(_) {}
}
// æªåé¡åããžãã¯ãå
±éå (Accountçš)
const unassignAccount = (draggedId) => {
unassignItemGeneric({
FOLDERS_KEY: ACCOUNTS_FOLDERS_KEY,
loadItems: loadAccounts,
saveItems: saveAccounts,
itemId: draggedId,
});
showToast(i18n.t('toastReordered'));
renderAccounts();
};
// æªåé¡åããžãã¯ãå
±éå (Listçš)
const unassignList = (draggedId) => {
unassignItemGeneric({
FOLDERS_KEY: LISTS_FOLDERS_KEY,
loadItems: loadLists,
saveItems: saveLists,
itemId: draggedId,
});
showToast(i18n.t('toastReordered'));
renderLists();
};
// æªåé¡åããžãã¯ãå
±éå (Savedçš)
const unassignSaved = (draggedId) => {
unassignItemGeneric({
FOLDERS_KEY: SAVED_FOLDERS_KEY,
loadItems: () => migrateList(loadJSON(SAVED_KEY, [])),
saveItems: (arr) => saveJSON(SAVED_KEY, migrateList(arr)),
itemId: draggedId,
});
showToast(i18n.t('toastReordered'));
renderSaved();
};
/* ========= Favorites Logic ========= */
const FAV_KEY = 'advFavorites_v1';
const FAV_SORT_KEY = 'advFavoritesSort_v1';
// é«éå: ã¡ã¢ãªãã£ãã·ã¥å€æ°ãå®çŸ©
let _favCache = null; // é
åããŒã¿ (ã¬ã³ããªã³ã°çš)
let _favSet = null; // IDæ€çŽ¢çš Set (O(1)å€å®çš)
// ããŒã¿æ§é : { id: tweetId, text, user: {name, handle, avatar}, media: [], ts, ... }
// ãã£ãã·ã¥ãããã°ãããè¿ãããªããã°ããŒãããŠãã£ãã·ã¥æ§ç¯ã
const loadFavorites = () => {
if (_favCache) return _favCache;
const raw = loadJSON(FAV_KEY, []);
_favCache = raw;
_favSet = new Set(raw.map(x => x.id));
return _favCache;
};
// ä¿åæã«ãã£ãã·ã¥ãšSetãåæã«æŽæ°ãã
const saveFavorites = (arr) => {
_favCache = arr;
_favSet = new Set(arr.map(x => x.id));
saveJSON(FAV_KEY, arr);
};
const toggleFavorite = (tweetMeta) => {
// loadFavoritesã¯ãã£ãã·ã¥ãè¿ãã®ã§é«é
const list = loadFavorites();
const idx = list.findIndex(x => x.id === tweetMeta.id);
if (idx >= 0) {
// --- åé€ (Remove) ---
// é
åãçŽæ¥å€æŽãããæ°ããé
åãäœã£ãŠæŽåæ§ãä¿ã€ã®ããã¹ãã ã
// ããã§ã¯å
ã®ããžãã¯ã«åãããŠç Žå£ç倿ŽãããŠãã saveFavorites ã§å
šäœæŽæ°ãã
list.splice(idx, 1);
saveFavorites(list); // ããã§ _favSet ãæŽæ°ããã
// è§£é€æã¯ã¿ã°ããŒã¿ãåé€ãã
if (ft_state && ft_state.tweetTags && ft_state.tweetTags[tweetMeta.id]) {
delete ft_state.tweetTags[tweetMeta.id];
ft_saveState();
}
renderFavorites(); // (ãæ°ã«å
¥ãã¿ããéããŠããå Žåçš)
showToast(i18n.t('toastUnfavorited'));
} else {
list.unshift({ ...tweetMeta, ts: Date.now() });
saveFavorites(list); // ããã§ _favSet ãæŽæ°ããã
renderFavorites();
showToast(i18n.t('toastFavorited'));
}
// æåŸã«å
šåæ
updateAllFavoriteButtons(); // ãã¿ã³æŽæ°
refreshTagChipsForTweet(tweetMeta.id); // ã¿ã°ãããæŽæ°
return idx < 0; // 远å ãããã true
};
// Setã䜿ã£ãè¶
é«éå€å® (JSON.parseãçºçããªã)
const isFavorited = (tweetId) => {
if (!_favSet) loadFavorites(); // ååããŒãããŸã ãªãå®è¡
return _favSet.has(tweetId);
};
const deleteFavorite = (id) => {
// 1. ãæ°ã«å
¥ããªã¹ãããåé€
const list = loadFavorites().filter(x => x.id !== id);
saveFavorites(list); // ããã§ _favSet ãæŽæ°ããã
// 2. ã¿ã°ããŒã¿ãåé€ãã (ããŒã¿ã®ã¯ãªãŒã³ã¢ãã)
if (ft_state && ft_state.tweetTags && ft_state.tweetTags[id]) {
delete ft_state.tweetTags[id];
ft_saveState(); // ç¶æ
ãä¿å
}
// 3. UIæŽæ° (ãªã¹ãåæç» & ããŒã¹ã)
renderFavorites();
showToast(i18n.t('toastDeleted'));
// 4. ã¿ã€ã ã©ã€ã³äžã®èŠãç®ãåæ
updateAllFavoriteButtons(); // ãã¿ã³ã®è²ãæŽæ°
refreshTagChipsForTweet(id); // ã¿ã°ããããæ¶å»
};
// ãæ°ã«å
¥ããªã¹ãã®ã€ãã³ãå§è²ãã³ãã©
function setupFavoritesDelegation() {
const listEl = document.getElementById('adv-favorites-list');
// ãŸã èŠçŽ ããªãããŸãã¯æ¢ã«ç»é²æžã¿ãªãã¹ããã
if (!listEl || listEl._delegationAttached) return;
listEl._delegationAttached = true;
listEl.addEventListener('click', (e) => {
const target = e.target;
if (!target) return;
// A. åé€ãã¿ã³
const deleteBtn = target.closest('[data-action="delete"]');
if (deleteBtn) {
const row = deleteBtn.closest('.adv-item');
if (row && row.dataset.id) {
e.stopPropagation();
deleteFavorite(row.dataset.id);
}
return;
}
// B. Openãã¿ã³
const openBtn = target.closest('[data-action="open"]');
if (openBtn) {
const row = openBtn.closest('.adv-item');
if (row && row.dataset.id) {
e.stopPropagation();
// IDããææ°ããŒã¿ãåŒãïŒã¯ããŒãžã£ããªãããïŒ
const item = loadFavorites().find(x => x.id === row.dataset.id);
if (item) {
const url = `/${item.user.handle}/status/${item.id}`;
spaNavigate(url, { ctrlMeta: e.ctrlKey || e.metaKey });
if (window.innerWidth <= 700) closeModal();
}
}
return;
}
// C. ãŠãŒã¶ãŒãªã³ã¯
const userLink = target.closest('.adv-link-user');
if (userLink) {
e.preventDefault();
e.stopPropagation();
const href = userLink.getAttribute('href');
if (href) {
spaNavigate(href, { ctrlMeta: e.ctrlKey || e.metaKey });
if (window.innerWidth <= 700) closeModal();
}
return;
}
// D. ã¡ãã£ã¢ãµã ãã€ã«
const mediaImg = target.closest('.adv-media-thumb');
if (mediaImg) {
e.stopPropagation();
const row = mediaImg.closest('.adv-item');
if (!row) return;
// ææ°ããŒã¿ã®åååŸ
const item = loadFavorites().find(x => x.id === row.dataset.id);
if (!item) return;
const type = mediaImg.dataset.type;
const index = mediaImg.dataset.index;
const isQuote = mediaImg.dataset.isQuote === '1';
// åŒçšã§IDããªãå Žå(ååŸäžèœ)ã¯ç¡èŠ
if (isQuote && item.quote && !item.quote.id) return;
let targetBaseUrl = `/${item.user.handle}/status/${item.id}`;
if (isQuote && item.quote && item.quote.id) {
targetBaseUrl = `/${item.quote.user.handle}/status/${item.quote.id}`;
}
let targetPath = targetBaseUrl;
if (type === 'image') {
targetPath = `${targetBaseUrl}/photo/${index}`;
} else if (type === 'video') {
// åç»ã®å Žåãã€ã³ããã¯ã¹ä»ãURLãžé·ç§»ããã
targetPath = `${targetBaseUrl}/video/${index}`;
}
spaNavigate(targetPath, { ctrlMeta: e.ctrlKey || e.metaKey });
if (window.innerWidth <= 700) closeModal();
return;
}
// E. æ¬æäžã®ãªã³ã¯ (.adv-content-link)
const contentLink = target.closest('.adv-content-link');
if (contentLink) {
e.stopPropagation();
// target="_blank" ãªããã©ãŠã¶ããã©ã«ãåäœãž
return;
}
});
}
// è¡ã¬ã³ããªã³ã°
function renderFavoriteRow(item) {
const row = document.createElement('div');
row.className = 'adv-item';
row.style.paddingRight = '60px';
row.dataset.id = item.id;
const text = item.text || '';
const bodyHtml = safeLinkify(text);
const displayTime = item.postedAt ? fmtTime(item.postedAt) : fmtTime(item.ts);
// ã¡ãã£ã¢HTMLçæ
const buildMediaHtml = (mediaList, isQuote = false) => {
if (!mediaList || mediaList.length === 0) return '';
let html = '<div class="adv-item-media-row">';
mediaList.forEach((m, i) => {
const mediaType = m.type || 'image';
const isVideo = mediaType === 'video';
const playIcon = isVideo
? `<div class="adv-media-play-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"></path></svg></div>`
: '';
// åŒçšã§IDããªãå Žåãã¯ãªãã¯ã§ããªãèŠèŠè¡šçŸ(cursor:default)ãHTMLçææç¹ã§é©çš
let styleAttr = '';
if (isQuote && item.quote && !item.quote.id) {
styleAttr = 'style="cursor:default"';
}
html += `<div class="adv-media-wrap">
<img src="${escapeAttr(m.url)}"
data-type="${mediaType}"
data-index="${i + 1}"
data-is-quote="${isQuote ? '1' : '0'}"
class="adv-media-thumb" loading="lazy" alt="Media" title="Open Media"
${styleAttr}>
${playIcon}
</div>`;
});
html += '</div>';
return html;
};
const mainMediaHtml = buildMediaHtml(item.media, false);
// --- Link Card HTML ---
let cardHtml = '';
if (item.card) {
let domain = item.card.domain || '';
if (!domain) {
try {
const u = new URL(item.card.url);
domain = u.hostname;
} catch(e) { domain = 'link'; }
}
// ç»åéšåã®HTMLçæ
let mediaPart = '';
if (item.card.img) {
mediaPart = `<img src="${escapeAttr(item.card.img)}" class="adv-card-image" loading="lazy" />`;
} else if (item.card.svg) {
// SVGãããå ŽåïŒSmall Cardã§ç»åããªãå Žåãªã©ïŒ
mediaPart = `<div class="adv-card-icon-container">${item.card.svg}</div>`;
}
// ã¯ã©ã¹ã®åãæ¿ã
const boxClass = item.card.style === 'small' ? 'adv-card-box small-card' : 'adv-card-box';
cardHtml = `
<a href="${escapeAttr(item.card.url)}" target="_blank" rel="noopener noreferrer nofollow" class="${boxClass}">
${mediaPart}
<div class="adv-card-content">
<div class="adv-card-domain">${escapeHTML(domain)}</div>
<div class="adv-card-title">${escapeHTML(item.card.title)}</div>
</div>
</a>
`;
}
// --- åŒçšHTML ---
let quoteHtml = '';
if (item.quote) {
const q = item.quote;
const qUserUrl = `/${escapeAttr(q.user.handle)}`;
const qMediaHtml = buildMediaHtml(q.media, true);
let qBodyHtml = safeLinkify(q.text);
if (q.showMore && q.showMore.url) {
// class="adv-link" ãä»ããããšã§ãäžéšã® addEventListener ã«ãŒããé©çšããSPAé·ç§»ã«ãªã
qBodyHtml += ` <a href="${escapeAttr(q.showMore.url)}" class="adv-link" style="color:var(--modal-primary-color); white-space:nowrap;">${escapeHTML(q.showMore.text)}</a>`;
}
quoteHtml = `
<div class="adv-quote-box">
<div class="adv-quote-header">
${q.user.avatar ? `<a class="adv-link adv-link-user" href="${qUserUrl}"><img src="${escapeAttr(q.user.avatar)}" class="adv-quote-avatar"></a>` : ''}
<a class="adv-link adv-link-user" href="${qUserUrl}" title="Quote User">
<span class="adv-quote-name">${escapeHTML(q.user.name)}</span>
<span class="adv-quote-handle">@${escapeHTML(q.user.handle)}</span>
</a>
</div>
<div class="adv-quote-text">${qBodyHtml}</div>
${qMediaHtml}
</div>
`;
}
const userUrl = `/${escapeAttr(item.user.handle)}`;
row.innerHTML = `
${item.user.avatar
? `<a class="adv-item-avatar-link adv-link adv-link-user" href="${userUrl}">
<img class="adv-item-avatar" src="${escapeAttr(item.user.avatar)}">
</a>`
: `<a class="adv-item-avatar-link adv-link adv-link-user" href="${userUrl}">
<div class="adv-item-avatar"></div>
</a>`
}
<div class="adv-item-main">
<div class="adv-item-title">
<a class="adv-link adv-link-user" href="${userUrl}" title="Open Profile">${escapeHTML(item.user.name)} <span style="font-weight:normal;color:var(--modal-text-secondary)">@${escapeHTML(item.user.handle)}</span></a>
<span class="adv-fav-tag-container"></span>
</div>
<div class="adv-item-body-text">${bodyHtml}</div>
${mainMediaHtml}
${cardHtml} ${quoteHtml}
<div class="adv-item-sub">
<span>${displayTime}</span>
</div>
</div>
<button class="adv-chip primary adv-fav-btn-pos adv-fav-btn-top" data-action="open">${i18n.t('buttonOpen')}</button>
<button class="adv-chip danger adv-fav-btn-pos adv-fav-btn-bottom" data-action="delete">${i18n.t('delete')}</button>
`;
// æ¢åã³ãŒãã«ãããã®åŠçããadv-link ã¯ã©ã¹ãæã€èŠçŽ ã«SPAé·ç§»ã€ãã³ããäžæ¬ç»é²
row.querySelectorAll('a.adv-link').forEach(a => {
a.addEventListener('click', (ev) => {
if (ev.defaultPrevented || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || ev.button !== 0) return;
ev.preventDefault();
const href = a.getAttribute('href') || `/${item.user.handle}`;
spaNavigate(href, { ctrlMeta: false });
if (window.innerWidth <= 700) {
closeModal();
}
});
});
const tagContainer = row.querySelector('.adv-fav-tag-container');
if (tagContainer && typeof ft_buildTagChip === 'function') {
const chip = ft_buildTagChip(item.id);
tagContainer.appendChild(chip);
}
return row;
}
/* --- âŒâŒâŒ æ±çšããŒãžããŒã·ã§ã³é¢æ° âŒâŒâŒ */
const PAGINATION_STATE = {}; // { [key]: { list, cursor, observer, renderer, container, sentinelClass } }
const PAGE_SIZE = 50;
// ãæ°ã«å
¥ãã¿ãå°çšã®çŸåšã®çµã蟌ã¿ç¶æ
ïŒã¡ã¢ãªä¿æïŒ
let favFilterTagId = 'ALL'; // 'ALL', 'UNCAT', or tagId
let favSearchQuery = '';
function renderPagedList(key, container, items, rowRenderer, emptyEl, emptyMsg) {
if (!container) return;
// ç¶æ
åæå or ååŸ
if (!PAGINATION_STATE[key]) {
PAGINATION_STATE[key] = { observer: null };
}
const state = PAGINATION_STATE[key];
// 以åã®Observerãããã°è§£é€
if (state.observer) {
state.observer.disconnect();
state.observer = null;
}
// ç¶æ
æŽæ°
state.list = items;
state.cursor = 0;
state.renderer = rowRenderer;
state.container = container;
state.sentinelClass = `adv-sentinel-${key}`;
// 衚瀺ã¯ãªã¢
container.innerHTML = '';
// 空ã®å Žå
if (items.length === 0) {
if (emptyEl) {
emptyEl.textContent = emptyMsg || '';
emptyEl.style.display = 'block';
}
return;
} else {
if (emptyEl) emptyEl.style.display = 'none';
}
// ãããåŠç颿°
const renderBatch = () => {
const nextBatch = state.list.slice(state.cursor, state.cursor + PAGE_SIZE);
if (nextBatch.length === 0) return;
const frag = document.createDocumentFragment();
nextBatch.forEach(item => {
frag.appendChild(state.renderer(item));
});
// ã»ã³ããã«ç®¡ç
let sentinel = container.querySelector(`.${state.sentinelClass}`);
if (!sentinel) {
sentinel = document.createElement('div');
sentinel.className = state.sentinelClass;
sentinel.style.height = '40px';
sentinel.style.margin = '10px 0';
// ãŸã DOMã«ç¡ããªãããã©ã°ã¡ã³ãã®åŸãã«è¿œå äºå®ïŒåŸè¿°ïŒ
}
// ãªã¹ããžã®æ¿å
¥
if (container.contains(sentinel)) {
container.insertBefore(frag, sentinel);
} else {
container.appendChild(frag);
container.appendChild(sentinel);
}
state.cursor += nextBatch.length;
updateSentinel(sentinel);
};
// ã»ã³ããã«ã®ç¶æ
æŽæ°ãšç£èŠ
const updateSentinel = (sentinel) => {
const hasMore = state.cursor < state.list.length;
if (hasMore) {
sentinel.style.display = 'block';
// Observerèšå®
if (!state.observer) {
state.observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// é£ç¶çºç«é²æ¢ã®ããäžæŠç£èŠè§£é€
state.observer.unobserve(entries[0].target);
setTimeout(renderBatch, 50);
}
}, {
root: container.closest('.adv-modal-body'), // ã¹ã¯ããŒã«èŠªèŠçŽ
rootMargin: '200px'
});
}
state.observer.observe(sentinel);
} else {
sentinel.style.display = 'none';
if (state.observer) state.observer.unobserve(sentinel);
}
};
// ååãããå®è¡
renderBatch();
}
function renderFavorites() {
const listEl = document.getElementById('adv-favorites-list');
const emptyEl = document.getElementById('adv-favorites-empty');
if (!listEl) return;
// 1. ããŒã«ããŒã®çæïŒãŸã ç¡ããã°ïŒ
if (!listEl.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
const bar = document.createElement('div');
bar.className = 'adv-folder-toolbar';
// ã¿ã°çµã蟌ã¿ãã¿ã³ããœãŒãéžæãæ€çŽ¢ããã¯ã¹
bar.innerHTML = `
<div style="display:flex; gap:6px; align-items:center; flex:0 0 auto;">
<button id="adv-favorites-tag-filter-btn" class="ft-filter-button" type="button">
<span class="ft-filter-button-label"></span>
<span class="ft-filter-button-caret">âŸ</span>
</button>
<select id="adv-favorites-sort" class="adv-select" style="max-width:140px; font-size:12px;">
<option value="saved_newest" data-i18n="sortSavedNewest"></option>
<option value="saved_oldest" data-i18n="sortSavedOldest"></option>
<option value="posted_newest" data-i18n="sortPostedNewest"></option>
<option value="posted_oldest" data-i18n="sortPostedOldest"></option>
</select>
</div>
<input id="adv-favorites-search" class="adv-input" type="text" placeholder="${i18n.t('placeholderSearchSaved')}" style="flex:1; min-width:80px;">
`;
// 翻蚳é©çšïŒåççæã®ããããã§é©çšïŒ
bar.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = i18n.t(el.dataset.i18n); });
listEl.parentElement.insertBefore(bar, listEl);
// ã€ãã³ããªã¹ããŒç»é²
const btn = bar.querySelector('#adv-favorites-tag-filter-btn');
const sortSel = bar.querySelector('#adv-favorites-sort');
const inp = bar.querySelector('#adv-favorites-search');
// A. ã¿ã°ãã£ã«ã¿
btn.addEventListener('click', (ev) => {
ev.stopPropagation();
ev.preventDefault();
ft_openFilterDropdown(btn, favFilterTagId, (val) => {
favFilterTagId = val;
renderFavorites();
});
});
// B. ãœãŒã倿Ž
sortSel.value = kv.get(FAV_SORT_KEY, 'saved_newest');
sortSel.addEventListener('change', () => {
kv.set(FAV_SORT_KEY, sortSel.value);
renderFavorites();
});
// C. æ€çŽ¢
inp.addEventListener('input', debounce(() => {
favSearchQuery = inp.value;
renderFavorites();
}, 200));
}
// 2. ããŒã«ããŒã®ç¶æ
æŽæ°ïŒã©ãã«èšå®ãªã©ïŒ
const btn = document.getElementById('adv-favorites-tag-filter-btn');
const labelSpan = btn ? btn.querySelector('.ft-filter-button-label') : null;
const inp = document.getElementById('adv-favorites-search');
const sortSel = document.getElementById('adv-favorites-sort');
if (inp) inp.placeholder = i18n.t('placeholderSearchSaved');
if (labelSpan) {
let labelText = i18n.t('FT_FILTER_ALL');
if (favFilterTagId === FT_FILTER_UNCATEGORIZED) {
labelText = i18n.t('FT_UNCATEGORIZED');
} else if (favFilterTagId !== 'ALL') {
const tag = ft_getTagById(favFilterTagId);
if (tag) {
labelText = ft_getTagDisplayLabelFromTag(tag) || tag.name;
} else {
favFilterTagId = 'ALL';
}
}
labelSpan.textContent = labelText;
}
if (inp && inp.value !== favSearchQuery) inp.value = favSearchQuery;
// ãœãŒãèšå®ã®èªã¿èŸŒã¿ïŒUIãšåæïŒ
const currentSort = kv.get(FAV_SORT_KEY, 'saved_newest');
if (sortSel && sortSel.value !== currentSort) sortSel.value = currentSort;
// 3. ããŒã¿ã®ããŒããšãã£ã«ã¿ãªã³ã°
const allItems = loadFavorites(); // { id, text, user, postedAt, ts, ... }
// ãã£ã«ã¿çµæãããŒã«ã«å€æ°ã«
let filteredList = allItems.filter(item => {
// A. ããã¹ãæ€çŽ¢
const q = favSearchQuery.trim().toLowerCase(); // æ€çŽ¢æã«åããŠæ£èŠåãã
if (q) {
const targetText = (item.text + ' ' + item.user.name + ' ' + item.user.handle).toLowerCase();
if (!targetText.includes(q)) return false;
}
// B. ã¿ã°ãã£ã«ã¿
if (favFilterTagId === 'ALL') return true;
// ft_state ããã³ ft_state.tweetTags ã®ååšç¢ºèªãè¡ã
const itemTagId = (ft_state && ft_state.tweetTags) ? ft_state.tweetTags[item.id] : null;
if (favFilterTagId === FT_FILTER_UNCATEGORIZED) return !itemTagId;
return itemTagId ? ft_isTagInSubtree(itemTagId, favFilterTagId) : false;
});
// 4. ãœãŒãé©çš
// ts: è¿œå æ¥æ, postedAt: æçš¿æ¥æ
// postedAt ãç¡ãå€ãããŒã¿ã¯ ts ããã©ãŒã«ããã¯ãšããŠäœ¿ã
filteredList.sort((a, b) => {
const tsA = a.ts || 0;
const tsB = b.ts || 0;
const postedA = a.postedAt || tsA; // fallback
const postedB = b.postedAt || tsB; // fallback
switch (currentSort) {
case 'saved_oldest': return tsA - tsB;
case 'posted_newest': return postedB - postedA;
case 'posted_oldest': return postedA - postedB;
case 'saved_newest':
default: return tsB - tsA;
}
});
// 5. æ±çšããŒãžããŒã·ã§ã³é¢æ°ã§æç»
renderPagedList('favorites', listEl, filteredList, renderFavoriteRow, emptyEl, i18n.t('emptyFavorites'));
}
/* ã¿ãããšä¿åã«å¯Ÿå¿ */
const ZOOM_KEYS = {
modal_ui: 'advZoom_modal_ui_v1',
tabs: 'advZoom_tabs_v1',
search: 'advZoom_tab_search_v1',
history: 'advZoom_tab_history_v1',
saved: 'advZoom_tab_saved_v1',
favorites: 'advZoom_tab_favorites_v1',
lists: 'advZoom_tab_lists_v1',
accounts:'advZoom_tab_accounts_v1',
mute: 'advZoom_tab_mute_v1',
};
const ZOOM_MIN = 0.5, ZOOM_MAX = 2.0, ZOOM_STEP = 0.1;
/* åã¿ãã®çŸåšå€ïŒã¡ã¢ãªãã£ãã·ã¥ïŒ */
const zoomByTab = {
modal_ui: 1.0,
tabs: 1.0,
search: 1.0,
history: 1.0,
saved: 1.0,
lists: 1.0,
accounts:1.0,
mute: 1.0,
};
const getActiveTabName = () => {
const btn = document.querySelector('.adv-tab-btn.active');
return btn?.dataset?.tab || 'search';
};
const getActiveZoomRoot = () =>
document.querySelector('.adv-tab-content.active .adv-zoom-root') ||
document.getElementById('adv-zoom-root');
const clampZoom = z => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(z*100)/100));
const loadZoomFor = (tab) => {
try {
const k = ZOOM_KEYS[tab] || ZOOM_KEYS.search;
// ããã©ã«ãå€ã '1' ããåå²ããã
const defaultZoom = (tab === 'search') ? '0.87' : '1'; // æ€çŽ¢ã¿ãã®ã¿ 0.87 ã«
const v = parseFloat(kv.get(k, defaultZoom)); // '1' ã ã£ãéšåã defaultZoom ã«å€æŽ
if (!Number.isNaN(v)) zoomByTab[tab] = clampZoom(v);
} catch {}
};
const saveZoomFor = (tab) => {
try {
const k = ZOOM_KEYS[tab] || ZOOM_KEYS.search;
kv.set(k, String(zoomByTab[tab]));
} catch {}
};
/* åæããŒãïŒå
šã¿ãïŒ */
Object.keys(zoomByTab).forEach(loadZoomFor);
const applyZoom = () => {
// 1. ã³ã³ãã³ããšãªã¢ã®ãºãŒã é©çš (åã¿ãã®äžèº«)
const tab = getActiveTabName();
const el = getActiveZoomRoot();
if (el) {
const z = zoomByTab[tab] ?? 1.0;
el.style.zoom = ''; el.style.transform = ''; el.style.width = '';
if ('zoom' in el.style) { el.style.zoom = z; }
else { el.style.transform = `scale(${z})`; el.style.width = `${(100 / z).toFixed(3)}%`; }
}
// 2. ã¿ãããŒ(.adv-tabs)ã®ãºãŒã é©çš
const tabsEl = document.querySelector('.adv-tabs');
if (tabsEl) {
const zTabs = zoomByTab.tabs ?? 1.0; // ã㌠'tabs' ã䜿çš
tabsEl.style.zoom = ''; tabsEl.style.transform = ''; tabsEl.style.width = '';
if ('zoom' in tabsEl.style) {
tabsEl.style.zoom = zTabs;
} else {
tabsEl.style.transform = `scale(${zTabs})`;
tabsEl.style.width = `${(100 / zTabs).toFixed(3)}%`;
}
}
// 3. ã¢ãŒãã«ããã㌠& ããã¿ãŒ (modal_ui) ã®ãºãŒã é©çš
const uiElements = [
document.querySelector('.adv-modal-header'),
document.querySelector('.adv-modal-footer')
];
const zUI = zoomByTab.modal_ui ?? 1.0;
uiElements.forEach(uiEl => {
if (!uiEl) return;
uiEl.style.zoom = ''; uiEl.style.transform = ''; uiEl.style.width = '';
if ('zoom' in uiEl.style) {
uiEl.style.zoom = zUI;
} else {
uiEl.style.transform = `scale(${zUI})`;
// ããããŒ/ããã¿ãŒã¯ width:100% ãåºæ¬ãªã®ã§ãscaleæã¯å¹
ãè£æ£ããŠã¬ã€ã¢ãŠã厩ããé²ã
uiEl.style.width = `${(100 / zUI).toFixed(3)}%`;
}
});
};
const setZoomTarget = (z, targetKey) => {
const key = targetKey || getActiveTabName();
zoomByTab[key] = clampZoom(z);
applyZoom();
saveZoomFor(key);
};
const onWheelZoom = (e) => {
const isAccel = e.ctrlKey || e.metaKey;
if (!isAccel) return;
// ã«ãŒãœã«äœçœ®ã®å€å®
const isTabs = e.target.closest('.adv-tabs');
const isContent = e.target.closest('.adv-zoom-root');
// ããããŒ(äž)ãŸãã¯ããã¿ãŒ(äž)ãå€å®
const isModalUI = e.target.closest('.adv-modal-header, .adv-modal-footer');
// ã©ãã«ã該åœããªããã°ç¡èŠ
if (!isTabs && !isContent && !isModalUI) return;
e.preventDefault();
// ã¿ãŒã²ããããŒã®æ±ºå®
let targetKey = getActiveTabName(); // ããã©ã«ãã¯ã³ã³ãã³ã
if (isTabs) {
targetKey = 'tabs';
} else if (isModalUI) {
targetKey = 'modal_ui';
}
const cur = zoomByTab[targetKey] ?? 1.0;
const factor = e.deltaY > 0 ? (1 - ZOOM_STEP) : (1 + ZOOM_STEP);
setZoomTarget(cur * factor, targetKey);
};
const onKeyZoom = (e) => {
const accel = (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey;
if (!accel) return;
if (!e.target.closest('.adv-zoom-root')) return; // ã¿ãããŒçã¯é€å€
const k = e.key;
const tab = getActiveTabName();
const cur = zoomByTab[tab] ?? 1.0;
if (k === '+' || k === '=') { e.preventDefault(); setZoomActiveTab(cur + ZOOM_STEP); }
else if (k === '-' || k === '_') { e.preventDefault(); setZoomActiveTab(cur - ZOOM_STEP); }
else if (k === '0') { e.preventDefault(); setZoomActiveTab(1.0); }
};
/* ååé©çšïŒè¡šç€ºæã«åé©çš */
requestAnimationFrame(applyZoom);
modal.addEventListener('wheel', onWheelZoom, { passive:false });
modal.addEventListener('keydown', onKeyZoom);
const modalDisplayObserver = new MutationObserver(() => {
if (modal.style.display === 'flex') applyZoom();
});
modalDisplayObserver.observe(modal, { attributes:true, attributeFilter:['style'] });
/* ã¿ãåæ¿æã«ããºãŒã åé©çš */
const searchInputSelectors = [
'div[data-testid="primaryColumn"] input[data-testid="SearchBox_Search_Input"]', // æ€çŽ¢ããŒãž
'div[data-testid="sidebarColumn"] input[data-testid="SearchBox_Search_Input"]', // ãµã€ãããŒ
'input[aria-label="Search query"]', // æšæºïŒè±èªïŒ
'input[aria-label="æ€çŽ¢ã¯ãšãª"]' // æšæºïŒæ¥æ¬èªïŒ
];
// âŒ é¢æ°åã getActiveSearchInputs (è€æ°åœ¢) ã«å€æŽ
const getActiveSearchInputs = () => {
const inputs = new Set(); // éè€æé€
// 1. æšæºã®æ€çŽ¢çªãæ¢ã
for (const selector of searchInputSelectors) {
const input = document.querySelector(selector);
if (input && input.offsetParent !== null) {
inputs.add(input);
}
}
// ãã©ãŒã«ããã¯ïŒdata-testid ã®ã¿ïŒ
document.querySelectorAll('input[data-testid="SearchBox_Search_Input"]').forEach(input => {
if (input && input.offsetParent !== null) {
inputs.add(input);
}
});
return Array.from(inputs); // Set ãé
åã«ããŠè¿ã
};
// React controlled input ã確å®ã«åæãããå
±é颿°
const syncControlledInput = (el, nextVal) => {
try {
const proto = Object.getPrototypeOf(el) || HTMLInputElement.prototype;
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
if (desc && desc.set) {
desc.set.call(el, nextVal); // React ã® setter ãå©ã
} else {
el.value = nextVal;
}
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
} catch {
try { el.value = nextVal; el.dispatchEvent(new Event('input', { bubbles: true })); } catch {}
}
};
const MODAL_STATE_KEY = 'advSearchModalState_v3.2';
const TRIGGER_STATE_KEY = 'advSearchTriggerState_v1.0';
const INITIAL_TAB_KEY = 'advInitialTab_v1';
const HISTORY_KEY = 'advSearchHistory_v2';
const SAVED_KEY = 'advSearchSaved_v2';
const SECRET_KEY = 'advSearchSecretMode_v1';
const MUTE_KEY = 'advMutedWords_v1';
const NATIVE_SEARCH_WIDTH_KEY = 'advNativeSearchWidth_v1';
const migrateMuted = (list) =>
Array.isArray(list)
? list
.map(it => ({
id: it.id || uid(),
word: (it.word||'').trim(),
cs: !!it.cs,
wb: !!it.wb, // wb (Word Boundary) ãç¶æ
enabled: it.enabled !== false,
ts: it.ts || Date.now()
}))
.filter(it => it.word)
: [];
const loadMuted = () => migrateMuted(loadJSON(MUTE_KEY, []));
const saveMuted = (arr) => saveJSON(MUTE_KEY, migrateMuted(arr));
// åŒæ°ã« wb ã远å
const addMuted = (word, cs=false, wb=false) => {
const w = (word||'').trim();
if (!w) return;
const list = loadMuted();
// éè€ãã§ãã¯ã« wb ãå«ãã
if (list.some(it => it.word === w && !!it.cs === !!cs && !!it.wb === !!wb)) return;
list.unshift({ id: uid(), word: w, cs: !!cs, wb: !!wb, enabled: true, ts: Date.now() });
saveMuted(list);
renderMuted();
rescanAllTweetsForFilter();
};
const deleteMuted = (id) => {
const list = loadMuted().filter(it => it.id !== id);
saveMuted(list);
renderMuted();
rescanAllTweetsForFilter();
};
const toggleMutedCS = (id) => {
const list = loadMuted().map(it => it.id === id ? { ...it, cs: !it.cs, ts: Date.now() } : it);
saveMuted(list);
renderMuted();
rescanAllTweetsForFilter();
};
// åèªåäœã®äžèŽåãæ¿ã
const toggleMutedWB = (id) => {
const list = loadMuted().map(it => it.id === id ? { ...it, wb: !it.wb, ts: Date.now() } : it);
saveMuted(list);
renderMuted();
rescanAllTweetsForFilter();
};
const toggleMutedEnabled = (id) => {
const list = loadMuted().map(it => it.id === id ? { ...it, enabled: !it.enabled, ts: Date.now() } : it);
saveMuted(list);
renderMuted();
rescanAllTweetsForFilter();
};
// æ€çŽ¢çªãªãµã€ã¶ãŒã®ã»ããã¢ãã颿°
const setupNativeSearchResizer = () => {
// ãµã€ãããŒããã³ã¡ã€ã³ã«ã©ã ã®æ€çŽ¢ãã©ãŒã ã察象ã«ãã
const forms = document.querySelectorAll('div[data-testid="sidebarColumn"] form[role="search"], div[data-testid="primaryColumn"] form[role="search"]');
// ExploreããŒãž (/explore é
äž) ã®å Žåã¯æ©èœãé€å€ããŠãã€ãã£ãã«æ»ã
if (location.pathname.startsWith('/explore')) {
forms.forEach(form => {
// å¹
æå®ãåé€ããŠXæ¬æ¥ã®CSSã¬ã€ã¢ãŠãã«æ»ã
if (form.style.width) {
form.style.width = '';
}
// æ¢ã«ãªãµã€ã¶ãŒãä»äžãããŠããå Žåã¯åé€ãã (SPAé·ç§»ã§æ®ã£ãŠããå Žåãªã©)
const existingResizer = form.querySelector('.adv-native-search-resizer');
if (existingResizer) {
existingResizer.remove();
}
});
return; // ããã§åŠççµäº
}
// ä¿åãããå¹
ãååŸ
const savedWidth = kv.get(NATIVE_SEARCH_WIDTH_KEY, null);
forms.forEach(form => {
// æ¢ã«é©çšæžã¿ãªãã¹ããã
if (form.querySelector('.adv-native-search-resizer')) {
// ãã ãå¹
ãæªé©çšã®å Žåã¯åé©çšïŒDOMæžãæã察çïŒ
if (savedWidth && form.style.width !== savedWidth) {
form.style.width = savedWidth;
}
return;
}
// å¹
ã®åæé©çš
if (savedWidth) {
form.style.width = savedWidth;
}
// ãªãµã€ãºãã³ãã«ãäœæ
const resizer = document.createElement('div');
resizer.className = 'adv-native-search-resizer';
resizer.title = 'Drag to resize search box';
form.appendChild(resizer);
// ãã©ãã°åŠç
let isResizing = false;
let startX = 0;
let startW = 0;
const onPointerDown = (e) => {
if (e.button !== 0) return; // å·Šã¯ãªãã¯ã®ã¿
e.preventDefault();
e.stopPropagation();
isResizing = true;
startX = e.clientX;
startW = form.getBoundingClientRect().width;
document.body.classList.add('adv-dragging');
try { resizer.setPointerCapture(e.pointerId); } catch (_) {}
};
const onPointerMove = (e) => {
if (!isResizing) return;
e.preventDefault();
// å¹
ã®èšç®
const dx = e.clientX - startX;
const newW = Math.max(200, startW + dx); // æå°å¹
200px
form.style.width = `${newW}px`;
};
const onPointerUp = (e) => {
if (!isResizing) return;
isResizing = false;
document.body.classList.remove('adv-dragging');
try { resizer.releasePointerCapture(e.pointerId); } catch (_) {}
// ä¿å
kv.set(NATIVE_SEARCH_WIDTH_KEY, form.style.width);
};
resizer.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
});
};
const SETTINGS_EXPORT_VERSION = 2;
function buildSettingsExportJSON() {
// ã¿ãããšã®ãºãŒã
const zoom = {};
try {
for (const [tab, key] of Object.entries(ZOOM_KEYS)) {
zoom[tab] = kv.get(key, '1');
}
} catch (_) {}
const safeParse = (key, def) => {
try { return JSON.parse(kv.get(key, JSON.stringify(def))); } catch (_) { return def; }
};
const data = {
// ã¢ããªèå¥å
appName: 'AdvancedSearchForX',
v: SETTINGS_EXPORT_VERSION,
// èšèªã»é€å€èšå®ã»ãã¥ãŒã
lang: kv.get(LANG_OVERRIDE_KEY, ''),
initialTab: kv.get(INITIAL_TAB_KEY, 'last'),
excludeFlags: loadExcludeFlags(),
muteMaster: loadMuteMaster(),
muteMode: loadMuteMode(),
muted: loadMuted(),
// æ€çޢ履æŽã»ä¿åæžã¿æ€çŽ¢
history: loadJSON(HISTORY_KEY, []),
saved: loadJSON(SAVED_KEY, []),
favorites: loadJSON(FAV_KEY, []),
// ã·ãŒã¯ã¬ããã¢ãŒãã»å±¥æŽãœãŒã
secret: kv.get(SECRET_KEY, '0') === '1',
historySort: kv.get(HISTORY_SORT_KEY, 'newest'),
// æ€çŽ¢çªã®å¹
nativeSearchWidth: kv.get(NATIVE_SEARCH_WIDTH_KEY, null),
// ã¿ãç¶æ
tabs: {
last: kv.get(LAST_TAB_KEY, 'search'),
order: loadJSON(TABS_ORDER_KEY, []),
visibility: loadTabsVisibility(),
},
// ã¢ãŒãã«ïŒããªã¬ãŒäœçœ®ã»ãµã€ãº
modalState: safeParse(MODAL_STATE_KEY, null),
triggerState: safeParse(TRIGGER_STATE_KEY, null),
// ãºãŒã
zoom,
// ã¢ã«ãŠã³ãã»ãªã¹ãã»åãã©ã«ã
accounts: (typeof loadAccounts === 'function') ? loadAccounts() : [],
lists: (typeof loadLists === 'function') ? loadLists() : [],
folders: {
accounts: (typeof loadFolders === 'function' && typeof ACCOUNTS_FOLDERS_KEY !== 'undefined')
? loadFolders(ACCOUNTS_FOLDERS_KEY, '')
: [],
lists: (typeof loadFolders === 'function' && typeof LISTS_FOLDERS_KEY !== 'undefined')
? loadFolders(LISTS_FOLDERS_KEY, '')
: [],
saved: (typeof loadFolders === 'function' && typeof SAVED_FOLDERS_KEY !== 'undefined')
? loadFolders(SAVED_FOLDERS_KEY, i18n.t('defaultSavedFolders'))
: [],
},
// Unassigned ã®æ¿å
¥äœçœ®
unassignedIndex: {
saved: parseInt(kv.get('advSavedUnassignedIndex_v1', '0'), 10) || 0,
accounts: parseInt(kv.get('advAccountsUnassignedIndex_v1', '0'), 10) || 0,
lists: parseInt(kv.get('advListsUnassignedIndex_v1', '0'), 10) || 0,
},
/* --- Favorite Tags Data --- */
favoriteTags: (typeof ft_loadState === 'function') ? ft_loadState() : ft_createDefaultState(),
};
return JSON.stringify(data, null, 2);
}
function applySettingsImportJSON(text) {
let data;
try {
data = JSON.parse(text);
} catch (_) {
alert(i18n.t('alertInvalidJSON'));
return false;
}
if (!data || typeof data !== 'object') {
alert(i18n.t('alertInvalidData'));
return false;
}
// ããªããŒã·ã§ã³ããžãã¯
// 1. ã¢ããªèå¥å (appName) ãããããã§ãã¯
const hasSignature = (data.appName === 'AdvancedSearchForX');
// 2. èå¥åããªãå Žåããã®ã¢ããªç¹æã®æ§é ïŒvãããã㣠+ äž»èŠãªé
åã®ããããïŒãæã£ãŠããããã§ãã¯ïŒåŸæ¹äºææ§ææžïŒ
const hasValidStructure = (
typeof data.v === 'number' &&
(Array.isArray(data.history) || Array.isArray(data.saved) || Array.isArray(data.favorites) || typeof data.tabs === 'object')
);
if (!hasSignature && !hasValidStructure) {
alert(i18n.t('alertInvalidApp'));
return false;
}
// ããªããŒã·ã§ã³çµäº
// --- åºæ¬èšå®ïŒv1/v2 å
±éïŒ ---
if (data.lang !== undefined) {
try { kv.set(LANG_OVERRIDE_KEY, data.lang || ''); } catch (_) {}
}
if (data.initialTab !== undefined) {
try { kv.set(INITIAL_TAB_KEY, data.initialTab || 'last'); } catch (_) {}
}
if (data.excludeFlags) {
saveExcludeFlags({
name: !!data.excludeFlags.name,
handle: !!data.excludeFlags.handle,
reposts: !!data.excludeFlags.reposts,
hashtags: !!data.excludeFlags.hashtags,
});
}
if (Array.isArray(data.muted)) {
saveMuted(data.muted);
}
if (typeof data.muteMaster === 'boolean') {
saveMuteMaster(data.muteMaster);
}
// ãã¥ãŒãã¢ãŒãã®èªã¿èŸŒã¿ãšä¿å
if (data.muteMode && (data.muteMode === 'hidden' || data.muteMode === 'collapsed')) {
saveMuteMode(data.muteMode);
}
// --- v2 以éã§è¿œå ãããä¿åããŒã¿ ---
if (Array.isArray(data.history)) {
saveJSON(HISTORY_KEY, data.history);
}
if (Array.isArray(data.saved)) {
saveJSON(SAVED_KEY, data.saved);
}
// saveFavorites ãçµç±ãããŠãã£ãã·ã¥(_favSet)ãæŽæ°ãã
if (Array.isArray(data.favorites)) {
saveFavorites(data.favorites);
}
if (typeof data.secret === 'boolean') {
try { kv.set(SECRET_KEY, data.secret ? '1' : '0'); } catch (_) {}
}
if (data.historySort) {
try { kv.set(HISTORY_SORT_KEY, data.historySort); } catch (_) {}
}
// æ€çŽ¢çªã®å¹
埩å
if (data.nativeSearchWidth !== undefined) {
try {
if (data.nativeSearchWidth) kv.set(NATIVE_SEARCH_WIDTH_KEY, data.nativeSearchWidth);
else kv.del(NATIVE_SEARCH_WIDTH_KEY);
} catch (_) {}
}
if (data.tabs && typeof data.tabs === 'object') {
if (data.tabs.last) {
try { kv.set(LAST_TAB_KEY, data.tabs.last); } catch (_) {}
}
if (Array.isArray(data.tabs.order)) {
saveJSON(TABS_ORDER_KEY, data.tabs.order);
}
if (data.tabs.visibility && typeof data.tabs.visibility === 'object') {
saveTabsVisibility(data.tabs.visibility);
}
}
if (data.modalState) {
try { kv.set(MODAL_STATE_KEY, JSON.stringify(data.modalState)); } catch (_) {}
}
if (data.triggerState) {
try { kv.set(TRIGGER_STATE_KEY, JSON.stringify(data.triggerState)); } catch (_) {}
}
if (data.zoom && typeof data.zoom === 'object') {
try {
for (const [tab, key] of Object.entries(ZOOM_KEYS)) {
if (data.zoom[tab] != null) {
kv.set(key, String(data.zoom[tab]));
}
}
} catch (_) {}
}
if (Array.isArray(data.accounts) && typeof saveAccounts === 'function') {
try { saveAccounts(data.accounts); } catch (_) {}
}
if (Array.isArray(data.lists) && typeof saveLists === 'function') {
try { saveLists(data.lists); } catch (_) {}
}
if (data.folders && typeof data.folders === 'object') {
if (Array.isArray(data.folders.accounts) && typeof ACCOUNTS_FOLDERS_KEY !== 'undefined') {
try { saveFolders(ACCOUNTS_FOLDERS_KEY, data.folders.accounts); } catch (_) {}
}
if (Array.isArray(data.folders.lists) && typeof LISTS_FOLDERS_KEY !== 'undefined') {
try { saveFolders(LISTS_FOLDERS_KEY, data.folders.lists); } catch (_) {}
}
if (Array.isArray(data.folders.saved) && typeof SAVED_FOLDERS_KEY !== 'undefined') {
try { saveFolders(SAVED_FOLDERS_KEY, data.folders.saved); } catch (_) {}
}
}
if (data.unassignedIndex && typeof data.unassignedIndex === 'object') {
if ('saved' in data.unassignedIndex) try { kv.set('advSavedUnassignedIndex_v1', String(data.unassignedIndex.saved | 0)); } catch (_) {}
if ('accounts' in data.unassignedIndex) try { kv.set('advAccountsUnassignedIndex_v1', String(data.unassignedIndex.accounts | 0)); } catch (_) {}
if ('lists' in data.unassignedIndex) try { kv.set('advListsUnassignedIndex_v1', String(data.unassignedIndex.lists | 0)); } catch (_) {}
}
/* --- Favorite Tags Data --- */
if (data.favoriteTags && typeof ft_saveState === 'function') {
try {
const s = data.favoriteTags;
ft_normalizeTagOrdersFor(s);
ft_clampUncategorizedOrderFor(s);
ft_saveState(s); // ã¹ãã¬ãŒãžãžã®ä¿å
if (typeof ft_state !== 'undefined') {
ft_state = s;
}
} catch (_) {}
}
// èšèªãåé©çš
try {
const override = kv.get(LANG_OVERRIDE_KEY, '');
if (override && i18n.translations[override]) {
i18n.lang = override;
} else if (!override) {
i18n.init();
}
} catch (_) {}
try {
i18n.apply(document.getElementById('advanced-search-modal'));
i18n.apply(document.getElementById('adv-settings-modal'));
} catch (_) {}
try { applySecretBtn(); } catch (_) {}
try { renderHistory(); } catch (_) {}
try { renderSaved(); } catch (_) {}
try { renderLists(); } catch (_) {}
try { renderAccounts(); } catch (_) {}
try { renderMuted(); } catch (_) {}
// ãæ°ã«å
¥ããªã¹ããåæç»ãããã¿ã³ç¶æ
ã»ã¿ã°ããããå
šæŽæ°ãã
try {
renderFavorites();
updateAllFavoriteButtons();
} catch (_) {}
try { rescanAllTweetsForFilter(); } catch (_) {}
/* --- Favorite Tags UI Refresh --- */
try {
if (typeof ft_refreshAllTagChips === 'function') ft_refreshAllTagChips();
} catch (_) {}
// ã¿ãã®è¡šç€ºç¶æ
ãé©çš
try { applyTabsVisibility(); } catch (_) {}
/* âŒâŒâŒ ã€ã³ããŒãããèšå®ãå³åº§ã«ç»é¢ã«åæ ããåŠç âŒâŒâŒ */
// 1. ãºãŒã èšå®ã®åæ
// Storageããã¡ã¢ãªå€æ°(zoomByTab)ãžåããŒãããDOMã«é©çš
try {
Object.keys(zoomByTab).forEach(tab => loadZoomFor(tab));
applyZoom();
} catch (_) {}
// 2. ã¢ãŒãã«äœçœ®ã»ãµã€ãºã®åæ
// Storageããèªã¿èŸŒã¿çŽããäœçœ®è£æ£(keepModalInViewport)ãå«ããŠé©çš
try {
loadModalState(); // å
éšã§ applyModalStoredPosition() ãåŒã°ãã座æšãšãµã€ãºãã»ããããã
requestAnimationFrame(keepModalInViewport);
} catch (_) {}
// 3. ããªã¬ãŒãã¿ã³äœçœ®ã®åæ
try {
applyTriggerStoredPosition();
requestAnimationFrame(keepTriggerInViewport);
} catch (_) {}
// 4. æ€çŽ¢çªã®å¹
ã®åæ
try {
setupNativeSearchResizer();
} catch (_) {}
showToast(i18n.t('toastImported'));
return true;
}
// ãã¹ã¿ãŒON/OFFïŒå
šäœã®é©çšãæ¢ããã ããåãšã³ããªã® enabled ã¯ä¿æïŒ
const MUTE_MASTER_KEY = 'advMuteMasterEnabled_v1';
const MUTE_MODE_KEY = 'advMuteMode_v1';
const LAST_TAB_KEY = 'advSearchLastTab_v1';
const TABS_ORDER_KEY = 'advTabsOrder_v1';
const TABS_VISIBILITY_KEY = 'advTabsVisibility_v1';
const loadMuteMaster = () => { try { return kv.get(MUTE_MASTER_KEY, '1') === '1'; } catch(_) { return true; } };
const saveMuteMaster = (on) => { try { kv.set(MUTE_MASTER_KEY, on ? '1' : '0'); } catch(_) {} };
const loadMuteMode = () => { try { return kv.get(MUTE_MODE_KEY, 'hidden'); } catch(_) { return 'hidden'; } };
const saveMuteMode = (v) => { try { kv.set(MUTE_MODE_KEY, v); } catch(_) {} };
const tabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
// Get tab panels for background drop
const tabAccountsPanel = document.getElementById('adv-tab-accounts');
const tabListsPanel = document.getElementById('adv-tab-lists');
const tabSavedPanel = document.getElementById('adv-tab-saved');
const activateTab = (name) => {
let targetName = name;
const visibility = loadTabsVisibility();
// ã¿ãŒã²ãããé衚瀺ã«èšå®ãããŠããããã§ãã¯
if (visibility[targetName] === false) {
// é衚瀺ã®å Žåããã©ãŒã«ããã¯å
ãæ¢ã
// 'search' 㯠true ãä¿èšŒãããŠããã®ã§ãå¿
ã 'search' ã«ãã©ãŒã«ããã¯ããã
const orderedButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
const firstVisible = orderedButtons.find(btn => {
const tab = btn.dataset.tab;
return tab && visibility[tab] !== false;
});
targetName = firstVisible ? firstVisible.dataset.tab : 'search';
}
// tabButtons 㯠DOM ã®é åºãšåæããŠããå¿
èŠããããããDOM ããåååŸ
const currentTabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
currentTabButtons.forEach(b => b.classList.toggle('active', b.dataset.tab === targetName));
// [tabSearch, tabHistory, tabSaved, tabLists, tabAccounts, tabMute] // å€ãé
ååç
§ãåé€
document.querySelectorAll('.adv-tab-content').forEach(el => {
el.classList.toggle('active', el.id === `adv-tab-${targetName}`);
});
footerEl.style.display = (targetName === 'search') ? '' : 'none';
// æåŸã«éããã¿ããä¿å (é衚瀺ã§ãèŠæ±ãããã¿ããä¿åãã)
try {
kv.set(LAST_TAB_KEY, name); // â
å
ã® name ãä¿åãã
} catch(e) {
console.error('Failed to save last tab state:', e);
}
if (targetName === 'history') renderHistory();
if (targetName === 'saved') renderSaved();
if (targetName === 'lists') renderLists();
if (targetName === 'accounts') renderAccounts();
if (targetName === 'mute') renderMuted();
if (targetName === 'favorites') renderFavorites();
if (targetName === 'search') updateSaveButtonState();
/* ã¿ãåæ¿ããšã«è©²åœã¿ãã®ãºãŒã çãåæ */
applyZoom();
};
const applyTabsVisibility = () => {
const visibility = loadTabsVisibility();
// tabButtons 㯠DOM ã®é åºãåæ ããŠããå¿
èŠããããããDOM ããåååŸ
const currentTabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
let firstVisibleTab = 'search'; // ãã©ãŒã«ãã㯠(searchã¯trueåºå®ãªã®ã§)
for (const btn of currentTabButtons) {
const tabName = btn.dataset.tab;
if (!tabName) continue;
// visibility[tabName] ã false ã®å Žåã®ã¿é衚瀺 (true ã undefined ã¯è¡šç€º)
const isVisible = visibility[tabName] !== false;
btn.style.display = isVisible ? '' : 'none';
// ãã©ãŒã«ããã¯å
ã¿ããæ±ºå® (search ãæåªå
)
if (isVisible && firstVisibleTab === 'search' && tabName !== 'search') {
firstVisibleTab = tabName; // search 以å€ã§æåã«èŠã€ãã£ã衚瀺å¯èœãªã¿ã
}
}
// 'search' ã衚瀺å¯èœãç¢ºèª (true åºå®ã ã念ã®ãã)
if (visibility['search'] === true) {
firstVisibleTab = 'search';
}
// æåŸã«ã¢ã¯ãã£ãã ã£ãã¿ããé衚瀺ã«ãªã£ãŠããªãããã§ãã¯
const activeBtn = document.querySelector('.adv-tab-btn.active');
if (activeBtn && activeBtn.style.display === 'none') {
// é衚瀺ã«ãããã®ã§ã衚瀺å¯èœãªæåã®ã¿ã (é垞㯠'search') ã«åãæ¿ãã
activateTab(firstVisibleTab);
}
};
// ã¿ãã®é åºãèªã¿èŸŒãã§é©çš
(function applyTabsOrder() {
const tabsContainer = document.querySelector('.adv-tabs');
if (!tabsContainer) return;
// çŸåšã®ãã¿ã³ã data-tab ãããŒã«ãã Map ãšããŠä¿æ
const currentButtons = new Map();
const defaultOrder = [];
tabsContainer.querySelectorAll('.adv-tab-btn[data-tab]').forEach(btn => {
const tabName = btn.dataset.tab;
if (tabName) {
currentButtons.set(tabName, btn);
defaultOrder.push(tabName);
}
});
// ä¿åãããé åºãèªã¿èŸŒã
const savedOrder = loadJSON(TABS_ORDER_KEY, defaultOrder);
// ä¿åãããé åºãæ€èšŒããäžè¶³åãè£ã
const finalOrder = [];
const seen = new Set();
// 1. ä¿åãããé åºã®ãã¡ãçŸåšãååšãããã®ã远å
savedOrder.forEach(tabName => {
if (currentButtons.has(tabName)) {
finalOrder.push(tabName);
seen.add(tabName);
}
});
// 2. ããã©ã«ãé åºã®ãã¡ããŸã 远å ãããŠããªããã®ïŒïŒæ°ããã¿ãïŒãæ«å°Ÿã«è¿œå
defaultOrder.forEach(tabName => {
if (!seen.has(tabName)) {
finalOrder.push(tabName);
}
});
// é åºãå®éã«å€æŽãããŠããã確èª
if (JSON.stringify(savedOrder) !== JSON.stringify(finalOrder)) {
saveJSON(TABS_ORDER_KEY, finalOrder);
}
// DOMãäžŠã³æ¿ãã
finalOrder.forEach(tabName => {
const btn = currentButtons.get(tabName);
if (btn) {
tabsContainer.appendChild(btn);
}
});
// tabButtons é
åãåååŸïŒé åºã倿ŽãããããïŒ
tabButtons.splice(0, tabButtons.length, ...Array.from(document.querySelectorAll('.adv-tab-btn')));
})();
// ã¿ãã®è¡šç€º/é衚瀺ãDOMã«é©çš (activateTab ã®åã«åŒã¶)
applyTabsVisibility();
const saveModalRelativeState = () => {
// ⌠SPæã¯ãPCçšã®ã¬ã€ã¢ãŠãèšå®ïŒééç¶æ
å«ãïŒã絶察ã«äžæžããããªã
// ããã«ãããSPã§éããŠããPCã«æ»ã£ããéãããŸãŸããå®çŸãã
if (window.innerWidth <= 700) return;
if (modal.style.display === 'none') {
try {
const current = (()=>{
try { return JSON.parse(kv.get(MODAL_STATE_KEY, '{}')); } catch(_) { return {}; }
})();
current.visible = false;
kv.set(MODAL_STATE_KEY, JSON.stringify(current));
} catch(_) {}
return;
}
const rect = modal.getBoundingClientRect();
const winW = window.innerWidth, winH = window.innerHeight;
const fromRight = winW - rect.right, fromBottom = winH - rect.bottom;
const h_anchor = rect.left < fromRight ? 'left' : 'right';
const h_value = h_anchor === 'left' ? rect.left : fromRight;
const v_anchor = rect.top < fromBottom ? 'top' : 'bottom';
const v_value = v_anchor === 'top' ? rect.top : fromBottom;
const state = { h_anchor, h_value, v_anchor, v_value, visible: true,
w: Math.round(rect.width), h: Math.round(rect.height) };
kv.set(MODAL_STATE_KEY, JSON.stringify(state));
};
const applyModalStoredPosition = () => {
try {
const s = JSON.parse(kv.get(MODAL_STATE_KEY, '{}'));
const h_anchor = s.h_anchor || 'right';
const h_value = s.h_value ?? 20;
const v_anchor = s.v_anchor || 'top';
const v_value = s.v_value ?? 80;
modal.style.left = modal.style.right = modal.style.top = modal.style.bottom = 'auto';
if (h_anchor === 'right') modal.style.right = `${h_value}px`; else modal.style.left = `${h_value}px`;
if (v_anchor === 'bottom') modal.style.bottom = `${v_value}px`; else modal.style.top = `${v_value}px`;
const minW = 300, minH = 240;
if (s.w) modal.style.width = `${Math.max(minW, Math.min(s.w, window.innerWidth - 20))}px`;
else modal.style.width = '380px';
if (s.h) modal.style.height = `${Math.max(minH, Math.min(s.h, window.innerHeight - 20))}px`;
else modal.style.height = '730px';
} catch(e) { console.error('Failed to apply modal position:', e); }
};
const keepModalInViewport = () => {
// ⌠SPæã¯CSSã«ã¬ã€ã¢ãŠããä»»ãããããJSã«ããè£æ£ãè¡ããªã
if (window.innerWidth <= 700) return;
if (modal.style.display === 'none') return;
const rect = modal.getBoundingClientRect();
const winW = window.innerWidth, winH = window.innerHeight, m = 10;
const minW = 300, minH = 240;
const maxW = Math.max(minW, winW - 2*m);
const maxH = Math.max(minH, winH - 2*m);
const w = Math.min(Math.max(rect.width, minW), maxW);
const h = Math.min(Math.max(rect.height, minH), maxH);
if (Math.round(w) !== Math.round(rect.width)) modal.style.width = `${w}px`;
if (Math.round(h) !== Math.round(rect.height)) modal.style.height = `${h}px`;
let x = rect.left, y = rect.top;
if (x < m) x = m; if (y < m) y = m;
if (x + w > winW - m) x = winW - w - m;
if (y + h > winH - m) y = winH - h - m;
if (Math.round(x) !== Math.round(rect.left) || Math.round(y) !== Math.round(rect.top)) {
modal.style.left = `${x}px`; modal.style.top = `${y}px`;
modal.style.right = 'auto'; modal.style.bottom = 'auto';
}
};
const loadModalState = () => {
try { applyModalStoredPosition(); } catch(e) {
console.error('Failed to load modal state:', e);
kv.del(MODAL_STATE_KEY);
}
};
// ã¬ã€ã¢ãŠãã¢ãŒãã®å€å® (SP < 500 <= Tablet < 1000 <= PC)
const getTriggerLayoutMode = () => {
const w = window.innerWidth;
if (w < 500) return 'sp';
if (w < 1000) return 'tablet';
return 'pc';
};
const saveTriggerRelativeState = () => {
// SP/Tabletã§ãä¿åãããã¢ãŒãããšã«ããŒãåããã
const rect = trigger.getBoundingClientRect();
const winW = window.innerWidth, winH = window.innerHeight;
const fromRight = winW - rect.right, fromBottom = winH - rect.bottom;
const h_anchor = rect.left < fromRight ? 'left' : 'right';
const h_value = h_anchor === 'left' ? rect.left : fromRight;
const v_anchor = rect.top < fromBottom ? 'top' : 'bottom';
const v_value = v_anchor === 'top' ? rect.top : fromBottom;
// å
šäœã®ä¿åããŒã¿ãèªã¿èŸŒã¿
let allStates = {};
try { allStates = JSON.parse(kv.get(TRIGGER_STATE_KEY, '{}')) || {}; } catch(_) {}
// çŸåšã®ã¢ãŒãã«å¯ŸããŠä¿å
const mode = getTriggerLayoutMode();
allStates[mode] = { h_anchor, h_value, v_anchor, v_value };
kv.set(TRIGGER_STATE_KEY, JSON.stringify(allStates));
};
const applyTriggerStoredPosition = () => {
try {
let allStates = {};
try { allStates = JSON.parse(kv.get(TRIGGER_STATE_KEY, '{}')) || {}; } catch(_) {}
// æ§ããŒãžã§ã³ããŒã¿ïŒçŽäžã«ããããã£ãããå ŽåïŒã®ãã€ã°ã¬ãŒã·ã§ã³
if (allStates.h_anchor && !allStates.pc) {
allStates = { pc: { ...allStates }, tablet: {}, sp: {} };
}
const mode = getTriggerLayoutMode();
const s = allStates[mode] || {};
// ããã©ã«ãå€ã®åå²
let defHAnchor = 'right', defHValue = 20;
let defVAnchor = 'top', defVValue = 18;
if (mode === 'sp') {
// SPã®ããã©ã«ã: å³äž (æçš¿ãã¿ã³ã®äžããã)
defHAnchor = 'right'; defHValue = 23.5;
defVAnchor = 'bottom'; defVValue = 140;
} else if (mode === 'tablet') {
// Tabletã®ããã©ã«ãèšå®
// ä¿åãããäœçœ®ããªãïŒåæç¶æ
ïŒå ŽåãDOMäžã®æçš¿ãã¿ã³ã®äœçœ®ãæ¢ããŠãã®äžã«é
眮ãã
if (!s.h_anchor && !s.v_anchor) {
const postBtn = document.querySelector('[data-testid="SideNav_NewTweet_Button"]');
if (postBtn) {
const rect = postBtn.getBoundingClientRect();
// ãã¿ã³ãèŠããŠããŠåº§æšãåããå Žåã®ã¿èšç®
if (rect.width > 0 && rect.height > 0) {
// ããªã¬ãŒã®ãµã€ãº(CSSã§50px)ã®ååãåŒããŠã»ã³ã¿ãªã³ã°
const triggerSize = 50;
const centerX = rect.left + (rect.width / 2) - (triggerSize / 2);
const topY = rect.bottom + 20; // ãã¿ã³ã®äž 20px ã®äœçœ
trigger.style.left = `${centerX}px`;
trigger.style.top = `${topY}px`;
trigger.style.right = 'auto';
trigger.style.bottom = 'auto';
return; // èªåé
眮ã§ããã®ã§ããã§åŠççµäº
}
}
}
// èŠã€ãããªãå Žåãä¿åæžã¿ãããå Žåã®ãã©ãŒã«ããã¯ïŒå³äžïŒ
defHAnchor = 'right'; defHValue = 20;
defVAnchor = 'bottom'; defVValue = 100;
}
const h_anchor = s.h_anchor || defHAnchor;
const h_value = s.h_value ?? defHValue;
const v_anchor = s.v_anchor || defVAnchor;
const v_value = s.v_value ?? defVValue;
trigger.style.left = trigger.style.right = trigger.style.top = trigger.style.bottom = 'auto';
if (h_anchor === 'right') trigger.style.right = `${h_value}px`; else trigger.style.left = `${h_value}px`;
if (v_anchor === 'bottom') trigger.style.bottom = `${v_value}px`; else trigger.style.top = `${v_value}px`;
} catch(e) { console.error('Failed to apply trigger position:', e); }
};
const keepTriggerInViewport = () => {
const rect = trigger.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const winW = window.innerWidth, winH = window.innerHeight, m = 6;
let x = rect.left, y = rect.top;
if (x < m) x = m; if (y < m) y = m;
if (x + rect.width > winW - m) x = winW - rect.width - m;
if (y + rect.height > winH - m) y = winH - rect.height - m;
// è£æ£ãå¿
èŠãªå Žåã®ã¿ã¹ã¿ã€ã«ãäžæžããã
if (Math.round(x) !== Math.round(rect.left) || Math.round(y) !== Math.round(rect.top)) {
trigger.style.left = `${x}px`; trigger.style.top = `${y}px`;
trigger.style.right = 'auto'; trigger.style.bottom = 'auto';
saveTriggerRelativeState();
}
};
const setupTriggerDrag = () => {
const DRAG_THRESHOLD = 4;
let isPointerDown = false, isDragging = false, start = {x:0,y:0,left:0,top:0}, suppressClick=false;
const onPointerDown = (e) => {
if (e.button !== 0) return;
isPointerDown = true; isDragging = false; suppressClick=false;
const rect = trigger.getBoundingClientRect();
start = { x:e.clientX, y:e.clientY, left:rect.left, top:rect.top };
try{ trigger.setPointerCapture(e.pointerId);}catch(_){}
};
const onPointerMove = (e) => {
if (!isPointerDown) return;
const dx = e.clientX - start.x, dy = e.clientY - start.y;
if (!isDragging) {
if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
isDragging = true;
trigger.style.right = 'auto'; trigger.style.bottom = 'auto';
trigger.style.left = `${start.left}px`; trigger.style.top = `${start.top}px`;
document.body.classList.add('adv-dragging');
}
const winW = window.innerWidth, winH = window.innerHeight;
const w = trigger.offsetWidth, h = trigger.offsetHeight;
let nx = start.left + dx, ny = start.top + dy;
nx = Math.max(0, Math.min(nx, winW - w)); ny = Math.max(0, Math.min(ny, winH - h));
trigger.style.left = `${nx}px`; trigger.style.top = `${ny}px`;
};
const onPointerUp = (e) => {
if (!isPointerDown) return; isPointerDown = false;
try{ trigger.releasePointerCapture(e.pointerId);}catch(_){}
if (isDragging) {
isDragging = false; document.body.classList.remove('adv-dragging');
suppressClick = true; setTimeout(()=>{suppressClick=false;},150);
saveTriggerRelativeState();
}
};
trigger.addEventListener('click', (e)=> {
if (suppressClick) {
e.preventDefault();
e.stopPropagation();
suppressClick = false;
return;
}
}, true);
trigger.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
};
applyTriggerStoredPosition();
requestAnimationFrame(keepTriggerInViewport);
setupTriggerDrag();
const readScopesFromControls = () => ({ pf: accountScopeSel.value === 'following', lf: locationScopeSel.value === 'nearby' });
const applyScopesToControls = ({pf=false, lf=false}) => {
accountScopeSel.value = pf ? 'following' : '';
locationScopeSel.value = lf ? 'nearby' : '';
};
const readScopesFromURL = (urlStr) => {
try {
const u = new URL(urlStr || location.href, location.origin);
const pf = (u.searchParams.get('pf') || '') === 'on';
const lf = (u.searchParams.get('lf') || '') === 'on';
return { pf, lf };
} catch { return { pf:false, lf:false }; }
};
const STATE_SYNC = {
parseFromSearchToModal: () => {
if (isUpdating || modal.style.display === 'none') return;
// âŒ è€æ°åœ¢ã«å€æŽããæåã®èŠçŽ ãååŸ
const inputs = getActiveSearchInputs();
const si = inputs[0]; // è€æ°ã®ãã¡æåã®ã代衚ãšããŠèªã¿èŸŒã
parseQueryAndApplyToModal(si ? si.value : '');
applyScopesToControls(readScopesFromURL());
updateSaveButtonState();
}
};
const buildQueryStringFromModal = () => {
const q = [];
const fields = {
all: document.getElementById('adv-all-words').value.trim(),
exact: document.getElementById('adv-exact-phrase').value.trim(),
any: document.getElementById('adv-any-words').value.trim(),
not: document.getElementById('adv-not-words').value.trim(),
hash: document.getElementById('adv-hashtag').value.trim(),
lang: document.getElementById('adv-lang').value,
replies: document.getElementById('adv-replies').value,
min_replies: document.getElementById('adv-min-replies').value,
min_faves: document.getElementById('adv-min-faves').value,
min_retweets: document.getElementById('adv-min-retweets').value,
since: document.getElementById('adv-since').value,
until: document.getElementById('adv-until').value,
};
if (fields.all) q.push(fields.all);
if (fields.exact) q.push(`"${fields.exact.replace(/"/g,'')}"`);
// åŒçšã§ 1 èªãšããŠæ±ããOR é£çµãçæ
if (fields.any) {
const tokens = tokenizeQuotedWords(fields.any).map(t => {
// æ¢ã« "âŠ": ãã®ãŸãŸãæªåŒçšã§ç©ºçœãå«ã â åŒçšãä»ãã
if (/^".*"$/.test(t)) return t;
if (/\s/.test(t)) return `"${t.replace(/"/g,'')}"`;
return t;
});
if (tokens.length) q.push(`(${tokens.join(' OR ')})`);
}
if (fields.not) q.push(...fields.not.split(/\s+/).filter(Boolean).map(w=>`-${w}`));
if (fields.hash) q.push(...fields.hash.split(/\s+/).filter(Boolean).map(h=>`#${h.replace(/^#/,'')}`));
if (fields.lang) q.push(`lang:${fields.lang}`);
const createAccountQuery = (inputId, operator) => {
const value = document.getElementById(inputId).value.trim();
if (!value) return null;
const isExclude = document.getElementById(`${inputId}-exclude`).checked;
const terms = value.split(/\s+/).filter(Boolean);
if (isExclude) return terms.map(t=>`-${operator}${t.replace(/^@/,'')}`).join(' ');
const processed = terms.map(t=>`${operator}${t.replace(/^@/,'')}`);
return processed.length>1 ? `(${processed.join(' OR ')})` : processed[0];
};
const fromQ = createAccountQuery('adv-from-user','from:'); if (fromQ) q.push(fromQ);
const toQ = createAccountQuery('adv-to-user','to:'); if (toQ) q.push(toQ);
const mentionQ = createAccountQuery('adv-mentioning','@'); if (mentionQ) q.push(mentionQ);
if (fields.min_replies) q.push(`min_replies:${fields.min_replies}`);
if (fields.min_faves) q.push(`min_faves:${fields.min_faves}`);
if (fields.min_retweets) q.push(`min_retweets:${fields.min_retweets}`);
if (fields.since) q.push(`since:${fields.since}`);
if (fields.until) q.push(`until:${fields.until}`);
const addFilter = (type, mapping) => {
const include = document.getElementById(`adv-filter-${type}-include`).checked;
const exclude = document.getElementById(`adv-filter-${type}-exclude`).checked;
if (include) q.push(mapping);
if (exclude) q.push(`-${mapping}`);
};
addFilter('verified','is:verified');
addFilter('links','filter:links');
addFilter('images','filter:images');
addFilter('videos','filter:videos');
if (fields.replies) {
const replyMap = { include:'include:replies', only:'filter:replies', exclude:'-filter:replies' };
if (replyMap[fields.replies]) q.push(replyMap[fields.replies]);
}
return q.join(' ');
};
const parseQueryAndApplyToModal = (query) => {
if (isUpdating) return; isUpdating = true;
const formEl = document.getElementById('advanced-search-form');
formEl.reset();
// ãã©ãŒã ãªã»ããæã« disabled ãè§£é€
['verified', 'links', 'images', 'videos'].forEach(groupName => {
const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
if (includeEl) includeEl.disabled = false;
if (excludeEl) excludeEl.disabled = false;
});
try {
const st = loadExcludeFlags();
const nameEl = document.getElementById('adv-exclude-hit-name');
const handleEl = document.getElementById('adv-exclude-hit-handle');
const repostsEl = document.getElementById('adv-filter-reposts-exclude');
const hashtagsEl = document.getElementById('adv-filter-hashtags-exclude');
if (nameEl) { nameEl.checked = nameEl.defaultChecked = !!st.name; }
if (handleEl) { handleEl.checked = handleEl.defaultChecked = !!st.handle; }
if (repostsEl) { repostsEl.checked = repostsEl.defaultChecked = !!st.reposts; }
if (hashtagsEl) { hashtagsEl.checked = hashtagsEl.defaultChecked = !!st.hashtags; }
} catch (_) {}
// ã¯ãšãªãæ£èŠåïŒã¹ããŒãåŒçšã»%xxã»ç©ºçœïŒ
const rawNorm = normalizeForParse(query || '');
// ãããã¬ãã« OR ãå
ã«èŠãïŒçŽç² OR / ãã€ããªãã OR ã®åãåãïŒ
const orParts = splitTopLevelOR(rawNorm);
if (orParts && isPureORQuery(rawNorm)) {
// åŒçšã 1 èªãšããŠæ°ããããŒã¯ãã€ã¶
const tokenize = (s) => tokenizeQuotedWords(s).filter(Boolean);
const tokenized = orParts.map(p => tokenize(p));
const allAreSingle = tokenized.every(ts => ts.length === 1);
if (allAreSingle) {
// â çŽç² ORïŒå
šéš any ã«å
¥ããïŒexact/all ã¯ç©ºïŒâ æ©æ return
document.getElementById('adv-any-words').value = orParts.join(' ');
isUpdating = false;
return;
}
const head = tokenized[0];
const rest = tokenized.slice(1);
const restAllSingle = rest.every(ts => ts.length === 1);
if (head.length >= 2 && restAllSingle) {
// â¡ ãã€ããªãã ORïŒ
// - å
é çã®ãæåŸã®ããŒã¯ã³ãâ OR éå
// - å
é çã®ããã以å€ã â allïŒå¿
é èªïŒ
// - åŸç¶çïŒåäžããŒã¯ã³ïŒ â OR éå
const required = head.slice(0, -1);
const orTokens = [head[head.length - 1], ...rest.map(ts => ts[0])];
document.getElementById('adv-all-words').value = required.join(' ');
document.getElementById('adv-any-words').value = orTokens.join(' ');
// exact ã¯ç©ºã®ãŸãŸïŒåŒçšã¯ any åŽãžïŒ
isUpdating = false;
return;
}
// ãã以å€ïŒã¬ã¢ïŒã¯éåžžããŒã¹ã«ãã©ãŒã«ããã¯
}
// ããããéåžžããŒã¹ïŒrawNorm ãããŒã¹ïŒ
let q = ` ${rawNorm} `;
// èšèªãæŒç®åã¯å
ã«æãïŒåŒçšã®ååŸã©ã¡ãã§ãOKã ããå
ã«ãããšèŠèŠçã«æåŸ
éãïŒ
const extract = (regex, cb) => {
let m;
while ((m = regex.exec(q)) !== null) {
cb(m[1].trim());
q = q.replace(m[0], ' ');
regex.lastIndex = 0;
}
};
// èšèª
extract(/\blang:([^\s()"]+)/gi, v => { document.getElementById('adv-lang').value = v.toLowerCase(); });
// ããã·ã¥ã¿ã°
extract(/\s#([^\s)"]+)/g, v => {
const el = document.getElementById('adv-hashtag');
el.value = (el.value + ' ' + v).trim();
});
// æå°ãšã³ã²ãŒãžã¡ã³ãã»æé
extract(/\bmin_replies:(\d+)\b/gi, v => document.getElementById('adv-min-replies').value = v);
extract(/\bmin_faves:(\d+)\b/gi, v => document.getElementById('adv-min-faves').value = v);
extract(/\bmin_retweets:(\d+)\b/gi,v => document.getElementById('adv-min-retweets').value= v);
extract(/\bsince:(\d{4}-\d{2}-\d{2})\b/gi, v => document.getElementById('adv-since').value = v);
extract(/\buntil:(\d{4}-\d{2}-\d{2})\b/gi, v => document.getElementById('adv-until').value = v);
// ãã£ã«ã¿
const filterMap = { 'is:verified':'verified', 'filter:links':'links', 'filter:images':'images', 'filter:videos':'videos' };
Object.entries(filterMap).forEach(([op,id])=>{
const r = new RegExp(`\\s(-?)${op.replace(':','\\:')}\\b`, 'gi');
q = q.replace(r, (m, neg) => {
document.getElementById(`adv-filter-${id}-${neg ? 'exclude' : 'include'}`).checked = true;
return ' ';
});
});
// è¿ä¿¡
if (/\binclude:replies\b/i.test(q)) { document.getElementById('adv-replies').value='include'; q=q.replace(/\binclude:replies\b/ig,' '); }
else if (/\bfilter:replies\b/i.test(q)) { document.getElementById('adv-replies').value='only'; q=q.replace(/\bfilter:replies\b/ig,' '); }
else if (/\b-filter:replies\b/i.test(q)) { document.getElementById('adv-replies').value='exclude'; q=q.replace(/\b-filter:replies\b/ig,' '); }
// ã¢ã«ãŠã³ãæŒç®å
const parseAccountField = (inputId, operator) => {
const exclOp = `-${operator}`;
const values = [];
// é€å€
const reEx = new RegExp(`\\s${exclOp.replace(/[-:]/g,'\\$&')}([^\\s()"]+)`, 'gi');
q = q.replace(reEx, (m, u) => { values.push(u); document.getElementById(`${inputId}-exclude`).checked = true; return ' '; });
// å
å«ïŒæ¬åŒ§ OR ã°ã«ãŒãïŒ
const reGroup = new RegExp(`\\((?:${operator.replace(':','\\:')}([^\\s()"]+))(?:\\s+OR\\s+${operator.replace(':','\\:')}([^\\s()"]+))*\\)`, 'gi');
q = q.replace(reGroup, (m) => {
m.replace(new RegExp(`${operator.replace(':','\\:')}([^\\s()"]+)`, 'gi'), (_m, u) => { values.push(u); return _m; });
return ' ';
});
// åäœ
const reIn = new RegExp(`\\s(?!-)${operator.replace(':','\\:')}([^\\s()"]+)`, 'gi');
q = q.replace(reIn, (m, u) => { values.push(u); return ' '; });
if (values.length) document.getElementById(inputId).value = [...new Set(values)].join(' ');
};
parseAccountField('adv-from-user','from:');
parseAccountField('adv-to-user','to:');
parseAccountField('adv-mentioning','@');
// ⌠æ¬åŒ§å
OR 㯠any ãžïŒ**å
ã«ãã**ãåŒçšã¯å£ããªããã°ã«ãŒãäžžããšé€å»ïŒ
{
const groups = q.match(/\((?:[^()"]+|"[^"]*")+\)/g); // åŒçšå¯Ÿå¿ã®ç°¡æç
if (groups) {
const tokens = groups
.map(g => g.slice(1, -1)) // (...) â äžèº«
.flatMap(s => s.split(/\s+OR\s+/i))
.map(s => s.trim())
.filter(Boolean);
if (tokens.length) {
const el = document.getElementById('adv-any-words');
el.value = (el.value ? el.value + ' ' : '') + tokens.join(' ');
}
// ã°ã«ãŒãã¯äžžããšåãïŒä»¥åŸã®åŒçšæœåºã«å·»ã蟌ãŸããªã
q = q.replace(/\((?:[^()"]+|"[^"]*")+\)/g, ' ');
}
}
// ⌠åŒçšãã¬ãŒãºïŒæ¬åŒ§ã®å€ã ããæ®ã£ãŠããïŒãexact ã¯æåã®1ä»¶ã®ã¿
{
let exactSet = false;
q = q.replace(/"([^"]+)"/g, (_m, p1) => {
if (!exactSet) {
document.getElementById('adv-exact-phrase').value = p1.trim();
exactSet = true;
}
return ' ';
});
}
// é€å€èª
const nots = (q.match(/\s-\S+/g) || []).map(w => w.trim().slice(1));
if (nots.length) document.getElementById('adv-not-words').value = nots.join(' ');
q = q.replace(/\s-\S+/g,' ');
document.getElementById('adv-all-words').value =
q.trim().split(/\s+/).filter(Boolean).join(' ');
// ãã£ã«ã¿é©çšåŸã« disabled ç¶æ
ãåè©äŸ¡
['verified', 'links', 'images', 'videos'].forEach(groupName => {
const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
if (!includeEl || !excludeEl) return;
if (includeEl.checked) excludeEl.disabled = true;
if (excludeEl.checked) includeEl.disabled = true;
});
isUpdating = false;
};
const syncFromModalToSearchBox = () => {
if (isUpdating) return; isUpdating=true;
const finalQuery = buildQueryStringFromModal();
// âŒ è€æ°åœ¢ã«å€æŽããã«ãŒãåŠç
const inputs = getActiveSearchInputs();
if (inputs.length > 0) {
inputs.forEach(si => {
if (si) { syncControlledInput(si, finalQuery); }
});
}
isUpdating=false;
updateSaveButtonState();
};
const syncFromSearchBoxToModal = STATE_SYNC.parseFromSearchToModal;
const showToast = (msg) => {
toastEl.textContent = msg;
toastEl.classList.add('show');
setTimeout(()=> toastEl.classList.remove('show'), 1500);
};
function openSettingsModal() {
if (!settingsModal) return;
settingsModal.style.display = 'flex';
// UIèšèªã®èªã¿èŸŒã¿
try {
const override = kv.get(LANG_OVERRIDE_KEY, '');
if (settingsLangSel) settingsLangSel.value = override || '';
} catch (_) {}
try {
const initTab = kv.get(INITIAL_TAB_KEY, 'last');
if (settingsInitialTabSel) settingsInitialTabSel.value = initTab;
} catch (_) {}
// ã¿ã衚瀺èšå®ã®èªã¿èŸŒã¿ãšèšå®
try {
const visibility = loadTabsVisibility();
DEFAULT_TABS.forEach(tabName => {
const toggle = document.getElementById(`adv-settings-tab-toggle-${tabName}`);
if (!toggle) return;
// ç¶æ
ãåæ (search 㯠disabled checked ã«ãªã£ãŠããã®ã§ loadTabsVisibility ã«è¿œåŸ)
toggle.checked = visibility[tabName] !== false;
// å€éç»é²ã鲿¢
if (toggle.dataset.listenerAttached) return;
toggle.dataset.listenerAttached = 'true';
toggle.addEventListener('change', () => {
const currentState = loadTabsVisibility();
currentState[tabName] = toggle.checked;
saveTabsVisibility(currentState);
// å³åº§ã«ã¿ãããŒã«åæ
applyTabsVisibility();
});
});
} catch (e) {
console.error('[AdvSearch] Failed to setup Tab Toggles:', e);
}
try {
const dialog = settingsModal.querySelector('.adv-settings-dialog');
themeManager.applyTheme(dialog, trigger);
} catch (_) {}
}
function closeSettingsModal() {
if (!settingsModal) return;
settingsModal.style.display = 'none';
}
if (settingsOpenBtn) {
settingsOpenBtn.addEventListener('click', (e)=>{
e.stopPropagation();
openSettingsModal();
});
}
if (settingsCloseBtn) {
settingsCloseBtn.addEventListener('click', (e)=>{
e.stopPropagation();
closeSettingsModal();
});
}
if (settingsCloseFooterBtn) {
settingsCloseFooterBtn.addEventListener('click', (e)=>{
e.stopPropagation();
closeSettingsModal();
});
}
if (settingsModal) {
settingsModal.addEventListener('click', (e)=>{
if (e.target === settingsModal) {
closeSettingsModal();
}
});
}
if (settingsExportBtn) {
settingsExportBtn.addEventListener('click', () => {
const json = buildSettingsExportJSON();
try {
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
const fname =
`advanced-search-for-x-twitter-backup-${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}` +
`-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}.json`;
a.href = url;
a.download = fname;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (_) {
// 倱æããŠãããšããããããŒã¹ãã ãã¯åºã
}
showToast(i18n.t('toastExported'));
});
}
if (settingsImportBtn && settingsFileInput) {
let importResetTimer = null;
settingsImportBtn.addEventListener('click', () => {
settingsFileInput.click();
});
settingsFileInput.addEventListener('change', () => {
const file = settingsFileInput.files && settingsFileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
let ok = false;
try {
ok = applySettingsImportJSON(String(reader.result || ''));
} finally {
// åããã¡ã€ã«ãç¶ããŠéžã¹ãããã«ãªã»ãã
settingsFileInput.value = '';
}
if (ok && settingsImportBtn) {
const successLabel = i18n.t('buttonImportSuccess');
const normalLabel = i18n.t('buttonImport');
settingsImportBtn.textContent = successLabel;
settingsImportBtn.disabled = true;
if (importResetTimer) clearTimeout(importResetTimer);
importResetTimer = setTimeout(() => {
settingsImportBtn.disabled = false;
settingsImportBtn.textContent = normalLabel;
}, 2000);
}
};
reader.readAsText(file);
});
}
if (settingsResetBtn) {
settingsResetBtn.addEventListener('click', () => {
if (!confirm(i18n.t('confirmResetAll'))) return;
const KEYS_TO_DELETE = [
MODAL_STATE_KEY,
TRIGGER_STATE_KEY,
HISTORY_KEY,
INITIAL_TAB_KEY,
SAVED_KEY,
SECRET_KEY,
MUTE_KEY,
MUTE_MASTER_KEY,
MUTE_MODE_KEY,
LAST_TAB_KEY,
TABS_ORDER_KEY,
TABS_VISIBILITY_KEY,
LANG_OVERRIDE_KEY,
HISTORY_SORT_KEY,
EXC_NAME_KEY,
EXC_HANDLE_KEY,
EXC_REPOSTS_KEY,
EXC_HASHTAGS_KEY,
NATIVE_SEARCH_WIDTH_KEY,
FAV_KEY,
'advSavedUnassignedIndex_v1',
'advAccountsUnassignedIndex_v1',
'advListsUnassignedIndex_v1',
...Object.values(ZOOM_KEYS),
FT_STATE_KEY,
];
KEYS_TO_DELETE.forEach(k => {
try { kv.del(k); } catch (_) {}
});
// åçš®é
åç³»ã¯ç©ºé
åã§äžæžã
try { saveMuted([]); } catch (_) {}
try { saveJSON(HISTORY_KEY, []); } catch (_) {}
try { saveJSON(SAVED_KEY, []); } catch (_) {}
try { saveFavorites([]); } catch (_) {}
try { saveAccounts([]); } catch (_) {}
try { saveLists([]); } catch (_) {}
try { saveFolders(ACCOUNTS_FOLDERS_KEY, []); } catch (_) {}
try { saveFolders(LISTS_FOLDERS_KEY, []); } catch (_) {}
try { saveFolders(SAVED_FOLDERS_KEY, []); } catch (_) {}
/* --- Favorite Tags Data --- */
try {
if (typeof ft_createDefaultState === 'function' && typeof ft_saveState === 'function') {
const defaultBmState = ft_createDefaultState();
// saveState ã¯å
éšã§ saveJSON ãåŒã¶
ft_saveState(defaultBmState);
if (typeof ft_state !== 'undefined' && ft_state !== null) {
// ã°ããŒãã«å€æ°ããªã»ãã
Object.assign(ft_state, defaultBmState);
}
}
} catch (_) {}
// ãºãŒã ãã£ãã·ã¥ãšããŒã¹ãã£ãã·ã¥ããªã»ãã
try {
Object.keys(zoomByTab).forEach(tab => {
zoomByTab[tab] = (tab === 'search') ? 0.87 : 1.0;
});
} catch (_) {}
__cachedSearchTokens = null;
__cachedSearchQuery = null;
// èšèªèšå®ãåé©çšïŒãªãŒããŒã©ã€ãããªããã°èªåæ€åºïŒ
try {
const override = kv.get(LANG_OVERRIDE_KEY, '');
if (override && i18n.translations[override]) {
i18n.lang = override;
} else {
i18n.init();
}
} catch (_) {
i18n.init();
}
try {
i18n.apply(document.getElementById('advanced-search-modal'));
i18n.apply(document.getElementById('adv-settings-modal'));
} catch (_) {}
// UI ç¶æ
ãåæå
try {
// ã¿ãã®è¡šç€ºç¶æ
ããªã»ãã
applyTabsVisibility();
// æåŸã®ã¿ãã 'search' ã«ãªã»ãã
activateTab('search');
parseQueryAndApplyToModal('');
applyScopesToControls({ pf: false, lf: false });
applySecretBtn();
renderHistory();
renderSaved();
renderLists();
renderAccounts();
renderMuted();
// ãã¥ãŒãã¢ãŒãã®éžæç¶æ
ãUIã«åæ
if (muteModeSel) {
muteModeSel.value = loadMuteMode();
}
updateSaveButtonState();
rescanAllTweetsForFilter();
/* --- Favorite Tags UI Refresh --- */
try {
if (typeof ft_refreshAllTagChips === 'function') ft_refreshAllTagChips();
} catch (_) {}
} catch (_) {}
// ã¢ãŒãã«äœçœ®ã»ãµã€ãºãããã©ã«ãã«è¿ãç¶æ
ãžæ»ã
try {
modal.style.width = '';
modal.style.height = '';
modal.style.left = '';
modal.style.right = '';
modal.style.top = '';
modal.style.bottom = '';
loadModalState();
requestAnimationFrame(keepModalInViewport);
} catch (_) {}
// ããªã¬ãŒãã¿ã³ã®äœçœ®ããªã»ãã
try {
trigger.style.left = '';
trigger.style.right = '';
trigger.style.top = '';
trigger.style.bottom = '';
applyTriggerStoredPosition();
keepTriggerInViewport();
} catch (_) {}
showToast(i18n.t('toastReset'));
});
}
if (settingsLangSel) {
settingsLangSel.addEventListener('change', ()=>{
const v = settingsLangSel.value;
try { kv.set(LANG_OVERRIDE_KEY, v || ''); } catch (_) {}
if (v && i18n.translations[v]) {
i18n.lang = v;
} else {
i18n.init();
try {
const override = kv.get(LANG_OVERRIDE_KEY, '');
if (override && i18n.translations[override]) i18n.lang = override;
} catch (_) {}
}
try {
i18n.apply(document.getElementById('advanced-search-modal'));
i18n.apply(document.getElementById('adv-settings-modal'));
} catch (_) {}
trigger.setAttribute('aria-label', i18n.t('tooltipTrigger'));
historyClearAllBtn.textContent = i18n.t('historyClearAll');
applySecretBtn();
try { renderHistory(); } catch (_) {}
try { renderSaved(); } catch (_) {}
try { renderLists(); } catch (_) {}
try { renderAccounts(); } catch (_) {}
try { renderMuted(); } catch (_) {}
try { renderFavorites(); } catch (_) {}
});
}
if (settingsInitialTabSel) {
settingsInitialTabSel.addEventListener('change', () => {
kv.set(INITIAL_TAB_KEY, settingsInitialTabSel.value);
});
}
const loadSecret = () => { try { return kv.get(SECRET_KEY, '0') === '1'; } catch(_) { return false; } };
const saveSecret = (on) => { try { kv.set(SECRET_KEY, on ? '1' : '0'); } catch(_) {} };
const applySecretBtn = () => {
const on = loadSecret();
secretBtn.classList.toggle('on', on);
secretBtn.classList.toggle('off', !on);
secretBtn.title = i18n.t(on ? 'secretOn' : 'secretOff');
secretStateEl.textContent = on ? 'ON' : 'OFF';
};
secretBtn.addEventListener('click', (e)=>{
e.stopPropagation();
const on = !loadSecret();
saveSecret(on);
applySecretBtn();
showToast(i18n.t(on ? 'secretOn' : 'secretOff'));
});
applySecretBtn();
const migrateList = (list) => Array.isArray(list) ? list.map(it => ({ id:it.id||uid(), q:it.q||'', ts:it.ts||Date.now(), pf:!!it.pf, lf:!!it.lf })) : [];
const recordHistory = (q, pf, lf) => {
if (!q || loadSecret()) return;
const now = Date.now();
if (lastHistory.q === q && lastHistory.pf === pf && lastHistory.lf === lf && (now - lastHistory.ts) < 3000) return;
lastHistory.q = q; lastHistory.pf = pf; lastHistory.lf = lf; lastHistory.ts = now;
const listRaw = loadJSON(HISTORY_KEY, []);
const list = migrateList(listRaw);
const idx = list.findIndex(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf);
if (idx === 0) {
list[0].ts = now;
} else if (idx > 0) {
const [item] = list.splice(idx, 1);
item.ts = now;
list.unshift(item);
} else {
list.unshift({ id: uid(), q, pf: !!pf, lf: !!lf, ts: now });
// if (list.length > 50) list.length = 50;
}
saveJSON(HISTORY_KEY, list);
renderHistory();
};
const deleteHistory = (id) => {
const listRaw = loadJSON(HISTORY_KEY, []);
const list = migrateList(listRaw);
const next = list.filter(it => it.id !== id);
saveJSON(HISTORY_KEY, next);
renderHistory();
showToast(i18n.t('toastDeleted'));
};
const clearAllHistory = () => {
if (!confirm(i18n.t('confirmClearHistory'))) return;
saveJSON(HISTORY_KEY, []);
renderHistory();
showToast(i18n.t('toastDeleted'));
};
const addSaved = (q, pf, lf) => {
const listRaw = loadJSON(SAVED_KEY, []);
const list = migrateList(listRaw);
if (list.some(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf)) {
updateSaveButtonState();
return;
}
const item = { id: uid(), q, pf: !!pf, lf: !!lf, ts: Date.now() };
list.push(item);
saveJSON(SAVED_KEY, list);
renderSaved();
showToast(i18n.t('toastSaved'));
updateSaveButtonState();
};
const deleteSaved = (id) => {
const listRaw = loadJSON(SAVED_KEY, []);
const list = migrateList(listRaw);
const next = list.filter(it => it.id !== id);
saveJSON(SAVED_KEY, next);
renderSaved();
showToast(i18n.t('toastDeleted'));
updateSaveButtonState();
};
const fmtTime = (ts) => { try { return new Date(ts).toLocaleString(); } catch { return ''; } };
const updateSaveButtonState = () => {
const q = buildQueryStringFromModal().trim();
const {pf, lf} = readScopesFromControls();
const saved = migrateList(loadJSON(SAVED_KEY, []));
const exists = !!q && saved.some(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf);
saveButton.disabled = !q || exists;
saveButton.textContent = i18n.t(exists ? 'buttonSaved' : 'buttonSave');
saveButton.setAttribute('aria-disabled', saveButton.disabled ? 'true' : 'false');
};
// ã¿ãã®ã¯ãªãã¯ã€ãã³ããšD&Dã€ãã³ããªã¹ããŒãã»ããã¢ãã
(function setupTabDragAndDrop() {
const tabsContainer = document.querySelector('.adv-tabs');
if (!tabsContainer) return;
tabButtons.forEach(btn => {
// 1. ã¯ãªãã¯ã€ãã³ãïŒæ¢åã®ããžãã¯ïŒ
btn.addEventListener('click', (e) => {
e.preventDefault();
activateTab(btn.dataset.tab);
});
// 2. D&Dã€ãã³ãïŒæ°èŠïŒ
btn.draggable = true;
btn.addEventListener('dragstart', (ev) => {
btn.classList.add('dragging');
ev.dataTransfer.setData('text/plain', btn.dataset.tab);
ev.dataTransfer.effectAllowed = 'move';
});
btn.addEventListener('dragend', () => {
btn.classList.remove('dragging');
});
});
tabsContainer.addEventListener('dragover', (ev) => {
ev.preventDefault();
const dragging = tabsContainer.querySelector('.adv-tab-btn.dragging');
if (!dragging) return;
// æ°Žå¹³æ¹åã®æ¿å
¥äœçœ®ãèšç®
const after = getDragAfterElementHorizontal(tabsContainer, ev.clientX, '.adv-tab-btn');
if (after == null) {
tabsContainer.appendChild(dragging);
} else {
tabsContainer.insertBefore(dragging, after);
}
});
tabsContainer.addEventListener('drop', (ev) => {
ev.preventDefault();
const dragging = tabsContainer.querySelector('.adv-tab-btn.dragging');
if (dragging) {
dragging.classList.remove('dragging');
}
// æçµçãªé åºãDOMããèªã¿åã£ãŠä¿å
const newOrder = [...tabsContainer.querySelectorAll('.adv-tab-btn[data-tab]')]
.map(btn => btn.dataset.tab)
.filter(Boolean);
saveJSON(TABS_ORDER_KEY, newOrder);
// tabButtons é
åãæŽæ°
tabButtons.splice(0, tabButtons.length, ...Array.from(document.querySelectorAll('.adv-tab-btn')));
showToast(i18n.t('toastReordered'));
});
})();
const scopeChipsHTML = (pf, lf) => {
const chips = [];
if (pf) chips.push(`<span class="adv-chip scope" role="note">${i18n.t('chipFollowing')}</span>`);
if (lf) chips.push(`<span class="adv-chip scope" role="note">${i18n.t('chipNearby')}</span>`);
return chips.join('');
};
const historyEmptyEl = document.getElementById('adv-history-empty');
const historyListEl = document.getElementById('adv-history-list');
const historySearchEl = document.getElementById('adv-history-search');
const historySortEl = document.getElementById('adv-history-sort');
const renderHistory = () => {
const listAll = migrateList(loadJSON(HISTORY_KEY, []));
// 1. Get filter/sort values
const q = (historySearchEl?.value || '').toLowerCase().trim();
const sort = historySortEl?.value || kv.get(HISTORY_SORT_KEY, 'newest');
if (historySortEl && historySortEl.value !== sort) {
historySortEl.value = sort;
}
// 2. Filter
const listFiltered = q
? listAll.filter(item => (item.q || '').toLowerCase().includes(q))
: listAll;
// 3. Sort
const listSorted = listFiltered.sort((a, b) => {
switch (sort) {
case 'oldest': return (a.ts || 0) - (b.ts || 0);
case 'name_asc': return (a.q || '').localeCompare(b.q || '');
case 'name_desc': return (b.q || '').localeCompare(a.q || '');
case 'newest':
default:
return (b.ts || 0) - (a.ts || 0);
}
});
// 4. Render with Pagination
const renderHistoryRow = (item) => {
const row = document.createElement('div');
row.className = 'adv-item';
row.dataset.id = item.id;
row.innerHTML = `
<div class="adv-item-main">
<div class="adv-item-title">${escapeHTML(item.q)}</div>
<div class="adv-item-sub">
<span>${fmtTime(item.ts)}</span>
${scopeChipsHTML(!!item.pf, !!item.lf)}
</div>
</div>
<div class="adv-item-actions">
<button class="adv-chip primary" data-action="run">${i18n.t('run')}</button>
<button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
</div>
`;
row.querySelector('[data-action="run"]').addEventListener('click', () => {
parseQueryAndApplyToModal(item.q);
applyScopesToControls({ pf: !!item.pf, lf: !!item.lf });
executeSearch({ pf: item.pf, lf: item.lf });
});
row.querySelector('[data-action="delete"]').addEventListener('click', () => {
deleteHistory(item.id);
});
return row;
};
renderPagedList('history', historyListEl, listSorted, renderHistoryRow, historyEmptyEl, i18n.t('emptyHistory'));
};
historyClearAllBtn.addEventListener('click', clearAllHistory);
// å±¥æŽã¿ãã®æ€çŽ¢ãšãœãŒãã®ã€ãã³ããªã¹ããŒ
if (historySearchEl) {
historySearchEl.addEventListener('input', debounce(renderHistory, 150));
}
if (historySortEl) {
historySortEl.value = kv.get(HISTORY_SORT_KEY, 'newest'); // åæå€ãèšå®
historySortEl.addEventListener('change', () => {
kv.set(HISTORY_SORT_KEY, historySortEl.value);
renderHistory();
});
}
const savedEmptyEl = document.getElementById('adv-saved-empty');
const savedListEl = document.getElementById('adv-saved-list');
const renderSaved = () => {
ensureFolderToolbars();
const itemsLoader = () => migrateList(loadJSON(SAVED_KEY, []));
const itemsSaver = (arr) => saveJSON(SAVED_KEY, migrateList(arr));
renderFolderedCollection({
hostId: 'adv-saved-list',
emptyId: 'adv-saved-empty',
filterSelectId: 'adv-saved-folder-filter',
searchInputId: 'adv-saved-search',
newFolderBtnId: 'adv-saved-new-folder',
foldersKey: SAVED_FOLDERS_KEY,
defaultFolderName: i18n.t('defaultSavedFolders'),
loadItems: itemsLoader,
saveItems: itemsSaver,
renderRow: (item) => {
// 以åã® renderSavedRow ãšåãèŠãç®
const row = document.createElement('div');
row.className = 'adv-item';
row.draggable = true;
row.dataset.id = item.id;
row.innerHTML = `
<div class="adv-item-handle" title="Drag">â¡</div>
<div class="adv-item-main">
<div class="adv-item-title">${escapeHTML(item.q)}</div>
<div class="adv-item-sub">
<span>${fmtTime(item.ts)}</span>
${scopeChipsHTML(!!item.pf, !!item.lf)}
</div>
</div>
<div class="adv-item-actions">
<button class="adv-chip primary" data-action="run">${i18n.t('run')}</button>
<button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
</div>
`;
row.querySelector('[data-action="run"]').addEventListener('click', ()=>{
parseQueryAndApplyToModal(item.q);
applyScopesToControls({pf:!!item.pf, lf:!!item.lf});
// activateTab('search');
executeSearch({pf:item.pf, lf:item.lf});
});
row.querySelector('[data-action="delete"]').addEventListener('click', ()=> deleteSaved(item.id));
row.addEventListener('dragstart', (ev) => {
row.classList.add('dragging');
ev.dataTransfer.setData('text/plain', item.id);
ev.dataTransfer.effectAllowed = 'move';
});
row.addEventListener('dragend', () => row.classList.remove('dragging'));
return row;
},
onUnassign: unassignSaved,
onMoveToFolder: moveSavedToFolder,
emptyMessage: i18n.t('emptySaved'),
unassignedIndexKey: 'advSavedUnassignedIndex_v1',
});
updateSaveButtonState();
};
const getDragAfterElement = (container, y) => {
const els = [...container.querySelectorAll('.adv-item:not(.dragging)')];
let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
for (const el of els) {
const box = el.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
closest = { offset, element: el };
}
}
return closest.element;
};
// === ã»ã¯ã·ã§ã³ïŒãã©ã«ã/UnassignedïŒçšïŒçžŠæ¹åã®æ¿å
¥äœçœ®èšç® ===
function getSectionAfterElement(container, y) {
const els = [...container.querySelectorAll('.adv-folder:not(.dragging-folder), .adv-unassigned:not(.dragging-folder)')];
let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
for (const el of els) {
const box = el.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
closest = { offset, element: el };
}
}
return closest.element;
}
// === æ±çšãã©ã«ãæç»ã¬ã³ãã© ===
// åã¿ãïŒSaved/Accounts/Listsãªã©ïŒã®éè€ããžãã¯ã1ãæã«éçŽããŸãã
function renderFolderedCollection(cfg) {
const {
// åºæID/ããŒ
hostId, emptyId,
filterSelectId, searchInputId, newFolderBtnId,
foldersKey, defaultFolderName,
// ããŒã¿I/O
loadItems, saveItems, loadFoldersFn = loadFolders, saveFoldersFn = saveFolders,
// Rowæç»/æäœ
renderRow, onUnassign, onMoveToFolder,
// æèš/ä¿åããŒ
emptyMessage,
unassignedIndexKey, // ex: 'advAccountsUnassignedIndex_v1' / 'advSavedUnassignedIndex_v1'
} = cfg;
// ããŒã«ããŒã¯åŒã³åºãåŽã§ ensureFolderToolbars() ããŠããåæ
const host = document.getElementById(hostId);
const empty = document.getElementById(emptyId);
const sel = document.getElementById(filterSelectId);
const qInput = document.getElementById(searchInputId);
const addBtn = document.getElementById(newFolderBtnId);
if (!host) return;
// 1) ããŒã¿ããŒã
const items = loadItems();
let folders = loadFoldersFn(foldersKey, defaultFolderName);
const idToItem = Object.fromEntries(items.map(x => [x.id, x]));
// 2) æ»ç¥šæé€ïŒãã©ã«ãã® order ããååšããªãIDãé€å»ïŒ
let needSave = false;
for (const f of folders) {
const before = f.order.length;
f.order = f.order.filter(id => !!idToItem[id]);
if (f.order.length !== before) { needSave = true; f.ts = Date.now(); }
}
if (needSave) saveFoldersFn(foldersKey, folders);
// 3) æªæå±ã»ãã
const allIds = new Set(items.map(x => x.id));
const inFolders = new Set(folders.flatMap(f => f.order));
const unassignedIds = [...allIds].filter(id => !inFolders.has(id));
// 4) ãã£ã«ã¿UIïŒã»ã¬ã¯ãïŒæ€çŽ¢ïŒæ°èŠãã©ã«ãïŒ
if (sel) {
const prev = sel.value;
sel.innerHTML = '';
const optAll = document.createElement('option'); optAll.value='__ALL__'; optAll.textContent=i18n.t('folderFilterAll'); sel.appendChild(optAll);
const optUn = document.createElement('option'); optUn.value='__UNASSIGNED__'; optUn.textContent=i18n.t('folderFilterUnassigned'); sel.appendChild(optUn);
folders.forEach(f=>{
const o = document.createElement('option'); o.value = f.id; o.textContent = f.name; sel.appendChild(o);
});
sel.value = [...sel.options].some(o=>o.value===prev) ? prev : '__ALL__';
sel.onchange = () => renderFolderedCollection(cfg);
}
if (qInput && !qInput._advBound) {
qInput._advBound = true;
// debounce ãé©çš
qInput.addEventListener('input', debounce(() => renderFolderedCollection(cfg), 150));
}
if (addBtn && !addBtn._advBound) {
addBtn._advBound = true;
addBtn.addEventListener('click', () => {
const nm = prompt(i18n.t('promptNewFolder'), '');
if (!nm || !nm.trim()) return;
const fs = loadFoldersFn(foldersKey, defaultFolderName);
fs.push({ id: uid(), name: nm.trim(), order: [], ts: Date.now() });
saveFoldersFn(foldersKey, fs);
renderFolderedCollection(cfg);
});
}
const filterFolder = sel?.value || '__ALL__';
const q = (qInput?.value || '').toLowerCase().trim();
const matchItem = (it) => {
// JSONåãããæ€çŽ¢å¯Ÿè±¡ã«ãªããããã£ãŒã«ãã®å€ãçŽæ¥çµåããŠå€å®ãã
// ããã«ãããä¿åã¯ãšãªå
ã® " (ããã«ã¯ã©ãŒã) ããšã¹ã±ãŒããããã«æ€çŽ¢å¯èœã«ãªã
const targetText = [
it.q, // Saved / History çš
it.name, // Accounts / Lists / Folders çš
it.handle, // Accounts çš
it.url, // Lists çš
it.user?.name, // (äºå)
it.user?.handle // (äºå)
].map(val => (val || '').toString().toLowerCase()).join(' ');
return !q || targetText.includes(q);
};
host.innerHTML = '';
empty.textContent = items.length ? '' : (emptyMessage || '');
// 5) Unassigned ã€ã³ããã¯ã¹ä¿æ
const getUnIdx = () => {
try { const v = GM_getValue(unassignedIndexKey, 0); return Math.max(0, Math.min(folders.length, +v || 0)); }
catch { return 0; }
};
const setUnIdx = (idx) => { try { GM_setValue(unassignedIndexKey, String(idx)); } catch {} };
// 6) 衚瀺察象ãã©ã«ã
const foldersToDraw =
filterFolder === '__ALL__' ? [...folders] :
filterFolder === '__UNASSIGNED__' ? [] :
folders.filter(f => f.id === filterFolder);
// 7) ã»ã¯ã·ã§ã³äžŠã³ïŒ__ALL__ ã®å Žåã®ã¿ Unassigned ãæ··åšïŒ
const buildSectionsOrder = () => {
if (filterFolder !== '__ALL__') return foldersToDraw.map(f => f.id);
const idx = getUnIdx();
const arr = foldersToDraw.map(f => f.id);
arr.splice(Math.max(0, Math.min(arr.length, idx)), 0, '__UNASSIGNED__');
return arr;
};
// 8) DOM â é åºä¿å
const persistSectionsFromDOM = () => {
const order = [...host.querySelectorAll('.adv-folder, .adv-unassigned')].map(sec => sec.dataset.folderId);
// ãã©ã«ãé ïŒUnassigned ãé€ããé åºã§ä¿åïŒ
const newFolderOrderIds = [...new Set(order.filter(id => id !== '__UNASSIGNED__'))];
let fs = loadFoldersFn(foldersKey, defaultFolderName);
const map = Object.fromEntries(fs.map(f => [f.id, f]));
const reordered = newFolderOrderIds.map(id => map[id]).filter(Boolean);
fs.forEach(f => { if (!reordered.includes(f)) reordered.push(f); });
saveFoldersFn(foldersKey, reordered);
// Unassigned ã®äœçœ®ãä¿å
const unIdx = order.indexOf('__UNASSIGNED__');
if (unIdx >= 0) setUnIdx(unIdx);
showToast(i18n.t('toastReordered'));
};
// 9) Unassigned ã»ã¯ã·ã§ã³
const renderUnassignedSection = () => {
const sec = document.createElement('section');
sec.className = 'adv-unassigned';
sec.dataset.folderId = '__UNASSIGNED__';
sec.setAttribute('draggable', 'true');
const list = document.createElement('div'); list.className = 'adv-list';
const itemsUn = unassignedIds.map(id => idToItem[id]).filter(Boolean).filter(matchItem);
itemsUn.forEach(it => list.appendChild(renderRow(it)));
// ã»ã¯ã·ã§ã³D&DïŒã»ã¯ã·ã§ã³å
¥æ¿ïŒ
const SECT_MIME = 'adv/folder';
sec.addEventListener('dragstart', (ev) => {
const item = ev.target.closest('.adv-item');
if (!item) {
ev.dataTransfer.setData(SECT_MIME, '__UNASSIGNED__');
ev.dataTransfer.effectAllowed = 'move';
sec.classList.add('dragging-folder');
}
});
sec.addEventListener('dragend', () => sec.classList.remove('dragging-folder'));
sec.addEventListener('dragover', (ev) => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) {
ev.preventDefault();
const dragging = host.querySelector('.dragging-folder');
if (!dragging || dragging === sec) return;
const after = getSectionAfterElement(host, ev.clientY);
if (after == null) host.appendChild(dragging);
else host.insertBefore(dragging, after);
}
});
// ã¢ã€ãã ã®ãã¬ãã¥ãŒç§»åïŒDOMïŒ
list.addEventListener('dragover', ev => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // ã»ã¯ã·ã§ã³D&Dã¯ç¡èŠ
ev.preventDefault(); ev.stopPropagation();
/* âŒâŒâŒ ããã§ãã©ã«ããŒãèæ¯ã®ãã€ã©ã€ãã匷å¶çã«æ¶ã âŒâŒâŒ */
document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => delete el.dataset.drop);
document.querySelectorAll('.adv-bg-drop-active').forEach(el => el.classList.remove('adv-bg-drop-active'));
const dragging = document.querySelector('.adv-item.dragging');
if (!dragging) return;
const after = getDragAfterElement(list, ev.clientY);
if (after == null) list.appendChild(dragging);
else list.insertBefore(dragging, after);
});
// âŒãæªåé¡åããã³ãã©ïŒã»ã¯ã·ã§ã³èæ¯çšïŒ
// ãã©ã«ããããããããããå Žåã« "å
é ã«ç§»å" ãããã
const dropToUnassign = (ev) => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
ev.preventDefault(); ev.stopPropagation();
const draggedId = ev.dataTransfer.getData('text/plain');
if (draggedId) onUnassign(draggedId); // onUnassign 㯠"å
é ã«ç§»å" ãã
};
// âŒãæªåé¡ã¢ã€ãã ã®äžŠã³æ¿ãããã³ãã©ïŒãªã¹ãæ¬äœçšïŒ
// æªåé¡ãªã¹ãå
ã§ã®äžŠã³æ¿ãããŸãã¯ãã©ã«ãããç¹å®äœçœ®ãžã®ããããã
const dropToReorderUnassigned = (ev) => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
ev.preventDefault(); ev.stopPropagation();
const draggedId = ev.dataTransfer.getData('text/plain');
if (!draggedId) return;
// 1. DOMã®èŠèŠçãªé åºïŒdragoverã§å€æŽæžã¿ïŒãIDé
åãšããŠèªã¿åã
const newOrderIdsInList = [...list.querySelectorAll('.adv-item')].map(el => el.dataset.id);
// 2. ãã¹ã¿ãŒãªã¹ãïŒå
šã¢ã€ãã ïŒãšãã©ã«ãå
ã¢ã€ãã ã®æ
å ±ãããŒã
const allItems = loadItems();
const allItemsMap = new Map(allItems.map(it => [it.id, it]));
const allFolderItems = new Set(folders.flatMap(f => f.order));
// 3. æ°ãããã¹ã¿ãŒãªã¹ããæ§ç¯
const nextMasterList = [];
const seen = new Set();
// 3a. ãŸããDOMããèªã¿åã£ããæªåé¡ã®æ°ããé åºãã§ã¢ã€ãã ã远å
for (const id of newOrderIdsInList) {
// ãã®ãªã¹ãã«ããã¹ãã¢ã€ãã ïŒïŒãã¹ã¿ãŒã«ååšãããã©ã«ãã«å±ããªãïŒã®ã¿
if (id && allItemsMap.has(id) && !allFolderItems.has(id)) {
nextMasterList.push(allItemsMap.get(id));
seen.add(id);
}
}
// 3b. 次ã«ãæ®ãã®ã¢ã€ãã ïŒå
šãã©ã«ãå
ã®ã¢ã€ãã ïŒäœããã®çç±ã§æŒããæªåé¡ã¢ã€ãã ïŒã远å
// ããã«ããããã¹ã¿ãŒãªã¹ãã®é åºã¯ãæªåé¡ã®äžŠã³æ¿ãé ãïŒããã以å€ããšãªã
for (const item of allItems) {
if (!seen.has(item.id)) {
nextMasterList.push(item);
}
}
// 4. ãã¹ã¿ãŒãªã¹ããä¿å
saveItems(nextMasterList);
// 5. ããã¢ã€ãã ããã©ã«ãããç§»åããŠããå Žåããã©ã«ãããåé€ïŒã¯ãªãŒã³ã¢ããïŒ
const fs = loadFoldersFn(foldersKey, defaultFolderName);
let folderChanged = false;
for (const f of fs) {
const before = f.order.length;
f.order = f.order.filter(id => id !== draggedId);
if (f.order.length !== before) { f.ts = Date.now(); folderChanged = true; }
}
if (folderChanged) {
saveFoldersFn(foldersKey, fs);
// ãã©ã«ãæ§æãå€ãã£ãå Žåã¯ããªã¹ãå
šäœãåæç»
showToast(i18n.t('toastReordered'));
renderFolderedCollection(cfg);
} else {
// æªåé¡å
ã§ã®ç§»åã ããªãåæç»ã¯äžèŠïŒDOMã¯æŽæ°æžã¿ïŒ
showToast(i18n.t('toastReordered'));
}
};
// ⌠ãªã¹ãæ¬äœã«ã¯ãäžŠã³æ¿ããããã»ã¯ã·ã§ã³èæ¯ã«ã¯ãæªåé¡åããå²ãåœãŠã
list.addEventListener('drop', dropToReorderUnassigned);
sec.addEventListener('dragover', ev => { if (!(ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME))) { ev.preventDefault(); ev.stopPropagation(); }});
sec.addEventListener('drop', dropToUnassign);
sec.appendChild(list);
return sec;
};
// 10) ãã©ã«ãã»ã¯ã·ã§ã³
const renderFolderSection = (folder) => {
const section = document.createElement('section');
section.className = 'adv-folder';
section.dataset.folderId = folder.id;
if (folder.collapsed) section.classList.add('adv-folder-collapsed');
const header = document.createElement('div');
header.className = 'adv-folder-header';
header.setAttribute('draggable', 'true');
const toggleBtn = renderFolderToggleButton(!!folder.collapsed);
const titleWrap = document.createElement('div'); titleWrap.className = 'adv-folder-title';
titleWrap.appendChild(toggleBtn);
const nameEl = document.createElement('strong'); nameEl.textContent = folder.name; titleWrap.appendChild(nameEl);
const countEl = document.createElement('span'); countEl.className='adv-item-sub'; countEl.textContent = `(${folder.order.length})`;
titleWrap.appendChild(countEl);
const actions = document.createElement('div');
actions.className = 'adv-folder-actions';
actions.innerHTML = `
<button class="adv-chip" data-action="rename" title="${i18n.t('folderRenameTitle')}">${i18n.t('folderRename')}</button>
<button class="adv-chip danger" data-action="delete" title="${i18n.t('folderDeleteTitle')}">${i18n.t('folderDelete')}</button>
`;
header.appendChild(titleWrap);
header.appendChild(actions);
// ã»ã¯ã·ã§ã³D&D
const SECT_MIME = 'adv/folder';
header.addEventListener('dragstart', (ev) => {
if (ev.target && (ev.target.closest('.adv-folder-actions') || ev.target.closest('.adv-folder-toggle-btn'))) { ev.preventDefault(); return; }
ev.dataTransfer.setData(SECT_MIME, folder.id);
ev.dataTransfer.effectAllowed = 'move';
section.classList.add('dragging-folder');
});
header.addEventListener('dragend', () => section.classList.remove('dragging-folder'));
section.addEventListener('dragover', (ev) => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) {
ev.preventDefault();
ev.stopPropagation();
const dragging = host.querySelector('.dragging-folder');
if (!dragging || dragging === section) return;
const after = getSectionAfterElement(host, ev.clientY);
if (after == null) host.appendChild(dragging);
else host.insertBefore(dragging, after);
} else {
ev.preventDefault();
ev.stopPropagation(); // ããã§ãæ ç·ãã«ä¹ã£ãæã«èæ¯ãå
ãã®ãé²ã
// ããã§ã¯ section.dataset.drop='1' ã¯ããªãïŒäžèº«ã®ãªã¹ãã«å
¥ã£ãæã«å
ããããã®ã§ïŒ
// ããæ ç·ã§ãå
ããããå Žåã¯ããã« dataset.drop='1' ãæžããŠãOK
}
});
// æãããã¿
const collapseToggle = () => {
section.classList.toggle('adv-folder-collapsed');
const all = loadFoldersFn(foldersKey, defaultFolderName);
const f = all.find(x => x.id === folder.id);
if (f) { f.collapsed = section.classList.contains('adv-folder-collapsed'); f.ts = Date.now(); saveFoldersFn(foldersKey, all); }
updateFolderToggleButton(toggleBtn, !!section.classList.contains('adv-folder-collapsed'));
};
toggleBtn.addEventListener('click', (e)=>{ e.stopPropagation(); collapseToggle(); });
toggleBtn.addEventListener('keydown', (e)=>{ if (e.key===' '||e.key==='Enter'){ e.preventDefault(); collapseToggle(); } });
// Rename / Delete
actions.querySelector('[data-action="rename"]').addEventListener('click', ()=>{
const nm = prompt(i18n.t('promptNewFolder'), folder.name);
if (!nm || !nm.trim()) return;
const fArr = loadFoldersFn(foldersKey, defaultFolderName);
const f = fArr.find(x=>x.id===folder.id); if (!f) return;
f.name = nm.trim(); f.ts = Date.now(); saveFoldersFn(foldersKey, fArr);
renderFolderedCollection(cfg); showToast(i18n.t('updated'));
});
actions.querySelector('[data-action="delete"]').addEventListener('click', ()=>{
if (!confirm(i18n.t('confirmDeleteFolder'))) return;
// 1. åé€å¯Ÿè±¡ã®ã¢ã€ãã IDã»ãããååŸ
const itemsToDelete = new Set(folder.order || []);
// 2. ã¢ã€ãã ã®ãã¹ã¿ãŒãªã¹ããã該åœã¢ã€ãã ãåé€
if (itemsToDelete.size > 0) {
try {
const allItems = loadItems(); // 芪ã¹ã³ãŒãã® loadItems ã䜿çš
const nextItems = allItems.filter(item => !itemsToDelete.has(item.id));
saveItems(nextItems); // 芪ã¹ã³ãŒãã® saveItems ã䜿çš
} catch (e) {
console.error('Failed to delete items in folder:', e);
// ã¢ã€ãã åé€ã«å€±æããŠãããã©ã«ãåé€ã¯ç¶è¡
}
}
// 3. ãã©ã«ãèªäœãåé€
let fArr = loadFoldersFn(foldersKey, defaultFolderName);
const idx = fArr.findIndex(x=>x.id===folder.id); if (idx<0) return;
fArr.splice(idx,1);
saveFoldersFn(foldersKey, fArr);
// 4. åæç»
renderFolderedCollection(cfg); showToast(i18n.t('toastDeleted'));
});
// ãã©ã«ãèŠåºãã«ãããã â ãã®ãã©ã«ããžç§»å
header.addEventListener('dragover', ev => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
ev.preventDefault();
ev.stopPropagation();
/* âŒâŒâŒ èæ¯ã®ç Žç·ã匷å¶çã«æ¶ã âŒâŒâŒ */
document.querySelectorAll('.adv-bg-drop-active').forEach(el => el.classList.remove('adv-bg-drop-active'));
// æä»å¶åŸ¡: ä»ã®ãã©ã«ãã®ãã€ã©ã€ããæ¶ã
document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => {
if (el !== section) delete el.dataset.drop;
});
section.dataset.drop='1';
});
header.addEventListener('dragleave', (ev) => {
// åèŠçŽ ãžã®ç§»åã§ãäžæŠæ¶ãããdragoverã§ãã埩掻ãã
delete section.dataset.drop;
});
header.addEventListener('drop', ev => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
ev.preventDefault(); delete section.dataset.drop;
const draggedId = ev.dataTransfer.getData('text/plain');
if (!draggedId) return;
onMoveToFolder(draggedId, folder.id);
});
// ãªã¹ãæ¬äœ
const list = document.createElement('div'); list.className = 'adv-list';
const itemsInFolder = folder.order.map(id => idToItem[id]).filter(Boolean).filter(matchItem);
itemsInFolder.forEach(it => list.appendChild(renderRow(it)));
// 䞊ã³ãã¬ãã¥ãŒ
list.addEventListener('dragover', ev => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // ã¬ãŒã远å
ev.preventDefault();
ev.stopPropagation(); // äŒæåæ¢ã远å
/* âŒâŒâŒ èæ¯ã®ç Žç·ã匷å¶çã«æ¶ã âŒâŒâŒ */
document.querySelectorAll('.adv-bg-drop-active').forEach(el => el.classList.remove('adv-bg-drop-active'));
// æä»å¶åŸ¡: ä»ã®ãã©ã«ãã®ãã€ã©ã€ããæ¶ã
document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => {
if (el !== section) delete el.dataset.drop;
});
section.dataset.drop='1';
const dragging = document.querySelector('.adv-item.dragging');
if (!dragging) return;
const after = getDragAfterElement(list, ev.clientY);
if (after == null) list.appendChild(dragging);
else list.insertBefore(dragging, after);
});
list.addEventListener('dragleave', ev => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
ev.stopPropagation();
// åèŠçŽ ãžã®ç§»åã§ãäžæŠæ¶ãããdragoverã§ãã埩掻ãã
delete section.dataset.drop;
});
// 䞊ã³ç¢ºå®ïŒãã€å¥ãã©ã«ãâãã®ãã©ã«ããžã®âç§»åâãåžåïŒ
list.addEventListener('drop', (ev) => {
if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // ã¬ãŒã远å
ev.preventDefault(); ev.stopPropagation();
delete section.dataset.drop;
const draggedId = ev.dataTransfer.getData('text/plain');
if (!draggedId) return;
const newOrder = [...list.querySelectorAll('.adv-item')].map(el => el.dataset.id);
const fArr = loadFoldersFn(foldersKey, defaultFolderName);
const f = fArr.find(x=>x.id===folder.id);
if (!f) return;
const isMove = !f.order.includes(draggedId);
if (isMove) {
for (const f_other of fArr) {
if (f_other.id === folder.id) continue;
const o_before = f_other.order.length;
f_other.order = f_other.order.filter(id => id !== draggedId);
if (f_other.order.length !== o_before) f_other.ts = Date.now();
}
}
f.order = newOrder;
f.ts = Date.now();
saveFoldersFn(foldersKey, fArr);
showToast(i18n.t('toastReordered'));
if (isMove) renderFolderedCollection(cfg);
});
section.appendChild(header);
section.appendChild(list);
return section;
};
// 11) åäžè¡šç€ºãALL衚瀺ã
const order = (filterFolder !== '__ALL__')
? (filterFolder === '__UNASSIGNED__' ? ['__UNASSIGNED__'] : foldersToDraw.map(f => f.id))
: buildSectionsOrder();
order.forEach(id => {
if (id === '__UNASSIGNED__') host.appendChild(renderUnassignedSection());
else {
const f = folders.find(x => x.id === id);
if (f) host.appendChild(renderFolderSection(f));
}
});
if (!host._advFolderDropAttached) { // å€éç»é²é²æ¢ãã©ã°
host._advFolderDropAttached = true;
host.addEventListener('drop', (ev) => {
const SECT_MIME = 'adv/folder';
if (!(ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME))) {
// ã¢ã€ãã ã®ãããã (text/plain) ã¯ä»ã®ãªã¹ããŒãåŠçããããç¡èŠ
return;
}
// ã»ã¯ã·ã§ã³äžŠã³æ¿ã (adv/folder) ã® drop ã€ãã³ã
const sectionEl = ev.target.closest('.adv-folder, .adv-unassigned');
// ã€ãã³ãã host (ã³ã³ãã) ãŸãã¯ãã®çŽäžã®åã»ã¯ã·ã§ã³ã§çºçããå Žåã®ã¿åŠç
if (ev.target === host || (sectionEl && sectionEl.parentElement === host)) {
ev.preventDefault();
ev.stopPropagation();
// dragover ã§ DOM ã¯æ¢ã«å
¥ãæ¿ãã£ãŠããã¯ã
persistSectionsFromDOM(); // DOMã®çŸåšã®é åºãä¿å
// ä¿ååŸã«åæç»
renderFolderedCollection(cfg);
}
});
}
}
// ã¿ãäžŠã³æ¿ãïŒæ°Žå¹³ïŒçšã®ãã«ããŒ
const getDragAfterElementHorizontal = (container, x, selector) => {
const els = [...container.querySelectorAll(`${selector}:not(.dragging)`)];
let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
for (const el of els) {
const box = el.getBoundingClientRect();
// æ°Žå¹³æ¹åã®äžå¿ããã®ãªãã»ãããèšç®
const offset = x - box.left - box.width / 2;
// æ¿å
¥ãã¹ããæ¬¡ã®èŠçŽ ãïŒãªãã»ããããã€ãã¹ã§æã0ã«è¿ãïŒãæ¢ã
if (offset < 0 && offset > closest.offset) {
closest = { offset, element: el };
}
}
return closest.element;
};
function escapeHTML(s) {
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}
// ããã¹ãå
ã®URLãã¡ã³ã·ã§ã³ãããã·ã¥ã¿ã°ããªã³ã¯åãã
function safeLinkify(text) {
if (!text) return '';
let escaped = escapeHTML(text);
// Xã®ä»æ§ã§ https:// ã®çŽåŸã«äžå¯èŠãªç©ºçœãæ¹è¡ãå«ãŸããå Žåãããããé€å»
escaped = escaped.replace(/(https?:\/\/)\s+/gi, '$1');
// 1. URL (å€éšãªã³ã¯: adv-content-link)
// Group 1: http/https/www ã§å§ãŸãURL
// Group 2: ãããã³ã«ãªãã®ãã¡ã€ã³
const urlRegex = /((?:https?:\/\/|www\.)[^\s\u0080-\uFFFF]+)|((?<![@\w.:/\-])\b(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.)+[a-zA-Z]{2,}(?:\/[^\s\u0080-\uFFFF]*)?)/gi;
// URL眮æãå
ã«è¡ãããã¬ãŒã¹ãã«ããŒã«çœ®ãæããïŒã¡ã³ã·ã§ã³/ããã·ã¥ã¿ã°èª€ç鲿¢ïŒ
const placeholders = [];
escaped = escaped.replace(urlRegex, (match) => {
let cleanUrl = match;
let suffix = '';
const trailingMatch = cleanUrl.match(/[.,;:)\]]+$/);
if (trailingMatch) {
suffix = trailingMatch[0];
cleanUrl = cleanUrl.slice(0, -suffix.length);
}
let href = cleanUrl;
if (!href.match(/^(?:https?:|:\/\/)/i)) {
href = 'https://' + href;
}
placeholders.push(`<a href="${href}" target="_blank" rel="noopener noreferrer nofollow" class="adv-content-link">${cleanUrl}</a>${suffix}`);
return `__URL_PLACEHOLDER_${placeholders.length - 1}__`;
});
// 2. Mentions (@username) -> SPAé·ç§» (adv-link)
// ååŸã«è±æ°åããªã @ + è±æ°åã¢ã³ããŒã¹ã³ã¢
escaped = escaped.replace(/(^|[^a-zA-Z0-9_!#$%&*@ïŒ \/])@([a-zA-Z0-9_]{1,50})/g, (match, prefix, handle) => {
return `${prefix}<a href="/${handle}" class="adv-link" style="color:var(--modal-primary-color)">@${handle}</a>`;
});
// 3. Hashtags (#tag) -> SPAé·ç§» (adv-link)
escaped = escaped.replace(/(^|[^a-zA-Z0-9_!#$%&*@ïŒ \/])#([^\s!@#$%^&*(),.?":{}|<>]+)/g, (match, prefix, tag) => {
return `${prefix}<a href="/hashtag/${tag}" class="adv-link" style="color:var(--modal-primary-color)">#${tag}</a>`;
});
// URLãã¬ãŒã¹ãã«ããŒã埩å
escaped = escaped.replace(/__URL_PLACEHOLDER_(\d+)__/g, (_, index) => placeholders[index]);
return escaped;
}
function escapeAttr(s) {
return String(s).replace(/[&<>"']/g, c => (
{'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]
));
}
function parseSearchTokens(queryOrURL) {
// 0) ã¯ãšãªååŸïŒURLâæ€çŽ¢ããã¯ã¹âã¢ãŒãã«ã®é ã§ãã©ãŒã«ããã¯ïŒ
let qRaw = '';
try {
if (queryOrURL) {
qRaw = String(queryOrURL);
} else {
const u = new URL(location.href);
qRaw = u.searchParams.get('q') || '';
}
} catch (_) {}
if (!qRaw) {
// âŒ è€æ°åœ¢ã«å€æŽ
const inputs = typeof getActiveSearchInputs === 'function' ? getActiveSearchInputs() : [];
const si = inputs[0]; // 代衚ãšããŠæåã®ãã®ã䜿ã
if (si?.value) qRaw = si.value;
}
if (!qRaw && typeof buildQueryStringFromModal === 'function') {
qRaw = buildQueryStringFromModal() || '';
}
// ååŸããã¯ãšãªæååããã£ãã·ã¥ãšåäžãªããããŒã¹ãããã£ãã·ã¥ãè¿ã
if (__cachedSearchQuery === qRaw && __cachedSearchTokens) {
return __cachedSearchTokens;
}
// ã¯ãšãªãç°ãªããããããŒã¹ãç¶è¡
__cachedSearchQuery = qRaw; // æ°ããã¯ãšãªããã£ãã·ã¥
__cachedSearchTokens = null; // å€ãããŒã¯ã³ãç Žæ£ïŒããŒã¹å€±æã«åããïŒ
// æ£èŠåïŒ%xx/ã¹ããŒãåŒçš/ç©ºçœæŽåœ¢ïŒ
const rawNorm0 = normalizeForParse(qRaw);
let q = ` ${rawNorm0} `;
// 1) é€å€èªïŒ-xxxïŒãæ§ããŠã®ã¡ã«å·®ãåŒã
const NEG = [];
(q.match(/\s-\S+/g) || []).forEach(w => NEG.push(w.trim().slice(1)));
// 2) ORã°ã«ãŒãïŒæ¬åŒ§ïŒãå
ã«æãåºãïŒåŒçšãå«ãç°¡æå¯Ÿå¿ïŒ
const orGroups = [];
const groupRegex = /\((?:[^()"]+|"[^"]*")+\)/g;
let groupMatch;
while ((groupMatch = groupRegex.exec(q)) !== null) {
const inner = groupMatch[0].slice(1, -1); // (...) äžèº«
const parts = inner.split(/\s+OR\s+/i).map(s => s.trim()).filter(Boolean);
if (parts.length >= 2) {
const tokens = parts.flatMap(p => tokenizeQuotedWords(p)).filter(Boolean);
if (tokens.length) orGroups.push(tokens);
}
}
// ã°ã«ãŒãã¯äžžããšåãïŒä»¥éã®æœåºãå®å®åïŒ
q = q.replace(groupRegex, ' ');
// 3) çŽç²ãããã¬ãã«ORïŒæ¬åŒ§ãªãïŒæ€åºïŒäŸïŒ`foo OR "bar baz" OR #tag`ïŒ
const pureOr = splitTopLevelOR(rawNorm0);
let pureOrTokens = [];
if (pureOr && isPureORQuery(rawNorm0)) {
pureOrTokens = pureOr.flatMap(p => tokenizeQuotedWords(p)).filter(Boolean);
if (pureOrTokens.length >= 2) {
orGroups.push(pureOrTokens);
// çŽç²OR㯠required ã«ã¯å
¥ããªãïŒåŸã§ words ããé€å€ïŒ
}
}
// 4) åŒçšãã¬ãŒãºãæœåºïŒexactã¯ANDçžåœãšããŠæ±ãïŒ
const phrases = [];
q = q.replace(/"([^"]+)"/g, (_m, p1) => {
if (p1 && (p1 = p1.trim())) phrases.push(p1);
return ' ';
});
// 5) ããã·ã¥ã¿ã°æœåº
const hashtags = [];
q = q.replace(/\s#([^\s)"]+)/g, (_m, p1) => {
const tag = '#' + p1;
hashtags.push(tag);
return ' ';
});
// 6) from:/to:/@ïŒé€å€ã§ã¯ãªããã®ïŒâ äŸå€å€å®çš opUsers
const opUsers = new Set();
rawNorm0.replace(/(?:^|\s)(?:from:|to:|@)([^\s()]+)/g, (m, user) => {
// çŽåã "-" ã®åŠå®æŒç®åãªãé€å€ïŒäŸ: "-from:foo"ïŒ
if (!/^\s*-/.test(m)) {
opUsers.add(String(user || '').toLowerCase());
}
return m;
});
// 7) èšèª/æå°å€/æ¥ä»/ãã£ã«ã¿/ã¢ã«ãŠã³ãæŒç®åãªã©ã q ããé€å»
q = q
.replace(/\s(?:lang|min_replies|min_faves|min_retweets|since|until):[^\s]+/gi, ' ')
.replace(/\s(?:is:verified|filter:(?:links|images|videos|replies)|include:replies|-filter:replies)\b/gi, ' ')
.replace(/\s(?:from:|to:|@)[^\s()]+/gi, ' ')
.replace(/[()ïŒïŒ]/g, ' ')
.replace(/\bOR\b/gi, ' ');
// 8) æ®ããåèªåïŒå¥èªç¹å¥ããã#ã¯æž©åæžã¿ïŒ
const trimPunctKeepHash = (s) => {
if (!s) return '';
if (s.startsWith('#')) return s;
return s.replace(/^[\p{P}\p{S}]+/gu, '').replace(/[\p{P}\p{S}]+$/gu, '');
};
let words = q
.split(/\s+/)
.map(s => s.trim())
.filter(Boolean)
.map(trimPunctKeepHash)
.filter(Boolean);
// 9) NEG ãå·®ãåŒã
const normalize = (s) => String(s || '').toLowerCase();
const NEGnorm = NEG.map(normalize);
// 10) çŽç²ORã§æŸã£ãããŒã¯ã³ã¯ AND åè£ããå
ã«é€å€ïŒéè€/è¡çªãé¿ããïŒ
if (pureOrTokens.length) {
const pureSet = new Set(pureOrTokens.map(t => t.toLowerCase()));
const stripQuote = (s) => s.replace(/^"(.*)"$/, '$1').toLowerCase();
words = words.filter(w => !pureSet.has(stripQuote(w)));
}
// 11) requiredïŒANDçžåœïŒãæ§æïŒãã¬ãŒãº + ããã·ã¥ã¿ã° + éåžžèª
const requiredTermsArr = [
...phrases,
...hashtags,
...words.filter(w => !NEGnorm.includes(normalize(w))),
];
// 12) includeTermsïŒåŸæ¥äºæïŒïŒrequired + ORå
šããŒã¯ã³å¹³åŠå
const includeTerms = new Set([
...requiredTermsArr,
...orGroups.flatMap(g => g),
]);
// 13) hashtagSet
const hashtagSet = new Set(
hashtags.map(h => h.startsWith('#') ? h : ('#' + h)).map(normalize)
);
// 14) è¿åŽïŒrequiredã¯SetãorGroupsã¯é
åã®é
åïŒ
const result = {
requiredTerms: new Set(requiredTermsArr),
orGroups, // [ ['ente','ã»ãŒã«'], ['foo','bar'] , ... ]
includeTerms, // AND/ORãã¹ãŠãå¹³åŠåããå
å«èªéå
opUsers,
hashtagSet,
};
__cachedSearchTokens = result; // ããŒã¹çµæããã£ãã·ã¥ã«ä¿å
return result;
}
function pickTweetFields(article) {
// 1. æ¬æã®ååŸ
const bodyEl = article.querySelector('[data-testid="tweetText"]');
const body = bodyEl ? bodyEl.innerText : '';
let disp = '';
let handle = '';
try {
// 2. ãŠãŒã¶ãŒæ
å ±ãšãªã¢ (User-Name) ãååŸ
// å
±æããã ããDOMã§ã¯ãããã«è¡šç€ºåãšãã³ãã«(@xxx)ã®äž¡æ¹ãå«ãŸããŠããŸã
const userRow = article.querySelector('[data-testid="User-Name"]');
if (userRow) {
// User-Nameå
ã®ãã¹ãŠã®ããªã³ã¯ããŸãã¯ãããã¹ãã³ã³ãããããã§ãã¯
// å
±æDOMã§ã¯ aã¿ã°ã®äžã« span ãããæ§é ã§ã
const anchors = Array.from(userRow.querySelectorAll('a[href^="/"], div[dir="ltr"] span'));
for (const node of anchors) {
// ããã¹ããååŸïŒååŸã®ç©ºçœãé€å»ïŒ
const text = node.innerText.trim();
// 空æåããŸãã¯æé衚瀺ã®åºåãèšå·ã·ããªã©ã¯ç¡èŠ
if (!text || text === '·') continue;
// ⌠å€å®ããžãã¯: ããã¹ãã '@' ã§å§ãŸããªããã³ãã«ãããã§ãªããã°è¡šç€ºå
if (text.startsWith('@')) {
// ãã³ãã«ãèŠã€ãã£ã (@ãé€å»ããŠä¿å)
handle = text.replace(/^@/, '');
} else {
// ãŸã 衚瀺åãã»ãããããŠããªããã°ãããã衚瀺åãšãã
// (æ€èšŒæžã¿ã¢ã«ãŠã³ãã®ã¢ã€ã³ã³ãªã©ãããã¹ããšããŠæ··ããã®ãé²ããããããçšåºŠã®é·ããã§ãã¯ãå
¥ããŠãè¯ãããåºæ¬ã¯ãã®ãŸãŸã§OK)
if (!disp) {
disp = text;
}
}
}
// ãã©ãŒã«ããã¯: äžèšã§èŠã€ãããªãã£ãå ŽåãUser-NameçŽäžã®å
šããã¹ãããè§£æ
if (!handle) {
const allText = userRow.innerText.split('\n');
for (const t of allText) {
const trimT = t.trim();
if (trimT.startsWith('@')) {
handle = trimT.replace(/^@/, '');
} else if (!disp && trimT && trimT !== '·') {
disp = trimT;
}
}
}
}
} catch(e) {
console.error('[pickTweetFields] Error parsing user info:', e);
}
// 3. è¿ä¿¡å
ãã³ãã«ã®ååŸ ("Replying to @..." ã®éšå)
// æ¬æãèªåã®åå以å€ã§ãããããŒä»è¿ã«ãã @ãªã³ã¯ ãæ¢ã
const replyHandles = Array.from(
article.querySelectorAll('div[dir="ltr"] a[href^="/"]')
)
.filter(a => {
const txt = (a.innerText || '').trim();
// @ã§å§ãŸããªã³ã¯ã§ããããš
if (!txt.startsWith('@')) return false;
// æ¬æå
ã®ã¡ã³ã·ã§ã³ã¯é€å€
if (bodyEl && bodyEl.contains(a)) return false;
// éä¿¡è
èªèº«ã®ãã³ãã«è¡šèšã¯é€å€
const userRow = article.querySelector('[data-testid="User-Name"]');
if (userRow && userRow.contains(a)) return false;
return true;
})
.map(a => a.innerText.trim())
.filter(Boolean);
return { body, disp, handle, replyHandles };
}
function getTweetCell(article) {
return article.closest('[data-testid="cellInnerDiv"]') || article;
}
/* ⌠æ»ãå€ã boolean ãã string|null (ãããããåèª) ã«å€æŽ */
function shouldHideTweetByNameHandle(article, flags, tokens) {
const {
requiredTerms = new Set(),
orGroups = [],
includeTerms = new Set(),
opUsers,
hashtagSet
} = tokens || {};
if (includeTerms.size === 0) return null; // false -> null
const { body, disp, handle, replyHandles } = pickTweetFields(article);
const normSpace = (s) => String(s || '').toLowerCase().replace(/[_.\-]+/g, ' ').replace(/\s+/g, ' ').trim();
const normId = (s) => String(s || '').replace(/^@/, '').toLowerCase();
const stripNonAlnum = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9\u00c0-\u024f]+/gi, '');
const textBody = normSpace(body);
const textName = normSpace(disp);
// ãã³ãã«çŸ€ã®æ£èŠå
const handlesRaw = [handle, ...replyHandles].map(normId).filter(Boolean);
const handlesSpace = handlesRaw.map(normSpace);
const handlesTok = handlesSpace.map(h => h.split(' ').filter(Boolean));
const handlesTight = handlesRaw.map(stripNonAlnum);
// æ¬æã«çŸããèªïŒæ£èŠåæžã¿ïŒãæ§ãã
const inBody = new Set();
for (const term of includeTerms) {
const t = normSpace(term);
if (t && textBody.includes(t)) inBody.add(t);
}
const inMeta = new Set();
const markMetaHit = (tSpace, tTight) => {
if (tSpace && !inBody.has(tSpace)) inMeta.add(tSpace);
if (tTight) inMeta.add(tTight);
};
// --- 衚瀺åãããã®èšé²ïŒçèªã¬ãŒãã€ãïŒ ---
if (flags.name) {
for (const term of includeTerms) {
const t = normSpace(term);
if (!t) continue;
// 2æå以äžã®è±åã®ã¿ã¯ç¡èŠïŒéå°é€å€é²æ¢ïŒ
if (/^[a-z]{1,2}$/.test(t)) continue;
if (textName.includes(t) && !inBody.has(t)) {
markMetaHit(t, null);
}
}
}
if (flags.handle) {
for (const term of includeTerms) {
const raw = String(term || '');
const rawLC = raw.trim().toLowerCase();
if (rawLC.startsWith('#') || (hashtagSet && hashtagSet.has(rawLC.startsWith('#') ? rawLC : '#' + rawLC))) continue;
const bare = raw.replace(/^@/, '').toLowerCase();
if (opUsers && opUsers.has(bare)) continue; // from:/to:/@ æç€ºã¯äŸå€
const tSpace = normSpace(raw);
const tTight = stripNonAlnum(raw);
// çèªã¬ãŒãïŒè±æ°ã®ã¿ã§é·ã<3ã¯ç¡èŠ
if (/^[a-z0-9]+$/.test(tTight) && tTight.length < 3) continue;
// 1) ããŒã¯ã³äžèŽ/é£ç¶ããŒã¯ã³äžèŽ
if (tSpace) {
const tTokens = tSpace.split(' ').filter(Boolean);
for (const hTokens of handlesTok) {
if (tTokens.length === 1) {
if (hTokens.some(tok => tok === tTokens[0]) && !inBody.has(tSpace)) {
markMetaHit(tSpace, null);
break;
}
} else {
for (let i = 0; i + tTokens.length <= hTokens.length; i++) {
let ok = true;
for (let j = 0; j < tTokens.length; j++) {
if (hTokens[i + j] !== tTokens[j]) { ok = false; break; }
}
if (ok && !inBody.has(tSpace)) {
markMetaHit(tSpace, null);
break;
}
}
}
}
}
// 2) éè±æ°åé€å»ã®å®å
šäžèŽïŒéšåäžèŽã¯äžå¯ïŒ
if (tTight && handlesTight.some(h => h === tTight) && !(tSpace && inBody.has(tSpace))) {
markMetaHit(tSpace, tTight);
}
}
}
// === æçµå€å®: ãããããåèªãè¿ã ===
for (const t of requiredTerms) {
const s = normSpace(t);
// æ¬æã«ãªããã¡ã¿æ
å ±(åå/ID)ã§ã®ã¿ãããããå Žåããã®åèªãè¿ã
if (s && !inBody.has(s) && (inMeta.has(s) || inMeta.has(stripNonAlnum(t)))) {
return t;
}
}
for (const group of orGroups) {
let anyBody = false;
let metaHitWord = null;
for (const w of group) {
const s = normSpace(w);
const tight = stripNonAlnum(w);
if (s && inBody.has(s)) anyBody = true;
if ((s && inMeta.has(s)) || (tight && inMeta.has(tight))) {
if (!metaHitWord) metaHitWord = w;
}
}
if (!anyBody && metaHitWord) return metaHitWord;
}
return null;
}
// âŒâŒâŒ åãã¥ãŒããã¿ã³ã®æ³šå
¥/åé€ããžã㯠âŒâŒâŒ
function injectRemuteButton(article, triggerWord, onRemute) {
// æ¢åãããã°äœãããªã
if (article.querySelector('.adv-btn-remute')) return;
// 1. ãŸãGrokãã¿ã³ãæ¢ã (èšèªäŸå察çã§ "Grok" ãå«ãã©ãã«ãæ€çŽ¢)
const grokBtn = article.querySelector('button[aria-label*="Grok"]');
// 2. ãªããã°Caret(âŠ)ãã¿ã³ãæ¢ã
const caretBtn = article.querySelector('[data-testid="caret"]');
// æ¿å
¥åºæºãšãªããã¿ã³ã決å®ïŒGrokåªå
ããªããã°CaretïŒ
const targetBtn = grokBtn || caretBtn;
if (!targetBtn) return;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'adv-btn-remute';
// ã©ãã«ã®èšå®
btn.textContent = i18n.t('buttonRemute');
btn.title = i18n.t('buttonRemute') + (triggerWord ? ` (${triggerWord})` : '');
// ã¯ãªãã¯ã€ãã³ã
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
if (onRemute) onRemute();
});
// ã¿ãŒã²ãããšãªããã¿ã³(Grok ãŸã㯠Caret)ã®çŽåã«æ¿å
¥ãã
if (targetBtn.parentElement) {
targetBtn.parentElement.insertBefore(btn, targetBtn);
}
}
function removeRemuteButton(article) {
const btn = article.querySelector('.adv-btn-remute');
if (btn) btn.remove();
}
/* ⌠evaluateTweetForFiltering: triggerWord ãç¹å®ããŠè¡šç€ºã«äœ¿çš (Full Code) */
function evaluateTweetForFiltering(art, flags, muteSettings, tokens) {
const cell = getTweetCell(art);
const reasons = [];
let tweetBodyText = null;
let triggerWord = ''; // â
ãããããåèªãä¿æ
const { hasMute, muteCI, muteCS, muteMode } = muteSettings;
// 1. åå/ãã³ãã«é€å€
if ((flags.name || flags.handle) && tokens) {
const hitWord = shouldHideTweetByNameHandle(art, flags, tokens);
if (hitWord) {
reasons.push('name_handle_only');
if (!triggerWord) triggerWord = hitWord; // ãããèªå¥ãèšé²
}
}
// 2. ãã¥ãŒãã¯ãŒãé€å€
if (hasMute) {
tweetBodyText = tweetBodyText ?? (art.querySelector('[data-testid="tweetText"]')?.innerText || '');
const bodyCI = tweetBodyText.toLowerCase();
let hideByMute = false;
// A. åçŽäžèŽ (Case Insensitive)
if (muteSettings.simpleCI && muteSettings.simpleCI.size > 0) {
for (const w of muteSettings.simpleCI) {
if (bodyCI.includes(w)) {
hideByMute = true;
if (!triggerWord) triggerWord = w;
break;
}
}
}
// B. åçŽäžèŽ (Case Sensitive)
if (!hideByMute && muteSettings.simpleCS && muteSettings.simpleCS.size > 0) {
for (const w of muteSettings.simpleCS) {
if (tweetBodyText.includes(w)) {
hideByMute = true;
if (!triggerWord) triggerWord = w;
break;
}
}
}
// C. æ£èŠè¡šçŸ/åèªåäœ (wb=true)
if (!hideByMute && muteSettings.regexRules && muteSettings.regexRules.length > 0) {
for (const rule of muteSettings.regexRules) {
// rule.rx 㯠(?:^|[^a-zA-Z0-9_])word(?:$|[^a-zA-Z0-9_]) ã®åœ¢
if (rule.rx.test(tweetBodyText)) {
hideByMute = true;
if (!triggerWord) triggerWord = rule.word;
break;
}
}
}
if (hideByMute) reasons.push('muted_word');
}
// 3. ãªãã¹ãé€å€
if (flags.reposts) {
const socialContext = art.querySelector('[data-testid="socialContext"]');
if (socialContext) {
const pinIconPath = 'M7 4.5C7 3.12 8.12 2 9.5 2h5C15.88 2 17 3.12 17 4.5v5.26L20.12 16H13v5l-1 2-1-2v-5H3.88L7 9.76V4.5z';
const isPinned = art.querySelector(`svg path[d="${pinIconPath}"]`);
if (!isPinned) {
reasons.push('repost');
if (!triggerWord) triggerWord = 'Repost';
}
}
}
// 4. ããã·ã¥ã¿ã°é€å€
if (flags.hashtags) {
tweetBodyText = tweetBodyText ?? (art.querySelector('[data-testid="tweetText"]')?.innerText || '');
if (tweetBodyText.includes('#')) {
reasons.push('hashtag');
if (!triggerWord) {
// æåã®ããã·ã¥ã¿ã°ãæœåºããŠè¡šç€º
const m = tweetBodyText.match(/#[^\s\u3000]+/);
triggerWord = m ? m[0] : '#Hashtag';
}
}
}
// âŒâŒâŒ æçµå€å® & UIå¶åŸ¡ âŒâŒâŒ
if (reasons.length > 0) {
// Case A: ãã¥ãŒã察象ã ãããŠãŒã¶ãŒãæ¢ã«ã衚瀺ããããæŒããŠããå Žå
if (art.dataset.advMutedShown === '1') {
// ã³ã³ãã³ãã¯é ããªã
cell.removeAttribute('data-adv-hidden');
cell.removeAttribute('data-adv-collapsed');
// ãã®ä»£ãããããããŒã«ãåãã¥ãŒãããã¿ã³ã泚å
¥
injectRemuteButton(art, triggerWord, () => {
// åãã¥ãŒãã¯ãªãã¯æã®åŠç
delete art.dataset.advMutedShown; // ãã©ã°ãæ¶ã
// ååž°åŒã³åºãããŠå³åº§ã«é ã
evaluateTweetForFiltering(art, flags, muteSettings, tokens);
});
} else {
// Case B: ãã¥ãŒã察象ã§ããŸã é ããŠããå Žå
removeRemuteButton(art); // ãã¿ã³ãããã°æ¶ãïŒå¿µã®ããïŒ
// ããã¥ãŒãã¯ãŒã(muted_word)ã以å€ã®çç±ãå«ãŸããŠãããå€å®
// å«ãŸããŠããå Žå(isHardHide = true)ã¯ãèšå®ããæãããã¿ãã§ã匷å¶çã«ãé衚瀺ãã«ãã
const isHardHide = reasons.some(r => r !== 'muted_word');
if (!isHardHide && muteMode === 'collapsed') {
// [æãããã¿ã¢ãŒã] (ãã¥ãŒãã¯ãŒãã®ã¿ãããããå Žå)
cell.removeAttribute('data-adv-hidden');
cell.setAttribute('data-adv-collapsed', reasons.join(' '));
let ph = cell.querySelector('.adv-collapsed-placeholder');
if (!ph) {
ph = document.createElement('div');
ph.className = 'adv-collapsed-placeholder';
// ããã§ triggerWord ã衚瀺ãã
ph.innerHTML = `
<div class="adv-collapsed-label">
<span style="opacity:0.8">${i18n.t('muteLabel')} ${escapeHTML(triggerWord)}</span>
</div>
<button class="adv-btn-show">${i18n.t('buttonShow')}</button>
`;
const uncollapse = (e) => {
e.stopPropagation();
e.preventDefault();
art.dataset.advMutedShown = '1';
evaluateTweetForFiltering(art, flags, muteSettings, tokens);
};
ph.addEventListener('click', uncollapse);
ph.querySelector('button').addEventListener('click', uncollapse);
cell.appendChild(ph);
} else {
const labelEl = ph.querySelector('.adv-collapsed-label span');
if (labelEl) labelEl.innerHTML = `${i18n.t('muteLabel')} ${escapeHTML(triggerWord)}`;
}
} else {
// [å®å
šé衚瀺ã¢ãŒã] (Hard Hide ãŸã㯠hiddenèšå®)
cell.removeAttribute('data-adv-collapsed');
cell.setAttribute('data-adv-hidden', reasons.join(' '));
}
}
} else {
// Case C: ãã¥ãŒã察象ã§ã¯ãªã
delete art.dataset.advMutedShown; // äžèŠãªãã©ã°ã¯æé€
cell.removeAttribute('data-adv-hidden');
cell.removeAttribute('data-adv-collapsed');
removeRemuteButton(art);
}
}
// ⌠ãã¥ãŒãèšå®å€æŽæãªã©ã«ãå
šãã€ãŒãã匷å¶åã¹ãã£ã³ãã
function rescanAllTweetsForFilter() {
try {
const flags = {
name: document.getElementById('adv-exclude-hit-name')?.checked ?? true,
handle: document.getElementById('adv-exclude-hit-handle')?.checked ?? true,
reposts: document.getElementById('adv-filter-reposts-exclude')?.checked ?? false,
hashtags: document.getElementById('adv-filter-hashtags-exclude')?.checked ?? false,
};
const masterOn = loadMuteMaster();
const muteMode = loadMuteMode(); // ã¢ãŒãèªã¿èŸŒã¿
const muted = loadMuted();
const hasMute = masterOn && muted.length > 0;
// æ£èŠè¡šçŸã«ãŒã«ãšåçŽäžèŽã«ãŒã«ãæºå
const regexRules = [];
const simpleCI = new Set();
const simpleCS = new Set();
if (hasMute) {
muted.filter(m => m.enabled !== false).forEach(m => {
if (m.wb) {
// åèªåäœ(Word Boundary)ã®å Žåã¯æ£èŠè¡šçŸãäœæ
// #ad -> (?:^|[^a-zA-Z0-9_])#ad(?:$|[^a-zA-Z0-9_]) ãšãããã¿ãŒã³ãçæããŠ
// ååŸã«è±æ°å(ãšã¢ã³ããŒã¹ã³ã¢)ããªãããšã確èªãã
const flags = m.cs ? '' : 'i';
const esc = escapeRegExp(m.word);
// è±æ°å以å€ãå¢çãšãã
const pattern = `(?:^|[^a-zA-Z0-9_])${esc}(?:$|[^a-zA-Z0-9_])`;
regexRules.push({ rx: new RegExp(pattern, flags), word: m.word });
} else {
// éšåäžèŽã®å Žåã¯é«éãªSet/Includesã䜿çš
if (m.cs) simpleCS.add(m.word);
else simpleCI.add(m.word.toLowerCase());
}
});
}
const muteSettings = {
hasMute,
muteMode,
regexRules,
simpleCI,
simpleCS
};
// å
šãŠç¡å¹ãªã屿§ãäžæããŠçµäº
if (!flags.name && !flags.handle && !hasMute && !flags.reposts && !flags.hashtags) {
document.querySelectorAll('[data-adv-hidden], [data-adv-collapsed]').forEach(cell => {
cell.removeAttribute('data-adv-hidden');
cell.removeAttribute('data-adv-collapsed');
});
cleanupAdjacentSeparators();
return;
}
const tokens = (flags.name || flags.handle) ? parseSearchTokens() : null;
// å
šãã€ãŒãã察象
const list = document.querySelectorAll('article[data-testid="tweet"]');
for (const art of list) {
// å
±é颿°ãåŒã¶
evaluateTweetForFiltering(art, flags, muteSettings, tokens);
}
cleanupAdjacentSeparators();
} catch (e) {
console.error('rescanAllTweetsForFilter failed', e);
}
}
function cleanupAdjacentSeparators() {
// ïŒæ¢åã®ãŸãŸïŒå¿
èŠãªãããã«åºåãç·ã»ã«ã®é衚瀺åŠçïŒ
}
const executeSearch = async (scopesOverride) => {
const finalQuery = buildQueryStringFromModal().trim();
if (!finalQuery) return;
const scopes = scopesOverride || readScopesFromControls();
const params = new URLSearchParams({ q: finalQuery, src: 'typed_query' });
if (scopes.pf) params.set('pf', 'on');
if (scopes.lf) params.set('lf', 'on');
const targetPath = `/search?${params.toString()}`;
// 1) ãŸãæ€çŽ¢ããã¯ã¹ãèŠã€ããã° React state ãæŽæ°ããŠèŠãç®ãšäžèº«ãå調
// âŒ è€æ°åœ¢ã«å€æŽããã«ãŒãåŠç
const inputs = getActiveSearchInputs?.() || [];
if (inputs.length > 0) {
inputs.forEach(si => {
if (si) { syncControlledInput(si, finalQuery); }
});
}
// 2) ã«ãŒãã«é¢ãããåžžã« SPA é·ç§»ã§æ€çŽ¢ã確å®
recordHistory(finalQuery, scopes.pf, scopes.lf);
const before = location.href;
try {
await spaNavigate(targetPath);
if (window.innerWidth <= 700) {
closeModal();
}
} catch {
// SPA 倱ææã®ãã©ãŒã«ããã¯
location.assign(`https://x.com${targetPath}`);
return;
}
// 3) é·ç§»ãæåãããäœèšãª replaceState ã¯ããªãïŒURL ãšã«ãŒã¿ãŒ state ã®ä¹é¢ãé¿ããïŒ
// ãããã©ãŒã«ã¹ãæ®ã£ãŠãããå€ã
// ⌠ã«ãŒãåŠç
try { inputs.forEach(si => si && si.blur()); } catch {}
};
const onScopeChange = async () => {
// âŒ è€æ°åœ¢ã«å€æŽ
const inputs = getActiveSearchInputs();
const si = inputs[0]; // 代衚ãšããŠæåã®ãã®ã䜿ã
const q = (() => {
if (si && si.value && si.value.trim()) return si.value.trim();
return buildQueryStringFromModal().trim();
})();
const { pf, lf } = readScopesFromControls();
const params = new URLSearchParams({ src: 'typed_query' });
if (q) params.set('q', q);
if (pf) params.set('pf', 'on');
if (lf) params.set('lf', 'on');
// å
¥ååŽãå
ã«ææ°å
// ⌠ã«ãŒãåŠç
if (inputs.length > 0) {
inputs.forEach(input => {
if (input) syncControlledInput(input, q);
});
}
recordHistory(q, pf, lf);
const path = `/search?${params.toString()}`;
try {
await spaNavigate(path);
} catch {
location.assign(`https://x.com${path}`);
}
};
accountScopeSel.addEventListener('change', onScopeChange);
locationScopeSel.addEventListener('change', onScopeChange);
const setupModalDrag = () => {
const header = modal.querySelector('.adv-modal-header');
let dragging=false, offset={x:0,y:0};
header.addEventListener('mousedown', e=>{
// ⌠SPæã¯ãã©ãã°éå§ããªã
if (window.innerWidth <= 700) return;
if (e.target.matches('button,a') && !e.target.classList.contains('adv-secret-btn')) return;
dragging=true;
const rect = modal.getBoundingClientRect();
modal.style.right=modal.style.bottom='auto';
modal.style.left=`${rect.left}px`; modal.style.top=`${rect.top}px`;
offset = { x:e.clientX-rect.left, y:e.clientY-rect.top };
document.body.classList.add('adv-dragging');
});
document.addEventListener('mousemove', e=>{
if(!dragging) return;
let nx = e.clientX - offset.x, ny = e.clientY - offset.y;
nx=Math.max(0,Math.min(nx,window.innerWidth - modal.offsetWidth));
ny=Math.max(0,Math.min(ny,window.innerHeight - modal.offsetHeight));
modal.style.left=`${nx}px`; modal.style.top=`${ny}px`;
});
document.addEventListener('mouseup', ()=>{
if(dragging){ dragging=false; document.body.classList.remove('adv-dragging'); saveModalRelativeState(); }
});
};
const setupModalResize = () => {
const MIN_W = 300, MIN_H = 240;
const MARGIN = 10;
let resizing = null;
const onPointerDown = (e) => {
// ⌠SPæã¯ãªãµã€ãºéå§ããªã
if (window.innerWidth <= 700) return;
const h = e.target.closest('.adv-resizer');
if (!h) return;
e.preventDefault();
const dir = h.dataset.dir;
const r = modal.getBoundingClientRect();
modal.style.right = 'auto';
modal.style.bottom= 'auto';
modal.style.left = `${r.left}px`;
modal.style.top = `${r.top}px`;
resizing = {
dir,
startX: e.clientX,
startY: e.clientY,
startLeft: r.left,
startTop: r.top,
startW: r.width,
startH: r.height
};
try { h.setPointerCapture(e.pointerId); } catch(_) {}
document.body.classList.add('adv-dragging');
};
const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
const onPointerMove = (e) => {
if (!resizing) return;
const dx = e.clientX - resizing.startX;
const dy = e.clientY - resizing.startY;
let newLeft = resizing.startLeft;
let newTop = resizing.startTop;
let newW = resizing.startW;
let newH = resizing.startH;
const dir = resizing.dir;
if (dir.includes('e')) newW = resizing.startW + dx;
if (dir.includes('w')) { newW = resizing.startW - dx; newLeft = resizing.startLeft + dx; }
if (dir.includes('s')) newH = resizing.startH + dy;
if (dir.includes('n')) { newH = resizing.startH - dy; newTop = resizing.startTop + dy; }
const maxW = window.innerWidth - 2*MARGIN;
const maxH = window.innerHeight - 2*MARGIN;
newW = clamp(newW, MIN_W, maxW);
newH = clamp(newH, MIN_H, maxH);
newLeft = clamp(newLeft, MARGIN, Math.max(MARGIN, window.innerWidth - newW - MARGIN));
newTop = clamp(newTop, MARGIN, Math.max(MARGIN, window.innerHeight - newH - MARGIN));
modal.style.left = `${Math.round(newLeft)}px`;
modal.style.top = `${Math.round(newTop)}px`;
modal.style.width = `${Math.round(newW)}px`;
modal.style.height = `${Math.round(newH)}px`;
};
const onPointerUp = (e) => {
if (!resizing) return;
document.body.classList.remove('adv-dragging');
try { e.target.releasePointerCapture?.(e.pointerId); } catch(_) {}
resizing = null;
saveModalRelativeState();
};
modal.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
};
/* ========= Accounts storage & UI ========= */
function renderAccountRow(item) {
const row = document.createElement('div');
row.className = 'adv-item';
row.draggable = true;
row.dataset.id = item.id;
const title = escapeHTML(item.name || `@${item.handle}`);
const sub = escapeHTML(`@${item.handle}`);
row.innerHTML = `
<div class="adv-item-handle" title="Drag">â¡</div>
${
item.avatar
? `<a class="adv-item-avatar-link adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">
<img class="adv-item-avatar" src="${escapeAttr(item.avatar)}" alt="@${escapeAttr(item.handle)}">
</a>`
: `<a class="adv-item-avatar-link adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">
<div class="adv-item-avatar" aria-hidden="true"></div>
</a>`
}
<div class="adv-item-main">
<div class="adv-item-title">
<a class="adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">${title}</a>
</div>
<div class="adv-item-sub">
<a class="adv-link" href="/${escapeAttr(item.handle)}">@${escapeHTML(item.handle)}</a>
<span>${fmtTime(item.ts)}</span>
</div>
</div>
<div class="adv-item-actions">
<button class="adv-chip primary" data-action="confirm">${i18n.t('buttonConfirm')}</button>
<button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
</div>
`;
row.querySelector('[data-action="confirm"]').addEventListener('click', (e) => {
spaNavigate(`/${item.handle}`, { ctrlMeta: e.ctrlKey || e.metaKey });
if (window.innerWidth <= 700) {
closeModal();
}
});
row.querySelectorAll('a.adv-link').forEach(a => {
a.addEventListener('click', (ev) => {
if (ev.defaultPrevented || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || ev.button !== 0) return;
ev.preventDefault();
const href = a.getAttribute('href') || `/${item.handle}`;
spaNavigate(href, { ctrlMeta: false });
if (window.innerWidth <= 700) {
closeModal();
}
});
});
row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteAccount(item.id));
row.addEventListener('dragstart', (ev) => {
row.classList.add('dragging');
ev.dataTransfer.setData('text/plain', item.id);
ev.dataTransfer.effectAllowed = 'move';
});
row.addEventListener('dragend', () => row.classList.remove('dragging'));
return row;
}
function renderAccounts() {
ensureFolderToolbars();
renderFolderedCollection({
hostId: 'adv-accounts-list',
emptyId: 'adv-accounts-empty',
filterSelectId: 'adv-accounts-folder-filter',
searchInputId: 'adv-accounts-search',
newFolderBtnId: 'adv-accounts-new-folder',
foldersKey: ACCOUNTS_FOLDERS_KEY,
defaultFolderName: i18n.t('optAccountAll'),
loadItems: loadAccounts,
saveItems: saveAccounts,
renderRow: renderAccountRow,
onUnassign: unassignAccount,
onMoveToFolder: moveAccountToFolder,
emptyMessage: i18n.t('emptyAccounts'),
unassignedIndexKey: 'advAccountsUnassignedIndex_v1',
});
}
function renderListRow(item) {
const row = document.createElement('div');
row.className = 'adv-item';
row.draggable = true;
row.dataset.id = item.id;
const title = escapeHTML(item.name);
const sub = escapeHTML(item.url);
row.innerHTML = `
<div class="adv-item-handle" title="Drag">â¡</div>
<div class="adv-item-main">
<div class="adv-item-title">
<a class="adv-link" href="${escapeAttr(item.url)}">${title}</a>
</div>
<div class="adv-item-sub">
<a class="adv-link" href="${escapeAttr(item.url)}">${sub}</a>
<span>${fmtTime(item.ts)}</span>
</div>
</div>
<div class="adv-item-actions">
<button class="adv-chip primary" data-action="confirm">${i18n.t('buttonConfirm')}</button>
<button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
</div>
`;
row.querySelector('[data-action="confirm"]').addEventListener('click', (e) => {
spaNavigate(item.url, { ctrlMeta: e.ctrlKey || e.metaKey });
if (window.innerWidth <= 700) {
closeModal();
}
});
row.querySelectorAll('a.adv-link').forEach(a => {
a.addEventListener('click', (ev) => {
if (ev.defaultPrevented || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || ev.button !== 0) return;
ev.preventDefault();
const href = a.getAttribute('href') || item.url;
spaNavigate(href, { ctrlMeta: false });
if (window.innerWidth <= 700) {
closeModal();
}
});
});
row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteList(item.id));
row.addEventListener('dragstart', (ev) => {
row.classList.add('dragging');
ev.dataTransfer.setData('text/plain', item.id);
ev.dataTransfer.effectAllowed = 'move';
});
row.addEventListener('dragend', () => row.classList.remove('dragging'));
return row;
}
const ACCOUNTS_KEY = 'advAccounts_v1';
const ACCOUNTS_FOLDERS_KEY = 'advAccountsFolders_v1';
const LISTS_FOLDERS_KEY = 'advListsFolders_v1';
// ⌠ã»ã¯ã·ã§ã³ïŒãã©ã«ã㌠+ UnassignedïŒã®äžŠã³é ãæ°žç¶åããããŒ
const SAVED_FOLDERS_KEY = 'advSavedFolders_v1';
function loadFolders(key, _defaultName="") {
const raw = loadJSON(key, null);
if (raw && Array.isArray(raw.folders)) {
return raw.folders.map(f => ({
id: f.id,
name: f.name,
order: Array.isArray(f.order) ? f.order : [],
ts: f.ts || Date.now(),
collapsed: !!f.collapsed,
}));
}
// åæã¯ç©ºé
åïŒãã©ã«ããŒ0ä»¶ã®äžçïŒ
return [];
}
function saveFolders(key, folders) {
saveJSON(key, { folders: folders.map(f=>({
id:f.id, name:f.name, order:[...new Set(f.order)], ts:f.ts||Date.now(), collapsed: !!f.collapsed,
}))});
}
function ensureFolderToolbars() {
// Accounts tab
{
const host = document.getElementById('adv-accounts-list');
const empty = document.getElementById('adv-accounts-empty');
const target = empty || host; // emptyãããã°ãã®åã«æ¿å
¥ïŒHTMLé åºã empty->list ãªã®ã§äžçªäžã«ãªãïŒ
if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
const bar = document.createElement('div');
bar.className = 'adv-folder-toolbar';
bar.innerHTML = `
<select id="adv-accounts-folder-filter" class="adv-select"></select>
<input id="adv-accounts-search" class="adv-input" type="text" data-i18n-placeholder="placeholderFilterAccounts" placeholder="${i18n.t('placeholderFilterAccounts')}">
<button id="adv-accounts-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
`;
target.parentElement.insertBefore(bar, target);
}
}
// Lists tab
{
const host = document.getElementById('adv-lists-list');
const empty = document.getElementById('adv-lists-empty');
const target = empty || host;
if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
const bar = document.createElement('div');
bar.className = 'adv-folder-toolbar';
bar.innerHTML = `
<select id="adv-lists-folder-filter" class="adv-select"></select>
<input id="adv-lists-search" class="adv-input" type="text" data-i18n-placeholder="placeholderFilterLists" placeholder="${i18n.t('placeholderFilterLists')}">
<button id="adv-lists-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
`;
target.parentElement.insertBefore(bar, target);
}
}
// Saved tab
{
const host = document.getElementById('adv-saved-list');
const empty = document.getElementById('adv-saved-empty');
const target = empty || host;
if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
const bar = document.createElement('div');
bar.className = 'adv-folder-toolbar';
bar.innerHTML = `
<select id="adv-saved-folder-filter" class="adv-select"></select>
<input id="adv-saved-search" class="adv-input" type="text" data-i18n-placeholder="placeholderSearchSaved" placeholder="${i18n.t('placeholderSearchSaved')}">
<button id="adv-saved-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
`;
target.parentElement.insertBefore(bar, target);
}
}
}
const migrateAccounts = (list) =>
Array.isArray(list)
? list
.map(it => ({
id: it.id || uid(),
handle: (it.handle || '').replace(/^@/, '').trim(),
name: (it.name || '').trim(),
avatar: it.avatar || '',
ts: it.ts || Date.now(),
}))
.filter(it => it.handle)
: [];
const loadAccounts = () => migrateAccounts(loadJSON(ACCOUNTS_KEY, []));
const saveAccounts = (arr) => saveJSON(ACCOUNTS_KEY, migrateAccounts(arr));
// 远å or æŽæ°ïŒæ¢åãããã° name / avatar å·®åã®ã¿æŽæ°ïŒ
const addAccount = ({ handle, name='', avatar='' }) => {
const h = (handle || '').replace(/^@/, '').trim();
if (!h) return 'empty';
const list = loadAccounts();
const ix = list.findIndex(x => x.handle.toLowerCase() === h.toLowerCase());
if (ix >= 0) {
let changed = false;
if (name && name !== list[ix].name) { list[ix].name = name; changed = true; }
if (avatar && avatar !== list[ix].avatar) { list[ix].avatar = avatar; changed = true; }
if (changed) {
list[ix].ts = Date.now();
saveAccounts(list);
renderAccounts();
return 'updated';
}
return 'exists';
}
const id = uid();
list.unshift({ id, handle: h, name, avatar, ts: Date.now() });
saveAccounts(list);
// ãã©ã«ããŒãžã¯å
¥ããªãïŒæªæå±ã®ãŸãŸïŒ
try {
const folders = loadFolders(ACCOUNTS_FOLDERS_KEY, i18n.t('optAccountAll'));
// 念ã®ããå
šãã©ã«ããŒããéè€ãé€å»ã ãããŠä¿åïŒæªæå±ãä¿æïŒ
folders.forEach(f => { f.order = f.order.filter(x => x !== id); });
saveFolders(ACCOUNTS_FOLDERS_KEY, folders);
} catch(_) {}
renderAccounts();
return 'ok';
};
// æ¢åã¢ã«ãŠã³ããããå Žåã ã name / avatar ãæŽæ°ïŒæªç»é²ãªãäœãããªãïŒ
const updateAccountIfExists = ({ handle, name='', avatar='' }) => {
const h = (handle || '').replace(/^@/, '').trim();
if (!h) return 'empty';
const list = loadAccounts();
const ix = list.findIndex(x => x.handle.toLowerCase() === h.toLowerCase());
if (ix < 0) return 'not_found';
let changed = false;
if (name && name !== list[ix].name) { list[ix].name = name; changed = true; }
if (avatar && avatar !== list[ix].avatar) { list[ix].avatar = avatar; changed = true; }
if (changed) {
list[ix].ts = Date.now();
saveAccounts(list);
renderAccounts();
return 'updated';
}
return 'unchanged';
};
const deleteAccount = (id) => {
// ⌠åé€å¯Ÿè±¡ã®ãã³ãã«ãä¿æããŠãã
const accounts = loadAccounts();
const deletedAccount = accounts.find(x => x.id === id);
const deletedHandle = deletedAccount?.handle.toLowerCase();
const next = accounts.filter(x => x.id !== id); // accounts倿°ã䜿çš
saveAccounts(next);
renderAccounts();
showToast(i18n.t('toastDeleted'));
// ⌠ããŒãžäžã®ãã¿ã³ã匷å¶åæç»
// çŸåšã®ããŒãžãã³ãã«ãååŸ
const currentHandle = getProfileHandleFromURL()?.toLowerCase();
// ããåé€ããã¢ã«ãŠã³ãã®ããŒãžã«ä»ãŸãã«å±
ããªãããã¿ã³ãåŒ·å¶æŽæ°
if (deletedHandle && currentHandle === deletedHandle) {
ensureProfileAddButton(true);
}
};
const accountsListEl = document.getElementById('adv-accounts-list');
const advSavedListEl = document.getElementById('adv-saved-list');
function getProfileHandleFromURL(href = location.href) {
try {
const u = new URL(href, location.origin);
const segs = u.pathname.split('/').filter(Boolean);
if (segs.length === 0) return '';
// å
é ã»ã°ã¡ã³ããåè£ã«ãã
const first = segs[0];
// æãããªéãããã£ãŒã«ã®äºçŽã»ã°ã¡ã³ããé€å€
const RESERVED = new Set([
'home','explore','notifications','messages','i','settings',
'compose','search','login','signup','tos','privacy','about'
]);
if (RESERVED.has(first)) return '';
// ãŠãŒã¶ãŒåãã¿ãŒã³: ãããçŽäž/é
äžã¿ãïŒ/with_replies, /media, /likes çïŒã蚱容
if (/^[A-Za-z0-9_]{1,50}$/.test(first)) {
return first; // /<handle> ã /<handle>/with_replies /media /likes ... ããã¹ãŠã«ããŒ
}
return '';
} catch {
// DOM ãã©ãŒã«ããã¯
try {
const a = document.querySelector('[data-testid="User-Name"] a[href^="/"], [data-testid="UserName"] a[href^="/"]');
if (a) {
const m = (a.getAttribute('href') || '').match(/^\/([A-Za-z0-9_]{1,50})/);
if (m) return m[1];
}
} catch (_) {}
return '';
}
}
// æå®ãã³ãã«ã®ãããã£ãŒã«é åã ããã¹ã³ãŒãã«ã㊠name / avatar ãååŸ
function collectProfileMeta(handle) {
let name = '';
let avatar = '';
try {
const h = String(handle || '').replace(/^@/, '').trim();
// 1) ãããã£ãŒã«é åïŒè¡šç€ºåïŒ
// â» ã°ããŒãã«ãããã®èªåã®ååãæŸããªãããã«ãæåã« [data-testid="UserName"] ãåºæºã«éå®
const profileRoot =
document.querySelector('[data-testid="UserName"]') ||
document.querySelector('[data-testid="User-Name"]');
if (profileRoot) {
const texts = Array.from(profileRoot.querySelectorAll('span, div[dir="auto"]'))
.map(el => (el.textContent || '').trim())
.filter(Boolean);
// äŸ: ["ã¿ã¿ã@ç±³åœæ ªæè³", "@mimiru_usstock", ...]
name = texts.find(t => !t.startsWith('@')) || '';
}
// 2) ã¢ãã¿ãŒé åããã³ãã«ã§éå®
// DOMäŸ: <div data-testid="UserAvatar-Container-mimiru_usstock"> ... </div>
let avatarScope = null;
if (h) {
avatarScope = document.querySelector(`[data-testid="UserAvatar-Container-${CSS.escape(h)}"]`);
}
// ãã©ãŒã«ããã¯ïŒãã³ãã«ä»ã data-testid ãç¡ãå€ã/å·®åã¬ã€ã¢ãŠãïŒ
if (!avatarScope) {
// ãããã£ãŒã«ã®ãããå³åŽã®å¡ã«éå®
avatarScope = profileRoot?.closest('[data-testid="UserProfileHeader_Items"]')?.parentElement
|| profileRoot?.parentElement
|| document;
}
// 2-1) ãŸã <img> åªå
const img = avatarScope.querySelector('img[src*="profile_images"]');
if (img?.src) {
avatar = img.src;
} else {
// 2-2) èæ¯ç»å style="background-image:url(...)" ããæœåº
// æç€ºDOMã®:
// <div class="... r-1wyyakw ..." style="background-image:url('...')"></div>
const bg = avatarScope.querySelector('[style*="background-image"]');
if (bg) {
const m = String(bg.getAttribute('style') || '').match(/background-image:\s*url\((["']?)(.*?)\1\)/i);
if (m && m[2]) avatar = m[2];
}
}
// ãããŒ(header_photo) ã誀æ€åºããªãããã«ãããããããŒé åãé€å€
// ïŒbanner 㯠/header_photo ãžã®ãªã³ã¯é
äž; avatarScope å
ã«å
¥ããªãèšèšã ãä¿éºïŒ
if (avatar && /profile_banners\//.test(avatar)) {
avatar = '';
}
} catch {}
return { name, avatar };
}
let profileButtonObserver = null;
let profileButtonInstalledFor = '';
function ensureProfileAddButton(force = false) {
const handle = getProfileHandleFromURL();
if (!handle) return;
// åãã³ãã«å
ã¿ãé·ç§»æã§ããæ¢åãã¿ã³ãæ¶ããŠãããåèšçœ®ã§ããããã«ãã
if (!force && profileButtonInstalledFor === handle && document.getElementById('adv-add-account-btn')) {
return;
}
const moreBtn = document.querySelector('button[data-testid="userActions"]');
if (!moreBtn) return;
const parent = moreBtn.parentElement;
if (!parent) return; // 芪ã³ã³ããããªããã°æ¿å
¥ãã§ããªã
// ç¶æ
ïŒè¿œå æžã¿ãïŒãå
ã«å€å®
const h_lower = handle.toLowerCase();
const accounts = loadAccounts();
const existingAccount = accounts.find(x => x.handle.toLowerCase() === h_lower);
const isAdded = !!existingAccount;
const accountId = existingAccount?.id || null;
// æ¢åã®ãã¿ã³ãæ®ã£ãŠããã°ããã³ãã«ã«é¢ããã匷å¶çã«åé€ãã
const existingBtn = parent.querySelector('#adv-add-account-btn');
if (existingBtn) {
existingBtn.remove();
}
const btn = document.createElement('button');
btn.id = 'adv-add-account-btn';
btn.type = 'button';
// èŠãç®ãå®å
šåæïŒclass ã style ãã³ããŒïŒ
const syncVisual = (dst, src) => {
dst.className = src.className;
const st = src.getAttribute('style');
if (st !== null) dst.setAttribute('style', st);
// 念ã®ãã currentColor ç¶æ¿
dst.style.color ||= 'inherit';
};
syncVisual(btn, moreBtn);
// å°æ¥ã®ããŒãåæ¿ïŒhover ãªã©ã§ X ã style/class ãæžãæããã远åŸ
// 以åã®Observerãæ®ã£ãŠããã°ç Žæ£ãããªãŒã¯ãé²ã
if (profileButtonObserver) {
profileButtonObserver.disconnect();
}
const visMo = new MutationObserver(() => syncVisual(btn, moreBtn));
visMo.observe(moreBtn, { attributes: true, attributeFilter: ['class', 'style'] });
// æ°ããObserverã倿°ã«ä¿æ
profileButtonObserver = visMo;
// ç¶æ
ã«å¿ããŠã©ãã«ã倿Ž
const label = i18n.t(isAdded ? 'delete' : 'buttonAddAccount'); // ãåé€ãããŒãæµçš
btn.setAttribute('aria-label', label);
btn.title = label;
// ⌠å
åŽã® div / svg / span ãããclass ãš inline styleããæœåº
const innerDiv = moreBtn.querySelector('div[dir="ltr"]') || moreBtn.querySelector('div');
const innerCls = innerDiv?.getAttribute('class') || innerDiv?.classList?.value || '';
const innerStyle = innerDiv?.getAttribute('style') || '';
const svgEl = innerDiv?.querySelector('svg') || moreBtn.querySelector('svg');
const svgCls = svgEl?.getAttribute('class') || svgEl?.classList?.value || '';
const spanEl = innerDiv?.querySelector('span') || moreBtn.querySelector('span');
const spanCls = spanEl?.getAttribute('class') || spanEl?.classList?.value || '';
// ç¶æ
ã«å¿ããŠSVGãã¹ãåãæ¿ã
const ICON_PATH_ADD = 'M18 5h2v3h3v2h-3v3h-2V10h-3V8h3V5z';
const ICON_PATH_CHECK = 'M23 8l-5 5-3-3 1.5-1.5L18 10l3.5-3.5L23 8z'; // å³äžã«é
眮ãããã§ãã¯
const iconPath = isAdded ? ICON_PATH_CHECK : ICON_PATH_ADD;
btn.innerHTML = `
<div dir="ltr" class="${innerCls}" style="${innerStyle}">
<svg
viewBox="0 0 24 24"
aria-hidden="true"
role="img"
class="${svgCls}"
fill="currentColor"
>
<circle cx="10" cy="7.5" r="3.5"></circle>
<path d="M3.5 18.5C3.5 15.46 6.79 13 10 13s6.5 2.46 6.5 5.5V20H3.5v-1.5z"></path>
<path d="${iconPath}"></path>
</svg>
<span class="${spanCls}"></span>
</div>
`;
btn.addEventListener('click', () => {
if (isAdded) {
// è¿œå æžã¿ã®å ŽåïŒåé€
if (accountId) {
deleteAccount(accountId); // deleteAccount 㯠toast ãå
èµããŠãã
}
} else {
// æªè¿œå ã®å ŽåïŒè¿œå
const { name, avatar } = collectProfileMeta(handle);
const ret = addAccount({ handle, name, avatar });
if (ret === 'ok') showToast(i18n.t('toastAccountAdded'));
else if (ret === 'updated') showToast(i18n.t('updated'));
else if (ret === 'exists') showToast(i18n.t('toastAccountExists'));
}
// ç¶æ
ãå€ãã£ãã®ã§ããã¿ã³ãå³åº§ã«åæç»ïŒã¢ã€ã³ã³ãåãæ¿ãïŒ
ensureProfileAddButton(true); // force=true ã§åå®è¡
});
// moreBtn.parentElement?.insertBefore(btn, moreBtn);
parent.insertBefore(btn, moreBtn); // parent倿°ã䜿çš
profileButtonInstalledFor = handle;
// ãããã£ãŒã«ã«æ¥ãã¿ã€ãã³ã°ã§èªååæ
// æªç»é²ã¯è¿œå ããªããæ¢åæã®ã¿å·®åæŽæ°ã
try {
const { name, avatar } = collectProfileMeta(handle);
const status = updateAccountIfExists({ handle, name, avatar });
if (status === 'updated') showToast(i18n.t('updated'));
// 'not_found' / 'unchanged' ã¯ç¡éç¥ã§OK
} catch {}
}
/* ========= Lists storage & UI ========= */
const LISTS_KEY = 'advLists_v1';
const migrateLists = (list) =>
Array.isArray(list)
? list
.map(it => ({
id: it.id || uid(),
name: (it.name || '').trim(),
url: (it.url || '').trim(),
ts: it.ts || Date.now(),
}))
.filter(it => it.name && it.url)
: [];
const loadLists = () => migrateLists(loadJSON(LISTS_KEY, []));
const saveLists = (arr) => saveJSON(LISTS_KEY, migrateLists(arr));
const addList = ({ name, url }) => {
const nm = (name || '').trim();
let u = (url || '').trim();
if (!nm || !u) return 'empty';
try {
const parsed = new URL(u, location.origin);
if (parsed.origin === location.origin) u = parsed.pathname + parsed.search + parsed.hash;
} catch {}
const list = loadLists();
if (list.some(x => x.url === u)) return 'exists';
const id = uid();
list.unshift({ id, name: nm, url: u, ts: Date.now() });
saveLists(list);
// ãã©ã«ããŒãžã¯å
¥ããªãïŒæªæå±ã®ãŸãŸïŒ
try {
const folders = loadFolders(LISTS_FOLDERS_KEY, i18n.t('optLocationAll'));
folders.forEach(f => { f.order = f.order.filter(x => x !== id); });
saveFolders(LISTS_FOLDERS_KEY, folders);
} catch(_) {}
renderLists();
return 'ok';
};
const deleteList = (id) => {
// ⌠åé€å¯Ÿè±¡ã®URLãä¿æããŠãã
const lists = loadLists();
const deletedList = lists.find(x => x.id === id);
const deletedUrl = deletedList?.url;
const next = lists.filter(x => x.id !== id); // lists倿°ã䜿çš
saveLists(next);
renderLists();
showToast(i18n.t('toastDeleted'));
// ⌠ããŒãžäžã®ãã¿ã³ã匷å¶åæç»
// çŸåšããªã¹ãããŒãžãããã®URLã¯äœããååŸ
if (isListPath()) {
const { url: currentUrl } = getListMeta();
// ããåé€ãããªã¹ãã®ããŒãžã«ä»ãŸãã«å±
ããªãããã¿ã³ãåŒ·å¶æŽæ°
if (deletedUrl && currentUrl === deletedUrl) {
ensureListAddButton(true);
}
}
};
const advListsListEl = document.getElementById('adv-lists-list');
// ===== FOLDER MIGRATION =====
(function migrateAccountsToFolders(){
// æ¢åãã©ã«ããŒããã£ãŠã root åæã®èªåäœæ/èªåå²åœã¯ããªãã
// å€ãããŒã¿ã§ item.folderId === 'root' ã®çè·¡ãããã°âæªæå±âã«æ£èŠåã
try {
let items = loadAccounts();
let changed = false;
items = items.map(it => {
if (it.folderId === 'root') { delete it.folderId; changed = true; }
return it;
});
if (changed) saveAccounts(items);
} catch(_) {}
})();
(function migrateListsToFolders(){
// root åæã®èªåäœæ/èªåå²åœã¯è¡ããªãã
try {
let items = loadLists();
let changed = false;
items = items.map(it => {
if (it.folderId === 'root') { delete it.folderId; changed = true; }
return it;
});
if (changed) saveLists(items);
} catch(_) {}
})();
// UI toolbars
ensureFolderToolbars();
function renderLists() {
ensureFolderToolbars();
renderFolderedCollection({
hostId: 'adv-lists-list',
emptyId: 'adv-lists-empty',
filterSelectId: 'adv-lists-folder-filter',
searchInputId: 'adv-lists-search',
newFolderBtnId: 'adv-lists-new-folder',
foldersKey: LISTS_FOLDERS_KEY,
defaultFolderName: i18n.t('optListsAll'),
loadItems: loadLists,
saveItems: saveLists,
renderRow: renderListRow,
onUnassign: unassignList,
onMoveToFolder: moveListToFolder,
emptyMessage: i18n.t('emptyLists'),
unassignedIndexKey: 'advListsUnassignedIndex_v1',
});
}
const isListPath = (pathname = location.pathname) => /^\/i\/lists\/\d+\/?$/.test(pathname);
function getListMeta() {
// 1) <title> ããåãåºãïŒæåªå
ïŒ
let rawTitle = '';
try { rawTitle = (document.title || '').trim(); } catch (_) {}
// æ«å°Ÿã® " / X" ãŸã㯠" / Twitter" ãåã
let baseTitle = rawTitle.replace(/\s*\/\s*(X|Twitter)\s*$/i, '').trim();
let name = '';
let m;
// ãã¿ãŒã³A: "@owner/ãªã¹ãå"
m = baseTitle.match(/^\s*@([A-Za-z0-9_]{1,50})\/\s*(.+)\s*$/);
if (m) {
name = (m[2] || '').trim();
}
// ãã¿ãŒã³B: "ãªã¹ãå (@owner)"
if (!name) {
m = baseTitle.match(/^\s*(.+?)\s*\(@[A-Za-z0-9_]{1,50}\)\s*$/);
if (m) {
name = (m[1] || '').trim();
}
}
// äœåãªåŒçšç¬Š â â " ' ã«å¯Ÿå¿
if (name) {
name = name.replace(/^[â"'](.+)[â"']$/, '$1').trim();
}
// 2) ã¿ã€ãã«ã§åããªã/æªããæã¯èŠåºãããæŸãïŒ@ãå«ã/é·æ/ãã«ãæã¯é€å€ïŒ
if (!name) {
try {
const headingRoot =
document.querySelector('[data-testid="ScrollSnap-ListHeader"]') ||
document.querySelector('[data-testid="primaryColumn"]') ||
document;
const candidates = Array.from(
headingRoot.querySelectorAll('h1[role="heading"], h2[role="heading"], h3[role="heading"]')
)
.flatMap(h => Array.from(h.querySelectorAll('span, div[dir="ltr"], div[dir="auto"]'))
.map(el => (el.textContent || '').trim()))
.filter(Boolean)
// ã@âŠãã¯ãªãŒããŒè¡šèšãªã®ã§é€å€
.filter(txt => !/^@/.test(txt))
// é·æããã«ãæïŒããŒããŒãã·ã§ãŒãã«ããç³»ïŒã匟ã
.filter(txt => {
const t = txt.replace(/\s+/g, ' ');
if (t.length > 80) return false;
const NG = ['ããŒããŒãã·ã§ãŒãã«ãã', 'keyboard', 'help', 'ã·ã§ãŒãã«ãã', 'press', '?'];
return !NG.some(ng => t.toLowerCase().includes(ng.toLowerCase()));
});
if (candidates.length) {
// äžçªçãåè£ïŒïŒè£
食ã®å°ãªãã¿ã€ãã«ã®å¯èœæ§ãé«ãïŒ
name = candidates.sort((a, b) => a.length - b.length)[0].trim();
}
} catch (_) {}
}
// 3) æçµãã©ãŒã«ããã¯
if (!name) name = '';
// URL ã¯çŸããŒãžïŒSPA察å¿ã§ã¯ãšãª/ããã·ã¥ãä¿æïŒ
const url = location.pathname + location.search + location.hash;
return { name, url };
}
let listButtonObserver = null;
let listButtonInstalledAt = '';
function ensureListAddButton(force = false) {
if (!isListPath()) return;
if (!force && listButtonInstalledAt === location.pathname) return;
// å¯èŠç¶æ
ã«ããã·ã§ã¢ãã¿ã³ãå³å¯ã«ç¹å®ãã
const shareBtns = Array.from(document.querySelectorAll('button[data-testid="share-button"]'));
// offsetParent ã null ã§ãªãïŒïŒè¡šç€ºãããŠããïŒãã¿ã³ãæ¢ã
// SPæã¯ TopNavBar å
ã«ããããšãå€ãããããããåªå
ããŠãè¯ãããå¯èŠãã§ãã¯ãæãæ±çšç
const shareBtn = shareBtns.find(btn => btn.offsetParent !== null);
if (!shareBtn) return;
const parent = shareBtn.parentElement;
if (!parent) return;
// âŒ ç¶æ
å€å®ããžãã¯ã远å
const { name: currentName, url: currentUrl } = getListMeta();
// ãªã¹ãåãURLãååŸã§ããªãïŒïŒãªã¹ãããŒãžã§ã¯ãªãïŒå Žåã¯ãã¿ã³ã远å ããªã
if (!currentName || !currentUrl) return;
const lists = loadLists();
const existingList = lists.find(x => x.url === currentUrl);
const isAdded = !!existingList;
const listId = existingList?.id || null;
// æ¢åã®ãã¿ã³ãæ®ã£ãŠããã°ã匷å¶çã«åé€ãã
const existingBtn = parent.querySelector('#adv-add-list-btn');
if (existingBtn) {
existingBtn.remove();
}
const btn = document.createElement('button');
btn.id = 'adv-add-list-btn';
btn.type = 'button';
const syncVisual = (dst, src) => {
dst.className = src.className;
const st = src.getAttribute('style');
if (st !== null) dst.setAttribute('style', st);
dst.style.color ||= 'inherit';
};
syncVisual(btn, shareBtn);
// 以åã®Observerãæ®ã£ãŠããã°ç Žæ£ãããªãŒã¯ãé²ã
if (listButtonObserver) {
listButtonObserver.disconnect();
}
const visMo = new MutationObserver(() => syncVisual(btn, shareBtn));
visMo.observe(shareBtn, { attributes: true, attributeFilter: ['class', 'style'] });
// æ°ããObserverã倿°ã«ä¿æ
listButtonObserver = visMo;
// ⌠isAdded ã«å¿ããŠã©ãã«ã倿ŽïŒ"åé€"ããŒãæµçšïŒ
const label = i18n.t(isAdded ? 'delete' : 'buttonAddList');
btn.setAttribute('aria-label', label);
btn.title = label;
const innerDiv = shareBtn.querySelector('div[dir="ltr"]') || shareBtn.querySelector('div');
const innerCls = innerDiv?.getAttribute('class') || innerDiv?.classList?.value || '';
const innerStyle = innerDiv?.getAttribute('style') || '';
const svgEl = innerDiv?.querySelector('svg') || shareBtn.querySelector('svg');
const svgCls = svgEl?.getAttribute('class') || svgEl?.classList?.value || '';
const spanEl = innerDiv?.querySelector('span') || shareBtn.querySelector('span');
const spanCls = spanEl?.getAttribute('class') || spanEl?.classList?.value || '';
// ⌠ã¢ã€ã³ã³ãã¹ãå®çŸ©
const ICON_PATH_ADD = 'M12 5c.55 0 1 .45 1 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H6a1 1 0 110-2h5V6c0-.55.45-1 1-1z';
// ã¢ã«ãŠã³ããã¿ã³ãšã¯ç°ãªããã·ã³ãã«ãªãã§ãã¯ããŒã¯ã䜿çš
const ICON_PATH_CHECK = 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z';
const iconPath = isAdded ? ICON_PATH_CHECK : ICON_PATH_ADD;
// ⌠iconPath ã䜿çšããããã« innerHTML ã倿Ž
btn.innerHTML = `
<div dir="ltr" class="${innerCls}" style="${innerStyle}">
<svg viewBox="0 0 24 24" aria-hidden="true" class="${svgCls}" fill="currentColor">
<g><path d="${iconPath}"></path></g>
</svg>
<span class="${spanCls}"></span>
</div>
`;
// ⌠ã¯ãªãã¯ã€ãã³ãã®ããžãã¯ããã°ã«ã«å€æŽ
btn.addEventListener('click', () => {
if (isAdded) {
// æ¢ã«ç»é²æžã¿ã®å ŽåïŒåé€
if (listId) {
deleteList(listId); // deleteList ã¯å
éšã§ toastDeleted ãåŒã³åºããŸã
}
} else {
// æªç»é²ã®å ŽåïŒè¿œå
// (颿°åé ã§ååŸãã currentName, currentUrl ã䜿çš)
const ret = addList({ name: currentName, url: currentUrl });
if (ret === 'ok') showToast(i18n.t('toastListAdded'));
else if (ret === 'exists') showToast(i18n.t('toastListExists'));
}
// ç¶æ
ãå€ãã£ãããããã¿ã³ã匷å¶çã«åæç»ïŒã¢ã€ã³ã³ãå³æåæ¿ïŒ
ensureListAddButton(true);
});
// å·Šé£ã«èšçœ®
// shareBtn.parentElement?.insertBefore(btn, shareBtn);
parent.insertBefore(btn, shareBtn); // parent倿°ã䜿çš
listButtonInstalledAt = location.pathname;
}
const reconcileUI = () => {
const stored = (()=>{ try { return JSON.parse(kv.get(MODAL_STATE_KEY,'{}')); } catch{ return {}; } })();
// âŒSPæã¯ãããŒã¿äžã§ã衚瀺(visible:true)ãã«ãªã£ãŠããŠã匷å¶çã« false (é衚瀺) æ±ãã«ãã
// PCæã¯ããŒã¿ã®éãã«ãã
const isSP = window.innerWidth <= 700;
const desiredVisible = isSP ? false : !!stored.visible;
const blocked = isBlockedPath(location.pathname);
const autoClose = isAutoClosePath(location.pathname);
// SPãµã€ãº ã〠ãã¹ã /i/grok ãŸã㯠/i/chat ãŸã㯠/messages ããå§ãŸãå Žå
const isMobileHiddenPath = isSP && (
location.pathname.startsWith('/i/grok') ||
location.pathname.startsWith('/i/chat') ||
location.pathname.startsWith('/messages')
);
// blockedïŒã¡ãã£ã¢ãã¥ãŒã¢çïŒãŸã㯠isMobileHiddenPath ãªãé衚瀺
if (blocked || isMobileHiddenPath) {
trigger.style.display = 'none';
} else {
trigger.style.display = '';
applyTriggerStoredPosition();
// CSSã®é©çš(å³äžé
眮)ãå®äºããã®ãå°ãåŸ
ã£ãŠãããç»é¢å€ãã§ãã¯ãè¡ã
setTimeout(() => requestAnimationFrame(keepTriggerInViewport), 100);
}
// SPã®å ŽåïŒdesiredVisible ã¯åžžã« false ãªã®ã§ãmanualOverrideOpen (ã¢ã€ã³ã³ã¯ãªãã¯) ããªããšè¡šç€ºãããªã
// PCã®å ŽåïŒdesiredVisible ã true ãªããautoClose ã§ãªããã°è¡šç€ºããã
const shouldShow = (!blocked) && ( (desiredVisible && !autoClose) || manualOverrideOpen );
const wasShown = (modal.style.display === 'flex');
modal.style.display = shouldShow ? 'flex' : 'none';
// è¡šç€ºç¶æ
ã«åãããŠbodyã«ã¯ã©ã¹ããã°ã«
if (shouldShow) {
document.body.classList.add('adv-modal-active');
} else {
document.body.classList.remove('adv-modal-active');
}
if (shouldShow) {
// æ¢ã«è¡šç€ºãããŠããå Žå(wasShown=true)ã¯ãäœçœ®ã®åŒ·å¶é©çšãã¹ããããã
if (!wasShown) {
applyModalStoredPosition();
}
// ç»é¢å€ã«ã¯ã¿åºããŠããªããã®ãã§ãã¯ã ãã¯æ¯åè¡ãïŒäœçœ®ãºã¬è£æ£ã®ããïŒ
requestAnimationFrame(keepModalInViewport);
if (!wasShown) {
syncFromSearchBoxToModal();
applyScopesToControls(readScopesFromURL());
updateSaveButtonState();
}
}
};
trigger.addEventListener('click', () => {
if (trigger.style.display === 'none') return;
const isVisibleNow = modal.style.display === 'flex';
if (isVisibleNow) {
manualOverrideOpen = false;
modal.style.display = 'none';
document.body.classList.remove('adv-modal-active');
saveModalRelativeState();
} else {
manualOverrideOpen = true;
modal.style.display = 'flex';
document.body.classList.add('adv-modal-active');
syncFromSearchBoxToModal();
applyScopesToControls(readScopesFromURL());
applyModalStoredPosition();
requestAnimationFrame(keepModalInViewport);
applyZoom();
saveModalRelativeState();
updateSaveButtonState();
}
});
const closeModal = () => {
manualOverrideOpen = false;
modal.style.display = 'none';
document.body.classList.remove('adv-modal-active');
saveModalRelativeState();
};
closeButton.addEventListener('click', closeModal);
clearButton.addEventListener('click', () => {
form.reset();
// ã¯ãªã¢æã« disabled ãè§£é€
['verified', 'links', 'images', 'videos'].forEach(groupName => {
const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
if (includeEl) includeEl.disabled = false;
if (excludeEl) excludeEl.disabled = false;
});
syncFromModalToSearchBox();
});
applyButton.addEventListener('click', () => {
// æ€çŽ¢ç¢ºå® â ã«ãŒãã£ã³ã°åæ åŸ
ã¡ â ã¹ãã£ã³
Promise.resolve(executeSearch())
.finally(() => setTimeout(() => processNewTweets(true), 800));
});
saveButton.addEventListener('click', () => {
const q = buildQueryStringFromModal().trim();
if (!q) return;
const {pf, lf} = readScopesFromControls();
addSaved(q, pf, lf);
activateTab('saved');
});
form.addEventListener('input', syncFromModalToSearchBox);
form.addEventListener('keydown', e => {
if (e.key === 'Enter' && (e.target.matches('input[type="text"], input[type="number"]'))) {
e.preventDefault();
// æ€çŽ¢ç¢ºå® â ã«ãŒãã£ã³ã°åæ åŸ
ã¡ â ã¹ãã£ã³
Promise.resolve(executeSearch())
.finally(() => setTimeout(() => processNewTweets(true), 800));
}
});
/* --- Date Shortcut Logic --- */
const dateShortcutSel = document.getElementById('adv-date-shortcut');
const sinceInput = document.getElementById('adv-since');
const untilInput = document.getElementById('adv-until');
if (dateShortcutSel && sinceInput && untilInput) {
const toYMD = (date) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
// ⌠æ¥ä»ããã·ã§ãŒãã«ãããéç®ããŠéžæç¶æ
ãåæãã颿°
const updateShortcutFromInputs = () => {
// ãŠãŒã¶ãŒãã·ã§ãŒãã«ãããæäœäžã®å Žåã¯ã«ãŒã鲿¢ã®ããåŠçããªã
if (document.activeElement === dateShortcutSel) return;
const sVal = sinceInput.value;
const uVal = untilInput.value;
// çžå¯Ÿæ¥ä»ã·ã§ãŒãã«ããã¯ãUntilã空ãã§ããããšãåæ
if (uVal !== '' || sVal === '') {
if (dateShortcutSel.value !== '') dateShortcutSel.value = '';
return;
}
const now = new Date();
const calcTargetDate = (fn) => {
const d = new Date(now);
fn(d);
return toYMD(d);
};
// å®çŸ©ãšæ¥ä»ã®å¯Ÿå¿è¡š
const targets = {
'1d': calcTargetDate(d => d.setDate(d.getDate() - 1)),
'1w': calcTargetDate(d => d.setDate(d.getDate() - 7)),
'1m': calcTargetDate(d => d.setMonth(d.getMonth() - 1)),
'3m': calcTargetDate(d => d.setMonth(d.getMonth() - 3)),
'6m': calcTargetDate(d => d.setMonth(d.getMonth() - 6)),
'1y': calcTargetDate(d => d.setFullYear(d.getFullYear() - 1)),
'2y': calcTargetDate(d => d.setFullYear(d.getFullYear() - 2)),
'3y': calcTargetDate(d => d.setFullYear(d.getFullYear() - 3)),
'5y': calcTargetDate(d => d.setFullYear(d.getFullYear() - 5)),
};
// äžèŽããã·ã§ãŒãã«ãããæ¢ã
const match = Object.entries(targets).find(([key, dateStr]) => dateStr === sVal);
const nextVal = match ? match[0] : '';
// å€ãå€ããå Žåã®ã¿æŽæ°ïŒç¡é§ãªæç»ãæå¶ïŒ
if (dateShortcutSel.value !== nextVal) {
dateShortcutSel.value = nextVal;
}
};
// ⌠1. ããã°ã©ã ã«ããå€å€æŽïŒãªããŒããURLè§£æã«ããã»ããïŒãæ€ç¥ãã仿ã
// input.value = '...' ãšãããæã«ã updateShortcutFromInputs ãèµ°ããã
const hookValueProperty = (input) => {
const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(input, 'value', {
configurable: true,
enumerable: true,
get: function() {
return descriptor.get.call(this);
},
set: function(v) {
descriptor.set.call(this, v);
// å€ãã»ãããããçŽåŸã«åæãã§ãã¯ãå®è¡
updateShortcutFromInputs();
}
});
};
hookValueProperty(sinceInput);
hookValueProperty(untilInput);
// ⌠2. ãŠãŒã¶ãŒã«ããæåå
¥åïŒã«ã¬ã³ããŒæäœãããŒå
¥åïŒã®æ€ç¥
['input', 'change'].forEach(evt => {
sinceInput.addEventListener(evt, updateShortcutFromInputs);
untilInput.addEventListener(evt, updateShortcutFromInputs);
});
// ⌠3. ã·ã§ãŒãã«ãããã«ããŠã³å€æŽæã®åŠçïŒæ¥ä»ãã»ããïŒ
dateShortcutSel.addEventListener('change', () => {
const val = dateShortcutSel.value;
// 'clear'以å€ã§å€ããªãå Žåã¯ç¡èŠ
if (!val && val !== 'clear') return;
const now = new Date();
let targetDate = new Date();
if (val === 'clear') {
sinceInput.value = '';
untilInput.value = '';
dateShortcutSel.value = '';
} else {
untilInput.value = ''; // æéæå®ã¯Until空
switch (val) {
case '1d': targetDate.setDate(now.getDate() - 1); break;
case '1w': targetDate.setDate(now.getDate() - 7); break;
case '1m': targetDate.setMonth(now.getMonth() - 1); break;
case '3m': targetDate.setMonth(now.getMonth() - 3); break;
case '6m': targetDate.setMonth(now.getMonth() - 6); break;
case '1y': targetDate.setFullYear(now.getFullYear() - 1); break;
case '2y': targetDate.setFullYear(now.getFullYear() - 2); break;
case '3y': targetDate.setFullYear(now.getFullYear() - 3); break;
case '5y': targetDate.setFullYear(now.getFullYear() - 5); break;
}
sinceInput.value = toYMD(targetDate);
}
// æ€çŽ¢ããã¯ã¹ãžã®åæ
if (typeof syncFromModalToSearchBox === 'function') {
syncFromModalToSearchBox();
}
});
}
const muteEmptyEl = document.getElementById('adv-mute-empty');
const muteListEl = document.getElementById('adv-mute-list');
const muteInputEl = document.getElementById('adv-mute-input');
const muteFilterEl = document.getElementById('adv-mute-filter');
const muteCsEl = document.getElementById('adv-mute-cs');
const muteWbEl = document.getElementById('adv-mute-wb');
const muteAddBtn = document.getElementById('adv-mute-add');
if (muteFilterEl) {
muteFilterEl.addEventListener('input', () => renderMuted());
}
const renderMuted = () => {
let list = loadMuted();
// æ€çŽ¢ããã¯ã¹ã«å€ãããã°ãã£ã«ã¿ãªã³ã°
if (muteFilterEl) {
const q = muteFilterEl.value.trim().toLowerCase();
if (q) {
list = list.filter(item => item.word.toLowerCase().includes(q));
}
}
const renderMuteRow = (item) => {
const row = document.createElement('div');
row.className = 'adv-mute-item';
if (!item.enabled) row.classList.add('disabled');
row.innerHTML = `
<div class="adv-mute-content-left">
<div class="adv-mute-word">${escapeHTML(item.word)}</div>
<div class="adv-mute-options-row">
<label class="adv-toggle">
<input type="checkbox" ${item.enabled ? 'checked' : ''} data-action="toggle-enabled">
<span data-i18n="labelEnabled">${i18n.t('labelEnabled')}</span>
</label>
<label class="adv-toggle">
<input type="checkbox" ${item.wb ? 'checked' : ''} data-action="toggle-wb">
<span data-i18n="labelWordBoundary">${i18n.t('labelWordBoundary')}</span>
</label>
<label class="adv-toggle">
<input type="checkbox" ${item.cs ? 'checked' : ''} data-action="toggle-cs">
<span data-i18n="labelCaseSensitive">${i18n.t('labelCaseSensitive')}</span>
</label>
</div>
</div>
<div class="adv-mute-actions-right">
<button class="adv-chip danger" data-action="delete" style="padding:2px 8px; font-size:11px;">${i18n.t('delete')}</button>
</div>
`;
row.querySelector('[data-action="toggle-enabled"]').addEventListener('change', () => toggleMutedEnabled(item.id));
row.querySelector('[data-action="toggle-cs"]').addEventListener('change', () => toggleMutedCS(item.id));
row.querySelector('[data-action="toggle-wb"]').addEventListener('change', () => toggleMutedWB(item.id));
row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteMuted(item.id));
return row;
};
renderPagedList('mute', muteListEl, list, renderMuteRow, muteEmptyEl, i18n.t('emptyMuted'));
};
function applyMuteVisualState() {
const listEl = document.getElementById('adv-mute-list');
if (!listEl) return;
const masterOn = loadMuteMaster();
// âŒ åæ¿ã®ç¬éã ããã©ã³ãžã·ã§ã³å
šåæ¢
listEl.classList.add('adv-no-anim');
// 匷å¶ãªãããŒã§ã¹ã¿ã€ã«ç¢ºå®
void listEl.offsetHeight;
listEl.classList.toggle('disabled', !masterOn);
// 次ãã¬ãŒã ã§è§£é€ïŒæç»ãè·šãããã®ããã€ã³ãïŒ
requestAnimationFrame(() => {
listEl.classList.remove('adv-no-anim');
});
}
muteAddBtn.addEventListener('click', () => {
addMuted(muteInputEl.value, !!muteCsEl.checked, !!(muteWbEl && muteWbEl.checked));
muteInputEl.value = '';
muteCsEl.checked = false;
if(muteWbEl) muteWbEl.checked = false;
});
muteInputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); muteAddBtn.click(); }
});
const muteEnableAllEl = document.getElementById('adv-mute-enable-all');
const muteModeSel = document.getElementById('adv-mute-mode');
if (muteEnableAllEl && !muteEnableAllEl._advBound) {
muteEnableAllEl._advBound = true;
try { muteEnableAllEl.checked = loadMuteMaster(); } catch {}
applyMuteVisualState();
muteEnableAllEl.addEventListener('change', () => {
saveMuteMaster(!!muteEnableAllEl.checked);
applyMuteVisualState();
rescanAllTweetsForFilter();
});
}
// ã¢ãŒãéžæã®åæåãšã€ãã³ã
if (muteModeSel && !muteModeSel._advBound) {
muteModeSel._advBound = true;
try { muteModeSel.value = loadMuteMode(); } catch {}
muteModeSel.addEventListener('change', () => {
saveMuteMode(muteModeSel.value);
rescanAllTweetsForFilter();
});
}
const installNavigationHooks = (onRouteChange) => {
let lastHref = location.href;
const _debounce = (fn, wait=60) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), wait); }; };
const fireIfChanged = _debounce(() => {
const now = location.href;
// ⌠lastHref ã null (匷å¶å®è¡ãã©ã°) ã®å Žåãééããã
if (lastHref === null || now !== lastHref) {
lastHref = now;
try {
const u = new URL(now, location.origin);
if (u.pathname.startsWith('/search')) {
const q = u.searchParams.get('q') || '';
const pf = (u.searchParams.get('pf') || '') === 'on';
const lf = (u.searchParams.get('lf') || '') === 'on';
if (q) recordHistory(decodeURIComponent(q), pf, lf);
} else if (u.pathname.startsWith('/hashtag/')) {
const hashtag = u.pathname.substring('/hashtag/'.length).split('/')[0];
if (hashtag) {
const q = `#${decodeURIComponent(hashtag)}`;
// ããã·ã¥ã¿ã°ããŒãžã¯ pf/lf ã¹ã³ãŒããæããªãæ³å®
recordHistory(q, false, false);
}
}
} catch(_) {}
onRouteChange();
}
}, 60);
const wrapHistory = (m) => {
const orig = history[m];
history[m] = function(...args){
try {
const href = args && args[2];
if (href) {
const u = new URL(href, location.href);
if (u.origin === location.origin && isBlockedPath(u.pathname)) {
hideUIImmediately(
document.getElementById('advanced-search-modal'),
document.getElementById('advanced-search-trigger')
);
}
}
} catch(_) {}
const ret = orig.apply(this, args);
// ⌠ããŒãžé·ç§»æã¯ãã£ãã·ã¥ãç Žæ£ããŠç¢ºå®ã«åå€å®ããã
lastHref = null;
queueMicrotask(fireIfChanged);
return ret;
};
};
wrapHistory('pushState'); wrapHistory('replaceState');
// ⌠ãã©ãŠã¶ããã¯æããã£ãã·ã¥ãç Žæ£ããŠç¢ºå®ã«åå€å®ããã
window.addEventListener('popstate', () => {
lastHref = null;
fireIfChanged();
});
document.addEventListener('click', (e) => {
const a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
if (!a) return;
try {
const u = new URL(a.href, location.href);
if (u.origin === location.origin) {
const sameTab = !(e.metaKey || e.ctrlKey || e.shiftKey || a.target === '_blank' || e.button === 1);
if (sameTab && isBlockedPath(u.pathname)) {
hideUIImmediately(
document.getElementById('advanced-search-modal'),
document.getElementById('advanced-search-trigger')
);
}
// ã¯ãªãã¯é·ç§»ã®å Žåãå°ãé
å»¶ãããŠãã§ãã¯
setTimeout(() => {
// æç€ºçãªé·ç§»ãªã®ã§åŒ·å¶ãã§ãã¯ããŠãè¯ããã
// é垞㯠pushState åŽã§æŸãããããããã¯è£å©
fireIfChanged();
}, 0);
}
} catch(_) {}
}, true);
return fireIfChanged;
};
// Reactã®å
éšããŒããã£ãã·ã¥ãã倿° (Global scope within closure)
let __cachedReactFiberKey = null;
// React Fiberããå
éšããŒã¿ãååŸããŠãªã³ã¯å
ãã¹ãæ¢ããã«ããŒ
function ft_getLinkFromReactFiber(dom) {
if (!dom) return null;
// UserScriptç°å¢(Firefoxç)察ç
const target = (typeof dom.wrappedJSObject !== 'undefined') ? dom.wrappedJSObject : dom;
// ããŒãæªååŸã®å Žåã®ã¿æ¢çŽ¢ãã (ãã£ãã·ã¥æŠç¥)
if (!__cachedReactFiberKey) {
__cachedReactFiberKey = Object.keys(target).find(k => k.startsWith('__reactFiber$'));
}
// ããŒãèŠã€ãããªãããŸãã¯èŠçŽ ããã®ããŒãæã£ãŠããªãå Žåã¯çµäº
if (!__cachedReactFiberKey || !target[__cachedReactFiberKey]) return null;
let fiber = target[__cachedReactFiberKey];
// 芪ãé¡ã£ãŠ props.link.pathname ãæ¢ã (æå€§20éå±€)
for (let i = 0; i < 20; i++) {
if (!fiber) break;
const props = fiber.memoizedProps;
if (props && props.link && props.link.pathname) {
return props.link.pathname;
}
// pendingProps ã念ã®ãã確èª
const pProps = fiber.pendingProps;
if (pProps && pProps.link && pProps.link.pathname) {
return pProps.link.pathname;
}
fiber = fiber.return; // 芪ãžç§»å
}
return null;
}
// âŒâŒâŒ ãã€ãŒãæ¬æããããã«ååŸãããã«ã㌠âŒâŒâŒ
function ft_getCleanTweetText(root) {
if (!root) return '';
// DOMãç Žå£ããªãããã«ã¯ããŒã³ããŠæäœ
const clone = root.cloneNode(true);
// 1. ç»å(çµµæå)ã alt ããã¹ãã«çœ®æ
clone.querySelectorAll('img').forEach(img => {
if (img.alt) img.replaceWith(document.createTextNode(img.alt));
});
// 2. ãªã³ã¯ã®åŠç
clone.querySelectorAll('a').forEach(a => {
const href = a.getAttribute('href');
// å€éšãªã³ã¯ïŒhttp/httpsã§å§ãŸãïŒã®å Žå
// DOMã®èŠãç®ïŒçç¥ãããŠããå¯èœæ§ãããïŒã§ã¯ãªããhrefïŒå®äœïŒãæ¡çšãã
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
// ãããã³ã«(https://)ãåé€ããŠèŠãç®ãã¹ãããªããã
let displayText = href.replace(/^https?:\/\//, '');
// æ«å°Ÿã® / ã¯åé€ããïŒèŠãç®ã®ãã€ãºè»œæžã®ããïŒ
if (displayText.endsWith('/')) {
displayText = displayText.slice(0, -1);
}
// ããã¹ãããŒããšããŠçœ®æ
a.replaceWith(document.createTextNode(displayText));
} else {
// ã¡ã³ã·ã§ã³(@user)ãããã·ã¥ã¿ã°(#tag)ãªã©ã®å
éšãªã³ã¯ã¯
// èŠãç®ã®ããã¹ãããã®ãŸãŸæ¡çšãã
const text = a.textContent || '';
a.replaceWith(document.createTextNode(text));
}
});
// 3. <br> ãæ¹è¡ã³ãŒãã«çœ®æ (textContent 㯠br ãç¡èŠãããã)
clone.querySelectorAll('br').forEach(br => {
br.replaceWith(document.createTextNode('\n'));
});
// 4. å
šäœã®ããã¹ããååŸ (ããã§åå²ãããURLãç¹ãããæ¹è¡ãä¿æããã)
return clone.textContent;
}
// ãã€ãŒãã®DOMããä¿åçšããŒã¿ãæœåº
function ft_extractTweetMeta(article, tweetId) {
// ã¡ã€ã³ããã¹ãæœåº
const textEl = article.querySelector('[data-testid="tweetText"]');
const text = ft_getCleanTweetText(textEl);
const userRow = article.querySelector('[data-testid="User-Name"]');
let name = '', handle = '', avatar = '';
if (userRow) {
name = userRow.querySelector('a span')?.innerText || '';
const userLink = userRow.querySelector('a[href^="/"]');
if (userLink) {
const href = userLink.getAttribute('href');
if (href) {
const parts = href.split('/');
if (parts.length >= 2) handle = parts[1];
}
}
}
const img = article.querySelector('[data-testid="Tweet-User-Avatar"] img');
if (img) avatar = img.src;
// æçš¿æ¥æã®ååŸ
let postedAt = Date.now(); // ãã©ãŒã«ããã¯ã¯çŸåšæå»
const timeEl = article.querySelector('time');
if (timeEl && timeEl.getAttribute('datetime')) {
postedAt = new Date(timeEl.getAttribute('datetime')).getTime();
}
// ã¡ãã£ã¢æœåºãã«ããŒ
const extractMedia = (rootElement, excludeElement) => {
const extracted = [];
// ã³ã³ãã(tweetPhoto)ãåºæºã«ã«ãŒãããããšã§ãDOMäžã®è¡šç€ºé åº(1,2,3,4)ãç¶æãã
const mediaContainers = Array.from(rootElement.querySelectorAll('div[data-testid="tweetPhoto"]'));
// åãã³ã³ãããäºéã«åŠçããªãããã®ã»ããïŒå¿µã®ããïŒ
const processedUrls = new Set();
mediaContainers.forEach(container => {
// åŒçšæ å
ã®é€å€å€å®
if (excludeElement && excludeElement.contains(container)) return;
// 1. ãŸã video ãæ¢ã (videoã¿ã°ãããã°åç»æ±ã)
const video = container.querySelector('video');
if (video) {
const url = video.poster || ''; // ãã¹ã¿ãŒç»åããµã ããšããŠäœ¿çš
if (url && !processedUrls.has(url)) {
extracted.push({ type: 'video', url: url });
processedUrls.add(url);
}
return; // åç»ãèŠã€ãã£ãããã®ã³ã³ããã¯åŠççµäº
}
// 2. ãªããã° img ãæ¢ã
const img = container.querySelector('img');
if (img && img.src) {
const url = img.src;
if (!processedUrls.has(url)) {
extracted.push({ type: 'image', url: url });
processedUrls.add(url);
}
}
});
return extracted;
};
// åŒçšã³ã³ããç¹å®
let quoteContainer = null;
const quoteCandidates = Array.from(article.querySelectorAll('div[role="link"]'));
quoteContainer = quoteCandidates.find(el => {
if (el.getAttribute('tabindex') !== '0') return false;
const qUser = el.querySelector('[data-testid="User-Name"]');
if (!qUser) return false;
const hasText = el.querySelector('[data-testid="tweetText"]');
const hasMedia = el.querySelector('[data-testid="tweetPhoto"]') || el.querySelector('video');
return hasText || hasMedia;
});
const mainMedia = extractMedia(article, quoteContainer);
let quote = null;
if (quoteContainer) {
// åŒçšããã¹ãæœåº
const qTextEl = quoteContainer.querySelector('[data-testid="tweetText"]');
const qText = ft_getCleanTweetText(qTextEl);
// âŒâŒâŒ åŒçšå
ã®ãããã«è¡šç€ºããªã³ã¯ãæœåº âŒâŒâŒ
let qShowMore = null;
const showMoreBtn = quoteContainer.querySelector('[data-testid="tweet-text-show-more-link"]');
if (showMoreBtn) {
qShowMore = {
text: showMoreBtn.innerText || 'Show more', // "ããã«è¡šç€º" ç
url: showMoreBtn.getAttribute('href') || ''
};
}
let qName = '', qHandle = '', qAvatar = '';
const qUserRow = quoteContainer.querySelector('[data-testid="User-Name"]');
if (qUserRow) {
qName = qUserRow.textContent.split('@')[0].trim();
const handleMatch = qUserRow.innerText.match(/@([a-zA-Z0-9_]+)/);
if (handleMatch) qHandle = handleMatch[1];
}
const qImg = quoteContainer.querySelector('img[src*="profile_images"]');
if (qImg) qAvatar = qImg.src;
let qTweetId = '';
// 1. åŸæ¥ã®DOMæ¢çŽ¢ (é«éã»äžè¬ç)
// éåžžã®åçãªã³ã¯ãªã©ãæ¢ã
const photoLink = quoteContainer.querySelector('a[href*="/status/"][href*="/photo/"]');
if (photoLink) {
const m = photoLink.getAttribute('href').match(/\/status\/(\d+)/);
if (m) qTweetId = m[1];
}
// ãããã«è¡šç€ºããªã³ã¯ãããå Žåã®è£å®
if (!qTweetId && qShowMore && qShowMore.url) {
const m = qShowMore.url.match(/\/status\/(\d+)/);
if (m) qTweetId = m[1];
}
// 2. React Fiberããã®ååŸ (äœéã»æçµææ®µ)
// DOMæ¢çŽ¢ã§èŠã€ãããªãã£ãå ŽåïŒåç»åŒçšãç¹æ®ãªã«ãŒããªã©ãaã¿ã°ããªãå ŽåïŒã®ã¿å®è¡
if (!qTweetId) {
const fiberPath = ft_getLinkFromReactFiber(quoteContainer);
if (fiberPath) {
const m = fiberPath.match(/\/status\/(\d+)/);
if (m) qTweetId = m[1];
}
}
const qMedia = extractMedia(quoteContainer, null);
quote = {
id: qTweetId,
text: qText,
user: { name: qName, handle: qHandle, avatar: qAvatar },
media: qMedia,
showMore: qShowMore
};
}
// --- Link Card Extraction ---
let card = null;
const cardWrapper = article.querySelector('[data-testid="card.wrapper"]');
// åŒçšã³ã³ããã®äžã«ããã«ãŒãã¯é€å€
if (cardWrapper && (!quoteContainer || !quoteContainer.contains(cardWrapper))) {
// URLååŸ (å
±é)
const cardLink = cardWrapper.querySelector('a[role="link"]');
const cardUrl = cardLink ? cardLink.getAttribute('href') : '';
if (cardUrl) {
let cardTitle = '';
let cardDomain = '';
let cardImg = '';
let cardSvg = ''; // SVGæååçš
let isSmall = false;
// A. Small Card (暪é·) ã®å Žå: æ§é ãç¹æ®ãªã®ã§å°çšã«ããŒã¹ãã
const smallDetail = cardWrapper.querySelector('[data-testid="card.layoutSmall.detail"]');
if (smallDetail) {
isSmall = true;
// ããã¹ãè¡ãååŸ (1è¡ç®:ãã¡ã€ã³, 2è¡ç®:ã¿ã€ãã« ã®é ã§äžŠãã§ããããšãå€ã)
const lines = Array.from(smallDetail.querySelectorAll('div[dir="auto"]'))
.map(el => el.innerText.trim())
.filter(Boolean);
if (lines.length >= 1) cardDomain = lines[0];
if (lines.length >= 2) cardTitle = lines[1];
// ç»åååŸã詊ã¿ã
const imgEl = cardWrapper.querySelector('img');
if (imgEl) {
cardImg = imgEl.src;
} else {
// ç»åããªããã°SVGãæ¢ã
const svgEl = cardWrapper.querySelector('svg');
if (svgEl) {
// SVGã¿ã°èªäœãæååãšããŠä¿å
cardSvg = svgEl.outerHTML;
}
}
}
// B. Large Card (瞊é·) ãŸãã¯ãã®ä»ã®å Žå: åŸæ¥ã®ããžãã¯
else {
const imgEl = cardWrapper.querySelector('img');
cardImg = imgEl ? imgEl.src : '';
// è€æ°ã®ãªã³ã¯ããããã¹ãæ
å ±ãæ¢ã
const allLinks = Array.from(cardWrapper.querySelectorAll('a[role="link"]'));
for (const link of allLinks) {
const text = link.innerText || '';
const aria = link.getAttribute('aria-label') || '';
if (text.trim() || aria.trim()) {
cardTitle = aria || text;
const rawAria = aria;
if (rawAria) {
const firstPart = rawAria.split(/\s+/)[0];
if (firstPart && firstPart.includes('.')) {
cardDomain = firstPart;
}
}
break;
}
}
// ã¿ã€ãã«ãããã¡ã€ã³ãé€å» (éè€å¯Ÿç)
cardTitle = cardTitle.replace(/\n/g, ' ').trim();
if (cardDomain && cardTitle.startsWith(cardDomain)) {
cardTitle = cardTitle.substring(cardDomain.length).trim();
}
}
// ããŒã¿ãããã°ä¿å
if (cardUrl && (cardImg || cardSvg || cardTitle)) {
card = {
url: cardUrl,
img: cardImg,
svg: cardSvg, // â
远å : SVGããŒã¿
title: cardTitle,
domain: cardDomain,
style: isSmall ? 'small' : 'large' // â
远å : 衚瀺ã¿ã€ã
};
}
}
}
return {
id: tweetId,
text,
user: { name, handle, avatar },
media: mainMedia,
quote,
card,
ts: Date.now(), // ä¿åæäœãããæ¥æïŒãœãŒãçšãªã©ã§äœ¿çšããå Žåã®ããæ®ãïŒ
postedAt: postedAt // å®éã®æçš¿æ¥æ
};
}
// ç¹å®ã®TweetIDãæã€èšäºã®ã¿ã°ãããã ããå³åº§ã«åè©äŸ¡ã»åæç»ãã
function refreshTagChipsForTweet(tweetId) {
// data-ft-tweet-id 屿§ãæã€èšäºããã³ãã€ã³ãã§ååŸ
const articles = document.querySelectorAll(`article[data-ft-tweet-id="${tweetId}"]`);
articles.forEach(article => {
// æ¢åã®ããžãã¯ãå©çšããŠãããã®çè±ãåå€å®
if (typeof ft_processTweetArticle === 'function') {
ft_processTweetArticle(article);
}
});
}
// ç»é¢äžã®ãã¹ãŠã®ãæ°ã«å
¥ããã¿ã³ã®ç¶æ
ãæŽæ°ãã
function updateAllFavoriteButtons() {
document.querySelectorAll('.adv-fav-btn').forEach(btn => {
const tid = btn.dataset.tweetId;
if (tid) {
const active = isFavorited(tid);
btn.classList.toggle('active', active);
}
});
}
// â
ããŒã¯ãã¿ã³ã®æ³šå
¥
function injectFavoriteButton(article, tweetId) {
const actionBar = article.querySelector('[role="group"]');
if (!actionBar) return;
// 1. ã·ã§ã¢ãã¿ã³ãæ¢ã
let shareBtn = actionBar.querySelector('[data-testid="share-button"]');
if (!shareBtn) {
const buttons = actionBar.querySelectorAll('button');
for (const b of buttons) {
const label = b.getAttribute('aria-label') || '';
if (label.includes('å
±æ') || label.includes('Share') || b.innerHTML.includes('M12 2.59l5.7 5.7')) {
shareBtn = b;
break;
}
}
}
// ã·ã§ã¢ãã¿ã³ãèŠã€ãããªãå Žåã¯ãã©ãŒã«ããã¯ïŒæ«å°Ÿè¿œå ïŒ
if (!shareBtn) {
if (actionBar.querySelector('.adv-fav-btn')) return;
const fallbackBtn = createFavButtonElement(article, tweetId, null);
const wrapper = document.createElement('div');
wrapper.style.cssText = 'display:flex;align-items:center;';
wrapper.appendChild(fallbackBtn);
actionBar.appendChild(wrapper);
return;
}
// 2. ã·ã§ã¢ãã¿ã³ã®èŠªã³ã³ããïŒdisplay: inline-grid ã®ãã€ïŒãæ¢ã
let shareContainer = shareBtn.closest('div[style*="display: inline-grid"]') || shareBtn.parentNode?.parentNode;
if (!shareContainer) shareContainer = shareBtn.parentNode;
if (shareContainer.querySelector('.adv-fav-btn')) return;
// 3. ãã¿ã³èŠçŽ ãäœæ
const btn = createFavButtonElement(article, tweetId, shareBtn);
// 4. 絶察é
眮çšã®ã¹ã¿ã€ã«ãé©çš
shareContainer.style.position = 'relative';
shareContainer.style.overflow = 'visible';
// ã³ã³ããã®å·ŠåŽã«ãæãã¿ã³ãå
¥ãåã®ã¹ããŒã¹(çŽ36px)ã匷å¶çã«ç©ºãã
// ããã«ãããã¿ã€ã ã©ã€ã³äžã§å·Šé£ã®ããã¯ããŒã¯ãã¿ã³ãšéãªããªããªããŸã
shareContainer.style.marginLeft = '36px';
btn.style.position = 'absolute';
btn.style.right = '100%';
btn.style.top = '50%';
btn.style.transform = 'translateY(-50%)';
btn.style.marginRight = '2px'; // ã·ã§ã¢ãã¿ã³ãšã®éé
// 5. æ¿å
¥
shareContainer.appendChild(btn);
}
// âŒâŒâŒ ãããã«è¡šç€ºããèªåå±éããéåæãã«ã㌠âŒâŒâŒ
async function ft_expandTweetTextIfNeeded(article) {
// 1. èšäºå
ã®å
šãŠã®ãããã«è¡šç€ºããã¿ã³ãååŸ
const allButtons = article.querySelectorAll('[data-testid="tweet-text-show-more-link"]');
let targetBtn = null;
// 2. ã¡ã€ã³æçš¿ã®ãã¿ã³ã ããç¹å®ãã
for (const btn of allButtons) {
// ãã¿ã³ã®èŠªãé¡ããdiv[role="link"] (åŒçšãã€ãŒãã®ã³ã³ãã) ãããã確èª
// ããããã°ãããã¯åŒçšå
ã®ãã¿ã³ãªã®ã§ç¡èŠãã
if (btn.closest('div[role="link"]')) {
continue;
}
// åŒçšå
ã§ã¯ãªããã¿ã³ãèŠã€ãã£ããããããã¡ã€ã³æçš¿ã®ãã¿ã³
targetBtn = btn;
break;
}
// ã¡ã€ã³æçš¿ã«å±éãã¿ã³ããªããã°äœãããªãïŒåŒçšã«ãã£ãŠãç¡èŠïŒ
if (!targetBtn) return;
const textContainer = article.querySelector('[data-testid="tweetText"]');
// äŸå€ã±ãŒã¹ïŒããã¹ãã³ã³ãããèŠã€ãããªãå Žåã¯ã¯ãªãã¯ã ãããŠå°ãåŸ
ã€
if (!textContainer) {
targetBtn.click();
return new Promise(r => setTimeout(r, 300));
}
// MutationObserverã§ããã¹ãã³ã³ããã®å€åïŒå±éïŒãåŸ
æ©ãã
return new Promise(resolve => {
let resolved = false;
const cleanup = () => {
if (resolved) return;
resolved = true;
observer.disconnect();
clearTimeout(timer);
resolve();
};
// äžãäžå€åããªãã£ãå Žåã®ã¿ã€ã ã¢ãŠãïŒ2ç§ïŒ
const timer = setTimeout(cleanup, 2000);
const observer = new MutationObserver(() => {
// DOMãå€ããã°å±éå®äºãšã¿ãªã
cleanup();
});
// ããã¹ãã³ã³ããã®äžèº«ã®å€åãç£èŠ
observer.observe(textContainer, { childList: true, subtree: true, characterData: true });
// ç£èŠãéå§ããŠãããç¹å®ãããã¿ã³ãã¯ãªãã¯
targetBtn.click();
});
}
// ãã¿ã³çæããžãã¯ãåé¢ïŒå
±éåïŒ
function createFavButtonElement(article, tweetId, sourceBtn) {
const btn = document.createElement('button');
// adv-native-style: CSSã§åºå®ãµã€ãºãè§£é€ããããã®ã¯ã©ã¹
btn.className = (sourceBtn ? sourceBtn.className : '') + ' adv-fav-btn adv-native-style';
btn.dataset.tweetId = tweetId;
btn.type = 'button';
btn.title = i18n.t('tabFavorites');
// SVG (Star)
btn.innerHTML = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
</svg>`;
const updateState = () => {
const active = isFavorited(tweetId);
btn.classList.toggle('active', active);
};
updateState();
btn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
// çŸåšã®ç¶æ
ã確èª
const isAlreadyFav = isFavorited(tweetId);
// ãŸã ãæ°ã«å
¥ãããŠããªãïŒïŒããããä¿åããïŒå Žåã®ã¿ãå
šæå±éãè¡ã
if (!isAlreadyFav) {
await ft_expandTweetTextIfNeeded(article);
}
const meta = ft_extractTweetMeta(article, tweetId);
toggleFavorite(meta);
updateState();
ft_processTweetArticle(article);
});
return btn;
}
// ============================================================
// High Performance Tweet Processor (O(1))
// ============================================================
function processSingleTweet(article) {
// éè€åŠçã¬ãŒã
if (article.dataset.advProcessed) return;
// 1. å
±é: ãã€ãŒãIDã®ç¢ºä¿
const tweetId = article.dataset.ftTweetId || ft_extractTweetId(article);
if (!tweetId) return;
article.dataset.ftTweetId = tweetId;
// 2. èšå®ããŒã (Advanced Searchçš) ...
const flags = {
name: document.getElementById('adv-exclude-hit-name')?.checked ?? true,
handle: document.getElementById('adv-exclude-hit-handle')?.checked ?? true,
reposts: document.getElementById('adv-filter-reposts-exclude')?.checked ?? false,
hashtags: document.getElementById('adv-filter-hashtags-exclude')?.checked ?? false,
};
// 3. Advanced Search Filtering ...
const masterOn = loadMuteMaster();
const muteMode = loadMuteMode();
const muted = loadMuted();
const hasMute = masterOn && muted.length > 0;
if (flags.name || flags.handle || hasMute || flags.reposts || flags.hashtags) {
const regexRules = [];
const simpleCI = new Set();
const simpleCS = new Set();
if (hasMute) {
muted.filter(m => m.enabled !== false).forEach(m => {
if (m.wb) {
// åèªåäœ: æ£èŠè¡šçŸãçæ
const flags = m.cs ? '' : 'i';
const esc = escapeRegExp(m.word);
const pattern = `(?:^|[^a-zA-Z0-9_])${esc}(?:$|[^a-zA-Z0-9_])`;
regexRules.push({ rx: new RegExp(pattern, flags), word: m.word });
} else {
// éåžžäžèŽ: Setã«æ¯ãåã
if (m.cs) simpleCS.add(m.word);
else simpleCI.add(m.word.toLowerCase());
}
});
}
const muteSettings = {
hasMute,
muteMode,
regexRules,
simpleCI,
simpleCS
};
const tokens = (flags.name || flags.handle) ? parseSearchTokens() : null;
try {
evaluateTweetForFiltering(article, flags, muteSettings, tokens);
} catch (e) { console.error('[AdvSearch] Filter error', e); }
}
// 4. Favorite Tags Processing (ã¿ã°è¡šç€º)
if (typeof ft_processTweetArticle === 'function') {
try {
ft_processTweetArticle(article);
} catch (e) { console.error('[FT] Tag error', e); }
}
// 5.Inject Favorite Button
injectFavoriteButton(article, tweetId);
// 6. åŠçæžã¿ãã©ã°ãç«ãŠã
article.dataset.advProcessed = '1';
}
// æåå®è¡ïŒèšå®å€æŽæãªã©ïŒãããŒãžé·ç§»æçš
// æ®æ®µã®ã¹ã¯ããŒã«æã¯ Observer ã processSingleTweet ãåŒã¶ã®ã§ãããã¯èµ°ããªã
function processNewTweets(force = false) {
// 匷å¶ãã©ã°ããªãå Žåãå
¥åäžã¯ã¹ããã
if (!force && isTyping()) return;
// å
šãã€ãŒããååŸããŠåŠç
const articles = document.querySelectorAll('article[data-testid="tweet"]');
for (const art of articles) {
processSingleTweet(art);
}
// åºåãç·ã®ã¯ãªãŒã³ã¢ãããªã©ã¯ããã§
cleanupAdjacentSeparators();
}
// çµ±åã¹ãã£ã³é¢æ° ãããŸã§ âŒ
const setupObservers = () => {
// URL倿޿€ç¥çšã®ãã¬ãŒã¹ãã«ã
let _fireIfChanged = () => {};
const observer = new MutationObserver((mutations) => {
let searchBoxChanged = false;
for (const m of mutations) {
// 远å ãããããŒãã ããã«ãŒããã (Differential Update)
if (m.addedNodes.length > 0) {
for (const node of m.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
// A. æ€çŽ¢ããã¯ã¹ã®æ€ç¥
if (!searchBoxChanged) {
if (node.matches?.('input[data-testid="SearchBox_Search_Input"]') ||
node.querySelector?.('input[data-testid="SearchBox_Search_Input"]')) {
searchBoxChanged = true;
}
}
// æ€çŽ¢ãã©ãŒã ã远å ãããããªãµã€ã¶ãŒãã»ããã¢ãã
if (node.matches?.('form[role="search"]') || node.querySelector?.('form[role="search"]')) {
// å°ãé
å»¶ãããŠDOMå®å®åŸã«å®è¡
setTimeout(setupNativeSearchResizer, 100);
}
// B. ãã€ãŒã (article) ãçŽæ¥è¿œå ãããå Žå
if (node.tagName === 'ARTICLE' && node.getAttribute('data-testid') === 'tweet') {
processSingleTweet(node);
}
// C. ã³ã³ãã (div/sectionç) ã远å ãããäžã«ãã€ãŒããå«ãŸããå Žå
else if (node.firstElementChild) {
// cellInnerDiv ãããã«å«ãŸãã
const nestedTweets = node.querySelectorAll('article[data-testid="tweet"]');
if (nestedTweets.length > 0) {
for (const tweet of nestedTweets) {
processSingleTweet(tweet);
}
}
}
}
}
}
// æ€çŽ¢ããã¯ã¹ãå€ãã£ãŠãããã¢ãŒãã«ãšåæ
if (searchBoxChanged) {
syncFromSearchBoxToModal();
}
// ãããã£ãŒã«/ãªã¹ãã®ãã¿ã³èšçœ®ïŒDOMå€åæã¯åžžã«ãã§ãã¯ããŠã軜éïŒ
try {
ensureProfileAddButton(false);
ensureListAddButton(false);
} catch (_) {}
// URL倿Žãã§ã㯠(Debounced)
_fireIfChanged();
});
const appContainer = document.querySelector('div[data-testid="app-container"]');
const observeTarget = appContainer || document.body;
observer.observe(observeTarget, { childList: true, subtree: true });
// installNavigationHooks ã¯ãã®ãŸãŸå©çš
_fireIfChanged = installNavigationHooks(() => {
// Navigation Change Logic...
if (profileButtonObserver) { profileButtonObserver.disconnect(); profileButtonObserver = null; }
if (listButtonObserver) { listButtonObserver.disconnect(); listButtonObserver = null; }
manualOverrideOpen = false;
reconcileUI();
syncFromSearchBoxToModal();
applyScopesToControls(readScopesFromURL());
updateSaveButtonState();
// ããŒãžé·ç§»æã¯åŒ·å¶çã«å
šã¹ãã£ã³ (Force)
processNewTweets(true);
setupNativeSearchResizer();
});
};
// Resizeã€ãã³ãã§ã¢ãŒããå€ãã£ãéã«å³åº§ã«äœçœ®ãåãæ¿ãã
let lastLayoutMode = getTriggerLayoutMode();
window.addEventListener('resize', debounce(()=>{
if (modal.style.display === 'flex') { applyModalStoredPosition(); requestAnimationFrame(keepModalInViewport); }
// ããªã¬ãŒã®äœçœ®åé©çš (ã¢ãŒããå€ãã£ãŠãããäœçœ®ãåãæ¿ãã)
const currentMode = getTriggerLayoutMode();
if (trigger.style.display !== 'none') {
// ãŠã£ã³ããŠãªãµã€ãºã§åº§æšããããã®ãè£æ£ããŸãã¯ã¢ãŒãåæ¿ã«ããäœçœ®å€æŽ
applyTriggerStoredPosition();
requestAnimationFrame(keepTriggerInViewport);
}
lastLayoutMode = currentMode;
}, 100));
loadModalState();
reconcileUI();
setupModalDrag();
setupModalResize();
// æä»ãã§ãã¯ããã¯ã¹ã®ããžãã¯
const setupExclusiveChecks = () => {
const groups = [
'verified', 'links', 'images', 'videos'
];
groups.forEach(groupName => {
const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
if (!includeEl || !excludeEl) return;
const handleChange = (eventSource, oppositeEl) => {
if (eventSource.checked) {
oppositeEl.disabled = true;
} else {
oppositeEl.disabled = false;
}
};
includeEl.addEventListener('change', () => handleChange(includeEl, excludeEl));
excludeEl.addEventListener('change', () => handleChange(excludeEl, includeEl));
});
};
setupExclusiveChecks();
setupObservers();
// ã€ãã³ãå§ä»»ã®ããã®ã«ãŒãèŠçŽ ãååŸ
const appContainer = document.querySelector('div[data-testid="app-container"]') || document.body;
// ç£èŠå¯Ÿè±¡ã»ã¬ã¯ã¿ãçµå
const allSearchSelectorsStr = searchInputSelectors.join(', ');
// 1. æ€çŽ¢ããã¯ã¹ã®å
¥å (ã€ãã³ãå§ä»»)
appContainer.addEventListener('input', (e) => {
if (!e.target || !e.target.matches(allSearchSelectorsStr)) return;
const input = e.target;
if (isUpdating || modal.style.display === 'none') return;
const activeInputs = getActiveSearchInputs();
if (activeInputs.includes(input)) {
parseQueryAndApplyToModal(input.value);
applyScopesToControls(readScopesFromURL());
updateSaveButtonState();
}
});
// 2. ã¿ã€ãã³ã°ã¬ãŒã (ã€ãã³ãå§ä»»)
const typingEvents = ['input', 'keydown', 'keyup', 'compositionstart', 'compositionupdate', 'compositionend'];
typingEvents.forEach(evName => {
appContainer.addEventListener(evName, (e) => {
if (e.target && e.target.matches(allSearchSelectorsStr)) {
markTyping();
}
}, { passive: true, capture: true }); // capture: true ã§ãã確å®ã«è£è¶³
});
// æ€çŽ¢å
¥åäžãã¢ããªå
šäœãæã¡äžããŠãµãžã§ã¹ãã衚瀺ãã€ã€ããµã€ãããŒã®ãã€ãºãé ã
const handleNativeSearchFocus = (e) => {
const target = e.target;
if (!target || target.nodeType !== 1) return;
const isSearchInput = target.matches(allSearchSelectorsStr) ||
target.getAttribute('data-testid') === 'SearchBox_Search_Input';
if (!isSearchInput) return;
const modalEl = document.getElementById('advanced-search-modal');
const reactRoot = document.getElementById('react-root');
if (!modalEl || !reactRoot) return;
// ã¢ãŒãã«ã衚瀺ãããŠããããã§ã㯠(é衚瀺ãªãäœãããªã)
const isModalVisible = window.getComputedStyle(modalEl).display !== 'none';
if (e.type === 'focusin') {
// ã¢ãŒãã«ãéããŠããæã ãçºå
if (isModalVisible) {
modalEl.classList.add('adv-z-lower');
reactRoot.classList.add('adv-app-lifted');
// ã¢ãŒãã«ã®äœçœ®ã確èªããå·Šã¡ãã¥ãŒãšè¢«ããªãå·Šã¡ãã¥ãŒãé ããã©ã°ãç«ãŠã
const modalRect = modalEl.getBoundingClientRect();
// å·Šã¡ãã¥ãŒã®å¹
ã¯æå€§ã§275pxçšåºŠ(PCç)
// ã¢ãŒãã«ã®å·Šç«¯ã 300px æªæºãªããå·Šã¡ãã¥ãŒã®äžã«éãªã£ãŠããããšã¿ãªã
if (modalRect.left < 300) {
reactRoot.classList.add('adv-overlap-left-menu');
} else {
reactRoot.classList.remove('adv-overlap-left-menu');
}
}
} else if (e.type === 'focusout') {
// ã¯ãªãã¯ç¶äºãæãããŠå
ã«æ»ã
setTimeout(() => {
modalEl.classList.remove('adv-z-lower');
reactRoot.classList.remove('adv-app-lifted');
reactRoot.classList.remove('adv-overlap-left-menu'); //ãã©ã°è§£é€
}, 200);
}
};
appContainer.addEventListener('focusin', handleNativeSearchFocus);
appContainer.addEventListener('focusout', handleNativeSearchFocus);
// 3. ãã©ãŒã éä¿¡ (ã€ãã³ãå§ä»»)
appContainer.addEventListener('submit', (e) => {
if (!e.target || !e.target.closest('form')) return;
// SearchBox_Search_Input ãå«ããã©ãŒã ãå€å®
const input = e.target.querySelector('input[data-testid="SearchBox_Search_Input"]');
if (input) {
const val = (input.value || '').trim();
const {pf, lf} = readScopesFromControls();
if (val) recordHistory(val, pf, lf);
}
}, true); // ãã£ããã£ãã§ãŒãºã§å®è¡
// ⌠Setup background drop zones âŒ
// ïŒãã®ãããã¯ã¯ãæåã® renderAccounts / renderLists / renderSaved ãåŒã¶åã«çœ®ãïŒ
setupBackgroundDrop(tabAccountsPanel, accountsListEl, unassignAccount);
setupBackgroundDrop(tabListsPanel, advListsListEl, unassignList);
setupBackgroundDrop(tabSavedPanel, advSavedListEl, unassignSaved);
// ft_init ãæåªå
ã§å®è¡ããstateãããŒãããã
if (typeof ft_init === 'function') {
ft_init();
}
// äžãäžããŒã倱æããŠããå Žåã«åããããã©ã«ãStateãæ³šå
¥ã㊠nullèœã¡ãé²ã
if (typeof ft_state === 'undefined' || !ft_state) {
ft_state = ft_createDefaultState();
}
setupFavoritesDelegation();
// ã¹ãã察å¿çšïŒã¿ããæäœããã©ãã°æäœãžå€æãããªã¹ããŒãç»é²
enableMobileDragSupport();
// ä¿åãããæåŸã®ã¿ããèªã¿èŸŒãã§ã¢ã¯ãã£ãã«ãã
const initTabSetting = kv.get(INITIAL_TAB_KEY, 'last'); // èšå®ãååŸ (ããã©ã«ã㯠'last')
let targetTab = 'search';
if (initTabSetting === 'last') {
// 'last' ã®å Žåã¯ååéããŠããã¿ãã䜿ã
targetTab = kv.get(LAST_TAB_KEY, 'search');
} else {
// ãã以å€ã®å Žåã¯æå®ãããã¿ãã䜿ã
targetTab = initTabSetting;
}
// ããæå®ãããã¿ããèšå®ã§ãé衚瀺ãã«ãªã£ãŠããå ŽåãactivateTab å
éšã®ããžãã¯ã§
// èªåçã« 'search' çã®è¡šç€ºå¯èœãªã¿ãã«ãã©ãŒã«ããã¯ããããããããã§ã¯åçŽã«æž¡ãã ãã§OK
activateTab(targetTab || 'search');
(async () => {
const input = await waitForElement(searchInputSelectors.join(','), 7000);
// æ€çŽ¢çªãèŠã€ãã£ãããªãµã€ã¶ãŒèšçœ®
setupNativeSearchResizer();
if (input) {
syncFromSearchBoxToModal();
applyScopesToControls(readScopesFromURL());
updateSaveButtonState();
processNewTweets(true);
// force=true ã§ååæç»ã匷å¶
ensureProfileAddButton(true);
ensureListAddButton(true);
/* --- Favorite Tags Init Scan --- */
try {
processNewTweets(true);
} catch(e) { console.error('[FT] Init error', e); }
}
})();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
};
// --- ç°å¢å€å®ããŒãããŒã㌠---
// 1. UserScriptç°å¢: GM_info ãããããã€ã¢ããã¿ãŒã«ããé
å»¶å®è¡åŸ
ã¡(window.__X_ADV_SEARCH_MAIN__)ã§ã¯ãªãå Žå
if (typeof GM_info !== 'undefined' && typeof window.__X_ADV_SEARCH_MAIN__ === 'undefined') {
__X_ADV_SEARCH_MAIN_LOGIC__();
}
// 2. æ¡åŒµæ©èœç°å¢: adapter.js ããåŒã°ããã®ãåŸ
ã€ããã« window ã«å
Ž
else {
window.__X_ADV_SEARCH_MAIN__ = __X_ADV_SEARCH_MAIN_LOGIC__;
}