Adds a floating modal for advanced search on X.com (Twitter). Syncs with search box and remembers position/display state. The top-right search icon is now draggable and its position persists.
× ××× ××××
// ==UserScript==
// @name Advanced Search for X (Twitter) ð
// @name:ja Advanced Search for XïŒTwitterïŒð
// @name:en Advanced Search for X (Twitter) ð
// @name:zh-CN Advanced Search for XïŒTwitterïŒð
// @name:zh-TW Advanced Search for XïŒTwitterïŒð
// @name:ko Advanced Search for X (Twitter) ð
// @name:fr Advanced Search for X (Twitter) ð
// @name:es Advanced Search for X (Twitter) ð
// @name:de Advanced Search for X (Twitter) ð
// @name:pt-BR Advanced Search for X (Twitter) ð
// @name:ru Advanced Search for X (Twitter) ð
// @version 6.3.2
// @description Adds a floating modal for advanced search on X.com (Twitter). Syncs with search box and remembers position/display state. The top-right search icon is now draggable and its position persists.
// @description:ja X.comïŒTwitterïŒã«é«åºŠãªæ€çŽ¢æ©èœãåŒã³åºãããããŒãã£ã³ã°ã»ã¢ãŒãã«ã远å ããŸããæ€çŽ¢ããã¯ã¹ãšåæ¹åã§åæããäœçœ®ãè¡šç€ºç¶æ
ãèšæ¶ããŸããå³äžã®æ€çŽ¢ã¢ã€ã³ã³ã¯ãã©ãã°ã§ç§»åã§ããäœçœ®ã¯ä¿åãããŸãã
// @description:en Adds a floating modal for advanced search on X.com (formerly Twitter). Syncs with search box and remembers position/display state. The top-right search icon is draggable with persistent position.
// @description:zh-CN 䞺X.comïŒTwitterïŒæ·»å é«çº§æçŽ¢æµ®åšæš¡ææ¡ïŒæ¯æäžæçŽ¢æ¡åååæ¥å¹¶è®°äœäœçœ®äžæŸç€ºç¶æãå³äžè§çæçŽ¢åŸæ 坿åšïŒå¹¶äŒè®°äœäœçœ®ã
// @description:zh-TW çº X.comïŒTwitterïŒå¢å é«çŽæå°æš¡æ
æ¡ïŒæ¯æŽèæå°æ¡éååæ¥äžŠèšäœäœçœ®è顯瀺çæ
ãå³äžè§æå°åç€ºå¯ææ³ïŒäœçœ®æè¢«ä¿åã
// @description:ko X.com(Twitter)ì ê³ êž ê²ì 몚ë¬ì ì¶ê°í©ëë€. ê²ì찜곌 ìë°©í¥ ëêž°ííë©° ìì¹ì íì ìí륌 êž°ìµí©ëë€. ì°ìëš ê²ì ììŽìœì ëëê·ž ìŽë ë° ìì¹ ì ì¥ìŽ ê°ë¥í©ëë€.
// @description:fr Ajoute une fenêtre modale de recherche avancée à X.com (Twitter), synchronisée avec la barre de recherche et mémorise de lâétat dâaffichage. LâicÃŽne de recherche en haut à droite est déplaçable.
// @description:es Agrega un modal flotante de búsqueda avanzada en X.com (Twitter), sincronizado con la caja de búsqueda y con estado persistente.
// @description:de FÃŒgt X.com (Twitter) ein modales Fenster fÃŒr erweiterte Suche hinzu, synchronisiert mit der Suchleiste und speichert Position/Zustand. Das Suchsymbol oben rechts ist per Drag & Drop verschiebbar und bleibt gespeichert.
// @description:pt-BR Adiciona um modal de busca avançada flutuante no X.com (Twitter), sincronizado com a caixa de busca e com estado salvo. O Ãcone de busca no canto superior direito é arrastável com posição persistente.
// @description:ru ÐПбавлÑÐµÑ ÐŒÐŸÐŽÐ°Ð»ÑМПе ПкМП ÑаÑÑОÑеММПгП пПОÑка Ма X.com (Twitter). СОМÑ
ÑПМОзОÑÑеÑÑÑ Ñ Ð¿ÐŸÐžÑкПвПй ÑÑÑПкПй О Ð·Ð°Ð¿ÐŸÐŒÐžÐœÐ°ÐµÑ ÑПÑÑПÑМОе. ÐÐœÐŸÐ¿ÐºÑ Ð¿ÐŸÐžÑка в пÑавПЌ веÑÑ
МеЌ ÑÐ³Ð»Ñ ÐŒÐŸÐ¶ÐœÐŸ пеÑеÑаÑкОваÑÑ; ÐµÑ Ð¿ÐŸÐ»ÐŸÐ¶ÐµÐœÐžÐµ ÑПÑ
ÑаМÑеÑÑÑ.
// @namespace https://github.com/koyasi777/advanced-search-for-x-twitter
// @author koyasi777
// @match https://x.com/*
// @match https://twitter.com/*
// @exclude https://x.com/i/tweetdeck*
// @exclude https://twitter.com/i/tweetdeck*
// @icon https://raw.githubusercontent.com/koyasi777/advanced-search-for-x-twitter/refs/heads/main/extension/icons/icon-128.png
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_info
// @run-at document-idle
// @license MIT
// @homepageURL https://github.com/koyasi777/advanced-search-for-x-twitter
// @supportURL https://github.com/koyasi777/advanced-search-for-x-twitter/issues
// ==/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);
})();
});
}
// ⌠SPA é·ç§»ã®æ žãpushState â åæ popstate â DOMé©çšåŸ
ã¡ â 倱æãªããã©ãŒã«ããã¯
async function spaNavigate(path, { ctrlMeta = false, timeoutMs = 1200 } = {}) {
try {
const to = new URL(path, location.origin);
if (to.origin !== location.origin) throw new Error('cross-origin');
history.pushState(history.state, '', to.pathname + to.search + to.hash);
// X ã®ã«ãŒã¿ãŒã¯ popstate ã賌èªããŠããæ³å®
window.dispatchEvent(new PopStateEvent('popstate', { state: history.state }));
const ok = await waitForRouteApply(to.pathname, timeoutMs);
if (ok) return; // æå
} catch (e) {
// fall through to fallback
}
// ãã©ãŒã«ããã¯ïŒä¿®é£ŸããŒãããªãæ°èŠã¿ãããªããã°éåžžé·ç§»
if (ctrlMeta) window.open(path, '_blank', 'noopener');
else location.assign(path);
}
const uid = () => Math.random().toString(36).slice(2) + Date.now().toString(36);
let isUpdating = false;
let manualOverrideOpen = false;
const lastHistory = { q: null, pf: null, lf: null, ts: 0 };
// ⌠ããŒã¹çµæããã£ãã·ã¥ïŒã¹ã¯ããŒã«æã®åããŒã¹é²æ¢ïŒ
let __cachedSearchTokens = null;
let __cachedSearchQuery = null; // ãã®ã¯ãšãªæååã§ __cachedSearchTokens ãçæããã
// ⌠å
¥åäžã¬ãŒãïŒIMEåæãå«ããŠã«ããŒïŒ
let __typingGuardUntil = 0;
const TYPING_GRACE_MS = 600; // å
¥åçµäºãããã®msã¯ã¹ãã£ã³åæ¢
const markTyping = () => { __typingGuardUntil = Date.now() + TYPING_GRACE_MS; };
const isTyping = () => Date.now() < __typingGuardUntil;
const isMediaViewPath = (pathname) => /\/status\/\d+\/(?:photo|video|media|analytics)(?:\/\d+)?\/?$/.test(pathname);
const isComposePath = (pathname) => /^\/compose\/post(?:\/|$)/.test(pathname);
const isProfileMediaPath = (pathname) => /^\/[A-Za-z0-9_]{1,50}\/(?:photo|header_photo)\/?$/.test(pathname);
const isBroadcastPath = (pathname) => /^\/i\/broadcasts\//.test(pathname);
const isBlockedPath = (pathname) => isMediaViewPath(pathname) || isComposePath(pathname) || isProfileMediaPath(pathname) || isBroadcastPath(pathname);
// ⌠èªåçã«éãããã¹ãã©ãããå€å®ãã颿°
const isAutoClosePath = (pathname) => {
const targets = ['/messages', '/i/grok', '/settings', '/i/chat', '/i/spaces'];
return targets.some(t => pathname.startsWith(t));
};
GM_addStyle(`
:root { --modal-primary-color:#1d9bf0; --modal-primary-color-hover:#1a8cd8; --modal-primary-text-color:#fff; }
#advanced-search-trigger { position:fixed; top:18px; right:20px; z-index:9999; background-color:var(--modal-primary-color); color:var(--modal-primary-text-color); border:none; border-radius:50%; width:50px; height:50px; font-size:24px; cursor:pointer; box-shadow:0 4px 12px rgba(0,0,0,0.15); display:flex; align-items:center; justify-content:center; transition:transform .2s, background-color .2s; }
#advanced-search-trigger:hover { transform:scale(1.1); background-color:var(--modal-primary-color-hover); }
#advanced-search-modal { position:fixed; z-index:10000; width:450px; display:none; flex-direction:column; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background-color:var(--modal-bg, #000); color:var(--modal-text-primary, #e7e9ea); border:1px solid var(--modal-border, #333); border-radius:16px; box-shadow:0 8px 24px rgba(29,155,240,.2); transition:background-color .2s,color .2s,border-color .2s; }
.adv-modal-header{padding:12px 16px;border-bottom:1px solid var(--modal-border,#333);cursor:move;display:flex;justify-content:space-between;align-items:center}
.adv-modal-title-left{display:flex;align-items:center;gap:8px;}
.adv-modal-header h2{margin:0;font-size:18px;font-weight:700}
.adv-settings-btn{
margin-left:6px;
width:26px;height:26px;
border-radius:9999px;
border:1px solid var(--modal-input-border,#38444d);
background:var(--modal-input-bg,#202327);
display:inline-flex;
align-items:center;
justify-content:center;
cursor:pointer;
padding:0;
}
.adv-settings-btn:hover{
background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1));
}
.adv-settings-btn svg{
width:14px;
height:14px;
}
.adv-modal-close{background:0 0;border:none;color:var(--modal-close-color,#e7e9ea);font-size:24px;cursor:pointer;width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color .2s}
.adv-modal-close:hover{background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1))}
.adv-modal-body{flex:1;overflow-y:auto;padding:0}
.adv-form-group{margin-bottom:16px}
.adv-form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:700;color:var(--modal-text-secondary,#8b98a5)}
.adv-form-group input[type=text],.adv-form-group input[type=number],.adv-form-group input[type=date],.adv-form-group select{width:100%;background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:4px;padding:8px 12px;color:var(--modal-text-primary,#e7e9ea);font-size:15px;box-sizing:border-box}
.adv-form-group input:focus,.adv-form-group select:focus{outline:0;border-color:var(--modal-primary-color)}
.adv-form-group input::placeholder{color:var(--modal-text-secondary,#536471)}
.adv-form-group-date-container {display:flex;gap:8px;align-items: center;}
.adv-form-group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
gap: 12px;
}
.adv-form-group-header label {
margin-bottom: 0;
white-space: nowrap;
flex-shrink: 0;
}
.adv-select-mini {
background-color: var(--modal-input-bg, #202327);
color: var(--modal-text-primary, #e7e9ea);
border: 1px solid var(--modal-input-border, #38444d);
border-radius: 18px !important;
font-size: 13px !important;
height: 34px;
line-height: normal;
padding: 0 8px;
width: auto;
min-width: 90px;
max-width: 200px;
text-overflow: ellipsis;
cursor: pointer;
outline: none;
}
.adv-select-mini:hover {
border-color: var(--modal-text-secondary, #8b98a5);
}
.adv-select-mini:focus {
border-color: var(--modal-primary-color);
}
.adv-form-group-date-container input[type=date] {flex:1;min-width: 0;width: auto !important;}
.adv-date-separator {color:var(--modal-text-secondary, #8b98a5);font-weight:700;user-select:none;flex-shrink:0;padding: 0 2px;}
.adv-filter-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.adv-checkbox-group{background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:8px;padding:10px;display:flex;flex-direction:column;gap:8px}
.adv-checkbox-group span{font-weight:700;font-size:14px;color:var(--modal-text-primary,#e7e9ea)}
.adv-checkbox-item{display:flex;align-items:center}
.adv-checkbox-item input{margin-right:8px; accent-color:var(--modal-primary-color);}
.adv-checkbox-item label{color:var(--modal-text-secondary,#8b98a5);margin-bottom:0}
.adv-checkbox-item input[type="checkbox"]:disabled {opacity:0.5; cursor:not-allowed;}
.adv-checkbox-item input[type="checkbox"]:disabled + label {opacity:0.5;cursor:not-allowed;text-decoration:line-through;}
.adv-modal-footer{padding:12px 16px;border-top:1px solid var(--modal-border,#333);display:flex;justify-content:flex-end;gap:12px}
.adv-modal-button{padding:5px 16px;border-radius:9999px;border:1px solid var(--modal-text-secondary,#536471);background-color:transparent;color:var(--modal-text-primary,#e7e9ea);font-weight:700;cursor:pointer;transition:background-color .2s}
.adv-modal-button:hover{background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1))}
.adv-modal-button.primary,
.adv-chip.primary {
background-color:var(--modal-primary-color);
border-color:var(--modal-primary-color);
color:var(--modal-primary-text-color);
}
.adv-modal-button.primary:hover{background-color:var(--modal-primary-color-hover)}
.adv-modal-button[disabled]{opacity:.5; cursor:not-allowed;}
#adv-settings-import.adv-modal-button[disabled]{opacity:1;}
.adv-modal-body::-webkit-scrollbar{width:8px}
.adv-modal-body::-webkit-scrollbar-track{background:var(--modal-scrollbar-track,#202327)}
.adv-modal-body::-webkit-scrollbar-thumb{background:var(--modal-scrollbar-thumb,#536471);border-radius:4px}
body.adv-dragging{user-select:none}
.adv-account-label-group{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.adv-exclude-toggle{display:flex;align-items:center}
.adv-exclude-toggle input{margin-right:4px}
.adv-exclude-toggle label{font-size:13px;font-weight:normal;color:var(--modal-text-secondary,#8b98a5);cursor:pointer}
hr.adv-separator{border:none;height:1px;background-color:var(--hr-color,#333);margin:20px 0;transition:background-color .2s}
/* â
å
šã¿ãå
±éã®ãºãŒã å¯Ÿè±¡ã«æ¡åŒµïŒæ€çŽ¢ã¿ãã®æ¢åidã«ãé©çšç¶æïŒ */
.adv-zoom-root, #adv-zoom-root{ transform-origin: top left; will-change: transform; padding:12px 11.6px 10px 11px; }
#adv-zoom-root {
padding-top: 16px; /* æ€çŽ¢ã¿ãã®äžäœçœã ãã 16px ã«äžæžã */
padding-left:16px; padding-right:20px;
}
.adv-modal-body{ overflow:auto; }
.adv-form-row.two-cols { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
@media (max-width: 480px) { .adv-form-row.two-cols { grid-template-columns:1fr; } }
.adv-tabs {
display: flex;
border-bottom: 1px solid var(--modal-border, #333);
padding: 0 8px 0 6px;
gap: 4px;
align-items: stretch;
flex-wrap: wrap;
container-type: inline-size;
/* ⌠åºå®è¡šç€ºèšå® */
position: sticky;
top: 0;
z-index: 10;
background-color: var(--modal-bg, #000);
box-shadow: var(--modal-tabs-shadow);
}
.adv-tab-btn {
appearance: none;
border: none;
background: transparent;
color: var(--modal-text-secondary, #8b98a5);
padding: 10px 8px;
cursor: pointer;
font-weight: 700;
border-radius: 8px 8px 0 0;
font-size: 0.78rem;
/* ãã¿ã³å
ã®ããã¹ãã¯æãè¿ããªã */
white-space: nowrap;
/* äœã£ãã¹ããŒã¹ãå
šå¡ã§åãåãïŒåçé
çœ®ã»æå€§åïŒ */
flex: 1 1 auto;
text-align: center;
/* ãªããããªå€å */
transition: font-size 0.1s, padding 0.1s, background-color 0.2s;
}
.adv-tab-btn.active {
color: var(--modal-text-primary, #e7e9ea);
background-color: var(--modal-input-bg, #202327);
border: 1px solid var(--modal-input-border, #38444d);
border-bottom: none;
/* ã¢ã¯ãã£ãã¿ãã¯å°ã匷調 */
z-index: 1;
}
/* âŒâŒâŒ ã³ã³ããã¯ãšãª: å¹
ã«å¿ããŠæé©å âŒâŒâŒ */
/* å¹
480px 以äž: ãã©ã³ããå°ãå°ãããã1è¡åãŸããçã */
@container (max-width: 480px) {
.adv-tab-btn {
font-size: 12px;
padding: 8px 4px;
}
}
/* å¹
380px 以äž: ããã«ãã©ã³ããè©°ãããã2è¡ã«ãªã£ãŠãéåæãªããµã€ãºã« */
@container (max-width: 380px) {
.adv-tab-btn {
font-size: 11px;
padding: 6px 2px;
border-radius: 6px; /* è§äžžãå°ãå°ãã */
}
/* 2è¡ã«ãªã£ãéã«äžäžã®åããã£ã€ããããªãããã«ãã */
.adv-tabs {
row-gap: 2px;
}
/* 2è¡ç®ã®ããŒããŒåŠçïŒèŠãç®ãæŽããïŒ */
.adv-tab-btn.active {
border-bottom: 1px solid var(--modal-input-bg, #202327);
margin-bottom: -1px;
}
}
.adv-tab-content { display:none; }
.adv-tab-content.active { display:block; }
.adv-secret-wrap { display:flex; align-items:center; gap:8px; }
.adv-secret-btn { cursor:pointer; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-weight:700; user-select:none; display:flex; align-items:center; gap:6px; font-size:12px; }
.adv-secret-btn .dot { width:7px; height:7px; border-radius:50%; background:#777; box-shadow:0 0 0px #0000; transition:all .2s; }
.adv-secret-btn.off { opacity:0.9; }
.adv-secret-btn.on { background-color:var(--modal-primary-color); border-color:var(--modal-primary-color); color:var(--modal-primary-text-color); }
.adv-secret-btn.on .dot { background:#fff; box-shadow:0 0 8px rgba(255,255,255,.9); }
.adv-list { display:flex; flex-direction:column; gap:8px; }
.adv-item { position: relative; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); border-radius:8px; padding:8px; display:flex; gap:8px; align-items:flex-start; }
.adv-item.dragging { opacity:.6; }
.adv-item-handle { cursor:grab; user-select:none; padding:4px 6px; border-radius:6px; border:1px dashed var(--modal-border,#333); touch-action: none; }
.adv-item-avatar { width:36px; height:36px; border-radius:9999px; object-fit:cover; flex:0 0 auto; background:var(--modal-border,#333); }
a.adv-link { color: inherit; text-decoration: none; }
a.adv-link:hover { text-decoration: underline; cursor: pointer; }
.adv-item-avatar-link { display:inline-block; border-radius:9999px; }
.adv-item-main { flex:1; min-width:0; }
.adv-item-title { font-size:14px; font-weight:700; color:var(--modal-text-primary,#e7e9ea); word-break:break-word; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.adv-item-sub { font-size:12px; color:var(--modal-text-secondary,#8b98a5); margin-top:2px; display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
.adv-item-actions { display:flex; gap:6px; align-items:center; align-self:center; }
.adv-chip { border:1px solid var(--modal-input-border,#38444d); background:transparent; color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-size:12px; cursor:pointer; }
.adv-fav-btn-pos { position: absolute; right: 8px; }
.adv-fav-btn-top { top: 8px; }
.adv-fav-btn-bottom { bottom: 8px; }
.adv-chip.danger { border-color:#8b0000; color:#ffb3b3; }
.adv-modal-button.danger {
border-color:#8b0000;
color:#ffb3b3;
}
.adv-modal-button.danger:hover{
background-color:rgba(139,0,0,0.2);
}
.adv-chip.scope { padding:2px 6px; font-size:11px; line-height:1.2; opacity:0.95; }
.adv-toast { position:fixed; z-index:10001; left:50%; transform:translateX(-50%); bottom:24px; background:#111a; color:#fff; backdrop-filter: blur(6px); border:1px solid #fff3; padding:8px 12px; border-radius:8px; font-weight:700; opacity:0; pointer-events:none; transition:opacity .2s, transform .2s; }
.adv-toast.show { opacity:1; transform:translateX(-50%) translateY(-6px); }
.adv-modal-footer { justify-content:flex-end; }
.adv-modal-footer .adv-modal-button#adv-save-button { margin-right:auto; }
.adv-tab-toolbar {
display:flex;
justify-content: space-between;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom:12px;
padding: 0 2px;
}
/* ããŒã«ããŒã®å·ŠåŽïŒæ€çŽ¢ã»ãœãŒãïŒ */
.adv-tab-toolbar-left {
display: flex;
align-items: center;
gap: 8px;
flex: 1 1 auto;
min-width: 150px;
}
/* ããŒã«ããŒã®å³åŽïŒãã¹ãŠåé€ãã¿ã³ïŒ */
.adv-tab-toolbar-right {
display: flex;
flex: 0 0 auto;
}
/* ããŒã«ããŒå
¥åæ¬ã®å
±éã¹ã¿ã€ã« */
.adv-select, .adv-input {
background-color:var(--modal-input-bg,#202327);
border:1px solid var(--modal-input-border,#38444d);
border-radius:8px;
padding:6px 10px;
color:var(--modal-text-primary,#e7e9ea);
}
/* æ€çŽ¢ããã¯ã¹ãšã»ã¬ã¯ãããã¯ã¹ã®ã¹ã¿ã€ã«ïŒ.adv-folder-toolbarå
ãšå
±éåïŒ */
/* å
±éã¹ã¿ã€ã«ã¯ .adv-input, .adv-select ãæ
åœ */
.adv-tab-toolbar .adv-input {
flex: 1;
min-width: 80px;
}
.adv-tab-toolbar .adv-select {
flex: 0 1 auto;
}
[data-testid="cellInnerDiv"][data-adv-hidden],
article[data-adv-hidden] {
display:none !important;
content-visibility: hidden;
contain: strict;
}
#advanced-search-modal { max-height:none; }
.adv-resizer { position:absolute; z-index:10002; background:transparent; }
.adv-resizer.e, .adv-resizer.w { top:-3px; bottom:-3px; width:8px; }
.adv-resizer.e { right:-3px; cursor: ew-resize; }
.adv-resizer.w { left:-3px; cursor: ew-resize; }
.adv-resizer.n, .adv-resizer.s { left:-3px; right:-3px; height:8px; }
.adv-resizer.n { top:-3px; cursor: ns-resize; }
.adv-resizer.s { bottom:-3px; cursor: ns-resize; }
.adv-resizer.se, .adv-resizer.ne, .adv-resizer.sw, .adv-resizer.nw { width:12px; height:12px; }
.adv-resizer.se { right:-4px; bottom:-4px; cursor:nwse-resize; }
.adv-resizer.ne { right:-4px; top:-4px; cursor:nesw-resize; }
.adv-resizer.sw { left:-4px; bottom:-4px; cursor:nesw-resize; }
.adv-resizer.nw { left:-4px; top:-4px; cursor:nwse-resize; }
/* â¶ Mute ã¿ã */
.adv-mute-add { display:flex; gap:8px; align-items:center; margin-bottom:10px; }
.adv-mute-add input[type=text]{ flex:1; border-radius:8px; padding: 6px 10px; font-size: 14px; }
.adv-mute-list { display:flex; flex-direction:column; gap:8px; }
/* ⌠ã°ããŒãã«ç¡å¹ïŒãã¹ã¿ãŒOFFïŒã®ãšãïŒãªã¹ãå
šäœãæ·¡ã */
.adv-mute-list.disabled {
opacity: .6;
filter: grayscale(35%);
}
/* ⌠åå¥ç¡å¹ïŒenabled=falseïŒã®è¡ã ãæ·¡ãïŒæã¡æ¶ãçã®èŠèŠ */
.adv-mute-item {
border:1px solid var(--modal-input-border,#38444d);
background:var(--modal-input-bg,#202327);
border-radius:8px;
padding:8px 10px;
display:flex;
gap:10px;
justify-content: space-between;
align-items:center;
transition: opacity .15s ease, filter .15s ease, border-color .15s ease;
}
.adv-mute-item.disabled {
opacity: .55;
filter: grayscale(25%);
border-color: color-mix(in oklab, var(--modal-input-border,#38444d), transparent 20%);
}
.adv-mute-item.disabled .adv-mute-word {
color: var(--modal-text-secondary,#8b98a5);
text-decoration: line-through;
}
/* å·ŠåŽã®ã³ã³ããïŒåèªïŒãªãã·ã§ã³ïŒ */
.adv-mute-content-left {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
.adv-mute-word {
font-weight:700;
color:var(--modal-text-primary,#e7e9ea);
word-break:break-word;
font-size: 14px;
}
/* å·Šäžã®ãªãã·ã§ã³çŸ€ */
.adv-mute-options-row {
display: flex;
gap: 12px;
align-items: center;
}
/* å³åŽã®ã³ã³ããïŒåé€ãã¿ã³ã®ã¿ïŒ */
.adv-mute-actions-right {
display:flex;
align-items:center;
justify-content:center;
flex: 0 0 auto;
white-space: nowrap;
padding-left: 4px;
}
@media (max-width: 480px) {
.adv-mute-actions { margin-top: 4px; }
}
.adv-toggle {
display: inline-flex;
gap: 6px;
align-items: center;
color: var(--modal-text-secondary,#8b98a5);
line-height: 1;
margin-bottom:0!important;
}
.adv-toggle input[type="checkbox"] {
width: 14px;
height: 14px;
margin: 0;
flex: 0 0 auto;
vertical-align: middle;
}
.adv-toggle span {
font-size: 11px;
line-height: 1;
}
/* âŒâŒâŒ Mute Header Fix âŒâŒâŒ */
.adv-mute-header {
display:flex;
justify-content:space-between;
align-items:center;
margin: 4px 0 12px;
gap: 10px;
flex-wrap: nowrap; /* æãè¿ããçŠæ¢ããŠ1è¡ã«åŒ·å¶ */
}
.adv-mute-header input[type="text"] {
flex: 1;
min-width: 0;
border-radius: 8px;
padding: 6px 10px;
font-size: 14px;
background-color: var(--modal-input-bg,#202327);
border: 1px solid var(--modal-input-border,#38444d);
color: var(--modal-text-primary,#e7e9ea);
}
.adv-mute-header input[type="text"]:focus {
outline: 0;
border-color: var(--modal-primary-color);
}
.adv-mute-title {
font-weight:700;
color: var(--modal-text-primary,#e7e9ea);
white-space: nowrap; /* ããã¹ãæãè¿ãçŠæ¢ */
overflow: hidden;
text-overflow: ellipsis; /* 溢ããã...ã«ãã */
flex-shrink: 1; /* å¹
äžè¶³æã¯ã¿ã€ãã«åŽãçž®ãã */
min-width: 0;
}
.adv-mute-header-controls {
display: flex;
align-items: center;
gap: 8px; /* äœçœãå°ãè©°ãã */
flex-shrink: 0; /* æäœããã«ã¯çž®ããªã */
}
#adv-mute-mode {
padding: 3px 24px 3px 8px; /* ç¢å°ã¹ããŒã¹èæ
® */
font-size: 12px;
height: 28px;
cursor: pointer;
width: auto;
}
/* ãã¹ã¿ãŒåæ¿ã®äžç¬ã ãä»ããã¬ãŒãã¯ã©ã¹ */
.adv-no-anim, .adv-no-anim * {
transition: none !important;
}
#adv-history-empty:not(:empty),
#adv-saved-empty:not(:empty),
#adv-favorites-empty:not(:empty),
#adv-accounts-empty:not(:empty),
#adv-lists-empty:not(:empty) {
padding-inline: 7px;
}
#adv-mute-empty:not(:empty) {
padding-top: 6px;
}
/* ⌠ãã¹ã¿ãŒOFFäžã¯ãåå¥ç¡å¹ã®âããã«èãâãæå¶ïŒèŠªã®èãã®ã¿é©çšïŒ */
.adv-mute-list.disabled .adv-mute-item.disabled {
opacity: 1; /* åã®è¿œå ã®èããç¡å¹åïŒèŠªã®opacityã®ã¿ãå¹ãïŒ */
filter: none; /* åã®è¿œå ã°ã¬ãŒã¹ã±ãŒã«ãç¡å¹åïŒèŠªåŽã®filterã®ã¿é©çšïŒ */
/* ããŒããŒã ãéåžžè²ã«æ»ã */
/* border-color: var(--modal-input-border,#38444d); */
}
/* === Trigger: ã¢ãŒãã«ãšå質ã®èŠãç®ã«åããã === */
#advanced-search-trigger.adv-trigger-search {
width: 49px; height: 49px;
border-radius: 9999px;
background-color: var(--modal-bg, #000);
color: var(--modal-text-primary, #e7e9ea);
border: 2px solid var(--modal-border, #2f3336); /* â ã¢ãŒãã«ãšåãæ è² */
box-shadow: 0 8px 24px rgba(29,155,240,.2); /* â ã¢ãŒãã«ãšåãshadow */
display:flex; align-items:center; justify-content:center;
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
#advanced-search-trigger.adv-trigger-search:hover {
/* èæ¯ã¯å€ãããæµ®ããã衚çŸã ã匷å */
transform: translateZ(0) scale(1.04);
box-shadow: 0 12px 36px rgba(29,155,240,.28);
border-color: var(--modal-border, #2f3336);
}
#advanced-search-trigger.adv-trigger-search:active {
transform: translateZ(0) scale(0.98);
box-shadow: 0 6px 18px rgba(29,155,240,.22);
}
#advanced-search-trigger.adv-trigger-search:focus-visible {
outline: none;
box-shadow:
0 8px 24px rgba(29,155,240,.2),
0 0 0 3px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 45%, transparent);
}
#advanced-search-trigger.adv-trigger-search svg {
width: 22px; height: 22px;
display:block;
/* æ€çŽ¢ã¢ã€ã³ã³ã¯ stroke="currentColor" ã䜿ã£ãŠããã®ã§é
è²ã¯èªåè¿œåŸ */
}
/* ãªã¹ãã³ã³ããèªäœã«ååãªé«ãã確ä¿ããäžéšã«ããããçšã®äœçœã匷å¶çã«åºãã */
#adv-accounts-list,
#adv-lists-list,
#adv-saved-list {
min-height: 200px; /* ã¢ã€ãã ã空ã§ãããããã§ããããã«ãã */
padding-bottom: 20px;
box-sizing: content-box; /* paddingåã確å®ã«é«ãã«å ãã */
}
/* æªåé¡ã»ã¯ã·ã§ã³ãç©ºã®æãããã©ãã°äžã¯å°ãåºããŠåãå
¥ãããããã */
body.adv-dragging .adv-unassigned {
min-height: 60px;
background-color: rgba(128, 128, 128, 0.05); /* èŠèŠçã«ãšãªã¢ãæç€º */
border-radius: 8px;
transition: min-height 0.2s ease, background-color 0.2s;
}
/* === Folders === */
.adv-folder { border:1px solid var(--modal-input-border,#38444d); border-radius:10px; margin-bottom:10px; }
.adv-folder-header {
display:flex; justify-content:space-between; align-items:center;
padding:8px 10px; background:var(--modal-input-bg,#202327); border-bottom:1px solid var(--modal-input-border,#38444d);
}
.adv-folder[data-drop="1"] { outline:2px dashed var(--modal-primary-color); outline-offset:-2px; }
.adv-folder-title { display:flex; gap:8px; align-items:baseline; }
.adv-folder-actions { display:flex; gap:6px; }
.adv-folder-toolbar { display:flex; gap:8px; align-items:center; margin:0 0 12px; padding:0 2px; }
.adv-folder-toolbar input[type="text"] { flex:1; min-width:80px; }
.adv-folder-collapsed .adv-list { display:none; }
/* â¶ Folder headers: show grab cursor except on action buttons */
.adv-folder-header { cursor: grab; touch-action: none; }
.adv-folder-header:active { cursor: grabbing; }
/* ãã¿ã³äžã§ã¯éåžžã®ãã€ã³ã¿ïŒ=ãã©ãã°éå§ãããªãèŠãç®ïŒ */
.adv-folder-header .adv-folder-actions,
.adv-folder-header .adv-folder-actions * {
cursor: pointer;
}
/* ⌠ãã°ã«ãã¿ã³ïŒå·Šç«¯ïŒ */
.adv-folder-toggle {
appearance: none;
border: none;
background: transparent;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 6px;
cursor: pointer;
margin-right: 6px;
}
.adv-folder-toggle:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 60%, transparent);
}
/* ⌠ã¢ã€ã³ã³ïŒchevronïŒ */
.adv-folder-toggle svg {
width: 16px; height: 16px;
transition: transform .15s ease;
}
/* ⌠ééã§åããå€ããïŒå³â¶ â äžâŒïŒ */
.adv-folder:not(.adv-folder-collapsed) .adv-folder-toggle svg {
transform: rotate(90deg);
}
/* ⌠éããŠããããããŒã¯ãããã«èæ¯åŒ·èª¿ */
.adv-folder:not(.adv-folder-collapsed) .adv-folder-header {
background: color-mix(in oklab, var(--modal-input-bg,#202327) 92%, var(--modal-primary-color,#1d9bf0));
}
/* ⌠ãã©ãã°ãã³ãã«ã¯âæŽããâèŠãç®ã匷調 */
.adv-folder-drag-handle {
cursor: grab;
user-select: none;
padding: 4px 6px;
border-radius: 6px;
border: 1px dashed var(--modal-border,#38444d);
}
.adv-folder-drag-handle:active { cursor: grabbing; }
/* ⌠Unassigned ã»ã¯ã·ã§ã³ïŒèŠåºããªãã»æ ãªãïŒ */
.adv-unassigned {
margin-bottom: 10px;
min-height: 30px; /* â
ç©ºã®æã§ãããããã§ããããã«æå°é«ããç¢ºä¿ */
}
.adv-unassigned .adv-list {
display: flex;
flex-direction: column;
gap: 8px;
}
/* ãã©ã«ããŒäžŠã³æ¿ãçšã®ãã©ãã°æã®èŠèŠïŒUnassigned ãå¯Ÿè±¡ïŒ */
.adv-unassigned.dragging-folder {
opacity: .6;
}
/* ã¿ãèæ¯ããã³ãªã¹ãã³ã³ããèæ¯ãžã®ãããããã€ã©ã€ã */
#adv-tab-accounts.adv-bg-drop-active,
#adv-tab-lists.adv-bg-drop-active,
#adv-tab-saved.adv-bg-drop-active,
#adv-accounts-list.adv-bg-drop-active,
#adv-lists-list.adv-bg-drop-active,
#adv-saved-list.adv-bg-drop-active {
outline: 2px dashed var(--modal-primary-color, #1d9bf0);
/* ãªã¹ãã³ã³ããåŽã¯ããã£ã³ã°ãç¡ããããªãã»ãããå°ãã */
outline-offset: -4px;
}
/* ã¿ãããã«ïŒäžéšäœçœïŒåŽã¯æ¢åã®ãªãã»ãããç¶æ */
#adv-tab-accounts.adv-bg-drop-active,
#adv-tab-lists.adv-bg-drop-active,
#adv-tab-saved.adv-bg-drop-active {
outline-offset: -8px;
}
/* èæ¯ïŒUnassigned å®ãŠïŒãããããäžã¯ããã©ã«ããŒå
ã®âèãæ®åâãæ¶ã */
#adv-tab-accounts.adv-bg-drop-active .adv-list .adv-item.dragging,
#adv-accounts-list.adv-bg-drop-active .adv-list .adv-item.dragging,
#adv-tab-lists.adv-bg-drop-active .adv-list .adv-item.dragging,
#adv-lists-list.adv-bg-drop-active .adv-list .adv-item.dragging,
#adv-tab-saved.adv-bg-drop-active .adv-list .adv-item.dragging,
#adv-saved-list.adv-bg-drop-active .adv-list .adv-item.dragging {
display: none !important;
}
/* === Settings modal === */
#adv-settings-modal.adv-settings-modal{
position:fixed;
inset:0;
z-index:10001;
display:none;
align-items:center;
justify-content:center;
background:rgba(0,0,0,.5);
}
.adv-settings-dialog{
width:420px;
max-width:90vw;
max-height:80vh;
background-color:var(--modal-bg,#000);
color:var(--modal-text-primary,#e7e9ea);
border-radius:16px;
border:1px solid var(--modal-border,#333);
box-shadow:0 8px 24px rgba(0,0,0,.3);
display:flex;
flex-direction:column;
overflow:hidden;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
.adv-settings-header{
padding:12px 16px;
border-bottom:1px solid var(--modal-border,#333);
display:flex;
align-items:center;
justify-content:space-between;
}
.adv-settings-title{
margin:0;
font-size:16px;
font-weight:700;
}
.adv-settings-close{
border:none;
background:transparent;
color:var(--modal-close-color,#e7e9ea);
font-size:20px;
width:32px;
height:32px;
border-radius:50%;
display:flex;
align-items:center;
justify-content:center;
cursor:pointer;
}
.adv-settings-close:hover{
background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1));
}
.adv-settings-body{
padding:12px 16px 23px 16px;
overflow-y:auto;
display:flex;
flex-direction:column;
gap:16px;
}
.adv-settings-group label{
display:block;
margin-bottom:4px;
font-size:14px;
font-weight:700;
color:var(--modal-text-secondary,#8b98a5);
}
.adv-settings-group select,
.adv-settings-group textarea{
width:100%;
background-color:var(--modal-input-bg,#202327);
border:1px solid var(--modal-input-border,#38444d);
border-radius:8px;
padding:8px 10px;
color:var(--modal-text-primary,#e7e9ea);
font-size:14px;
box-sizing:border-box;
}
.adv-settings-group textarea{
resize:vertical;
min-height:80px;
}
.adv-settings-section-header {
margin: 12px 0 2px 0;
padding-bottom: 4px;
border-bottom: 1px solid var(--modal-border,#333);
font-size: 13px;
font-weight: 700;
color: var(--modal-text-primary,#e7e9ea);
}
.adv-settings-toggle-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
}
.adv-settings-toggle-row .adv-toggle {
font-size: 14px;
color: var(--modal-text-primary,#e7e9ea);
user-select: none;
cursor: pointer;
}
.adv-settings-toggle-row .adv-toggle span {
font-size: 14px;
}
/* Simple toggle switch CSS */
.adv-switch {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
}
.adv-switch input {
opacity: 0;
width: 0;
height: 0;
}
.adv-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--modal-input-border,#38444d);
transition: .2s;
border-radius: 22px;
}
.adv-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 3px;
bottom: 3px;
background-color: var(--modal-bg, #000);
transition: .2s;
border-radius: 50%;
}
.adv-switch input:checked + .adv-slider {
background-color: var(--modal-primary-color);
}
.adv-switch input:checked + .adv-slider:before {
transform: translateX(18px);
}
.adv-settings-actions-inline{
display:flex;
gap:8px;
margin-top:6px;
flex-wrap:wrap;
}
.adv-settings-footer{
padding:10px 16px;
border-top:1px solid var(--modal-border,#333);
display:flex;
justify-content:flex-end;
gap:8px;
}
/* === Tab Drag & Drop === */
.adv-tab-btn {
user-select: none;
}
.adv-tab-btn:active {
cursor: grabbing;
}
.adv-tab-btn.dragging {
opacity: .5;
}
/* --- Favorite Tags CSS --- */
/* ⌠ããã¯ããŒã¯UIå°çšã®é
è²å€æ°ãå®çŸ© */
:root {
/* ããã©ã«ã (Dim / Dark) ã¯éçãªããŒã¯ããŒã */
--ft-bg: rgb(21, 24, 28);
--ft-border-light: rgba(239, 243, 244, 0.24);
--ft-border-dim: rgba(239, 243, 244, 0.15);
--ft-border-strong: rgba(239, 243, 244, 0.3);
--ft-border-accent: rgba(239, 243, 244, 0.8);
--ft-text-primary: rgb(239, 243, 244);
--ft-text-secondary: rgba(239, 243, 244, 0.7);
--ft-input-bg: rgba(0,0,0,0.2);
--ft-input-border: rgba(239,243,244,0.2);
--ft-hover-bg: rgba(255, 255, 255, 0.06);
--ft-hover-bg-strong: rgba(255, 255, 255, 0.08);
--ft-accent-color: #1d9bf0;
}
:root.x-theme-light {
/* LightããŒãã®æã ããXæ¬äœã®åç倿°ãåç
§ãã */
--ft-bg: var(--modal-bg);
--ft-border-light: var(--modal-border);
--ft-border-dim: var(--modal-border);
--ft-border-strong: var(--modal-text-secondary);
--ft-border-accent: var(--modal-text-primary);
--ft-text-primary: var(--modal-text-primary);
--ft-text-secondary: var(--modal-text-secondary);
--ft-input-bg: var(--modal-input-bg);
--ft-input-border: var(--modal-input-border);
--ft-hover-bg: var(--modal-button-hover-bg);
--ft-hover-bg-strong: var(--modal-button-hover-bg);
--ft-accent-color: var(--modal-primary-color);
}
/* Tag chip on tweet header */
.ft-tag-chip {
display: inline-flex;
align-items: center;
margin-left: 4px; /* JS (ft_attachTagChipToArticle) åŽã® gap: 4px ãšé£å */
padding: 1px 8px;
border-radius: 9999px;
border: 1px solid currentColor;
font-size: 11px;
line-height: 1.4;
cursor: pointer;
user-select: none;
white-space: nowrap;
background: rgba(255, 255, 255, 0.03); /* ããã¯éçãªãŸãŸ (ã»ãŒéæãªã®ã§) */
flex: 0 0 auto;
order: 9999;
}
.ft-tag-chip-label {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
.ft-tag-chip-uncategorized {
opacity: 0.7;
}
/* Dropdown for selecting tag / filter */
.ft-tag-dropdown {
position: fixed;
z-index: 2147482000;
min-width: 220px;
max-width: 260px;
max-height: 60vh;
overflow-y: auto;
padding: 8px;
border-radius: 12px;
border: 1px solid var(--ft-border-light);
background: var(--ft-bg);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.7);
font-size: 13px;
color: var(--ft-text-primary);
}
.ft-tag-dropdown-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
font-weight: 600;
}
.ft-tag-dropdown-close {
border: none;
background: transparent;
color: inherit;
cursor: pointer;
padding: 2px 4px;
}
.ft-tag-dropdown-tags {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 8px;
}
.ft-tag-dropdown-tag-item {
display: flex;
align-items: center;
padding: 4px 6px;
border-radius: 6px;
cursor: pointer;
}
.ft-tag-dropdown-tag-item:hover {
background: var(--ft-hover-bg);
}
.ft-tag-dropdown-tag-color {
width: 10px;
height: 10px;
border-radius: 9999px;
margin-right: 6px;
}
.ft-tag-dropdown-tag-label {
flex: 1;
}
.ft-tag-dropdown-tag-selected::after {
content: 'â';
margin-left: 6px;
font-size: 11px;
}
/* New tag row in dropdown */
.ft-tag-dropdown-new {
border-top: 1px solid var(--ft-border-dim);
padding-top: 6px;
display: flex;
flex-direction: column;
gap: 4px;
}
.ft-tag-dropdown-new-row {
display: flex;
gap: 4px;
}
.ft-tag-dropdown-new-input {
flex: 1;
background: var(--ft-input-bg);
border: 1px solid var(--ft-input-border);
border-radius: 6px;
padding: 3px 6px;
color: inherit;
}
.ft-tag-dropdown-new-color {
width: 36px;
padding: 0;
border-radius: 6px;
border: 1px solid var(--ft-input-border);
background: transparent;
}
.ft-tag-dropdown-new-button {
border-radius: 6px;
border: 1px solid var(--ft-border-strong);
background: transparent;
color: inherit;
padding: 2px 6px;
font-size: 12px;
cursor: pointer;
}
.ft-tag-dropdown-new-button:hover {
background: var(--ft-hover-bg);
}
/* Bookmark header controls (ããŒã倿°é©çš) */
.ft-filter-button {
border-radius: 8px;
border: 1px solid var(--modal-border, rgba(239,243,244,0.3));
background: var(--modal-input-bg, rgba(0,0,0,0.2));
color: var(--modal-text-primary, rgb(239,243,244));
font-size: 14px;
padding: 4px 10px;
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.ft-filter-button-label {
max-width: 140px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ft-filter-button-caret {
font-size: 10px;
opacity: 0.8;
}
.ft-filter-button[disabled] {
opacity: 0.4;
cursor: default;
}
.ft-filter-button:not([disabled]):hover {
background: var(--modal-button-hover-bg, rgba(255,255,255,0.06));
border-color: var(--modal-text-secondary, rgba(239,243,244,0.6));
}
.ft-settings-button {
border-radius: 9999px;
width: 26px;
height: 26px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--modal-border, rgba(239,243,244,0.3));
background: var(--modal-input-bg, rgba(0,0,0,0.2));
color: var(--modal-text-primary, rgb(239,243,244));
cursor: pointer;
}
.ft-settings-button:hover {
background: var(--modal-button-hover-bg, rgba(255,255,255,0.06));
}
/* Settings modal */
.ft-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
z-index: 2147483000;
display: flex;
align-items: center;
justify-content: center;
}
.ft-modal {
width: min(380px, 100vw - 32px);
max-height: 80vh;
border-radius: 16px;
background: var(--ft-bg);
border: 1px solid var(--ft-border-light);
box-shadow: 0 20px 40px rgba(0,0,0,0.75);
display: flex;
flex-direction: column;
color: var(--ft-text-primary);
}
.ft-modal-header {
padding: 10px 14px;
border-bottom: 1px solid var(--ft-border-dim);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.ft-modal-title {
font-size: 14px;
font-weight: 600;
}
.ft-modal-toggle {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.ft-modal-toggle input[type="checkbox"] {
accent-color: var(--ft-accent-color);
}
.ft-modal-body {
padding: 10px 14px 12px;
overflow-y: auto;
font-size: 13px;
}
.ft-modal-footer {
padding: 8px 14px 10px;
border-top: 1px solid var(--ft-border-dim);
display: flex;
justify-content: flex-end;
gap: 8px;
}
.ft-modal-button {
border-radius: 9999px;
border: 1px solid var(--ft-border-strong);
background: transparent;
color: inherit;
font-size: 12px;
padding: 4px 10px;
cursor: pointer;
}
.ft-modal-button:hover {
background: var(--ft-hover-bg);
}
/* Display settings section */
.ft-modal-display-settings {
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--ft-border-dim);
font-size: 12px;
}
.ft-modal-display-settings-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-top: 4px;
}
.ft-modal-display-radio {
display: inline-flex;
align-items: center;
gap: 4px;
}
/* Tag list in modal */
.ft-modal-tag-list {
display: flex;
flex-direction: column;
gap: 6px;
padding-bottom: 30px; /* äœçœã倧ããåã */
min-height: 100px; /* 空ã£ãœã§ãããããã§ããããã« */
position: relative; /* ã«ãŒãããããã®æ ç·è¡šç€ºçš */
box-sizing: content-box; /* paddingãå«ããªãé«ãèšç® */
}
/* äžçªäžã®äœçœã«ãã©ãã°ããæã«ããªã¹ãå
šäœã®äžã«æ ç·ãåºãã¯ã©ã¹ */
.ft-modal-tag-list.ft-drag-to-root::after {
content: '';
position: absolute;
bottom: 20px; /* äœçœã®äžã»ã©ã«ç·ãåŒã */
left: 0;
right: 0;
height: 2px;
background-color: var(--modal-primary-color, #1d9bf0);
box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0);
}
.ft-modal-tag-item {
position: relative;
display: grid;
/* [mainCell] [dragHandle] [orderButtons] [deleteBtn] */
grid-template-columns: minmax(0, 1fr) auto auto auto;
align-items: center;
gap: 6px;
/* cursor: grab; ãåé€ (ãã³ãã«ãæ
åœ) */
}
.ft-modal-tag-main {
display: flex;
align-items: center;
gap: 6px;
}
.ft-modal-tag-item-dragging {
opacity: 0.6;
}
.ft-modal-tag-item-drop-before::before,
.ft-modal-tag-item-drop-after::after {
content: '';
position: absolute;
left: 0;
right: 0;
height: 1px;
/* åŸæ¥ã®è²ïŒçœã£ãœãã°ã¬ãŒïŒ */
background-color: var(--ft-border-accent, rgba(239, 243, 244, 0.8));
border: none; /* border-top/bottom ã background-color ã«å€æŽããŠçµ±äž */
pointer-events: none;
}
.ft-modal-tag-item-drop-before::before { top: -3.5px; }
.ft-modal-tag-item-drop-after::after { bottom: -3.5px; }
/* ã«ãŒãéå±€çšïŒéãç·ïŒ */
.ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before,
.ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after {
background-color: var(--modal-primary-color, #1d9bf0);
box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0); /* çºå
ãããŠåŒ·èª¿ */
height: 2px;
z-index: 10;
}
/* éãç·ã®äœçœ®ïŒå€ªããªã£ãåããããã¯åŒ·èª¿ã®ããå°ãå€åŽã«åºããïŒ */
.ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before {
top: -4.2px;
}
.ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after {
bottom: -4.2px;
}
.ft-modal-tag-item-drop-child {
background: var(--ft-hover-bg-strong);
}
.ft-modal-tag-name {
background: var(--ft-input-bg);
border-radius: 6px;
border: 1px solid var(--ft-input-border);
padding: 3px 6px;
color: inherit;
font-size: 12px;
}
.ft-modal-tag-color {
width: 40px;
padding: 0;
border-radius: 6px;
border: 1px solid var(--ft-input-border);
background: transparent;
}
.ft-modal-tag-order,
.ft-modal-tag-delete {
border-radius: 6px;
border: 1px solid var(--ft-border-strong);
background: transparent;
color: inherit;
padding: 2px 4px;
cursor: pointer;
font-size: 11px;
}
.ft-modal-tag-order:hover,
.ft-modal-tag-delete:hover {
background: var(--ft-hover-bg-strong);
}
/* --- Drag handle for tag settings --- */
.ft-modal-tag-drag-handle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 4px;
cursor: grab;
color: var(--ft-text-secondary);
user-select: none;
touch-action: none;
}
.ft-modal-tag-drag-handle:hover {
background: var(--ft-hover-bg-strong);
color: var(--ft-text-primary);
}
/* Uncategorized: disable drag */
.ft-modal-tag-item[data-kind="uncat"] .ft-modal-tag-drag-handle {
cursor: not-allowed;
opacity: 0.5;
}
/* New tag row */
.ft-modal-new-tag {
border-top: 1px solid var(--ft-border-dim);
padding-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.ft-modal-new-tag-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
gap: 6px;
}
/* æªåé¡ã¯åå倿Žäžå¯ïŒåé€äžå¯ã®èŠèŠè¡šçŸ */
.ft-modal-tag-name[readonly] {
cursor: not-allowed;
opacity: 0.8;
}
.ft-modal-tag-delete:disabled {
cursor: not-allowed;
opacity: 0.4;
}
/* Hidden helper */
.ft-hidden {
display: none !important;
content-visibility: hidden;
contain: strict;
}
/* --- End Favorite Tags CSS --- */
/* --- Favorites Feature --- */
.adv-fav-btn {
display: inline-flex; align-items: center; justify-content: center;
background: transparent; border: none; cursor: pointer;
color: rgb(83, 100, 113); /* Default grey */
padding: 0; margin: 0;
width: 34.75px; height: 34.75px; /* X standard icon size touch target */
border-radius: 50%;
transition: background-color 0.2s, color 0.2s;
}
/* ãã€ãã£ãã®ã¯ã©ã¹ãåçšããæã¯åºå®ãµã€ãºãç¡å¹åãã */
.adv-fav-btn.adv-native-style {
width: auto;
height: auto;
min-width: 34.75px; /* æäœéã®å€§ããã¯ç¢ºä¿ */
min-height: 34.75px;
}
.adv-fav-btn:hover {
background-color: rgba(29, 155, 240, 0.1);
color: rgb(29, 155, 240);
}
.adv-fav-btn.active {
color: rgb(249, 24, 128); /* Pink/Red like Like, or Gold? Let's use Gold for Star */
color: rgb(255, 215, 0);
}
.adv-fav-btn.active:hover {
background-color: rgba(255, 215, 0, 0.1);
}
.adv-fav-btn svg {
width: 20px; height: 20px;
fill: currentColor;
}
.adv-item-body-text {
font-size: 13px; color: var(--modal-text-primary); margin-top: 4px;
white-space: pre-wrap; /* æ¹è¡ãç¶æ */
word-break: break-word; /* é·ãåèªãæãè¿ã */
}
/* Favorites Media */
.adv-item-media-row {
display: flex;
gap: 4px;
margin-top: 6px;
overflow-x: auto;
padding-bottom: 2px;
}
.adv-item-media-row::-webkit-scrollbar { height: 4px; }
.adv-item-media-row::-webkit-scrollbar-thumb { background: var(--modal-border); border-radius: 2px; }
.adv-media-thumb {
height: 60px;
min-width: 60px;
border-radius: 6px;
border: 1px solid var(--modal-border);
object-fit: cover;
cursor: pointer;
}
/* Favorites Quote */
.adv-quote-box {
margin-top: 8px;
border: 1px solid var(--modal-border);
border-radius: 12px;
padding: 8px 12px;
background-color: rgba(0, 0, 0, 0.03);
}
.adv-quote-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
font-size: 12px;
}
.adv-quote-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
object-fit: cover;
}
.adv-quote-name {
font-weight: 700;
color: var(--modal-text-primary);
}
.adv-quote-handle {
color: var(--modal-text-secondary);
}
.adv-quote-text {
font-size: 13px;
color: var(--modal-text-primary);
white-space: pre-wrap;
word-break: break-word;
}
/* Content Link */
.adv-content-link {
color: var(--modal-primary-color);
text-decoration: none;
}
.adv-content-link:hover {
text-decoration: underline;
}
/* Media Play Icon */
.adv-media-wrap {
position: relative;
display: inline-flex;
}
.adv-media-play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 24px;
height: 24px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none; /* ã¯ãªãã¯ãäžã®ç»å(ãªã³ã¯)ã«ééããã */
backdrop-filter: blur(1px);
z-index: 1;
}
.adv-media-play-icon svg {
width: 14px;
height: 14px;
fill: currentColor;
display: block;
margin-left: 2px;
}
/* Favorites Item Tag Container */
.adv-fav-tag-container {
margin-top:0.7px;
margin-left: 2px;
display: inline-flex;
align-items: center;
}
/* --- Mute Collapse Styles --- */
/* Hard Mute: data-adv-hidden */
[data-testid="cellInnerDiv"][data-adv-hidden],
article[data-adv-hidden] {
display: none !important;
content-visibility: hidden;
contain: strict;
}
/* Soft Mute: data-adv-collapsed */
/* 1. Hide original content */
[data-testid="cellInnerDiv"][data-adv-collapsed] > div:not(.adv-collapsed-placeholder),
article[data-adv-collapsed] > div:not(.adv-collapsed-placeholder) {
display: none !important;
}
/* 2. Show placeholder */
.adv-collapsed-placeholder {
display: none;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background-color: var(--modal-input-bg, #202327);
border-bottom: 1px solid var(--modal-border, #38444d);
cursor: pointer;
user-select: none;
}
.adv-collapsed-placeholder:hover {
background-color: color-mix(in srgb, var(--modal-input-bg, #202327) 85%, var(--modal-text-primary, #e7e9ea));
}
[data-testid="cellInnerDiv"][data-adv-collapsed] .adv-collapsed-placeholder,
article[data-adv-collapsed] .adv-collapsed-placeholder {
display: flex !important;
}
.adv-collapsed-label {
flex: 1;
font-size: 13px;
color: var(--modal-text-secondary, #8b98a5);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 12px;
}
.adv-btn-show {
background: transparent;
border: 1px solid var(--modal-primary-color, #1d9bf0);
color: var(--modal-primary-color, #1d9bf0);
border-radius: 9999px;
padding: 4px 16px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: background-color 0.2s;
}
.adv-btn-show:hover {
background-color: rgba(29, 155, 240, 0.1);
}
/* ã¿ã°ãããã®ãµã€ãºåŸ®èª¿æŽ */
.adv-item-sub .ft-tag-chip {
margin-left: 8px;
font-size: 10px;
padding: 0 6px;
height: 18px;
}
/* âŒâŒâŒ åãã¥ãŒããã¿ã³ã®ã¹ã¿ã€ã« âŒâŒâŒ */
.adv-btn-remute {
margin-right: 12px; /* Caret(âŠ)ãšã®ééãç¢ºä¿ */
padding: 4px 12px; /* ã¯ãªãã¯ããããããå°ãæ¡å€§ */
font-size: 12px;
font-weight: 700;
border-radius: 9999px;
border: 1px solid var(--modal-border, #38444d);
color: var(--modal-text-secondary, #8b98a5);
background: transparent;
cursor: pointer;
white-space: nowrap;
display: inline-flex;
align-items: center;
height: 28px; /* ããããŒã®ã¢ã¯ã·ã§ã³ãã¿ã³ã®é«ãã«åããã */
line-height: 1;
transition: all 0.2s;
}
.adv-btn-remute:hover {
background: rgba(244, 33, 46, 0.1); /* Red tint */
color: rgb(244, 33, 46);
border-color: rgb(244, 33, 46);
}
/* âŒâŒâŒ æ€çŽ¢å
¥åäžã®ãã©ãŒã«ã¹å¶åŸ¡ (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); /* ãããŒæã«èãéè²ã衚瀺 */
}
`);
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. User-Name ãèµ·ç¹ã«ãã (ã¿ã€ã ã©ã€ã³ã§ã詳现衚瀺ã§ãå¿
ãããããŒã«ååšãã)
const userName = article.querySelector('[data-testid="User-Name"]');
if (userName) {
// User-Name ã®èŠªãé¡ãããã³ãã«ããŒã (@...)ãæé衚瀺ãå«ããè¡ã³ã³ããããæ¢ã
// æ§é : [Container] -> [NameWrapper] -> [User-Name]
// L-> [HandleWrapper] -> [@handle]
let p = userName.parentElement;
// èŠªãæ°åé¡ã£ãŠãå
åŒèŠçŽ ã«ã@ããå§ãŸãããã¹ãïŒãã³ãã«ïŒããå«ãã³ã³ãããæ¢ã
// â»éåžžã¯2ïœ3éå±€äž
while (p && p !== article) {
// èªåã®èŠªã®çŽäž(å
åŒèŠçŽ )ã«ãèªå以å€ã§ã@ãããå§ãŸãããã¹ããæã€èŠçŽ ãããã確èª
const hasHandleSibling = Array.from(p.children).some(sib => {
// èªåèªèº«ã®ã©ãããŒã¯é€å€
if (sib.contains(userName)) return false;
// ããã¹ããååŸã㊠@ ã§å§ãŸã£ãŠãããå€å®
const txt = sib.innerText || '';
return txt.trim().startsWith('@');
});
if (hasHandleSibling) {
// ãã³ãã«ããŒã ãšäžŠãã§ããã³ã³ãããèŠã€ãã£ããããããæ¿å
¥å Žæ
return p;
}
p = p.parentElement;
}
}
// 2. ãã©ãŒã«ããã¯: åŸæ¥ã®Timeæ€çŽ¢ (ãã ãåŒçšãã€ãŒãå
ã®Timeã¯å³å¯ã«é€å€ãã)
const allTimes = article.querySelectorAll('time');
for (const timeEl of allTimes) {
// åŒçš(role="link")ã®äžã«ããtimeã¯ç¡èŠããŠã¹ããã
if (timeEl.closest('div[role="link"]')) continue;
const anchor = timeEl.closest('a');
if (anchor && anchor.parentElement && anchor.parentElement.parentElement) {
return anchor.parentElement.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;
// âŒâŒâŒ ã¹ã¿ã€ã«ã®é©çš âŒâŒâŒ
headerRow.style.display = 'flex';
headerRow.style.flexDirection = 'row';
headerRow.style.alignItems = 'center';
headerRow.style.justifyContent = 'flex-start';
headerRow.style.columnGap = '4px';
// ã¹ããŒã¹ãè¶³ããªãå Žåã«æãè¿ããèš±å¯ãã
headerRow.style.flexWrap = 'wrap';
// æãè¿ããéãäžäžã®è¡ã«ééãäœã
headerRow.style.rowGap = '8px';
let existing = headerRow.querySelector('.ft-tag-chip');
const chip = ft_buildTagChip(tweetId);
if (existing) {
existing.replaceWith(chip);
} else {
headerRow.appendChild(chip);
}
article.classList.add('ft-chip-attached');
}
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;
document.body.appendChild(modalContainer);
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}`;
}
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;
}
});
}
// è¡ã¬ã³ããªã³ã°
// addEventListener ãå
šåé€ããçŽç²ãªDOMçæã®ã¿ã«ãã
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);
// --- åŒçšHTML ---
let quoteHtml = '';
if (item.quote) {
const q = item.quote;
const qUserUrl = `/${escapeAttr(q.user.handle)}`;
const qMediaHtml = buildMediaHtml(q.media, true);
const qBodyHtml = safeLinkify(q.text);
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}
${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>
`;
// ã¿ã°ãããã®çæãšæ¿å
¥
const tagContainer = row.querySelector('.adv-fav-tag-container');
if (tagContainer && typeof ft_buildTagChip === 'function') {
const chip = ft_buildTagChip(item.id);
// 泚èš: ãã㯠ft_installGlobalListeners ã§å§è²ãããŠãããããåå¥ã® addEventListener ã¯äžèŠã§ã
// ãã ft_buildTagChip å
ã§ã€ãã³ããä»ããŠããå Žåã¯ãã®ãŸãŸæ©èœããŸã
tagContainer.appendChild(chip);
}
return row;
}
// ãæ°ã«å
¥ãã¿ãå°çšã®çŸåšã®çµã蟌ã¿ç¶æ
ïŒã¡ã¢ãªä¿æïŒ
let favFilterTagId = 'ALL'; // 'ALL', 'UNCAT', or tagId
let favSearchQuery = '';
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 filtered = 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 ããã©ãŒã«ããã¯ãšããŠäœ¿ã
filtered.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. ãªã¹ãæç»
listEl.innerHTML = '';
// å
šããŒã¿(allItems)ãç©ºã®æã ãã¡ãã»ãŒãžãåºãã
// æ€çŽ¢ããã£ã«ã¿ã§ãããããªãã£ãã ããªããã¡ãã»ãŒãžã¯åºããã«ç©ºæ¬ã«ããã
if (allItems.length === 0) {
emptyEl.textContent = i18n.t('emptyFavorites');
emptyEl.style.display = 'block';
} else {
emptyEl.style.display = 'none';
filtered.forEach(item => {
const row = renderFavoriteRow(item);
listEl.appendChild(row);
});
}
}
/* ã¿ãããšä¿åã«å¯Ÿå¿ */
const ZOOM_KEYS = {
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 = {
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 = () => {
const tab = getActiveTabName();
const el = getActiveZoomRoot();
if (!el) return;
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)}%`;
}
};
const setZoomActiveTab = (z) => {
const tab = getActiveTabName();
zoomByTab[tab] = clampZoom(z);
applyZoom();
saveZoomFor(tab);
};
/* ã¿ãèŠåºãã¯æ¡å€§ããªãïŒ.adv-zoom-rootã®å
åŽã ãåå¿ */
const onWheelZoom = (e) => {
const isAccel = e.ctrlKey || e.metaKey;
if (!isAccel) return;
if (!e.target.closest('.adv-zoom-root')) return; // ã¿ãããŒçã¯é€å€
e.preventDefault();
const tab = getActiveTabName();
const cur = zoomByTab[tab] ?? 1.0;
const factor = e.deltaY > 0 ? (1 - ZOOM_STEP) : (1 + ZOOM_STEP);
setZoomActiveTab(cur * factor);
};
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"]');
// ä¿åãããå¹
ãååŸ
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 (_) {}
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 = () => {
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 = '450px';
if (s.h) modal.style.height = `${Math.max(minH, Math.min(s.h, window.innerHeight - 20))}px`;
else modal.style.height = '';
} catch(e) { console.error('Failed to apply modal position:', e); }
};
const keepModalInViewport = () => {
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);
}
};
const saveTriggerRelativeState = () => {
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;
const state = { h_anchor, h_value, v_anchor, v_value };
kv.set(TRIGGER_STATE_KEY, JSON.stringify(state));
};
const applyTriggerStoredPosition = () => {
try {
const s = JSON.parse(kv.get(TRIGGER_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 ?? 18;
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();
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();
} 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
historyListEl.innerHTML = '';
historyEmptyEl.textContent = listAll.length === 0 ? i18n.t('emptyHistory') : '';
listSorted.forEach(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 });
// activateTab('search');
executeSearch({ pf: item.pf, lf: item.lf });
});
row.querySelector('[data-action="delete"]').addEventListener('click', () => {
deleteHistory(item.id);
});
historyListEl.appendChild(row);
});
};
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ããªã³ã¯åããïŒHTMLãšã¹ã±ãŒãæžã¿ããã¹ãã«å¯ŸããŠé©çšïŒ
function safeLinkify(text) {
if (!text) return '';
let escaped = escapeHTML(text);
// Xã®ä»æ§ã§ https:// ã®çŽåŸã«äžå¯èŠãªç©ºçœãæ¹è¡ãå«ãŸããå Žåãããããé€å»
// ããã«ãã "https:// amzn.to" ã "https://amzn.to" ã«çµåãããå
šäœãæ£ãããªã³ã¯åãããŸã
escaped = escaped.replace(/(https?:\/\/)\s+/gi, '$1');
// URLæ£èŠè¡šçŸ
// Group 1: http/https/www ã§å§ãŸãURL
// Group 2: ãããã³ã«ãªãã®ãã¡ã€ã³ (誀æ€ç¥é²æ¢ã®åŸèªã¿ä»ã)
const urlRegex = /((?:https?:\/\/|www\.)[^\s]+)|((?<![@\w.:/\-])\b[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/gi;
return 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;
}
return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="adv-content-link">${cleanUrl}</a>${suffix}`;
});
}
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=>{
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) => {
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 {}; } })();
const desiredVisible = !!stored.visible;
const blocked = isBlockedPath(location.pathname);
// ⌠çŸåšã®ãã¹ãèªåã¯ããŒãºå¯Ÿè±¡ãå€å®
const autoClose = isAutoClosePath(location.pathname);
if (blocked) {
trigger.style.display = 'none';
} else {
trigger.style.display = '';
applyTriggerStoredPosition();
requestAnimationFrame(keepTriggerInViewport);
}
// ⌠èªåã¯ããŒãºå¯Ÿè±¡ã®å ŽåãæåãªãŒãã³(manualOverrideOpen)ãããŠããªããã°é ã
// desiredVisibleïŒèšå®å€ïŒã true ã§ããautoClose ãšãªã¢ã«ããéã¯ç¡èŠããã
// ãšãªã¢ããåºãã° autoClose ã false ã«ãªããdesiredVisible ãåã³æå¹ã«ãªãïŒïŒåŸ©æŽ»ïŒ
const shouldShow = (!blocked) && ( (desiredVisible && !autoClose) || manualOverrideOpen );
const wasShown = (modal.style.display === 'flex');
modal.style.display = shouldShow ? 'flex' : 'none';
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';
saveModalRelativeState();
} else {
manualOverrideOpen = true;
modal.style.display = 'flex';
syncFromSearchBoxToModal();
applyScopesToControls(readScopesFromURL());
applyModalStoredPosition();
requestAnimationFrame(keepModalInViewport);
applyZoom();
saveModalRelativeState();
updateSaveButtonState();
}
});
const closeModal = () => {
manualOverrideOpen = false;
modal.style.display = 'none';
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));
}
}
muteListEl.innerHTML = '';
muteEmptyEl.textContent = list.length ? '' : i18n.t('emptyMuted');
list.forEach(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));
muteListEl.appendChild(row);
});
};
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;
if (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);
queueMicrotask(fireIfChanged);
return ret;
};
};
wrapHistory('pushState'); wrapHistory('replaceState');
window.addEventListener('popstate', 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(fireIfChanged, 0);
}
} catch(_) {}
}, true);
return fireIfChanged;
};
// ãã€ãŒãã®DOMããä¿åçšããŒã¿ãæœåº
function ft_extractTweetMeta(article, tweetId) {
const text = article.querySelector('[data-testid="tweetText"]')?.innerText || '';
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 = [];
rootElement.querySelectorAll('div[data-testid="tweetPhoto"] img').forEach(m => {
if (excludeElement && excludeElement.contains(m)) return;
if (m.src) extracted.push({ type: 'image', url: m.src });
});
rootElement.querySelectorAll('video').forEach(v => {
if (excludeElement && excludeElement.contains(v)) return;
if (v.poster) extracted.push({ type: 'video', url: v.poster });
});
const unique = [];
const seen = new Set();
for (const m of extracted) {
if (!seen.has(m.url)) { seen.add(m.url); unique.push(m); }
}
return unique;
};
// åŒçšã³ã³ããç¹å®
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 qText = quoteContainer.querySelector('[data-testid="tweetText"]')?.innerText || '';
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 = '';
const photoLink = quoteContainer.querySelector('a[href*="/status/"][href*="/photo/"]');
if (photoLink) {
const m = photoLink.getAttribute('href').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
};
}
return {
id: tweetId,
text,
user: { name, handle, avatar },
media: mainMedia,
quote,
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);
}
// ãã¿ã³çæããžãã¯ãåé¢ïŒå
±éåïŒ
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', (e) => {
e.preventDefault();
e.stopPropagation();
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();
});
};
window.addEventListener('resize', debounce(()=>{
if (modal.style.display === 'flex') { applyModalStoredPosition(); requestAnimationFrame(keepModalInViewport); }
if (trigger.style.display !== 'none') { applyTriggerStoredPosition(); requestAnimationFrame(keepTriggerInViewport); }
}, 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();
renderFavorites(); // åææç» (ããã§ ft_state ãããç¶æ
ã§èµ°ã)
renderHistory();
renderSaved();
renderAccounts();
renderMuted();
// ã¹ãã察å¿çšïŒã¿ããæäœããã©ãã°æäœãžå€æãããªã¹ããŒãç»é²
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__;
}