- Added checkSocialContext to TimelineOptions instead of hardcoding it for profile timelines

- Fixed Community Tweets being treated as Retweets
- Fixed handling timelines in Communities tabs
- Replaced hardcoding of the primary column selector

Options:
- Fixed options textarea styles
- Fixed background in macOS Safari options popup in dark mode
- Apply other settings changes after options input changes
This commit is contained in:
Jonny Buchanan 2025-06-15 18:55:44 +10:00
parent 331f79215b
commit b3076019c8
4 changed files with 93 additions and 61 deletions

View File

@ -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;
}

View File

@ -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

102
script.js
View File

@ -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),
})
@ -4903,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
@ -5542,7 +5543,12 @@ function onPopup($popup) {
*/
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()
@ -5587,7 +5593,7 @@ function onTimelineChange($timeline, page, seen, options = {}) {
let isBlueTweet = false
if ($tweet != null) {
itemType = getTweetType($tweet, isOnProfileTimeline)
itemType = getTweetType($tweet, checkSocialContext)
if (timelineHasSpecificHandling) {
isReply = isReplyToPreviousTweet($tweet)
if (isReply && hidPreviousItem != null) {
@ -5654,7 +5660,7 @@ function onTimelineChange($timeline, page, seen, options = {}) {
}
if (!timelineHasSpecificHandling) {
if (itemType != null) {
if (!hideItem && itemType != null) {
hideItem = shouldHideOtherTimelineItem(itemType)
}
}
@ -5797,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
@ -6242,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':
@ -6295,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() {
@ -6319,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`,
})
}
}
@ -6383,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
@ -6553,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) {
@ -6763,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) {
@ -6787,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() {
@ -6804,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'
})
@ -6920,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) {
@ -7013,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),
})

2
types.d.ts vendored
View File

@ -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