v1.9.3: 折疊時青線消失;+新建移至logo正右方獨立顯示
// ==UserScript==
// @name Perplexity Widescreen & Compact & Code Collapser
// @namespace http://tampermonkey.net/
// @version 1.9.3
// @description v1.9.3: 折疊時青線消失;+新建移至logo正右方獨立顯示
// @author ford933
// @license All Rights Reserved
// @match https://www.perplexity.ai/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const MIN_W = 140, MAX_W = 380;
let savedWidth = 185;
try { savedWidth = Math.min(Math.max(GM_getValue('pplx_sidebar_width', 185), MIN_W), MAX_W); } catch(e){}
const css = `
/* SCROLLBAR */
*::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}
*{scrollbar-width:none!important;-ms-overflow-style:none!important}
.scrollable-container::-webkit-scrollbar{display:block!important;width:4px!important}
.scrollable-container::-webkit-scrollbar-thumb{background:rgba(128,128,128,.25)!important;border-radius:2px!important}
.scrollable-container{scrollbar-width:thin!important;-ms-overflow-style:auto!important}
/* WIDESCREEN */
html,body{max-width:100%!important;overflow-x:hidden!important}
.scrollable-container{max-width:100%!important;width:100%!important;padding-left:16px!important;padding-right:12px!important}
.scrollable-container>div{max-width:100%!important;width:100%!important}
.scrollable-container div[class*="max-w-"],
.scrollable-container section[class*="max-w-"],
.scrollable-container article[class*="max-w-"],
.scrollable-container main[class*="max-w-"]{max-width:100%!important}
.scrollable-container .container,.scrollable-container .container.isolate{max-width:100%!important;width:100%!important;padding-left:0!important;padding-right:0!important}
.scrollable-container .erp-sidecar,.scrollable-container [class*="erp-sidecar"]{max-width:100%!important;width:100%!important}
.prose,.prose>*{max-width:100%!important;width:100%!important}
/* HEADER */
[class*="h-headerHeight"]{height:auto!important;min-height:0!important}
[class*="containerheader"],[class*="container"][class*="header"]{height:auto!important;min-height:0!important;max-height:none!important}
[role="tablist"] button[role="tab"]{padding-top:2px!important;padding-bottom:2px!important}
[role="tablist"]{height:auto!important;gap:12px!important}
/* PILL */
div[data-ask-input-container="true"]{max-width:520px!important;margin-left:auto!important;margin-right:auto!important}
#ask-input{max-height:30vh!important;overflow-y:auto!important}
div:has(>div[data-ask-input-container="true"]){padding-top:2px!important;padding-bottom:2px!important;margin-top:0!important;margin-bottom:0!important}
/* CHAT SPACING */
.scrollable-container .gap-y-lg{row-gap:4px!important}
.scrollable-container .gap-y-md{row-gap:2px!important}
.scrollable-container .gap-y-sm{row-gap:1px!important}
.scrollable-container .gap-5{gap:4px!important}
.scrollable-container .gap-4{gap:2px!important}
.scrollable-container .gap-md{gap:2px!important}
.scrollable-container .gap-sm{gap:1px!important}
.scrollable-container .mt-md:not(nav .mt-md):not(.prose .mt-md){margin-top:2px!important}
.scrollable-container .mt-xs:not(nav .mt-xs):not(.prose .mt-xs){margin-top:0!important}
.scrollable-container [class*="pt-md"]:not(nav [class*="pt-md"]):not(.prose [class*="pt-md"]){padding-top:2px!important}
.scrollable-container [class*="pt-lg"]:not(nav [class*="pt-lg"]):not(.prose [class*="pt-lg"]){padding-top:2px!important}
.scrollable-container .flex.items-center.justify-between{padding-top:0!important;margin-top:0!important}
.scrollable-container button.flex.items-center[class*="gap-sm"][class*="text-quiet"]{margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important}
.scrollable-container .text-sm.text-quiet[class*="font-sans"]{margin-top:0!important;margin-bottom:0!important;line-height:1.2!important}
/* PROSE */
.prose p,.prose li,.prose blockquote{line-height:1.35!important;margin-top:0!important;margin-bottom:2px!important;margin-block-start:0!important;margin-block-end:2px!important;padding-top:0!important;padding-bottom:0!important}
.prose{margin-top:2px!important}
.prose h1,.prose h2,.prose h3,.prose h4{margin-top:4px!important;margin-bottom:2px!important;line-height:1.25!important}
.prose ul,.prose ol{margin-top:0!important;margin-bottom:2px!important;padding-left:1.2em!important}
.prose li{margin:0!important}
.prose hr{margin-top:4px!important;margin-bottom:4px!important}
/* TABLE */
.scrollable-container table{border-collapse:separate!important;border-spacing:0!important}
.scrollable-container table th{padding:3px 6px!important;min-width:40px!important;vertical-align:bottom!important;white-space:normal!important;word-break:break-word!important}
.scrollable-container table td{padding:2px 6px!important;min-width:40px!important;vertical-align:top!important;white-space:normal!important;word-break:break-word!important}
.scrollable-container .overflow-auto[class*="rounded"]{overflow-x:auto!important;max-width:100%!important;display:block!important}
/* USER BUBBLE */
main [class*="justify-end"]{position:relative!important}
main [class*="justify-end"]>[class*="invisible"]{position:absolute!important;right:4px!important;top:4px!important;left:auto!important;z-index:20!important}
main [class*="justify-end"]>div:last-child,
main [class*="justify-end"]>*:last-child:not(button):not(svg){width:100%!important;max-width:100%!important;text-align:left!important}
main [class*="ml-auto"]:not(button):not(svg):not(input){max-width:100%!important}
main [class*="ms-auto"]:not(button):not(svg):not(input){max-width:100%!important}
[class*="UserQuery"],[class*="user-query"],[class*="human"]{max-width:100%!important}
.scrollable-container [class*="grouptitle"]{display:flex!important;width:100%!important;max-width:100%!important;flex-direction:column!important}
.scrollable-container [class*="groupquery"]{width:100%!important;max-width:100%!important}
.scrollable-container [class*="rounded-tl-2xl"][class*="bg-subtle"]{max-width:100%!important;width:100%!important}
/* ============================================================
SIDEBAR 緊湊
============================================================ */
div.w-sideBarWidth{flex-shrink:0!important;transition:width .05s!important}
nav.groupsidebar,nav[class*="groupsidebar"]{overflow-x:hidden!important}
nav span.w-full.overflow-hidden{display:block!important;white-space:nowrap!important;overflow:hidden!important;-webkit-mask-image:none!important;mask-image:none!important;line-height:1.3!important;max-height:1.3em!important}
nav .flex.flex-none.flex-col{height:auto!important;min-height:0!important;gap:0!important;padding:0!important}
nav a,nav button{padding-top:0!important;padding-bottom:0!important;margin-top:0!important;margin-bottom:0!important;min-height:0!important}
nav a[href],nav button{height:auto!important;max-height:22px!important;line-height:1.2!important}
nav a svg,nav button svg{width:14px!important;height:14px!important}
nav [class*="gap-"]{gap:0!important}
nav [class*="py-"]{padding-top:0!important;padding-bottom:0!important}
nav [class*="my-"]{margin-top:0!important;margin-bottom:0!important}
nav [class*="mt-"]{margin-top:0!important}
nav [class*="mb-"]{margin-bottom:0!important}
nav [class*="pt-"]{padding-top:0!important}
nav [class*="pb-"]{padding-bottom:0!important}
nav [class*="space-y"]>*{margin-top:1px!important}
nav [class*="px-md"]{padding-left:4px!important;padding-right:4px!important}
nav [class*="px-12px"]{padding-left:4px!important;padding-right:4px!important}
nav [class*="pl-14px"]{padding-left:6px!important}
nav [class*="ml-26px"]{margin-left:10px!important}
nav [class*="-ml-md"]{margin-left:-4px!important}
nav [class*="border-t"]{margin-top:2px!important;margin-bottom:2px!important}
nav [class*="group"][class*="sidebar-sub-menu"] > div[class*="h-10"],
nav [class*="group\/sidebar-sub-menu"] > div[class*="h-10"]{height:24px!important;min-height:24px!important;max-height:24px!important;padding-top:0!important;padding-bottom:0!important}
nav [class*="group"][class*="sidebar-sub-menu"] > div[class*="h-7"],
nav [class*="group\/sidebar-sub-menu"] > div[class*="h-7"]{height:20px!important;min-height:20px!important;max-height:20px!important;padding-top:0!important;padding-bottom:0!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="size-6"],
nav [class*="group\/sidebar-sub-menu"] [class*="size-6"]{width:16px!important;height:16px!important;min-width:16px!important;min-height:16px!important}
nav [class*="group"][class*="sidebar-sub-menu"] svg[width="18"],
nav [class*="group\/sidebar-sub-menu"] svg[width="18"]{width:14px!important;height:14px!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="text-sm"],
nav [class*="group\/sidebar-sub-menu"] [class*="text-sm"]{line-height:24px!important;font-size:11.5px!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="text-xs"],
nav [class*="group\/sidebar-sub-menu"] [class*="text-xs"]{line-height:20px!important;font-size:11px!important}
nav [class*="group"][class*="sidebar-sub-menu"] > div[class*="rounded-xl"],
nav [class*="group\/sidebar-sub-menu"] > div[class*="rounded-xl"]{border-radius:6px!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="pl-sm"],
nav [class*="group\/sidebar-sub-menu"] [class*="pl-sm"]{padding-left:4px!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="px-sm"],
nav [class*="group\/sidebar-sub-menu"] [class*="px-sm"]{padding-left:4px!important;padding-right:4px!important}
nav [class*="group"][class*="sidebar-sub-menu"] [class*="pr-3"]{padding-right:4px!important}
nav [class*="group"][class*="sidebar-sub-menu"] button[class*="h-6"],
nav [class*="group\/sidebar-sub-menu"] button[class*="h-6"]{height:18px!important;min-height:18px!important;padding:0!important}
nav > div{gap:0!important}
nav [class*="flex-col"]{gap:0!important}
/* 隱藏被快捷鈕取代的 nav 列 */
.pplx-hidden-nav-row{display:none!important}
/* ── 新建行 position:relative,讓快捷鈕可 absolute 定位 ── */
/* v1.9.3:+新建 icon 鈕,在 logo 正右方,獨立不融合 */
#pplx-new-btn-header{
display:inline-flex!important;align-items:center!important;justify-content:center!important;
width:26px!important;height:26px!important;
min-width:26px!important;min-height:26px!important;
border-radius:6px!important;border:none!important;cursor:pointer!important;
background:transparent!important;
color:var(--text-quiet,currentColor)!important;
opacity:.7!important;flex-shrink:0!important;
text-decoration:none!important;
margin-left:3px!important;margin-right:0!important;
padding:0!important;
transition:background 130ms,opacity 130ms!important;
vertical-align:middle!important;
}
#pplx-new-btn-header:hover{background:rgba(128,128,128,.15)!important;opacity:1!important}
#pplx-new-btn-header svg{width:15px!important;height:15px!important;display:block!important;stroke:currentColor!important;fill:none!important;pointer-events:none!important;}
.pplx-newbtn-anchor{position:relative!important;overflow:visible!important}
/* ── 快捷鈕群組:absolute 釘在行右端 ── */
.pplx-hdr-shortcuts{
position:absolute!important;
right:2px!important;top:50%!important;
transform:translateY(-50%)!important;
display:flex!important;align-items:center!important;
justify-content:space-around!important;
gap:0px!important;
width:calc(100% - 82px)!important;
z-index:9000!important;
pointer-events:auto!important;
visibility:visible!important;opacity:1!important;
}
/* ── 快捷鈕本體 ── */
a.pplx-hdr-btn{
display:inline-flex!important;align-items:center!important;justify-content:center!important;
flex:1!important;
height:22px!important;
max-height:22px!important;min-height:22px!important;
min-width:18px!important;
border-radius:5px!important;
background:transparent!important;border:none!important;cursor:pointer!important;
color:var(--text-quiet,currentColor)!important;
opacity:.7!important;flex-shrink:0!important;
text-decoration:none!important;
padding:0!important;margin:0!important;
line-height:1!important;
transition:background 130ms,opacity 130ms!important;
visibility:visible!important;pointer-events:auto!important;
overflow:visible!important;box-sizing:border-box!important;
}
a.pplx-hdr-btn:hover{background:rgba(128,128,128,.15)!important;opacity:1!important}
/* SVG 固定 14px — 覆蓋所有 nav svg 規則 */
a.pplx-hdr-btn svg{
width:14px!important;height:14px!important;
min-width:14px!important;min-height:14px!important;
max-width:14px!important;max-height:14px!important;
display:block!important;pointer-events:none!important;
stroke:currentColor!important;fill:none!important;
overflow:visible!important;
}
/* Tooltip */
a.pplx-hdr-btn::after{
content:attr(data-tip);display:none;
position:absolute;top:calc(100% + 4px);left:50%;
transform:translateX(-50%);
background:rgba(0,0,0,.82);color:#fff;
font-size:11px;padding:2px 7px;border-radius:4px;
white-space:nowrap;pointer-events:none;z-index:99999;
font-family:sans-serif;font-weight:normal;
}
a.pplx-hdr-btn:hover::after{display:block}
/* CODE */
pre,code{max-width:100%!important;word-break:break-all!important;white-space:pre-wrap!important}
.pplx-pre-container{position:relative!important;display:block!important;overflow:visible!important;margin-top:2px!important;margin-bottom:2px!important}
.pplx-code-wrapper{position:sticky!important;top:0!important;height:0!important;width:100%!important;display:flex!important;justify-content:flex-end!important;align-items:flex-start!important;z-index:200!important;pointer-events:none!important;overflow:visible!important}
.pplx-pre-container>pre{padding-top:38px!important;margin-top:0!important}
.pplx-code-toolbar{pointer-events:auto!important;margin-right:6px!important;margin-top:3px!important;display:flex!important;flex-direction:row!important;align-items:center!important;gap:5px!important;background:var(--background-base-color,#ffffff)!important;border:1px solid rgba(14,165,233,.22)!important;padding:3px 6px 3px 8px!important;border-radius:20px!important;box-shadow:0 2px 10px rgba(14,165,233,.18)!important;max-width:calc(100% - 40px)!important;min-width:100px!important}
.pplx-tb-left{display:flex!important;align-items:center!important;gap:4px!important;overflow:hidden!important;flex:1!important;min-width:0!important}
.pplx-code-name{font-size:11px!important;font-weight:700!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;color:#0ea5e9!important;user-select:none!important;background:rgba(14,165,233,.10)!important;padding:1px 7px 1px 5px!important;border-radius:10px!important;line-height:1.6!important;cursor:default!important}
.pplx-code-name::before{content:'●';margin-right:4px;font-size:7px;vertical-align:middle;opacity:.7}
.pplx-code-ver{font-size:10px!important;font-weight:500!important;white-space:nowrap!important;flex-shrink:0!important;color:#64748b!important;user-select:none!important;background:rgba(100,116,139,.10)!important;padding:1px 6px!important;border-radius:8px!important;line-height:1.6!important;cursor:default!important}
.pplx-code-arrow{flex-shrink:0!important;cursor:pointer!important;color:#fff!important;font-size:10px!important;padding:2px 8px!important;border-radius:10px!important;background:#0ea5e9!important;transition:background .15s!important;user-select:none!important;line-height:1.5!important;font-weight:bold!important}
.pplx-code-arrow:hover{background:#0284c7!important}
.pplx-code-arrow.pplx-expanded{background:#0284c7!important}
.pplx-code-copy{flex-shrink:0!important;cursor:pointer!important;font-size:13px!important;padding:2px 6px!important;border-radius:8px!important;line-height:1.4!important;transition:background .15s,transform .1s!important;user-select:none!important;margin-left:2px!important}
.pplx-code-copy:hover{background:rgba(14,165,233,.15)!important;transform:scale(1.12)!important}
.pplx-code-collapsed{max-height:0!important;min-height:0!important;overflow:hidden!important;opacity:.75!important;cursor:pointer!important;border-bottom:3px dashed #0ea5e9!important;padding:0!important;margin:0!important;transition:max-height .3s ease,opacity .2s!important}
.pplx-code-collapsed:hover{opacity:1!important}
/* DRAG HANDLE */
#pplx-resize-handle{width:8px;height:100vh;position:fixed;top:0;left:var(--pplx-sidebar-width,185px);z-index:9999999;cursor:col-resize;background:transparent}
#pplx-resize-handle::after{content:'';position:absolute;left:50%;top:0;bottom:0;width:1px;background:#22d3ee;transform:translateX(-50%)}
#pplx-resize-handle:hover::after,#pplx-resize-handle.active::after{background:#06b6d4;width:2px}
#pplx-drag-curtain{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999998;cursor:col-resize;display:none}
body.pplx-resizing #pplx-drag-curtain{display:block}
body.pplx-resizing *{user-select:none!important}
/* GLOBAL COLLAPSE BTN */
#pplx-global-collapse-btn{position:fixed!important;right:18px!important;bottom:60px!important;z-index:9999999!important;background:#0ea5e9!important;color:#fff!important;border:none!important;border-radius:20px!important;width:32px!important;height:32px!important;font-size:15px!important;cursor:pointer!important;box-shadow:0 3px 8px rgba(0,0,0,.25)!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:transform .2s,background .2s!important;user-select:none!important}
#pplx-global-collapse-btn:hover{transform:scale(1.08)!important;background:#0284c7!important}
`;
if (typeof GM_addStyle !== 'undefined') { GM_addStyle(css); }
else { const s = document.createElement('style'); s.textContent = css; document.documentElement.appendChild(s); }
// ── Sidebar Width ──
function applySidebarWidth(px) {
savedWidth = px;
document.documentElement.style.setProperty('--pplx-sidebar-width', px + 'px');
['div.w-sideBarWidth', 'nav.groupsidebar', 'nav[class*="groupsidebar"]'].forEach(sel => {
document.querySelectorAll(sel).forEach(el => {
el.style.setProperty('width', px + 'px', 'important');
// 不強制 min-width,讓 Perplexity 原生折疊鈕可自由縮小
el.style.removeProperty('min-width');
el.style.setProperty('max-width', px + 'px', 'important');
});
});
const h = document.getElementById('pplx-resize-handle');
if (h) h.style.left = px + 'px';
}
function watchSidebarElement() {
const el = document.querySelector('div.w-sideBarWidth');
if (!el || el._pplxWatched) return; el._pplxWatched = true;
new MutationObserver(() => { const _wm = parseInt(el.style.width); if (!isNaN(_wm) && _wm > 60 && _wm !== savedWidth) applySidebarWidth(savedWidth); }).observe(el, { attributes: true, attributeFilter: ['style'] });
}
function fixThreadSectionWidth() {
document.querySelectorAll('nav [style*="width: 200"]').forEach(el => el.style.setProperty('width', '9999px', 'important'));
document.querySelectorAll('nav span[style*="mask-image"]').forEach(el => { el.style.removeProperty('mask-image'); el.style.removeProperty('-webkit-mask-image'); });
}
function fixPageContentWidth() {
document.querySelectorAll('[style*="--page-content-width"]').forEach(el => {
if (el.style.getPropertyValue('--page-content-width') !== '9999px')
el.style.setProperty('--page-content-width', '9999px');
});
document.querySelectorAll('[class*="bg-subtle"][class*="rounded-2xl"][style*="max-width"]').forEach(el => { el.style.removeProperty('max-width'); });
document.querySelectorAll('[class*="bg-subtle"][class*="rounded-tl-2xl"][style*="max-width"]').forEach(el => { el.style.removeProperty('max-width'); });
document.querySelectorAll('[class*="grouptitle"]').forEach(el => {
if (getComputedStyle(el).display === 'inline-flex') {
el.style.setProperty('display', 'flex', 'important');
el.style.setProperty('width', '100%', 'important');
}
});
}
// ── 快捷鈕定義 ──
const SHORTCUT_DEFS = [
{
label: '空間', href: '/spaces',
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7c0-1.1.9-2 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="12" y1="10" x2="12" y2="16"/></svg>`
},
{
label: '成品', href: '/computer/artifacts',
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1.5"/><rect x="13" y="3" width="8" height="8" rx="1.5"/><rect x="3" y="13" width="8" height="8" rx="1.5"/><rect x="13" y="13" width="8" height="8" rx="1.5"/></svg>`
},
{
label: '自訂', href: '/computer/connectors',
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`
},
];
const HIDDEN_LABELS = new Set(['空間', '成品', '自訂']);
let shortcutsInjected = false;
// ── v1.8.6 核心:找「新建」所在的 collapsible-sidebar-section
// 對它加 position:relative,然後 append 一個 absolute 定位的快捷列
// 完全不改變父層 flex 方向,不影響 nav 結構
// ── v1.9.2:把 +新建 注入到 sidebar 頂部 header 列(logo 右方)──
function injectNewBtnToHeader() {
if (document.getElementById('pplx-new-btn-header')) return true;
// 找 sidebar header:nav 第一個直接子 div
const nav = document.querySelector('nav[class*="groupsidebar"]')
|| document.querySelector('nav[class*="sidebar"]')
|| document.querySelector('div.w-sideBarWidth nav');
if (!nav) return false;
const headerDiv = nav.firstElementChild;
if (!headerDiv) return false;
// 找 logo(第一個 a 元素)
const logoA = headerDiv.querySelector('a');
if (!logoA) return false;
const newBtn = document.createElement('a');
newBtn.id = 'pplx-new-btn-header';
newBtn.href = '/';
newBtn.setAttribute('aria-label', '新建');
newBtn.title = '新建對話';
// 只有 + 圖示,不加文字,保持 icon 風格一致
newBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>`;
// 插在 logo 的正後方(logo nextSibling 前)
if (logoA.nextSibling) {
headerDiv.insertBefore(newBtn, logoA.nextSibling);
} else {
headerDiv.appendChild(newBtn);
}
return true;
}
// ── v1.9.1:多選擇器找 section,完全不動 nav flex 結構 ──
function findAnchorSection() {
// 嘗試各種可能的「新建」aria-label(中英文,有無 draggable)
const selectors = [
'a[draggable="false"][aria-label="新建"]',
'a[aria-label="新建"]',
'a[aria-label="New"]',
'a[aria-label="新增"]',
'a[draggable="false"][href="/"]',
];
for (const sel of selectors) {
const link = document.querySelector(sel);
if (!link) continue;
let el = link;
for (let i = 0; i < 10; i++) {
el = el.parentElement;
if (!el) break;
if ((el.className || '').includes('collapsible-sidebar-section')) return el;
}
}
// 最後備援:取第一個 collapsible-sidebar-section
return document.querySelector('div[class*="collapsible-sidebar-section"]') || null;
}
function injectSidebarShortcuts() {
if (document.querySelector('.pplx-newbtn-anchor')) return true; // 已注入
const sectionEl = findAnchorSection();
if (!sectionEl) return false;
if (sectionEl.dataset.pplxDone) return true;
sectionEl.dataset.pplxDone = '1';
// 加 position:relative,讓快捷鈕 absolute 相對此 section 定位(不動 flex)
sectionEl.classList.add('pplx-newbtn-anchor');
// 建立快捷鈕群組
const wrap = document.createElement('div');
wrap.className = 'pplx-hdr-shortcuts';
SHORTCUT_DEFS.forEach(({ label, href, svg }) => {
const a = document.createElement('a');
a.href = href;
a.className = 'pplx-hdr-btn';
a.setAttribute('data-tip', label);
a.setAttribute('aria-label', label);
a.innerHTML = svg;
wrap.appendChild(a);
});
sectionEl.appendChild(wrap);
hideOriginalNavRows();
return true;
}
function hideOriginalNavRows() {
document.querySelectorAll('div[class*="collapsible-sidebar-section"]').forEach(section => {
if (section.classList.contains('pplx-hidden-nav-row')) return;
const link = section.querySelector('a[aria-label]');
if (link && HIDDEN_LABELS.has(link.getAttribute('aria-label'))) {
section.classList.add('pplx-hidden-nav-row');
}
});
}
applySidebarWidth(savedWidth);
setInterval(() => {
const el = document.querySelector('div.w-sideBarWidth');
const _w191 = parseInt(el ? el.style.width : '0');
if (el && !isNaN(_w191) && _w191 > 60 && _w191 !== savedWidth) applySidebarWidth(savedWidth);
// 折疊時隱藏青色 resize handle
const rh = document.getElementById('pplx-resize-handle');
if (rh) rh.style.display = (!isNaN(_w191) && _w191 < 60) ? 'none' : '';
fixThreadSectionWidth();
}, 400);
let scrollToBottomTimers = [];
function scheduleScrollToBottom() {
scrollToBottomTimers.forEach(t => clearTimeout(t));
scrollToBottomTimers = [];
[300, 700, 1300, 2500, 4000].forEach(delay => {
scrollToBottomTimers.push(setTimeout(() => {
const sc = document.querySelector('.scrollable-container');
if (sc) sc.scrollTop = sc.scrollHeight;
}, delay));
});
}
let lastUrl = location.href, lastBubbleScan = 0;
function checkUrlChange() {
if (location.href === lastUrl) return;
lastUrl = location.href;
lastBubbleScan = 0;
shortcutsInjected = false;
setTimeout(fixPageContentWidth, 300);
setTimeout(fixPageContentWidth, 800);
scheduleScrollToBottom();
}
function expandUserBubbles() {
const now = Date.now();
if (now - lastBubbleScan < 600) return;
lastBubbleScan = now;
const sc = document.querySelector('.scrollable-container');
if (!sc || sc.getBoundingClientRect().width < 10) return;
sc.querySelectorAll('[class*="justify-end"]').forEach(row => {
if (row._pplxRowFixed || row.children.length < 2) return;
const children = Array.from(row.children);
const bubble = children[children.length - 1];
const ac = children.slice(0, -1).filter(c => {
const cls = typeof c.className === 'string' ? c.className : '';
return cls.includes('invisible') || cls.includes('opacity-0') || c.querySelectorAll('button').length > 0;
});
if (!ac.length) return;
row._pplxRowFixed = true; row.style.position = 'relative';
ac.forEach(el => { el.style.position = 'absolute'; el.style.right = '4px'; el.style.left = 'auto'; el.style.top = '4px'; el.style.zIndex = '20'; });
bubble.style.setProperty('width', '100%', 'important');
bubble.style.setProperty('max-width', '100%', 'important');
bubble.style.setProperty('text-align', 'left', 'important');
});
}
function compressNavIcons() {
['32px', '28px'].forEach(px => {
document.querySelectorAll(`nav [style*="width: ${px}"]`).forEach(el => {
if (el.style.width === px || el.style.minWidth === px) {
el.style.setProperty('width', '20px', 'important');
el.style.setProperty('min-width', '20px', 'important');
}
});
});
}
const LANG_RE = /^(javascript|typescript|python|java|css|html|sql|bash|sh|shell|json|yaml|xml|go|rust|c\+\+|cpp|c#|ruby|php|swift|kotlin|r|matlab|scala|perl|lua|dart|vue|jsx|tsx|sass|scss|less|toml|ini|dockerfile|makefile|plaintext|text)$/i;
function getCleanCode(pre) {
const codeEl = pre.querySelector('code');
if (codeEl) return codeEl.innerText;
const clone = pre.cloneNode(true);
clone.querySelectorAll('[data-testid="code-language-indicator"]').forEach(el => el.remove());
clone.querySelectorAll('.pplx-code-toolbar,.pplx-code-wrapper').forEach(el => el.remove());
const raw = clone.innerText.trim();
const lines = raw.split('\n');
if (LANG_RE.test((lines[0] || '').trim())) return lines.slice(1).join('\n').trimStart();
return raw;
}
let isGlobalCollapsed = true;
function ensureGlobalBtn() {
if (document.getElementById('pplx-global-collapse-btn') || !document.body) return;
const btn = document.createElement('div');
btn.id = 'pplx-global-collapse-btn'; btn.textContent = '⏬'; btn.title = '全域折疊/展開代碼';
btn.onclick = () => {
isGlobalCollapsed = !isGlobalCollapsed;
btn.textContent = isGlobalCollapsed ? '⏬' : '⏫';
document.querySelectorAll('pre.pplx-processed').forEach(pre => {
const arrow = pre._pplxArrow;
if (isGlobalCollapsed) { pre.classList.add('pplx-code-collapsed'); if (arrow) { arrow.textContent = '▼ 展開'; arrow.classList.remove('pplx-expanded'); } }
else { pre.classList.remove('pplx-code-collapsed'); if (arrow) { arrow.textContent = '▲ 折疊'; arrow.classList.add('pplx-expanded'); } }
});
};
document.body.appendChild(btn);
}
function processCodeBlocks() {
document.querySelectorAll('pre:not(.pplx-processed)').forEach(pre => {
pre.classList.add('pplx-processed');
if (!pre.parentNode) return;
const txt = pre.innerText || '';
const nmMatch = txt.match(/@name\s+([^\n]+)/i);
const vmMatch = txt.match(/@version\s+([^\n]+)/i);
const container = document.createElement('div'); container.className = 'pplx-pre-container';
const wrap = document.createElement('div'); wrap.className = 'pplx-code-wrapper';
const tbar = document.createElement('div'); tbar.className = 'pplx-code-toolbar';
const left = document.createElement('div'); left.className = 'pplx-tb-left';
if (nmMatch) { const n = document.createElement('span'); n.className = 'pplx-code-name'; n.textContent = nmMatch[1].trim(); n.title = nmMatch[1].trim(); left.appendChild(n); }
if (vmMatch) { const v = document.createElement('span'); v.className = 'pplx-code-ver'; v.textContent = 'v' + vmMatch[1].trim(); left.appendChild(v); }
const arrow = document.createElement('span');
arrow.className = 'pplx-code-arrow'; arrow.title = '展開 / 折疊代碼';
if (isGlobalCollapsed) { arrow.textContent = '▼ 展開'; pre.classList.add('pplx-code-collapsed'); }
else { arrow.textContent = '▲ 折疊'; arrow.classList.add('pplx-expanded'); }
arrow.onclick = e => {
e.preventDefault(); e.stopPropagation();
const collapsed = pre.classList.toggle('pplx-code-collapsed');
arrow.textContent = collapsed ? '▼ 展開' : '▲ 折疊';
arrow.classList.toggle('pplx-expanded', !collapsed);
};
left.appendChild(arrow);
const copyBtn = document.createElement('span');
copyBtn.className = 'pplx-code-copy'; copyBtn.textContent = '📋'; copyBtn.title = '複製代碼';
copyBtn.onclick = e => {
e.preventDefault(); e.stopPropagation();
const code = getCleanCode(pre);
const fb = () => { copyBtn.textContent = '✅'; setTimeout(() => copyBtn.textContent = '📋', 2000); };
if (navigator.clipboard) navigator.clipboard.writeText(code).then(fb).catch(() => { const ta = document.createElement('textarea'); ta.value = code; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); fb(); });
else { const ta = document.createElement('textarea'); ta.value = code; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); fb(); }
};
pre.onclick = () => { if (pre.classList.contains('pplx-code-collapsed')) { pre.classList.remove('pplx-code-collapsed'); arrow.textContent = '▲ 折疊'; arrow.classList.add('pplx-expanded'); } };
tbar.appendChild(left); tbar.appendChild(copyBtn); wrap.appendChild(tbar);
pre.parentNode.insertBefore(container, pre);
container.appendChild(wrap); container.appendChild(pre);
pre._pplxArrow = arrow;
});
}
let isResizing = false, animId = null;
function ensureResizeHandle() {
if (document.getElementById('pplx-resize-handle') || !document.body) return;
const h = document.createElement('div'); h.id = 'pplx-resize-handle'; h.title = '拖曳側欄寬度 | 雙擊還原';
const c = document.createElement('div'); c.id = 'pplx-drag-curtain';
document.body.appendChild(h); document.body.appendChild(c);
h.addEventListener('mousedown', e => { isResizing = true; h.classList.add('active'); document.body.classList.add('pplx-resizing'); e.preventDefault(); });
window.addEventListener('mousemove', e => { if (!isResizing) return; if (animId) cancelAnimationFrame(animId); animId = requestAnimationFrame(() => applySidebarWidth(Math.min(Math.max(e.clientX, MIN_W), MAX_W))); });
window.addEventListener('mouseup', () => { if (!isResizing) return; isResizing = false; h.classList.remove('active'); document.body.classList.remove('pplx-resizing'); try { GM_setValue('pplx_sidebar_width', savedWidth); } catch (_) { } });
h.addEventListener('dblclick', () => { applySidebarWidth(185); try { GM_setValue('pplx_sidebar_width', 185); } catch (_) { } });
}
let originalTitle = document.title, tabState = 'IDLE', wasGenerating = false;
function setTabState(s) { let t = originalTitle; if (s === 'GENERATING') t = '【⏳】 ' + originalTitle; else if (s === 'DONE') t = '【✅】 ' + originalTitle; if (document.title !== t) document.title = t; }
window.addEventListener('focus', () => { if (tabState === 'DONE') { tabState = 'IDLE'; setTabState('IDLE'); } });
window.addEventListener('click', () => { if (tabState === 'DONE') { tabState = 'IDLE'; setTabState('IDLE'); } });
function detectGenerating() {
const stop = document.querySelector('button[aria-label*="Stop"],button[aria-label*="停止"]');
const gen = !!(stop && stop.offsetParent !== null);
if (gen && !wasGenerating) { tabState = 'GENERATING'; setTabState(tabState); wasGenerating = true; if (!originalTitle.includes('【')) originalTitle = document.title; }
else if (!gen && wasGenerating) { tabState = 'DONE'; setTabState(tabState); wasGenerating = false; }
if (tabState === 'IDLE' && !document.title.includes('【')) originalTitle = document.title;
}
function maintainUI() {
ensureResizeHandle();
watchSidebarElement();
ensureGlobalBtn();
processCodeBlocks();
detectGenerating();
checkUrlChange();
expandUserBubbles();
compressNavIcons();
fixPageContentWidth();
if (!shortcutsInjected) shortcutsInjected = injectSidebarShortcuts();
injectNewBtnToHeader();
hideOriginalNavRows();
}
setInterval(maintainUI, 300);
maintainUI();
scheduleScrollToBottom();
})();