mirror of
https://github.com/insin/control-panel-for-twitter.git
synced 2025-06-18 14:45:31 -04:00
Compare commits
4 Commits
dafaafd930
...
b3076019c8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b3076019c8 | ||
![]() |
331f79215b | ||
![]() |
c6ce159f34 | ||
![]() |
33fb01ff22 |
23
options.css
23
options.css
@ -129,7 +129,6 @@ section.group > label {
|
||||
|
||||
section.group > section > * {
|
||||
color: rgb(95, 99, 104);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
@ -148,12 +147,19 @@ section.group > section > p {
|
||||
|
||||
section.textarea {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 4px 12px 4px 0;
|
||||
margin: 8px 0;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
section.textarea > textarea {
|
||||
margin: 8px 12px 12px 0;
|
||||
}
|
||||
|
||||
section.textarea > button {
|
||||
margin: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
section.textarea > p {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
section.textarea button {
|
||||
@ -162,7 +168,9 @@ section.textarea button {
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* Prevent zooming */
|
||||
body.iOS.safari textarea {
|
||||
font-size: 16px;
|
||||
@ -498,6 +506,9 @@ body.iOS.safari .checkbox input:focus + .toggle {
|
||||
}
|
||||
|
||||
/* Safari dark mode overrides */
|
||||
body.macOS.safari {
|
||||
background-color: transparent;
|
||||
}
|
||||
body.macOS.safari p {
|
||||
color: rgb(184, 184, 184) !important;
|
||||
}
|
||||
|
@ -796,7 +796,7 @@
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<div id="version">v4.12.1<span id="debugCountdown"></span></div>
|
||||
<div id="version">v4.999<span id="debugCountdown"></span></div>
|
||||
</form>
|
||||
<script src="options.js" type="module"></script>
|
||||
</body>
|
||||
|
27
options.js
27
options.js
@ -186,6 +186,7 @@ const defaultConfig = {
|
||||
// Default based on the platform if the main script hasn't run on Twitter yet
|
||||
version: /(Android|iP(ad|hone))/.test(navigator.userAgent) ? 'mobile' : 'desktop',
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Config & variables
|
||||
/**
|
||||
@ -290,15 +291,18 @@ function onFormChanged(/** @type {Event} */ e) {
|
||||
if (e.target instanceof HTMLTextAreaElement) return
|
||||
|
||||
/** @type {import("./types").StoredConfig} */
|
||||
let changedConfig = {}
|
||||
let changedConfig = Object.create(null)
|
||||
/** @type {Partial<import("./types").UserSettings>} */
|
||||
let changedSettings = {}
|
||||
let changedSettings = Object.create(null)
|
||||
|
||||
// Handle input
|
||||
let $el = /** @type {HTMLInputElement} */ (e.target)
|
||||
if ($el.type == 'checkbox') {
|
||||
// All internal config is currently checkbox toggles
|
||||
if (INTERNAL_CONFIG_FORM_KEYSET.has($el.name)) {
|
||||
config[$el.name] = changedConfig[$el.name] = $el.checked
|
||||
}
|
||||
// Checkbox group toggle
|
||||
else if (checkboxGroups.has($el.name)) {
|
||||
checkboxGroups.get($el.name).forEach(checkboxName => {
|
||||
config.settings[checkboxName] = changedSettings[checkboxName] = $el.checked
|
||||
@ -306,24 +310,23 @@ function onFormChanged(/** @type {Event} */ e) {
|
||||
})
|
||||
$el.indeterminate = false
|
||||
}
|
||||
// Individual checkbox toggle
|
||||
else {
|
||||
// Individual checkbox change
|
||||
config.settings[$el.name] = changedSettings[$el.name] = $el.checked
|
||||
|
||||
// Don't try to redirect the Home timeline to Notifications if both are disabled
|
||||
if ($el.name == 'hideNotifications' &&
|
||||
$el.checked &&
|
||||
config.settings.disabledHomeTimelineRedirect == 'notifications') {
|
||||
let key = 'disabledHomeTimelineRedirect'
|
||||
$form.elements[key].value = config.settings[key] = changedSettings[key] = 'messages'
|
||||
}
|
||||
|
||||
updateCheckboxGroups()
|
||||
}
|
||||
} else {
|
||||
config.settings[$el.name] = changedSettings[$el.name] = $el.value
|
||||
}
|
||||
|
||||
// Apply other settings changes based on input
|
||||
// Don't try to redirect the Home timeline to Notifications if both are disabled
|
||||
if (changedSettings.hideNotifications &&
|
||||
config.settings.disabledHomeTimelineRedirect == 'notifications') {
|
||||
let key = 'disabledHomeTimelineRedirect'
|
||||
$form.elements[key].value = config.settings[key] = changedSettings[key] = 'messages'
|
||||
}
|
||||
|
||||
updateDisplay()
|
||||
if (Object.keys(changedSettings).length > 0) {
|
||||
changedConfig.settings = changedSettings
|
||||
|
137
script.js
137
script.js
@ -2701,7 +2701,7 @@ async function observeDesktopComposeTweetModal($popup) {
|
||||
* tab and re-added when you navigate to another Home timeline tab.
|
||||
*/
|
||||
async function observeDesktopHomeTimelineTweetBox() {
|
||||
let $container = await getElement('div[data-testid="primaryColumn"] > div', {
|
||||
let $container = await getElement(`${Selectors.PRIMARY_COLUMN} > div`, {
|
||||
name: 'Home timeline Tweet box container',
|
||||
stopIf: pageIsNot(currentPage),
|
||||
})
|
||||
@ -2782,7 +2782,7 @@ async function observeDesktopModalTimeline($popup) {
|
||||
function observeModalTimelineItems($timeline) {
|
||||
let seen = new Map()
|
||||
observeElement($timeline, () => {
|
||||
onIndividualTweetTimelineChange($timeline, {observers: modalObservers, seen})
|
||||
onIndividualTweetTimelineChange($timeline, seen, {observers: modalObservers})
|
||||
}, {
|
||||
name: 'modal timeline',
|
||||
observers: modalObservers,
|
||||
@ -2796,7 +2796,7 @@ async function observeDesktopModalTimeline($popup) {
|
||||
log('modal timeline replaced')
|
||||
seen = new Map()
|
||||
observeElement($newTimeline, () => {
|
||||
onIndividualTweetTimelineChange($newTimeline, {observers: modalObservers, seen})
|
||||
onIndividualTweetTimelineChange($newTimeline, seen, {observers: modalObservers})
|
||||
}, {
|
||||
name: 'modal timeline',
|
||||
observers: modalObservers,
|
||||
@ -3101,7 +3101,7 @@ async function observeIndividualTweetTimeline(page) {
|
||||
function observeTimelineItems($timeline) {
|
||||
let seen = new WeakMap()
|
||||
observeElement($timeline, () => {
|
||||
onIndividualTweetTimelineChange($timeline, {observers: pageObservers, seen})
|
||||
onIndividualTweetTimelineChange($timeline, seen, {observers: pageObservers})
|
||||
}, {
|
||||
leading: true,
|
||||
name: 'individual tweet timeline',
|
||||
@ -3335,8 +3335,9 @@ async function observeTimeline(page, options = {}) {
|
||||
* @param {HTMLElement} $timeline
|
||||
*/
|
||||
function observeTimelineItems($timeline) {
|
||||
let seen = new WeakMap()
|
||||
observeElement($timeline, () => {
|
||||
onTimelineChange($timeline, page, options)
|
||||
onTimelineChange($timeline, page, seen, options)
|
||||
}, {
|
||||
leading: true,
|
||||
name: 'timeline',
|
||||
@ -3353,8 +3354,9 @@ async function observeTimeline(page, options = {}) {
|
||||
let $newTimeline = $addedNode
|
||||
log('tab changed')
|
||||
onTabChanged?.()
|
||||
seen = new WeakMap()
|
||||
observeElement($newTimeline, () => {
|
||||
onTimelineChange($newTimeline, page, options)
|
||||
onTimelineChange($newTimeline, page, seen, options)
|
||||
}, {
|
||||
leading: true,
|
||||
name: 'timeline',
|
||||
@ -4901,10 +4903,11 @@ function getTweetType($tweet, checkSocialContext = false) {
|
||||
if ($tweet.closest(Selectors.PROMOTED_TWEET_CONTAINER)) {
|
||||
return 'PROMOTED_TWEET'
|
||||
}
|
||||
// Assume social context tweets are Retweets
|
||||
if ($tweet.querySelector('[data-testid="socialContext"]')) {
|
||||
// Assume social context tweets are Retweets if we're not checking
|
||||
if (checkSocialContext) {
|
||||
let svgPath = $tweet.querySelector('svg path')?.getAttribute('d') ?? ''
|
||||
if (svgPath.startsWith('M7.471 21H.472l.029-1.027c.184')) return 'COMMUNITY_TWEET'
|
||||
if (svgPath.startsWith('M7 4.5C7 3.12 8.12 2 9.5 2h5C1')) return 'PINNED_TWEET'
|
||||
}
|
||||
// Quoted tweets from accounts you blocked or muted are displayed as an
|
||||
@ -5242,12 +5245,12 @@ function isReplyToPreviousTweet($tweet) {
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} $timeline
|
||||
* @param {WeakMap<Element, import("./types").SeenTweetDetails>} seen
|
||||
* @param {import("./types").IndividualTweetTimelineOptions} options
|
||||
*/
|
||||
function onIndividualTweetTimelineChange($timeline, options) {
|
||||
function onIndividualTweetTimelineChange($timeline, seen, options) {
|
||||
let startTime = Date.now()
|
||||
|
||||
let {seen} = options
|
||||
let itemTypes = {}
|
||||
let hiddenItemCount = 0
|
||||
let hiddenItemTypes = {}
|
||||
@ -5535,11 +5538,17 @@ function onPopup($popup) {
|
||||
/**
|
||||
* @param {HTMLElement} $timeline
|
||||
* @param {string} page
|
||||
* @param {WeakMap<Element, import("./types").SeenTweetDetails>} seen
|
||||
* @param {import("./types").TimelineOptions?} options
|
||||
*/
|
||||
function onTimelineChange($timeline, page, options = {}) {
|
||||
function onTimelineChange($timeline, page, seen, options = {}) {
|
||||
let startTime = Date.now()
|
||||
let {classifyTweets = true, hideHeadings = true, isUserTimeline = false} = options
|
||||
let {
|
||||
checkSocialContext = false,
|
||||
classifyTweets = true,
|
||||
hideHeadings = true,
|
||||
isUserTimeline = false
|
||||
} = options
|
||||
|
||||
let isOnHomeTimeline = isOnHomeTimelinePage()
|
||||
let isOnListTimeline = isOnListPage()
|
||||
@ -5559,6 +5568,7 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
let itemTypes = {}
|
||||
let hiddenItemCount = 0
|
||||
let hiddenItemTypes = {}
|
||||
let processedCount = 0
|
||||
|
||||
/** @type {?boolean} */
|
||||
let hidPreviousItem = null
|
||||
@ -5566,6 +5576,11 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
let changes = []
|
||||
|
||||
for (let $item of $timeline.children) {
|
||||
if (seen.has($item)) {
|
||||
hidPreviousItem = seen.get($item).hidden
|
||||
continue
|
||||
}
|
||||
|
||||
/** @type {?import("./types").TimelineItemType} */
|
||||
let itemType = null
|
||||
/** @type {?boolean} */
|
||||
@ -5578,7 +5593,7 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
let isBlueTweet = false
|
||||
|
||||
if ($tweet != null) {
|
||||
itemType = getTweetType($tweet, isOnProfileTimeline)
|
||||
itemType = getTweetType($tweet, checkSocialContext)
|
||||
if (timelineHasSpecificHandling) {
|
||||
isReply = isReplyToPreviousTweet($tweet)
|
||||
if (isReply && hidPreviousItem != null) {
|
||||
@ -5645,7 +5660,7 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
}
|
||||
|
||||
if (!timelineHasSpecificHandling) {
|
||||
if (itemType != null) {
|
||||
if (!hideItem && itemType != null) {
|
||||
hideItem = shouldHideOtherTimelineItem(itemType)
|
||||
}
|
||||
}
|
||||
@ -5693,6 +5708,8 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
}
|
||||
|
||||
hidPreviousItem = hideItem
|
||||
seen.set($item, {itemType, hidden: hideItem})
|
||||
processedCount++
|
||||
}
|
||||
|
||||
for (let change of changes) {
|
||||
@ -5701,7 +5718,7 @@ function onTimelineChange($timeline, page, options = {}) {
|
||||
|
||||
if (debug && debugLogTimelineStats) {
|
||||
log(
|
||||
`processed ${$timeline.children.length} timeline item${s($timeline.children.length)} in ${Date.now() - startTime}ms`,
|
||||
`processed ${processedCount} new timeline item${s(processedCount)} in ${Date.now() - startTime}ms`,
|
||||
itemTypes, `hid ${hiddenItemCount}`, hiddenItemTypes
|
||||
)
|
||||
}
|
||||
@ -5786,21 +5803,27 @@ function onTitleChange(title) {
|
||||
)
|
||||
|
||||
if (newPage == currentPage) {
|
||||
log(`ignoring duplicate title change`)
|
||||
// Navigation within the Compose Tweet modal triggers duplcate title changes
|
||||
if (isDesktopComposeTweetModalOpen) {
|
||||
if (currentPath == ModalPaths.COMPOSE_TWEET && COMPOSE_TWEET_MODAL_PAGES.has(location.pathname)) {
|
||||
log('navigated away from Compose Tweet editor')
|
||||
disconnectObservers(modalObservers, 'modal')
|
||||
}
|
||||
else if (COMPOSE_TWEET_MODAL_PAGES.has(currentPath) && location.pathname == ModalPaths.COMPOSE_TWEET) {
|
||||
log('navigated back to Compose Tweet editor')
|
||||
observeDesktopComposeTweetModal($desktopComposeTweetModalPopup)
|
||||
if (isOnCommunitiesPage() &&
|
||||
URL_COMMUNITIES_RE.test(location.pathname) &&
|
||||
currentPath != location.pathname) {
|
||||
log('navigated between Communities tabs (no title change)')
|
||||
} else {
|
||||
log(`ignoring duplicate title change`)
|
||||
// Navigation within the Compose Tweet modal triggers duplcate title changes
|
||||
if (isDesktopComposeTweetModalOpen) {
|
||||
if (currentPath == ModalPaths.COMPOSE_TWEET && COMPOSE_TWEET_MODAL_PAGES.has(location.pathname)) {
|
||||
log('navigated away from Compose Tweet editor')
|
||||
disconnectObservers(modalObservers, 'modal')
|
||||
}
|
||||
else if (COMPOSE_TWEET_MODAL_PAGES.has(currentPath) && location.pathname == ModalPaths.COMPOSE_TWEET) {
|
||||
log('navigated back to Compose Tweet editor')
|
||||
observeDesktopComposeTweetModal($desktopComposeTweetModalPopup)
|
||||
}
|
||||
}
|
||||
currentNotificationCount = notificationCount
|
||||
currentPath = location.pathname
|
||||
return
|
||||
}
|
||||
currentNotificationCount = notificationCount
|
||||
currentPath = location.pathname
|
||||
return
|
||||
}
|
||||
|
||||
// Search terms are shown in the title
|
||||
@ -6190,7 +6213,11 @@ function shouldHideHomeTimelineItem(type, page) {
|
||||
return selectedHomeTabIndex >= 2 ? (
|
||||
settings.listRetweets == 'hide'
|
||||
) : (
|
||||
shouldHideSharedTweet(settings.retweets, page) || shouldHideSharedTweet(settings.quoteTweets, page)
|
||||
page != separatedTweetsTimelineTitle ? (
|
||||
shouldHideSharedTweet(settings.retweets, page) || shouldHideSharedTweet(settings.quoteTweets, page)
|
||||
) : (
|
||||
shouldHideSharedTweet(settings.retweets, page) && shouldHideSharedTweet(settings.quoteTweets, page)
|
||||
)
|
||||
)
|
||||
case 'TWEET':
|
||||
return page == separatedTweetsTimelineTitle
|
||||
@ -6227,6 +6254,8 @@ function shouldHideListTimelineItem(type) {
|
||||
*/
|
||||
function shouldHideOtherTimelineItem(type) {
|
||||
switch (type) {
|
||||
case 'COMMUNITY_TWEET':
|
||||
case 'PINNED_TWEET':
|
||||
case 'QUOTE_TWEET':
|
||||
case 'RETWEET':
|
||||
case 'RETWEETED_QUOTE_TWEET':
|
||||
@ -6280,23 +6309,26 @@ async function tweakBookmarksPage() {
|
||||
}
|
||||
|
||||
function tweakCommunitiesPage() {
|
||||
observeTimeline(currentPage)
|
||||
observeTimeline(currentPage, {
|
||||
checkSocialContext: true,
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
})
|
||||
}
|
||||
|
||||
function tweakCommunityPage() {
|
||||
if (settings.premiumBlueChecks != 'ignore') {
|
||||
observeTimeline(currentPage, {
|
||||
classifyTweets: false,
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
onTimelineAppeared() {
|
||||
// The About tab has static content at the top which can include a check
|
||||
if (/\/about\/?$/.test(location.pathname)) {
|
||||
processBlueChecks(document.querySelector(Selectors.PRIMARY_COLUMN))
|
||||
}
|
||||
observeTimeline(currentPage, {
|
||||
checkSocialContext: true,
|
||||
hideHeadings: false,
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
onTimelineAppeared() {
|
||||
// The About tab has static content at the top which can include a check
|
||||
if (/\/about\/?$/.test(location.pathname)) {
|
||||
processBlueChecks(document.querySelector(Selectors.PRIMARY_COLUMN))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function tweakCommunityMembersPage() {
|
||||
@ -6304,7 +6336,7 @@ function tweakCommunityMembersPage() {
|
||||
observeTimeline(currentPage, {
|
||||
classifyTweets: false,
|
||||
isTabbed: true,
|
||||
timelineSelector: 'div[data-testid="primaryColumn"] > div > div:last-child',
|
||||
timelineSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -6368,7 +6400,7 @@ async function tweakExplorePage() {
|
||||
observeTimeline(currentPage, {
|
||||
classifyTweets: false,
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: 'div[data-testid="primaryColumn"] > div > div:last-child > div',
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child > div`,
|
||||
})
|
||||
}
|
||||
return
|
||||
@ -6538,7 +6570,7 @@ function tweakHomeTimelinePage() {
|
||||
updateSelectedHomeTabIndex()
|
||||
wasForYouTabSelected = selectedHomeTabIndex == 0
|
||||
},
|
||||
tabbedTimelineContainerSelector: 'div[data-testid="primaryColumn"] > div > div:last-child',
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
})
|
||||
|
||||
if (desktop) {
|
||||
@ -6748,12 +6780,10 @@ function tweakNotificationsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.premiumBlueChecks != 'ignore' || settings.restoreLinkHeadlines) {
|
||||
observeTimeline(currentPage, {
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: 'div[data-testid="primaryColumn"] > div > div:last-child',
|
||||
})
|
||||
}
|
||||
observeTimeline(currentPage, {
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
})
|
||||
}
|
||||
|
||||
async function tweakOwnFocusedTweet($focusedTweet) {
|
||||
@ -6772,7 +6802,7 @@ async function tweakOwnFocusedTweet($focusedTweet) {
|
||||
})
|
||||
if (!$accountAnalyticsUpsell) return
|
||||
$accountAnalyticsUpsell.classList.add('PremiumUpsell')
|
||||
$focusedTweet.setAttribute('cpft-analytics-upsell-tagged', 'true')
|
||||
$focusedTweet.setAttribute('cpft-analytics-upsell-tagged', '')
|
||||
}
|
||||
|
||||
async function tweakProfilePage() {
|
||||
@ -6789,6 +6819,7 @@ async function tweakProfilePage() {
|
||||
let tab = currentPath.match(URL_PROFILE_RE)?.[2] || 'tweets'
|
||||
log(`on ${tab} tab`)
|
||||
observeTimeline(currentPage, {
|
||||
checkSocialContext: tab == 'tweets' || tab == 'with_replies',
|
||||
isUserTimeline: tab == 'affiliates'
|
||||
})
|
||||
|
||||
@ -6905,7 +6936,7 @@ function tweakSearchPage() {
|
||||
observeTimeline(currentPage, {
|
||||
hideHeadings: false,
|
||||
isTabbed: true,
|
||||
tabbedTimelineContainerSelector: 'div[data-testid="primaryColumn"] > div > div:last-child',
|
||||
tabbedTimelineContainerSelector: `${Selectors.PRIMARY_COLUMN} > div > div:last-child`,
|
||||
})
|
||||
|
||||
if (desktop) {
|
||||
@ -6998,7 +7029,7 @@ async function tweakTimelineTabs($timelineTabs) {
|
||||
* Restores "Tweet" button text.
|
||||
*/
|
||||
async function tweakTweetButton() {
|
||||
let $tweetButton = await getElement(`${desktop ? 'div[data-testid="primaryColumn"]': 'main'} button[data-testid^="tweetButton"]`, {
|
||||
let $tweetButton = await getElement(`${desktop ? Selectors.PRIMARY_COLUMN: 'main'} button[data-testid^="tweetButton"]`, {
|
||||
name: 'tweet button',
|
||||
stopIf: pageIsNot(currentPage),
|
||||
})
|
||||
|
5
types.d.ts
vendored
5
types.d.ts
vendored
@ -177,6 +177,7 @@ export type QuotedTweet = {
|
||||
export type SharedTweetsConfig = 'separate' | 'hide' | 'ignore'
|
||||
|
||||
export type TweetType =
|
||||
| 'COMMUNITY_TWEET'
|
||||
| 'PINNED_TWEET'
|
||||
| 'PROMOTED_TWEET'
|
||||
| 'QUOTE_TWEET'
|
||||
@ -202,6 +203,7 @@ export type TimelineItemType =
|
||||
| 'UNAVAILABLE'
|
||||
|
||||
export type TimelineOptions = {
|
||||
checkSocialContext?: boolean
|
||||
classifyTweets?: boolean
|
||||
hideHeadings?: boolean
|
||||
isTabbed?: boolean
|
||||
@ -214,10 +216,9 @@ export type TimelineOptions = {
|
||||
|
||||
export type IndividualTweetTimelineOptions = {
|
||||
observers: Map<string, Disconnectable>
|
||||
seen: WeakMap<Element, IndividualTweetDetails>
|
||||
}
|
||||
|
||||
export type IndividualTweetDetails = {
|
||||
export type SeenTweetDetails = {
|
||||
itemType: TimelineItemType,
|
||||
hidden: boolean | null,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user