This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547// Bsky+ Feed Width Manager// Allows expanding the center column width on wide desktop monitors (Bluesky defaults to ~600px).// Adapts the full layout (shifting left sidebar, right sidebar, center borders, tablist, and feed column)// and caps the maximum width dynamically based on available viewport space.
(function () { 'use strict';
const STYLE_ID = 'bskyplus-feed-width-style'; const TRANSITION_STYLE_ID = 'bskyplus-feed-width-transition-style'; const TRANSITION_ATTRIBUTE = 'data-bskyplus-feed-width-animating'; const TRANSITION_DURATION = 800; const TRANSITION_TARGETS = [ 'div[style*="max-width: 600px"]', 'div[style*="max-width:600px"]', 'div[style*="max-width: 550px"]', 'div[style*="max-width:550px"]', '[data-bskyplus-feed-column]', '[role="tablist"]', '[data-bskyplus-tablist]', '[data-bskyplus-center-borders]', 'div[style*="width: 602px"]', 'div[style*="width:602px"]', '[data-bskyplus-left-nav]', 'div[role="navigation"]:not([style*="bottom:"])', 'nav[role="navigation"]:not([style*="bottom:"])', '[data-bskyplus-right-nav]', 'div[style*="translateX(300px)"]', 'div[style*="translateX(195px)"]', '[data-bskyplus-settings-page]' ].join(', '); let currentConfiguredWidth = 600; let currentEffectiveWidth = 600; let hasLiveWidthUpdate = false; let transitionTimeout;
function startWidthTransition() { if (!isFeatureActive() || window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return;
let style = document.getElementById(TRANSITION_STYLE_ID); if (!style) { style = document.createElement('style'); style.id = TRANSITION_STYLE_ID; style.textContent = ` @media (prefers-reduced-motion: no-preference) { html[${TRANSITION_ATTRIBUTE}] :is(${TRANSITION_TARGETS}) { transition: max-width ${TRANSITION_DURATION}ms ease, width ${TRANSITION_DURATION}ms ease, margin-left ${TRANSITION_DURATION}ms ease, margin-right ${TRANSITION_DURATION}ms ease !important; } } `; (document.head || document.documentElement).appendChild(style); }
const root = document.documentElement; if (root.getAttribute(TRANSITION_ATTRIBUTE) === null) { root.setAttribute(TRANSITION_ATTRIBUTE, ''); // Apply the transition before changing widths, including the first live update. document.querySelectorAll(TRANSITION_TARGETS).forEach(element => { window.getComputedStyle(element).transitionDuration; }); }
if (transitionTimeout) window.clearTimeout?.(transitionTimeout); transitionTimeout = window.setTimeout?.(() => { root.removeAttribute(TRANSITION_ATTRIBUTE); transitionTimeout = undefined; }, TRANSITION_DURATION + 50); }
function isFeatureActive() { const flags = window.BskyPlusFeatureFlags; if (flags && !flags.isFeatureEnabled('feedWidth')) return false; return true; }
function findLeftNav() { const navs = document.querySelectorAll('div[role="navigation"], nav[role="navigation"], [role="navigation"]'); for (const nav of navs) { const s = nav.getAttribute('style') || ''; // Ignore mobile bottom navigation if (s.includes('bottom: 0') || s.includes('bottom:0')) continue;
// Check navigation links if (nav.querySelector && nav.querySelector('a[href="/"], a[href*="/notifications"], a[href*="/search"], a[href*="/messages"], a[href*="/settings"]')) { return nav; }
// Check for transform or styles matching Bluesky's desktop nav if (s.includes('translateX(-') || s.includes('top: 0') || s.includes('width: 245px') || s.includes('width: 240px') || nav.tagName === 'NAV') { return nav; }
// Desktop nav is on the left side (width <= 350px) if (nav.getBoundingClientRect) { try { const rect = nav.getBoundingClientRect(); const vw = (typeof window !== 'undefined' && window.innerWidth) ? window.innerWidth : 1400; if (rect.width > 0 && rect.width <= 350 && rect.left < vw * 0.4) { return nav; } } catch (e) {} } }
const homeLink = document.querySelector('a[href="/"][role="link"], a[href="/"]'); if (homeLink) { const nav = homeLink.closest('[role="navigation"]') || homeLink.closest('div[style*="position: fixed"], div[style*="position:fixed"]'); if (nav) return nav; }
return document.querySelector('div[role="navigation"]:not([style*="bottom:"]), nav[role="navigation"]:not([style*="bottom:"])'); }
function findFixedAncestor(element) { let parent = element?.parentElement; while (parent && parent !== document.body && parent !== document.documentElement) { const style = parent.getAttribute('style') || ''; if (style.includes('position: fixed') || style.includes('position:fixed') || style.includes('translateX') || window.getComputedStyle?.(parent)?.position === 'fixed') return parent; parent = parent.parentElement; } return null; }
function findRightNav() { // 1. Direct match on Bluesky's desktop fixed right column transform const direct = document.querySelector('div[style*="translateX(300px)"], div[style*="translateX( 300px)"], div[style*="translateX(195px)"], div[style*="translateX( 195px)"]'); if (direct) return direct;
const divs = document.querySelectorAll('div[style*="position: fixed"], div[style*="position:fixed"]'); for (const d of divs) { const s = d.getAttribute('style') || ''; if (s.includes('left: 50%') || s.includes('left:50%')) { const m = s.match(/translateX\(\s*(\d+)px\)/); if (m && Number(m[1]) >= 150) { return d; } } }
// 2. Check via desktop search input const searchInput = document.querySelector('input[placeholder*="Search" i], input[aria-label*="Search" i]'); const searchNav = findFixedAncestor(searchInput); if (searchNav) return searchNav;
// 3. Check via desktop right sidebar links (privacy policy, terms, etc.) const privacyLink = document.querySelector('a[href*="privacy-policy"], a[href*="tos"], a[href*="zendesk.com"]'); return findFixedAncestor(privacyLink); }
function findCenterBorders() { const borders = document.querySelector('div[style*="602px"], div[style*="602 px"]'); if (borders) return borders; const borderCandidates = document.querySelectorAll('div[style*="border-left-width"][style*="border-right-width"], div[style*="translateX(-50%)"]'); for (const el of borderCandidates) { if (el.children.length === 0) { return el; } } return null; }
function getMaxAvailableWidth() { const vw = (typeof window !== 'undefined' && window.innerWidth) ? window.innerWidth : 1400; if (vw < 800) { return 600; }
const leftEl = findLeftNav(); const rightEl = findRightNav();
// If neither sidebar is present in the DOM (e.g. unit test or minimal embed) if (!leftEl && !rightEl) { return Math.max(600, Math.min(1200, Math.floor((vw - 40) / 25) * 25)); }
// Determine sidebar widths let leftWidth = 0; if (leftEl) { try { const rect = leftEl.getBoundingClientRect(); if (rect.width > 30 && rect.width <= 350) leftWidth = Math.round(rect.width); } catch (e) {} } if (!leftWidth) { leftWidth = vw > 1300 ? 245 : 80; }
let rightWidth = 0; let hasRightNav = false; if (rightEl) { try { const rect = rightEl.getBoundingClientRect(); if (rect.width > 30 && rect.width <= 400) { rightWidth = Math.round(rect.width); hasRightNav = true; } } catch (e) {} } if (!rightWidth && (vw >= 1000 || rightEl)) { rightWidth = 280; hasRightNav = true; }
// Available width formula: // LeftNav + margin + Feed + margin + RightNav must fit comfortably in vw let maxW; if (hasRightNav) { const buffer = 30; maxW = vw - leftWidth - rightWidth - buffer; } else { const buffer = 20; maxW = vw - leftWidth - buffer; }
maxW = Math.floor(maxW / 25) * 25; return Math.max(600, Math.min(1200, maxW)); }
function clearLayoutTags() { const tagged = document.querySelectorAll('[data-bskyplus-left-nav], [data-bskyplus-right-nav], [data-bskyplus-center-borders], [data-bskyplus-feed-column], [data-bskyplus-tablist], [data-bskyplus-post-media]'); tagged.forEach(el => { if (el.dataset && el.dataset.bskyplusPostMedia) { delete el.dataset.bskyplusPostMedia; } if (el.dataset && el.dataset.bskyplusLeftNav) { if (el.style && el.style.removeProperty) el.style.removeProperty('margin-left'); delete el.dataset.bskyplusLeftNav; } if (el.dataset && el.dataset.bskyplusRightNav) { if (el.style && el.style.removeProperty) el.style.removeProperty('margin-left'); delete el.dataset.bskyplusRightNav; } if (el.dataset && el.dataset.bskyplusCenterBorders) { if (el.style && el.style.setProperty) { el.style.setProperty('width', '602px'); } else if (el.style && el.style.removeProperty) { el.style.removeProperty('width'); } delete el.dataset.bskyplusCenterBorders; } if (el.dataset && el.dataset.bskyplusTablist) { if (el.style && el.style.removeProperty) { el.style.removeProperty('max-width'); el.style.removeProperty('width'); el.style.removeProperty('margin-left'); el.style.removeProperty('margin-right'); } delete el.dataset.bskyplusTablist; } if (el.dataset && el.dataset.bskyplusFeedColumn) { if (el.dataset.bskyplusOrigMax) { if (el.style && el.style.setProperty) el.style.setProperty('max-width', el.dataset.bskyplusOrigMax); delete el.dataset.bskyplusOrigMax; } else { // Default Bluesky feed column max-width is 600px if (el.style && el.style.setProperty) el.style.setProperty('max-width', '600px'); } if (el.style && el.style.removeProperty) { el.style.removeProperty('margin-left'); el.style.removeProperty('margin-right'); el.style.removeProperty('width'); } delete el.dataset.bskyplusFeedColumn; } });
const leftNav = findLeftNav(); if (leftNav && leftNav.style && leftNav.style.removeProperty) { leftNav.style.removeProperty('margin-left'); } const rightNav = findRightNav(); if (rightNav && rightNav.style && rightNav.style.removeProperty) { rightNav.style.removeProperty('margin-left'); } }
function setColumnWidth(element, width) { if (!element.style?.setProperty) return; element.style.setProperty('max-width', `${width}px`, 'important'); element.style.setProperty('margin-left', 'auto', 'important'); element.style.setProperty('margin-right', 'auto', 'important'); element.style.setProperty('width', '100%', 'important'); }
function tagPostMedia() { document.querySelectorAll('[data-testid="contentHider-post"]').forEach(post => { for (const child of post.children) { const hasMedia = !child.querySelector('[data-testid="postText"]') && ( child.querySelector('video') || Array.from(child.querySelectorAll('img')).some(image => !image.closest('[data-testid="userAvatarImage"]')) ); if (hasMedia) { child.dataset.bskyplusPostMedia = 'true'; } else if (child.dataset.bskyplusPostMedia) { delete child.dataset.bskyplusPostMedia; } } }); }
function scanAndTagLayout(effectiveWidth) { if (!isFeatureActive() || effectiveWidth === 600) { clearLayoutTags(); return; }
const shift = Math.round((effectiveWidth - 600) / 2);
// 1. Left Nav const leftNav = findLeftNav(); if (leftNav) { if (leftNav.dataset) leftNav.dataset.bskyplusLeftNav = 'true'; if (leftNav.style && leftNav.style.setProperty) { leftNav.style.setProperty('margin-left', `-${shift}px`, 'important'); } }
// 2. Right Nav const rightNav = findRightNav(); if (rightNav) { if (rightNav.dataset) rightNav.dataset.bskyplusRightNav = 'true'; if (rightNav.style && rightNav.style.setProperty) { rightNav.style.setProperty('margin-left', `${shift}px`, 'important'); } }
// 3. Center Borders const borders = findCenterBorders(); if (borders) { if (borders.dataset) borders.dataset.bskyplusCenterBorders = 'true'; if (borders.style && borders.style.setProperty) { borders.style.setProperty('width', `${effectiveWidth + 2}px`, 'important'); } }
// 4. Center feed containers const elements = document.querySelectorAll('div[style*="max-width"], [data-bskyplus-feed-column]'); elements.forEach(el => { const isTagged = el.dataset && el.dataset.bskyplusFeedColumn; const styleAttr = el.getAttribute('style') || ''; if (isTagged || /max-width:\s*(550|600)px/i.test(styleAttr)) { if (el.dataset) { if (!el.dataset.bskyplusOrigMax && el.style && el.style.maxWidth && !isTagged) { el.dataset.bskyplusOrigMax = el.style.maxWidth; } el.dataset.bskyplusFeedColumn = 'true'; } setColumnWidth(el, effectiveWidth); } });
// 5. Tablist container and wrappers const tablists = document.querySelectorAll('div[role="tablist"], [role="tablist"]'); tablists.forEach(el => { if (el.dataset) el.dataset.bskyplusTablist = 'true'; setColumnWidth(el, effectiveWidth);
// Check parent header wrappers and feed column parents let parent = el.parentElement; while (parent && parent !== document.body && parent !== document.documentElement) { const isTagged = parent.dataset && parent.dataset.bskyplusFeedColumn; const pStyle = parent.getAttribute('style') || ''; if (isTagged || /max-width:\s*(550|600)px/i.test(pStyle)) { if (parent.dataset) parent.dataset.bskyplusFeedColumn = 'true'; setColumnWidth(parent, effectiveWidth); } if (parent.getAttribute('data-bskyplus-left-nav') || parent.getAttribute('data-bskyplus-right-nav')) { break; } parent = parent.parentElement; } });
tagPostMedia();
// Child positions differ between feeds and notifications. The column's // width constraint above identifies it without resizing avatar columns. }
function updateFeedWidthStyle(width) { let style = document.getElementById(STYLE_ID);
if (!isFeatureActive() || width === 600 || !width) { currentConfiguredWidth = 600; currentEffectiveWidth = 600; if (style) style.remove(); clearLayoutTags(); return; }
currentConfiguredWidth = width; const maxAllowed = getMaxAvailableWidth(); const effectiveWidth = Math.min(width, maxAllowed); currentEffectiveWidth = effectiveWidth;
if (!style) { style = document.createElement('style'); style.id = STYLE_ID; (document.head || document.documentElement).appendChild(style); }
const shift = Math.round((effectiveWidth - 600) / 2);
style.textContent = ` :root { --bskyplus-feed-width: ${effectiveWidth}px !important; --bskyplus-feed-shift: ${shift}px !important; } div[style*="max-width: 600px"], div[style*="max-width:600px"], div[style*="max-width: 550px"], div[style*="max-width:550px"], [data-bskyplus-feed-column] { max-width: ${effectiveWidth}px !important; margin-left: auto !important; margin-right: auto !important; width: 100% !important; } div[role="tablist"], [role="tablist"], [data-bskyplus-tablist] { max-width: ${effectiveWidth}px !important; margin-left: auto !important; margin-right: auto !important; width: 100% !important; } [data-bskyplus-center-borders], div[style*="width: 602px"], div[style*="width:602px"] { width: ${effectiveWidth + 2}px !important; } [data-bskyplus-left-nav], div[role="navigation"]:not([style*="bottom:"]), nav[role="navigation"]:not([style*="bottom:"]) { margin-left: -${shift}px !important; } [data-bskyplus-right-nav], div[style*="translateX(300px)"], div[style*="translateX(195px)"] { margin-left: ${shift}px !important; } [data-bskyplus-feed-column] [data-testid*="postThreadItem"] img, [data-bskyplus-feed-column] [data-testid*="feedItem"] img { max-width: 100% !important; } `;
scanAndTagLayout(effectiveWidth); }
function applyLiveFeedWidth(width) { hasLiveWidthUpdate = true; startWidthTransition(); // A live reset to 600 breaks the layout. Keep the saved default for reloads. updateFeedWidthStyle(width === 600 ? 599 : width); if (isFeatureActive() && width) currentConfiguredWidth = width; }
// Load the actual saved width on a fresh page. if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) { chrome.storage.local.get(['feedWidth'], (res) => { if (!hasLiveWidthUpdate && res && res.feedWidth) { updateFeedWidthStyle(res.feedWidth); } });
chrome.storage.onChanged.addListener((changes, area) => { if ((area === 'local' || area === 'sync') && changes.feedWidth) { applyLiveFeedWidth(changes.feedWidth.newValue); } }); }
// Handle runtime messages from popup (limits inquiry and live instant updates) if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg && msg.action === 'GET_FEED_WIDTH_LIMITS') { sendResponse({ currentWidth: currentEffectiveWidth, configuredWidth: currentConfiguredWidth, maxWidth: getMaxAvailableWidth(), viewportWidth: (typeof window !== 'undefined' && window.innerWidth) ? window.innerWidth : 1200 }); return true; } if (msg && (msg.action === 'SETTINGS_UPDATED' || msg.action === 'APPLY_SETTINGS') && msg.settings) { if (msg.settings.feedWidth !== undefined) { applyLiveFeedWidth(msg.settings.feedWidth); } } }); }
// Handle window message bridge (e.g. from tests or web pages) if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('message', (event) => { if (event.data && (event.data.type === 'BSKYPLUS_UPDATE_SETTINGS' || event.data.action === 'SETTINGS_UPDATED')) { const settings = event.data.settings || event.data; if (settings && settings.feedWidth !== undefined) { applyLiveFeedWidth(settings.feedWidth); } } }); }
// Re-evaluate on window resize if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('resize', () => { if (currentConfiguredWidth && (currentConfiguredWidth !== 600 || currentEffectiveWidth !== 600)) { applyLiveFeedWidth(currentConfiguredWidth); } }); }
// Observe dynamically loaded routes / views const observer = new MutationObserver(() => { if (currentEffectiveWidth !== 600) { scanAndTagLayout(currentEffectiveWidth); } });
if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); if (currentEffectiveWidth !== 600) { scanAndTagLayout(currentEffectiveWidth); } } else { document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true }); if (currentEffectiveWidth !== 600) { scanAndTagLayout(currentEffectiveWidth); } }); }
window.BskyPlusFeedWidth = { setWidth: applyLiveFeedWidth, getWidth: () => currentEffectiveWidth, getConfiguredWidth: () => currentConfiguredWidth, getMaxWidth: getMaxAvailableWidth };})();