T3 Chat wide code block (always expand)

Utilize available horizontal space in T3 Chat to make reading code blocks with very long lines on wide screens easier.

目前為 2025-05-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name         T3 Chat wide code block (always expand)
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Utilize available horizontal space in T3 Chat to make reading code blocks with very long lines on wide screens easier.
// @author       https://github.com/dicksonhk, https://t3.chat
// @license      MIT
// @match        https://beta.t3.chat/*
// @match        https://t3.chat/*
// @grant        GM_addStyle // Grant GM_addStyle for the first part
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const ALWAYS_EXPAND_CODEBLOCK = true;
  const EXPANDED_CODEBLOCK_WIDTH = 'fit-content';
  const MAX_CODEBLOCK_WIDTH = '100%';

  // The original max-width for the wrapper, needs to match [role="log"]'s max-width
  const WRAPPER_MAX_WIDTH = '48rem';

  const _S_LOG_SPECIFIC = `[role="log"].max-w-3xl.px-4`;
  const _S_LOG_FALLBACK = `[role="log"]`;
  const S_LOGS = [_S_LOG_SPECIFIC, _S_LOG_FALLBACK];

  const _S_MSG_GP_SPECIFIC = `${_S_LOG_SPECIFIC} > * > .max-w-full`;
  const _S_MSG_GP_FALLBACK = `${_S_LOG_FALLBACK} :has(> [role="article"])`;
  const S_MSG_GP = [_S_MSG_GP_SPECIFIC, _S_MSG_GP_FALLBACK];

  // absolute and non-absolute items are styled differently
  const S_MSG_GP_ITEMS = S_MSG_GP.map((group) => `${group} > :not(.absolute)`);
  const S_MSG_GP_ABSOLUTE_ITEMS = S_MSG_GP.map((group) => `${group} > .absolute`);

  const CUSTOM_CSS = `
/* Remove max-width constraint wrapper full width and center children */
${[
  ...S_LOGS.map((log) => `${log} > *:not(:has(> .max-w-full))`),
  ...S_LOGS,
].join(',\n')} {
    max-width: 100%;
    align-items: center;
}
/* Apply full width to wrapper direct children */
${[
  ...S_LOGS.map((log) => `${log} > *:not(.absolute)`),
].join(',\n')} {
    width: 100%;
}
/* Apply code blocks width */
${[
  ...(ALWAYS_EXPAND_CODEBLOCK
    ? S_MSG_GP_ITEMS.map((item) => `${item} > :has(pre)`)
    : S_MSG_GP_ITEMS.map((item) => `${item} > :has(.\\[\\&_pre\\]\\:whitespace-pre-wrap > pre)`)
  ),
].join(',\n')} {
    width: ${EXPANDED_CODEBLOCK_WIDTH};
    max-width: min(100%, ${MAX_CODEBLOCK_WIDTH});
    min-width: min(100%, 48rem);
    margin-left: auto;
    margin-right: auto;
}
/* Apply wrapper's original max-width to select children, except for text-wrap enabled code blocks */
/* TODO: handle non top-level code blocks */
${[
  ...S_LOGS.map((log) => `${log} > :not(:has(> .max-w-full)):not(.absolute)`),
  ...S_MSG_GP_ITEMS.map((item) => `${item} > :not(:has(pre))`),
  ...(ALWAYS_EXPAND_CODEBLOCK
    ? []
    : S_MSG_GP_ITEMS.map((item) => `${item} > :not(:has(.\\[\\&_pre\\]\\:whitespace-pre-wrap > pre))`)
  ),
].join(',\n')} {
    max-width: ${WRAPPER_MAX_WIDTH};
}
/* Apply full width to absolute wrappers */
${[
  ...S_MSG_GP_ABSOLUTE_ITEMS.map((item) => `${item}.left-0`),
].join(',\n')} {
    right: 0;
}
${[
  ...S_MSG_GP_ABSOLUTE_ITEMS.map((item) => `${item}.right-0`),
].join(',\n')} {
    left: 0;
}
/* Apply wrapper's original max-width to absolutely positioned children */
${[
  ...S_MSG_GP_ABSOLUTE_ITEMS.flatMap((item) => [
    `${item}.left-0::before`,
    `${item}.right-0::after`,
  ]),
].join(',\n')} {
    content: '';
    width: max(0px, calc((100% - ${WRAPPER_MAX_WIDTH}) / 2 - .25rem));
}
/* Other layout fixes */
${[
  ...S_MSG_GP_ITEMS.map((item) => `${item}:not(.absolute)`),
].join(',\n')} {
    position: relative;
}
${[
  ...S_MSG_GP_ITEMS.map((item) => `${item}.flex-col`),
].join(',\n')} {
    align-items: center;
}
${[
  ...S_MSG_GP_ITEMS.map((item) => `${item}:not(.flex-col) > *`),
  // S_MSG_GP_ITEMS.flatMap((item) => [
  //   `${item}:not(.flex-col) > :not(:has(pre))`,
  //   `${item}:not(.flex-col) > :not(:has(.\\[\\&_pre\\]\\:whitespace-pre-wrap > pre))`
  // ]),
].join(',\n')} {
    margin-left: auto;
    margin-right: auto;
}
`;

  injectCss(CUSTOM_CSS);

  // --- Function to add CSS, checking for GM_addStyle ---
  function injectCss(css) {
    // Check if GM_addStyle is available
    if (typeof GM_addStyle === 'function') {
      GM_addStyle(css);
      return;
    }

    // Inject using a style element
    const styleElement = document.createElement('style');
    styleElement.textContent = css;

    // Append to head if available
    if (document.head) {
      document.head.appendChild(styleElement);
      return;
    }

    // As a last resort, append to the document's root element
    // This is less ideal for performance but ensures the style is applied
    document.documentElement.appendChild(styleElement);
  }
})();