texlite 0.6.9 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/client/assets/{BinaryModule-Aj1vLnwf-BMr5UOlk.js → BinaryModule-Aj1vLnwf-wAe5fMVe.js} +1 -1
  2. package/dist/client/assets/GitDialog-b7cWzpdJ.js +3 -0
  3. package/dist/client/assets/{HistoryDialog-C2ibyUF6.js → HistoryDialog-F8tDuD3v.js} +1 -1
  4. package/dist/client/assets/LatexEditor-BT2E5IIK.js +59 -0
  5. package/dist/client/assets/PdfPreview-C2tw6xhu.js +1 -0
  6. package/dist/client/assets/{ProjectNavigationDialogs-BqzwJl7t.js → ProjectNavigationDialogs-CzrF5eh5.js} +1 -1
  7. package/dist/client/assets/{SystemMetricsDialog-CGyldSTF.js → SystemMetricsDialog-OAoXCVVy.js} +1 -1
  8. package/dist/client/assets/binary-CWru8eff.js +1 -0
  9. package/dist/client/assets/index-BLOqf18q.js +62 -0
  10. package/dist/client/assets/index-CwGialPz.css +1 -0
  11. package/dist/client/assets/{index-Cjwy6IYP.js → index-JmuE6_j5.js} +1 -1
  12. package/dist/client/assets/minus-D0DZYQR6.js +1 -0
  13. package/dist/client/assets/{spellCheck-DOSHWRcE.js → spellCheck-TTrb0uqA.js} +3 -3
  14. package/dist/client/index.html +2 -2
  15. package/dist/server/app.js +315 -2
  16. package/dist/server/citationLibrary.js +174 -0
  17. package/dist/server/db.js +37 -0
  18. package/package.json +2 -1
  19. package/dist/client/assets/GitDialog-8QrVaeyZ.js +0 -3
  20. package/dist/client/assets/LatexEditor-DaGzADFz.js +0 -59
  21. package/dist/client/assets/PdfPreview-BBQ4llIA.js +0 -1
  22. package/dist/client/assets/binary-Ds0O2e5F.js +0 -1
  23. package/dist/client/assets/index-CISv_aup.js +0 -17
  24. package/dist/client/assets/index-DdrajKQB.css +0 -1
  25. package/dist/client/assets/plus-DQ34kKNC.js +0 -1
@@ -0,0 +1 @@
1
+ :root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#202421;background:#f3f4f1;font-synthesis:none}*{box-sizing:border-box}html,body,#root{margin:0;min-height:100%}button,input,select,textarea{font:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.45}.muted{color:#737a75}.error{color:#b3261e;font-size:14px}.dialog-error{margin:0;padding:8px 10px;border:1px solid #e9b6b0;border-radius:6px;background:#fff3f1}.field-hint{color:#778179;font-size:11px;font-weight:400}.primary,.compile{border:0;border-radius:8px;background:#176b44;color:#fff;padding:10px 18px;font-weight:650}.icon-button{display:inline-flex;align-items:center;gap:6px}.ghost{border:0;background:transparent;color:inherit}.page{min-height:100vh;display:flex;flex-direction:column}.login-page{min-height:100vh;display:grid;grid-template-rows:1fr auto;place-items:center;padding-top:44px;background:radial-gradient(circle at 50% 0,#d8e8dd,#f5f5f1 45%)}.login-card{width:min(390px,calc(100vw - 32px));padding:38px;display:flex;flex-direction:column;gap:16px;background:#ffffffeb;border:1px solid #dfe4df;border-radius:16px;box-shadow:0 24px 70px #1d31241f}.login-card h1{text-align:center;margin:0;letter-spacing:-.04em}.login-card>p{text-align:center;margin:-8px 0 8px}.login-card label,.settings label{display:flex;flex-direction:column;gap:7px;font-size:13px;font-weight:650}.login-card input,.settings input,.settings select,.settings textarea{width:100%;border:1px solid #cbd2cc;border-radius:8px;padding:10px 11px;background:#fff;outline:none}.login-card input:focus,.settings input:focus{border-color:#176b44;box-shadow:0 0 0 3px #176b4420}.site-logo{width:40px;height:40px;flex:none;display:inline-flex;align-items:center;justify-content:center}.site-logo img{display:block;width:100%;height:100%}.site-logo.compact{width:34px;height:34px;margin:0 16px 0 10px}.site-logo.auth-logo{width:76px;height:76px;margin:0 auto 2px;filter:drop-shadow(0 10px 18px #1e2a4a25)}.brand-link{display:inline-flex;align-items:center;color:inherit;text-decoration:none}.brand-link:hover .site-title{color:#d9f0df}.compact-brand-link{flex:none}.site-title{margin-right:11px;font-size:20px;font-weight:800;letter-spacing:-.04em}.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.support{color:#737a75;text-align:center}.topbar,.editor-topbar{height:62px;display:flex;align-items:center;padding:0 26px;background:#123c2a;color:#fff}.top-actions{margin-left:auto;display:flex;align-items:center;gap:18px;font-size:14px}.top-actions .ghost{display:inline-flex;align-items:center;gap:5px;border:1px solid transparent;border-radius:6px;padding:6px 8px;background:transparent;color:inherit;transition:background .15s ease,border-color .15s ease,box-shadow .15s ease,color .15s ease,transform .08s ease}.top-actions .ghost:hover,.top-actions .ghost:focus-visible{border-color:#ffffff20;background:#ffffff16;color:#d9f0df;box-shadow:0 4px 12px #061c1130;outline:none}.top-actions .ghost:active{transform:translateY(1px);box-shadow:0 1px 4px #061c1126}.top-actions .ghost.top-return-action,.top-actions .ghost.top-return-action:hover,.top-actions .ghost.top-return-action:focus-visible,.top-actions .ghost.top-return-action:active{transform:none;box-shadow:none}.top-user-identity{min-width:0;display:flex;flex-direction:column;line-height:1.15}.top-user-identity strong{overflow:hidden;max-width:180px;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.top-user-identity small{overflow:hidden;max-width:180px;margin-top:3px;color:#b9d0c2;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.language-switcher{position:fixed;top:18px;right:20px;z-index:5;display:flex;align-items:center;gap:6px;color:#536059;font-size:12px}.language-switcher select{border:1px solid #c9d0ca;border-radius:6px;padding:5px 7px;background:#fff;color:#26342b}.language-switcher.compact{position:static;color:inherit}.language-switcher.compact select{border-color:#ffffff35;background:#ffffff10;color:inherit}.language-switcher.compact option{color:#202421}.dashboard{width:100%;max-width:1180px;flex:1;margin:0 auto;padding:52px 34px}.citation-library-page-shell{padding-top:34px}.citation-library-page{display:flex;flex-direction:column;gap:18px}.citation-library-page-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px}.citation-library-page-header>div{min-width:0}.citation-library-page-header h1{display:flex;align-items:center;gap:9px;margin:0;color:#202421;font-size:30px;letter-spacing:-.04em}.citation-library-page-header h1 svg{flex:none;color:#315b43}.citation-library-page-header p{max-width:720px;margin:8px 0 0;color:#737a75;font-size:12px;line-height:1.5}.citation-library-page-header>button{display:inline-flex;align-items:center;gap:5px;flex:none;border:1px solid #bfc8c1;border-radius:8px;padding:9px 12px;background:#fff;color:#52685a;font-size:11px}.citation-library-page-header>button:hover{border-color:#8eaa98;background:#edf4ef;color:#176b44}.citation-library-page .citation-library-dialog{padding:18px;border:1px solid #dfe4df;border-radius:12px;background:#fff;box-shadow:0 8px 26px #25352a0f}.citation-library-page .citation-entry-list{max-height:none}.citation-library-pagination{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:6px;color:#66736b;font-size:10px}.citation-library-pagination button{display:inline-flex;align-items:center;gap:4px;border:1px solid #cbd5ce;border-radius:6px;padding:6px 9px;background:#fff;color:#52685a;font-size:10px}.citation-library-pagination button:hover:not(:disabled){border-color:#8eaa98;background:#edf4ef;color:#176b44}.site-footer{width:100%;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:7px 16px;padding:18px 24px;border-top:1px solid #dfe3df;background:#eef0ed;color:#737b75;font-size:11px;text-align:center}.site-footer a{color:#176b44;text-decoration:none}.site-footer a:hover{text-decoration:underline}.login-page>.site-footer{align-self:end;background:#ffffff50}.section-title{display:flex;justify-content:space-between;align-items:end;margin-bottom:28px}.section-title h1{display:flex;align-items:center;gap:9px;margin:0;font-size:30px;letter-spacing:-.04em}.section-title h1 svg{flex:none;color:#315b43}.section-title p{margin:7px 0 0}.section-actions{display:flex;gap:9px}.section-actions>button{display:inline-flex;align-items:center;gap:6px;border:1px solid #bfc8c1;border-radius:8px;background:#fff;padding:10px 16px}.section-actions>.primary{border-color:#176b44;background:#176b44;color:#fff}.project-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-bottom:18px;padding:10px;background:#e9ece8;border-radius:10px}.project-toolbar>input{width:220px;border:1px solid #cbd2cc;border-radius:7px;padding:8px 10px;background:#fff}.project-scope{display:flex;flex:none;gap:2px}.project-scope button{display:flex;align-items:center;gap:5px;flex:none;border:1px solid transparent;border-radius:7px;padding:7px 9px;background:transparent;color:#536059;font-size:12px}.project-scope button.active{border-color:#b8c4ba;background:#fff;color:#174b33}.tag-filters{min-width:0;flex:1;display:flex;gap:6px;overflow-x:auto}.tag-filters button,.view-toggle button{display:flex;align-items:center;gap:5px;flex:none;border:1px solid transparent;border-radius:7px;padding:7px 9px;background:transparent;color:#536059;font-size:12px}.tag-filters button.active,.view-toggle button.active{border-color:#b8c4ba;background:#fff;color:#174b33}.project-sort{flex:none;display:flex;align-items:center;gap:5px;color:#536059;font-size:11px}.project-sort>span{white-space:nowrap}.project-sort select{max-width:150px;border:1px solid #c4ccc6;border-radius:7px;padding:7px 25px 7px 8px;background:#fff;color:#34433a;font-size:12px}.view-toggle{display:flex;gap:2px}.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:16px}.project-card{position:relative;min-height:250px;display:flex;flex-direction:column;text-align:left;background:#fff;border:1px solid #dfe2de;border-radius:12px;overflow:hidden;transition:.15s ease}.project-card:hover{border-color:#8bae9a;transform:translateY(-2px);box-shadow:0 10px 30px #25352a10}.project-card-open{all:unset;box-sizing:border-box;min-width:0;flex:1;display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:20px 20px 14px;cursor:pointer}.project-card-open:focus-visible{outline:3px solid #176b4455;outline-offset:-3px}.project-card strong{font-size:17px}.project-card-main{min-width:0;display:flex;flex-direction:column;gap:7px}.project-tags,.settings-tags{display:flex;flex-wrap:wrap;gap:5px}.owner-badge{max-width:100%;overflow:hidden;padding:6px 9px;border-radius:7px;background:#e2f0e7;color:#176b44;font-size:11px;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.project-grid:not(.list-view) .owned-project .owner-badge{max-width:calc(100% - 94px)}.project-transfer-action{position:absolute;z-index:2;top:17px;right:16px;display:inline-flex;align-items:center;gap:4px;border:1px solid #cbd7cf;border-radius:6px;padding:5px 7px;background:#f7faf8;color:#315c43;font-size:10px}.project-transfer-action:hover{border-color:#8caf99;background:#e8f1eb;color:#176b44}.project-meta{width:100%;display:grid;grid-template-columns:1fr;gap:8px;margin:auto 0 0}.project-meta div{min-width:0}.project-meta dt{display:flex;align-items:center;gap:4px;color:#8a918c;font-size:10px;text-transform:uppercase}.project-meta dt svg{flex:none;color:#6f8e7a}.project-meta dd{overflow:hidden;margin:2px 0 0;color:#555e58;font-size:11px;text-overflow:ellipsis;white-space:nowrap}.project-grid:not(.list-view) .project-meta dd{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.project-modified-by{color:#68716b}.project-card-actions{display:flex;flex-wrap:wrap;gap:5px;padding:9px 12px;border-top:1px solid #eceeeb;background:#fafbf9}.project-card-actions button,.project-card-actions a{display:inline-flex;align-items:center;gap:5px;border:0;border-radius:5px;padding:5px 7px;background:transparent;color:#4f5b53;font-size:11px;text-decoration:none}.project-card-actions button:hover,.project-card-actions a:hover{background:#e8eeea;color:#176b44}.project-card-actions .danger-text{margin-left:auto;color:#b3261e}.project-grid:not(.list-view) .project-card-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px}.project-grid:not(.list-view) .project-card-actions button,.project-grid:not(.list-view) .project-card-actions a{width:100%;min-width:0;justify-content:flex-start;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.project-grid:not(.list-view) .project-card-actions .danger-text{margin-left:0}.project-grid.list-view{display:flex;flex-direction:column;gap:7px}.project-grid.list-view .project-card{min-height:88px;flex-direction:row;align-items:stretch}.project-grid.list-view .project-card-open{display:grid;grid-template-columns:minmax(90px,140px) minmax(170px,1fr) minmax(390px,2fr);align-items:center;gap:14px;padding:12px 15px}.project-grid.list-view .project-card-main{min-width:170px}.project-grid.list-view .project-meta{grid-template-columns:minmax(145px,.75fr) minmax(0,1.25fr);gap:8px 12px;margin:0}.project-grid.list-view .project-meta>div:last-child dd{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.project-grid.list-view .project-card-actions{width:190px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:center;gap:4px;border-top:0;border-left:1px solid #eceeeb}.project-grid.list-view .project-card-actions button,.project-grid.list-view .project-card-actions a{width:100%;justify-content:flex-start}.project-grid.list-view .project-card-actions .danger-text{margin-left:0}.project-comments-badge{display:inline-flex;align-items:center;gap:3px;width:fit-content;border-radius:999px;padding:1px 6px;font-size:10px;font-weight:650;line-height:1.4}.project-comments-badge.unresolved{background:#fde8e7;color:#b3261e;border:1px solid #f8c8c5}.tag{display:inline-flex;align-items:center;gap:4px;width:fit-content;border-radius:999px;padding:2px 7px;background:#eef0ee;color:#4a524c;font-size:10px;font-weight:650}.tag-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:currentColor}.tag-red{--tag-color: #e04b45}.tag-orange{--tag-color: #ef9135}.tag-yellow{--tag-color: #dbb62f}.tag-green{--tag-color: #49a563}.tag-blue{--tag-color: #4c83db}.tag-purple{--tag-color: #9566ce}.tag-gray{--tag-color: #929994}.tag-dot[class*=tag-]{color:var(--tag-color)}.tag[class*=tag-]{border:1px solid color-mix(in srgb,var(--tag-color) 35%,transparent);background:color-mix(in srgb,var(--tag-color) 12%,white)}.empty,.center-card{padding:50px;color:#737a75;text-align:center}.project-empty{grid-column:1 / -1;width:100%;min-height:320px;display:flex;align-items:center;justify-content:center;flex-direction:column;box-sizing:border-box;padding:64px 24px;border:1px dashed #cbd7ce;border-radius:14px;background:linear-gradient(145deg,#fbfcfa,#f2f7f3);text-align:center}.project-empty-icon{width:58px;height:58px;display:grid;place-items:center;margin-bottom:18px;border-radius:18px;background:#dfeee4;color:#176b44}.project-empty h2{margin:0;color:#203f2e;font-size:clamp(27px,3vw,38px);line-height:1.15;letter-spacing:-.035em}.project-empty p{max-width:520px;margin:13px 0 0;color:#6e7b72;font-size:15px;line-height:1.6}.project-pagination{display:flex;align-items:center;justify-content:center;gap:12px;margin:22px 0 4px;color:#66726a;font-size:12px}.project-pagination button{display:inline-flex;align-items:center;gap:5px;border:1px solid #cbd4ce;border-radius:7px;padding:7px 10px;background:#fff;color:#405248;font-size:12px}.project-pagination button:hover:not(:disabled){border-color:#8bae9a;background:#f2f7f3;color:#176b44}.table-card{overflow:hidden;background:#fff;border:1px solid #dfe2de;border-radius:12px}table{width:100%;border-collapse:collapse}th,td{padding:14px 18px;border-bottom:1px solid #eceeeb;text-align:left;font-size:14px}th{background:#f8f9f7;color:#667069;font-size:12px;text-transform:uppercase}td small{display:block;color:#7d837f;margin-top:3px}td button{margin-right:8px;border:1px solid #cdd2ce;background:#fff;border-radius:5px;padding:5px 9px}.danger-text{color:#b3261e}.workspace{height:100vh;display:flex;flex-direction:column;overflow:hidden}.editor-topbar{height:56px;padding:0 14px;flex:none}.back{color:#fff;border:0;background:#ffffff10;width:32px;height:32px;display:grid;place-items:center;border-radius:6px}.project-heading{display:flex;flex-direction:column;min-width:0;margin-right:auto}.project-heading small{color:#b9d0c2;font-size:11px;margin-top:2px}.vim-status-badge{display:inline-flex;align-items:center;gap:5px;margin-right:8px;border:1px solid #7fd39e80;border-radius:999px;padding:5px 9px;background:#1d6b4638;color:#d7ffe3;font-size:10px;font-weight:750;letter-spacing:.04em;white-space:nowrap}.vim-status-badge svg{color:#9df0b5}.collaboration-presence{min-width:0;display:flex;align-items:center;gap:7px;padding-left:8px}.collaboration-status-dot{width:7px;height:7px;flex:none;border-radius:50%;background:#efb84b;box-shadow:0 0 0 2px #efb84b2e}.collaboration-connected .collaboration-status-dot{background:#69d391;box-shadow:0 0 0 2px #69d3912e}.collaboration-disconnected .collaboration-status-dot{background:#e07167;box-shadow:0 0 0 2px #e071672e}.collaboration-avatars{display:flex;align-items:center;padding-left:8px}.collaboration-avatar{--session-color: #1677c8;position:relative;width:28px;height:28px;display:grid;place-items:center;margin-left:-8px;border:2px solid #214d35;border-radius:50%;background:var(--session-color);color:#fff;font-size:11px;font-weight:750;cursor:default;box-shadow:0 1px 4px #061c1145}.collaboration-avatar.editing:after{content:"";position:absolute;right:-2px;bottom:-2px;width:7px;height:7px;border:2px solid #214d35;border-radius:50%;background:#8ff0af}.collaboration-avatar.local{outline:1px solid #ffffff8a;outline-offset:-4px}.collaboration-avatar-overflow{appearance:none;padding:0;border-color:#72877b;background:#f4f7f5;color:#284536;font-family:inherit;font-size:10px;cursor:pointer}.collaboration-avatar-overflow:hover,.collaboration-avatar-overflow:focus-visible{background:#dfe9e3;outline:2px solid #9bc6aa;outline-offset:1px}.collaboration-avatar-tooltip{position:absolute;z-index:80;top:calc(100% + 9px);right:-8px;width:max-content;max-width:280px;display:none;gap:2px;padding:9px 11px;border:1px solid #d7ded9;border-radius:7px;background:#fff;color:#20352a;font-size:12px;font-weight:400;line-height:1.35;text-align:left;box-shadow:0 8px 24px #061c1130}.collaboration-avatar-tooltip:before{content:"";position:absolute;top:-5px;right:15px;width:8px;height:8px;transform:rotate(45deg);border-left:1px solid #d7ded9;border-top:1px solid #d7ded9;background:#fff}.collaboration-avatar-tooltip strong,.collaboration-avatar-tooltip span,.collaboration-avatar-tooltip small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.collaboration-avatar-tooltip strong{font-size:13px}.collaboration-avatar-tooltip span{color:#526158}.collaboration-avatar-tooltip small{margin-top:3px;color:#77837b;font-size:11px}.collaboration-avatar:hover .collaboration-avatar-tooltip,.collaboration-avatar:focus-visible .collaboration-avatar-tooltip{display:block}.collaboration-recovery{min-width:0;display:inline-flex;align-items:center;gap:7px;margin:0 8px;border:1px solid #e0716780;border-radius:999px;padding:4px 6px 4px 9px;background:#6f2b2b66;color:#ffe0dd;font-size:10px;white-space:nowrap}.collaboration-recovery button{border:1px solid #ffffff55;border-radius:999px;padding:3px 7px;background:#ffffff18;color:inherit;font-size:10px}.collaboration-recovery button:hover{background:#ffffff2c}.compile-status-strip{min-height:31px;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 14px;background:#fff6dc;color:#72530d;border-bottom:1px solid #e8cd7d;font-size:11px}.compile-status-strip.failed{background:#fff0ee;color:#9a3028;border-bottom-color:#e8aaa4}.editor-actions{display:flex;align-items:center;gap:8px}.editor-actions button{padding:8px 10px;display:inline-flex;align-items:center;gap:6px;border-radius:6px;border:1px solid #ffffff28;color:#fff;background:#ffffff0d;font-size:12px;white-space:nowrap}.editor-actions .compile{background:#f3b943;border-color:#f3b943;color:#193023;font-weight:700}.editor-actions button.active{background:#d9eadf;color:#164c33}.format-action{min-width:112px;display:flex;flex-direction:column;align-self:center;gap:2px}.format-action-label{height:12px;display:flex;align-items:center;justify-content:center;gap:4px;padding:0 5px;color:#b9d0c2;font-size:9px;font-weight:750;letter-spacing:.08em;line-height:1;text-transform:uppercase}.format-action-options{height:27px;display:grid;grid-template-columns:1fr 1fr;overflow:hidden;border:1px solid #ffffff35;border-radius:6px;background:#ffffff0d}.editor-actions .format-action button{min-width:0;height:25px;justify-content:center;border:0;border-radius:0;padding:0 7px;font-size:11px}.editor-actions .format-action button+button{border-left:1px solid #ffffff35}.editor-actions .format-action-file{background:#2f7a58;color:#fff}.editor-actions .format-action-selected{background:#326b91;color:#fff}.editor-actions .format-action-file:hover:not(:disabled){background:#3a946c}.editor-actions .format-action-selected:hover:not(:disabled){background:#3d82ae}.editor-actions .format-action button:disabled{cursor:not-allowed;opacity:.45}.layout-menu{position:relative}.editor-actions .layout-trigger{min-width:138px;justify-content:flex-start;padding-block:5px}.layout-trigger>span{min-width:0;display:flex;flex:1;flex-direction:column;align-items:flex-start;line-height:1.15}.layout-trigger>span small{color:#b9d0c2;font-size:9px;font-weight:500;letter-spacing:.025em;text-transform:uppercase}.layout-trigger.active>span small{color:#52715f}.layout-popover{position:absolute;z-index:100;top:calc(100% + 8px);right:0;width:290px;padding:7px;border:1px solid #d4dcd6;border-radius:9px;background:#fff;color:#25362c;box-shadow:0 14px 38px #061c1138}.layout-popover-title{padding:6px 8px 8px;color:#768178;font-size:10px;font-weight:700;letter-spacing:.055em;text-transform:uppercase}.editor-actions .layout-option{width:100%;min-height:52px;display:grid;grid-template-columns:25px minmax(0,1fr) 18px;gap:9px;padding:8px 9px;border:0;color:#25362c;background:transparent;text-align:left}.editor-actions .layout-option:hover,.editor-actions .layout-option.selected{background:#edf5ef;color:#185d3d}.layout-option>span{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}.layout-option strong{font-size:12px}.layout-option small{color:#748078;font-size:10px;line-height:1.35;white-space:normal}.toast{position:fixed;z-index:10;top:66px;left:50%;transform:translate(-50%);background:#8f271f;color:#fff;padding:9px 16px;border-radius:7px;font-size:13px;box-shadow:0 8px 30px #0003}.toast.success{background:#1d6b46}.work-grid{min-height:0;flex:1;background:#fff}.resize-handle{position:relative;width:6px;flex:0 0 6px;display:grid;place-items:center;background:#e4e7e4;color:#98a09a;outline:none;transition:background .12s ease}.resize-handle:hover,.resize-handle[data-resize-handle-active]{background:#89ad98;color:#fff}.resize-handle svg{pointer-events:none}.sync-resize-handle{z-index:20!important;overflow:visible}.sync-direction-buttons{position:absolute;z-index:21;top:50%;left:50%;display:flex;flex-direction:row;gap:2px;padding:3px;transform:translate(-50%,-50%);border:1px solid #aebdb3;border-radius:7px;background:#fff;box-shadow:0 4px 16px #15251b38}.sync-direction-buttons button{width:27px;height:27px;display:grid;place-items:center;border:0;border-radius:4px;padding:0;background:transparent;color:#214d35;font-size:17px;font-weight:750;line-height:1}.sync-direction-buttons button:hover:not(:disabled){background:#deebe2;color:#176b44}.sync-direction-buttons button:disabled{cursor:not-allowed;opacity:.35}.sync-resize-handle .resize-grip{opacity:0}.cm-ySelectionInfo{opacity:1!important;padding:2px 5px!important;border-radius:4px 4px 4px 0;font-family:Inter,ui-sans-serif,system-ui,sans-serif!important;font-size:10px!important;transform:translateY(-2px);box-shadow:0 2px 5px #0002}.left-panel,.context-panel,.source-panel,.preview-panel{min-width:0;min-height:0;border-right:1px solid #dfe2de}.left-panel,.source-panel,.preview-panel,.context-panel{height:100%}.left-panel{display:flex;flex-direction:column;overflow:hidden}.files-panel{position:relative;min-height:180px;flex:3;overflow:auto;border-bottom:1px solid #dfe2de}.files-panel.drop-active{outline:2px solid #2c9564;outline-offset:-2px}.file-drop-overlay{position:absolute;z-index:8;inset:42px 0 0;min-height:130px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;padding:16px;background:#e8f4edee;color:#185d3c;text-align:center;pointer-events:none;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.file-drop-overlay strong{font-size:13px}.file-drop-overlay span{max-width:90%;overflow:hidden;color:#5e7668;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.outline-panel{min-height:150px;flex:2;overflow:auto}.panel-title{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid #e6e8e5;color:#647069;text-transform:uppercase;font-size:11px;font-weight:700}.panel-title>span{min-width:0;display:flex;align-items:center;gap:6px}.panel-title button{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:5px;background:transparent;color:#5d6961}.panel-title button:hover{background:#e8eeea;color:#176b44}.file-tools{flex:none;display:flex;align-items:center;gap:4px;margin-left:8px}.file-tools button{box-sizing:border-box}.file-list{padding:6px 7px 10px}.file-entry{min-width:0;height:calc(1em + 18px);min-height:30px;display:flex;align-items:center;border-radius:5px;color:#4f5952}.file-entry:hover{background:#f0f3f0}.file-entry.active,.file-entry.selected{background:#e2efe7;color:#15583a;font-weight:650}.file-entry-main{min-width:0;height:100%;flex:1;overflow:hidden;display:flex;align-items:center;gap:5px;border:0;padding:0 4px;background:transparent;color:inherit;text-align:left;font-size:inherit}.file-entry-main span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-entry-main svg{flex:none;color:#28724e}.file-entry.root-document .file-entry-main>svg{color:#2d6f9f}.file-entry.compile-target{background:#e9f2ec;box-shadow:inset 2px 0 #28724e}.file-entry-main small{flex:none;padding:1px 4px;border-radius:999px;background:#d9eadf;color:#1d5b3e;font-size:8px;line-height:1.35}.folder-entry .file-entry-main svg:first-child{color:#78837b}.folder-entry .file-entry-main svg:nth-child(2){color:#d49b2d}.root-entry{padding-left:5px}.root-entry .file-entry-main svg{color:#d49b2d}.file-entry-action{width:26px;height:26px;flex:none;display:none;place-items:center;border:0;border-radius:4px;background:transparent;color:#657069}.file-entry:hover .file-entry-action,.file-entry:focus-within .file-entry-action{display:grid}.file-entry-action:hover{background:#dfe7e1;color:#176b44}.resource-download{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.resource-preview-message{min-height:180px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#536158;text-align:center}.resource-preview-message strong{color:#263d2e;font-size:15px}.resource-preview-message span{max-width:440px;color:#748078;font-size:12px}.resource-image-wrap{display:flex;max-height:min(620px,65vh);align-items:center;justify-content:center;overflow:auto;padding:8px;background:#e9ecea;border-radius:7px}.resource-image{display:block;max-width:100%;max-height:min(590px,62vh);object-fit:contain}.resource-pdf{width:100%;height:min(650px,68vh);border:1px solid #d9dfda;border-radius:6px;background:#69706c}.resource-text{max-height:min(650px,68vh);overflow:auto;margin:0;padding:14px;border:1px solid #d9dfda;border-radius:6px;background:#f7f9f7;color:#26352c;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.source-panel{position:relative;overflow:hidden;display:flex;flex-direction:column;height:100%;min-height:0}.editor-tabs-bar{flex:none;min-height:35px;background:#eaefe9;border-bottom:1px solid #d4ded7;display:flex;align-items:stretch;padding:4px 6px 0;overflow:hidden}.editor-tabs-scroll{display:flex;align-items:stretch;gap:3px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.editor-tabs-scroll::-webkit-scrollbar{display:none}.editor-tab-item{display:inline-flex;align-items:center;max-width:220px;border:1px solid #ccd6ce;border-bottom:0;border-radius:6px 6px 0 0;background:#dce4de;color:#55665c}.editor-tab-item:hover{background:#e6eee8;color:#1e3628}.editor-tab-item.active{background:#fff;border-color:#cad7ce;box-shadow:0 -1px 3px #0000000a}.editor-tab{display:inline-flex;align-items:center;gap:5px;min-width:0;flex:1 1 auto;padding:4px 7px 4px 10px;border:0;border-radius:6px 0 0;background:transparent;color:inherit;font:inherit;font-size:12px;text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap}.editor-tab:hover{color:#1e3628}.editor-tab.active{color:#184f36;font-weight:600}.editor-tab:focus-visible{position:relative;z-index:1;outline:2px solid #4c9a6d;outline-offset:-2px}.editor-tab-icon{display:inline-flex;align-items:center;color:#697a70}.editor-tab.active .editor-tab-icon{color:#184f36}.editor-tab-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.editor-tab-badge{font-size:9px;padding:1px 4px;border-radius:4px;background:#e3eee6;color:#1a6442;font-weight:600;flex-shrink:0}.editor-tab-close{display:inline-flex;align-items:center;justify-content:center;width:22px;height:24px;border:0;border-radius:0 5px 0 0;background:transparent;color:#76887e;padding:0;cursor:pointer;flex-shrink:0}.editor-tab-close:hover{background:#cbd7cf;color:#153123}.editor-tab-item.active .editor-tab-close:hover{background:#e3ece5;color:#184f36}.editor-tab-close:focus-visible{position:relative;z-index:1;outline:2px solid #4c9a6d;outline-offset:-2px}.editor-content-container{position:relative;flex:1 1 auto;min-height:0;height:100%;overflow:hidden}.editor-centered-notice{position:absolute;z-index:8;top:50%;left:50%;max-width:calc(100% - 32px);transform:translate(-50%,-50%);pointer-events:none;border:1px solid #69a780;border-radius:8px;padding:9px 15px;background:#1d6b46ed;color:#fff;font:600 12px/1.4 inherit;text-align:center;box-shadow:0 8px 26px #071d1238}.editor-shell{height:100%;min-height:0;display:flex;flex-direction:column}.editor-shell .editor-host{height:auto;min-height:0;flex:1 1 auto}.editor-host,.cm-editor{height:100%;min-height:0}.editor-host .cm-editor{font-family:inherit;font-size:inherit;line-height:inherit}.editor-host .cm-editor{font-variant-ligatures:contextual;font-feature-settings:"liga" 1,"calt" 1}.editor-host .cm-scroller{min-height:0;height:auto;flex:1 1 auto;overflow:auto;font-family:inherit;line-height:inherit}.editor-host .cm-panels{flex:none}.editor-host .cm-vim-panel{min-height:25px;display:flex;align-items:center;box-sizing:border-box;width:100%;border-top:1px solid #cbd8ce;padding:3px 10px;background:#f1f6f2;color:#31523e;font:11px/1.3 SFMono-Regular,Consolas,monospace}.vim-editor-status{min-height:25px;flex:0 0 25px;display:flex;align-items:center;box-sizing:border-box;border-top:1px solid #cbd8ce;padding:3px 10px;background:#f1f6f2;color:#31523e;font:11px/1.3 SFMono-Regular,Consolas,monospace}.editor-host .cm-content,.editor-host .cm-gutters{font-family:inherit;font-size:inherit}.cm-editor.cm-focused{outline:none}.editor-host .cm-spell-error-active{border-radius:2px;background:#f6d66f;box-shadow:0 0 0 1px #c99324;color:#3f3010;text-decoration:underline wavy #9b6715 1.5px;text-decoration-skip-ink:none}.editor-host .cm-panel.cm-search{padding:8px 38px 8px 10px;border-top:1px solid #ccd5cf;background:#f7f9f7;color:#35443a;box-shadow:0 -3px 10px #1f382710;font-family:Inter,ui-sans-serif,system-ui,sans-serif;font-size:12px;line-height:1.4}.editor-host .cm-panel.cm-search .cm-textfield{min-width:170px;height:29px;margin:2px 6px 2px 0;border:1px solid #bcc8c0;border-radius:5px;padding:4px 8px;background:#fff;color:#202b24;outline:none;font-family:inherit;font-size:12px}.editor-host .cm-panel.cm-search .cm-textfield:focus{border-color:#3d8a60;box-shadow:0 0 0 2px #3d8a6025}.editor-host .cm-panel.cm-search .cm-button{height:29px;margin:2px 5px 2px 0;border:1px solid #bdc8c0;border-radius:5px;padding:3px 9px;background:#fff;background-image:none;color:#31513d;font-family:inherit;font-size:12px;text-transform:none}.editor-host .cm-panel.cm-search .cm-button:hover{border-color:#7c9f89;background:#e9f1ec}.editor-host .cm-panel.cm-search label{display:inline-flex;align-items:center;gap:3px;margin:3px 9px 3px 0;color:#59665d;font-family:inherit;font-size:12px}.editor-host .cm-panel.cm-search input[type=checkbox]{accent-color:#176b44}.editor-host .cm-panel.cm-search [name=close]{top:8px;right:10px;width:25px;height:25px;border-radius:5px;color:#647069;font-size:19px;line-height:1}.editor-host .cm-panel.cm-search [name=close]:hover{background:#e3e9e5;color:#244d36}.editor-host .cm-search-count{display:inline-flex;align-items:center;min-height:29px;margin:2px 5px 2px 4px;color:#506057;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.editor-host .cm-searchMatch{background:#f5d76e99;outline:1px solid #d9a91c88}.editor-host .cm-searchMatch.cm-searchMatch-selected{background:#f0a94a99;outline-color:#cc7720}.editor-host .cm-spell-error{text-decoration:underline wavy #c53b32 1.5px;text-decoration-skip-ink:none;text-decoration-thickness:1.5px}.editor-host .cm-grammar-warning{text-decoration:underline wavy #d19a26 1.5px;text-decoration-skip-ink:none;text-decoration-thickness:1.5px}.spell-suggestions-menu{position:fixed;z-index:1200;display:flex;min-width:190px;max-width:250px;flex-direction:column;gap:3px;padding:7px;border:1px solid #b9c8bd;border-radius:7px;background:#fff;box-shadow:0 10px 28px #1930222e;color:#26352c;font-size:12px}.spell-suggestions-title{padding:3px 6px 5px;color:#607067;font-size:10px;font-weight:700;letter-spacing:.02em;text-transform:uppercase}.spell-suggestions-message{padding:3px 6px 6px;border-bottom:1px solid #e2e8e3;color:#58685e;font-size:11px;line-height:1.4}.spell-suggestions-menu button{min-height:29px;border:1px solid transparent;border-radius:5px;padding:4px 8px;background:transparent;color:#214f37;font:inherit;text-align:left;cursor:pointer}.spell-suggestions-menu button:hover:not(:disabled),.spell-suggestions-menu button:focus-visible:not(:disabled){border-color:#a9c9b3;background:#edf6ef;outline:none}.spell-suggestions-menu button:disabled{color:#78847d;cursor:not-allowed;opacity:.7}.spell-suggestions-empty,.spell-suggestions-readonly{display:block;padding:4px 6px;color:#78847d;font-size:10px;line-height:1.35}.spell-suggestions-readonly{border-top:1px solid #e2e8e3;margin-top:3px;padding-top:7px}.cm-source-comment{background:#ffe49b99;border-bottom:2px solid #d68b00;cursor:text}.cm-source-comment-resolved{background:#dce5de99;border-bottom-color:#829087}.cm-comment-pin{color:#d68b00;font-size:9px;padding:0 2px;cursor:pointer}.preview-panel{display:flex;flex-direction:column;background:#6f7471}.preview-tabs{min-height:48px;flex:none;display:flex;align-items:stretch;padding:5px 6px 0;background:#4d544f}.preview-tab-list{min-width:0;flex:1;display:grid;grid-template-columns:minmax(0,1.8fr) minmax(120px,1fr);gap:3px}.preview-tabs button{min-width:0;display:flex;align-items:center;justify-content:center;gap:5px;border:0;border-radius:6px 6px 0 0;background:transparent;color:#d7dcd8;font-size:11px}.preview-tabs button.active{background:#f3f4f1;color:#184f36;font-weight:700}.preview-tabs button span{min-width:17px;padding:1px 4px;border-radius:999px;background:#ffffff20;font-size:9px}.preview-tabs button.active span{background:#dfe8e2}.preview-tabs .pdf-tab{justify-content:flex-start;padding:0 12px;font-size:12px}.preview-tabs button .pdf-tab-label{min-width:0;overflow:hidden;padding:0;border-radius:0;background:transparent;display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:12px;line-height:1;text-overflow:ellipsis;white-space:nowrap}.preview-tabs button.active .pdf-tab-label{background:transparent}.preview-tabs .pdf-tab small{max-width:100%;overflow:hidden;color:#bcc4bf;font-size:8px;font-weight:400;line-height:1;text-overflow:ellipsis;white-space:nowrap}.preview-tabs .pdf-tab.active small{color:#65756c}.pdf-download-top{flex:none;display:inline-flex;align-items:center;gap:5px;margin-left:4px;padding:0 11px;border:0;border-radius:6px 6px 0 0;color:#d7dcd8;font-size:10px;text-decoration:none;white-space:nowrap}.pdf-download-top:hover{background:#ffffff1b;color:#fff}.preview-subtabs{height:36px;flex:none;display:flex;align-items:center;gap:3px;padding:4px 7px;border-bottom:1px solid #cfd6d1;background:#e4e9e5}.preview-subtabs button{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 10px;border:0;border-radius:5px;background:transparent;color:#58665d;font-size:10px}.preview-subtabs button:hover{background:#d8e3db;color:#205c40}.preview-subtabs button.active{background:#fff;color:#184f36;font-weight:700;box-shadow:0 1px 3px #26312b22}.preview-subtabs button span{min-width:16px;padding:1px 4px;border-radius:999px;background:#d5ded7;font-size:9px;text-align:center}.preview-subtabs button.active span{background:#dfe8e2}.preview-content{min-height:0;flex:1}.preview-pdf{overflow:hidden}.pdf-viewer{position:relative;width:100%;height:100%;min-height:0;display:flex;flex-direction:column}.pdf-toolbar{height:36px;flex:none;display:flex;align-items:center;justify-content:flex-end;gap:3px;padding:4px 8px;border-bottom:1px solid #343a36;background:#414843;color:#e4e8e5;font:11px/1 Inter,ui-sans-serif,system-ui,sans-serif}.pdf-toolbar button{height:27px;min-width:27px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid #ffffff24;border-radius:5px;padding:0 6px;background:#ffffff0b;color:inherit}.pdf-toolbar button:hover:not(:disabled){border-color:#ffffff48;background:#ffffff1b}.pdf-toolbar button:disabled{opacity:.3}.pdf-zoom-value{min-width:42px;text-align:center;font-variant-numeric:tabular-nums}.pdf-toolbar .pdf-fit-width{margin-left:3px;padding:0 8px}.pdf-compiling-overlay{position:absolute;z-index:4;inset:36px 0 0;display:flex;align-items:flex-start;justify-content:center;gap:8px;padding-top:24px;background:#26312bb8;color:#fff;font-size:13px;pointer-events:none}.pdf-compiling-overlay span{padding:3px 8px;border-radius:5px;background:#17231cdd}.pdf-loading-state{width:100%;height:100%;min-height:220px;display:grid;place-items:center;align-content:center;gap:10px;background:#6f7471;color:#eef2ef;font-size:12px}.pdf-document{position:relative;width:100%;min-width:0;min-height:0;flex:1;overflow-x:scroll;overflow-y:auto;overscroll-behavior:contain;scrollbar-color:#aeb8b1 #4f5652;scrollbar-width:auto;scrollbar-gutter:stable;padding:14px 14px 20px;background:#6f7471}.pdf-document::-webkit-scrollbar{width:12px;height:12px}.pdf-document::-webkit-scrollbar-track{background:#4f5652}.pdf-document::-webkit-scrollbar-thumb{min-width:38px;border:2px solid #4f5652;border-radius:999px;background:#aeb8b1}.pdf-document::-webkit-scrollbar-thumb:hover{background:#d5dcd7}.pdf-document::-webkit-scrollbar-corner{background:#4f5652}.pdf-pages{min-width:100%}.pdf-loading{min-height:220px;display:grid;place-items:center;align-content:center;gap:10px;color:#eef2ef;font-size:12px}.pdf-page{position:relative;flex:none;max-width:none;overflow:hidden;margin:0 auto 16px;background:#fff;box-shadow:0 3px 14px #0004}.pdf-page.loading{background:#f7f8f7}.pdf-page canvas{display:block;width:100%!important;max-width:100%;height:auto!important}.pdf-annotation-layer{position:absolute;inset:0;z-index:3;pointer-events:none}.pdf-annotation-link{position:absolute;display:block;pointer-events:auto;border-radius:2px;background:transparent;cursor:pointer}.pdf-annotation-link:hover{background:#3f9b6426;box-shadow:inset 0 0 0 1px #3f9b6440}.pdf-page figcaption{position:absolute;right:7px;bottom:5px;padding:2px 5px;border-radius:4px;background:#26312bb5;color:#fff;font-size:9px;pointer-events:none}.pdf-sync-marker{position:absolute;z-index:2;width:14px;height:14px;border:3px solid #ef9c24;border-radius:50%;transform:translate(-50%,-50%);background:#ffe0a580;box-shadow:0 0 0 4px #ef9c2435;pointer-events:none;animation:sync-pulse 1.2s ease-out}@keyframes sync-pulse{0%{box-shadow:0 0 0 14px #ef9c2460}to{box-shadow:0 0 0 4px #ef9c2435}}.preview-empty{height:100%;display:grid;place-content:center;gap:8px;color:#e7e9e7;text-align:center}.preview-empty span{font-size:12px;color:#c2c7c4}.preview-log,.preview-warnings,.preview-errors{overflow:auto;background:#202522}.preview-clean{overflow:auto;background:#f4f6f4}.preview-artifacts{overflow:hidden;background:#f4f6f4}.artifact-browser{width:100%;height:100%;min-height:0;display:grid;grid-template-columns:minmax(135px,38%) minmax(0,1fr)}.artifact-list{min-width:0;overflow:auto;border-right:1px solid #d9dedb;background:#fff}.artifact-row{min-width:0;display:flex;align-items:stretch;border-bottom:1px solid #edf0ee}.artifact-row.active{background:#e5f0e9}.artifact-row>button{min-width:0;flex:1;display:flex;align-items:center;gap:7px;padding:8px 6px 8px 9px;border:0;background:transparent;color:#33463b;text-align:left}.artifact-row>button:disabled{cursor:default;opacity:.7}.artifact-row>button>span{min-width:0;display:flex;flex-direction:column;gap:2px}.artifact-row strong{overflow:hidden;font-size:11px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.artifact-row small{color:#89928c;font-size:9px}.artifact-row>a{width:30px;flex:none;display:grid;place-items:center;color:#52705f}.artifact-row>a:hover{background:#dce8e0;color:#176641}.artifact-preview{min-width:0;min-height:0;display:flex;flex-direction:column;background:#202522;color:#dbe2dd}.artifact-preview>header{height:36px;flex:none;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 10px;border-bottom:1px solid #39413c;background:#303732;font-size:11px}.artifact-preview>header strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.artifact-preview>header a{flex:none;display:inline-flex;align-items:center;gap:4px;color:#c9ddd1}.artifact-preview>pre{min-height:0;flex:1;margin:0;overflow:auto;padding:13px;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.55 SFMono-Regular,Consolas,monospace}.compile-output{min-height:100%;margin:0;padding:16px;color:#dbe2dd;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.55 SFMono-Regular,Consolas,monospace}.compile-output.compile-warning{color:#ffdfa0;background:#2d281c}.compile-output.compile-error{color:#ffc2bd;background:#301e1d}.compile-empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;color:#bfc7c1;font-size:12px}.compile-empty.compile-warning{color:#e8c46d;background:#2d281c}.compile-empty.compile-error{color:#e99b94;background:#301e1d}.compile-clean-panel{max-width:820px;margin:0 auto;padding:24px;color:#30453a}.compile-clean-intro{display:flex;align-items:flex-start;gap:12px;padding:15px;border:1px solid #e3d1a7;border-radius:9px;background:#fffaf0;color:#80631f;box-shadow:0 3px 12px #5c48100d}.compile-clean-intro>div{min-width:0}.compile-clean-intro strong{display:block;font-size:14px}.compile-clean-intro p,.compile-clean-actions p{margin:5px 0 0;color:#718078;font-size:11px;line-height:1.5}.compile-clean-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:14px}.compile-clean-actions article{display:grid;grid-template-columns:34px minmax(0,1fr);gap:10px;min-width:0;padding:14px;border:1px solid #d4dfd7;border-radius:8px;background:#fff}.compile-clean-actions article.danger{border-color:#e3c9c5}.compile-clean-action-icon{display:grid;place-items:center;width:32px;height:32px;border-radius:7px;background:#e7f2ea;color:#28714a}.compile-clean-actions article.danger .compile-clean-action-icon{background:#f8e8e6;color:#a6433a}.compile-clean-actions strong{color:#2d4638;font-size:12px}.compile-clean-actions article.danger strong{color:#874039}.compile-clean-actions button{display:inline-flex;align-items:center;gap:5px;margin-top:12px;padding:6px 10px;border:1px solid #b8d1bd;border-radius:5px;background:#f0f8f1;color:#21633d;font-size:11px}.compile-clean-actions button:hover:not(:disabled){border-color:#79aa86;background:#e3f2e6}.compile-clean-actions button.danger{border-color:#e2b9b4;background:#fff4f2;color:#a23d35}.compile-clean-actions button.danger:hover:not(:disabled){border-color:#c8837a;background:#fce7e4}.compile-clean-actions button:disabled{cursor:not-allowed;opacity:.45}.compile-diagnostic-list{min-height:100%;padding:8px;background:#202522;overflow:auto}.compile-diagnostic-list.compile-warning{background:#2d281c}.compile-diagnostic-list.compile-error{background:#301e1d}.compile-diagnostic-row{display:grid;grid-template-columns:20px minmax(0,1fr);gap:7px;width:100%;padding:9px 10px;border:0;border-bottom:1px solid #ffffff12;border-radius:4px;background:transparent;color:#eef2ef;text-align:left}.compile-diagnostic-row:not(:disabled):hover{background:#ffffff10;box-shadow:inset 2px 0 #63b889}.compile-diagnostic-row:disabled{opacity:.78;cursor:default}.compile-diagnostic-row>span{min-width:0;display:grid;gap:4px}.compile-diagnostic-row strong{font-size:11px;font-weight:500;white-space:normal}.compile-diagnostic-row small{color:#aeb8b1;font:10px/1.4 SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere}.compile-warning .compile-diagnostic-row>svg{color:#e8c46d}.compile-error .compile-diagnostic-row>svg{color:#e99b94}.context-panel{border-right:0;overflow:auto}.drawer-title{position:sticky;top:0;z-index:2;height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid #e3e6e3;background:#fff;font-size:13px}.drawer-title button{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:5px;background:transparent;color:#737a75}.drawer-title button:hover{background:#edf1ee}.tabs{height:42px;display:flex;border-bottom:1px solid #e3e6e3}.tabs button{flex:1;border:0;background:#fff;color:#707872;font-size:12px}.tabs button.active{color:#176b44;border-bottom:2px solid #176b44;font-weight:700}.outline{padding:10px 8px}.outline-item{width:100%;min-width:0;display:flex;align-items:stretch;border:0;padding:6px 0;background:transparent;font-size:12px;text-align:left}.outline-item:hover,.outline-item:focus-visible{background:#edf3ef;color:#176b44;outline:none}.outline-item.current{background:#e2efe7;color:#176b44;font-weight:700}.outline-guides{flex:none;background:repeating-linear-gradient(to right,#dfe5e0 0,#dfe5e0 1px,transparent 1px,transparent 12px)}.outline small{flex:none;min-width:22px;margin-left:8px;color:#a1a7a2}.outline-title{min-width:0;overflow:hidden;color:#3f4942;text-overflow:ellipsis;white-space:nowrap}.padded{padding:14px}.comments{padding:9px}.comments article{margin-bottom:9px;padding:10px;border:1px solid #dde2de;border-radius:7px;font-size:12px;cursor:pointer;user-select:text;-webkit-user-select:text}.comments article.resolved{opacity:.55;background:#f3f5f3}.comments article.orphaned{border-color:#d8847d;background:#fff4f2}.comments header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.comments header small{color:#858b87}.comments time{flex:none;color:#8b928d;font-size:9px;white-space:nowrap}.comment-times{flex:none;display:flex;align-items:flex-end;flex-direction:column;gap:2px}.comment-times small{font-size:8px;white-space:nowrap}.comment-author{min-width:0;display:flex;flex-direction:column;gap:1px}.comment-author strong,.comment-author small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comment-location{margin-top:5px;color:#768079;font-size:10px}.comments blockquote{margin:8px 0;padding:6px 8px;border-left:2px solid #85ad96;background:#f2f6f3;color:#657069;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.comments p{white-space:pre-wrap;user-select:text;-webkit-user-select:text;cursor:text}.comments blockquote,.comments .comment-content{user-select:text;-webkit-user-select:text;cursor:text}.comments .resolve{display:inline-flex;align-items:center;gap:4px;border:0;background:transparent;color:#176b44;padding:0;font-size:11px}.comment-replies{display:flex;flex-direction:column;gap:7px;margin:10px 0 8px;padding-left:9px;border-left:2px solid #d6dfd9}.comment-reply{position:relative;padding:7px 8px;border-radius:6px;background:#f5f7f5}.comment-reply p{margin:7px 0 1px}.comment-actions{display:flex;align-items:center;gap:12px}.comments .reply-action{display:inline-flex;align-items:center;gap:4px;border:0;padding:0;background:transparent;color:#526c5d;font-size:11px}.comment-owner-actions{display:inline-flex;align-items:center;gap:2px;margin-left:auto}.comment-reply>.comment-owner-actions{position:absolute;right:5px;bottom:4px}.comments .comment-owner-actions button{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:5px;background:transparent;color:#66716a}.comments .comment-owner-actions button:hover{background:#e2e8e3;color:#244c35}.comments .comment-owner-actions button.danger-text:hover{background:#f8e4e2;color:#a7312a}.comment-reply-form{display:flex;flex-direction:column;gap:7px;margin-top:9px}.comment-edit-form{margin:7px 0}.comment-reply-form textarea{width:100%;resize:vertical;border:1px solid #cbd2cc;border-radius:6px;padding:7px 8px;outline:none;font-size:11px}.comment-reply-form textarea:focus{border-color:#176b44;box-shadow:0 0 0 2px #176b4420}.comment-reply-form>div{display:flex;justify-content:flex-end;gap:5px}.comment-reply-form button{display:inline-flex;align-items:center;gap:4px;border:1px solid #cbd2cc;border-radius:5px;padding:5px 7px;background:#fff;font-size:10px}.comment-reply-form button.primary{border-color:#176b44;background:#176b44;color:#fff}.settings{display:flex;flex-direction:column;gap:14px}.settings>section{display:flex;flex-direction:column;gap:14px}.settings button{border:1px solid #c9d0ca;background:#fff;border-radius:6px;padding:7px 9px;font-size:12px}.settings-tabs{display:flex;gap:4px;padding:3px;border:1px solid #d7ded8;border-radius:9px;background:#f4f7f4}.settings-tab{display:inline-flex;flex:1;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px solid transparent!important;border-radius:7px!important;background:transparent!important;color:#66716a;font-weight:650}.settings-tab:hover{background:#eaf1eb!important;color:#31523e}.settings-tab.active{border-color:#c8d9cb!important;background:#fff!important;color:#176b44;box-shadow:0 1px 2px #233c2a12}.settings-tab:focus-visible{outline:2px solid #176b44;outline-offset:1px}.settings-actions{display:flex;width:100%;justify-content:flex-end;padding-top:2px}.settings-section-title{display:flex;align-items:center;gap:7px;color:#31523e;font-size:13px}.settings-description{margin:0;color:#7b837e;font-size:11px;line-height:1.45}.settings .editor-preference{display:flex;flex-direction:column;gap:4px}.settings .editor-preference .field-hint{margin:0 9px;line-height:1.4}.settings .editor-checkbox{flex-direction:row;align-items:center;gap:7px;padding:8px 9px;border:1px solid #d9ded9;border-radius:7px;font-weight:500}.settings .editor-checkbox input{width:auto;margin:0;accent-color:#176b44}.spell-check-result{display:inline-flex;align-items:center;gap:6px;color:#66716a;font-size:11px}.spell-check-result.has-issues{color:#a3342c;font-weight:650}.spell-check-controls{display:inline-flex;align-items:center;gap:2px;margin-left:3px;color:#6f4a45;font-weight:500}.spell-check-controls button{width:24px;height:24px;display:inline-grid;place-items:center;border:1px solid #d7b8b4;border-radius:4px;padding:0;background:#fff8f7;color:#8e4a42}.spell-check-controls button:hover:not(:disabled){border-color:#b98279;background:#fce9e6}.spell-check-controls button:disabled{cursor:not-allowed;opacity:.35}.spell-check-position{min-width:34px;text-align:center;color:#825850;font-variant-numeric:tabular-nums;white-space:nowrap}.password-generator{display:flex;align-items:stretch;gap:7px}.password-generator input{min-width:0;flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.password-generator button{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:0 10px;border:1px solid #c9d0ca;border-radius:6px;background:#f6f8f6;color:#31523e;font-size:11px;white-space:nowrap}.password-generator button:hover{border-color:#8eaa98;background:#edf3ef}.settings-save{width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px}.latexmkrc-editor{resize:vertical;min-height:160px;font:12px/1.5 SFMono-Regular,Consolas,monospace!important}.dictionary-add{display:flex;align-items:stretch;gap:7px}.dictionary-add input{min-width:0;flex:1}.dictionary-add button{flex:none;color:#176b44}.dictionary-words{display:flex;flex-wrap:wrap;gap:6px}.dictionary-word{display:inline-flex;align-items:center;gap:3px;min-height:27px;padding:3px 5px 3px 8px;border:1px solid #d5ded7;border-radius:999px;background:#f1f6f2;color:#31523e;font-size:11px}.dictionary-word code{font:11px/1.2 SFMono-Regular,Consolas,monospace}.dictionary-word button{width:20px;height:20px;display:grid;place-items:center;padding:0;border:0;background:transparent;color:#748078}.dictionary-word button:hover{color:#a7312a}.dictionary-empty{color:#7b837e;font-size:11px}.dictionary-error{margin:-6px 0 0;font-size:11px}.share-dialog{display:flex;flex-direction:column;gap:16px}.share-owner{display:flex;align-items:center;gap:10px;padding:11px 13px;border-radius:8px;background:#edf4ef;color:#18583a}.share-owner>span{display:flex;flex-direction:column;gap:2px}.share-owner small{color:#6f7d74;font-size:10px;text-transform:uppercase}.share-add{display:grid;grid-template-columns:minmax(0,1fr) 150px auto;align-items:end;gap:9px;padding-bottom:16px;border-bottom:1px solid #e4e7e4}.share-add .icon-button{height:39px;display:inline-flex;align-items:center;gap:6px;padding:0 13px;font-size:12px}.shared-members-heading{display:flex;align-items:center;justify-content:space-between;color:#48524b;font-size:13px}.shared-members-heading span{min-width:22px;padding:2px 6px;border-radius:999px;background:#e8ece9;color:#6a746d;font-size:10px;text-align:center}.shared-members{display:flex;flex-direction:column;gap:7px}.shared-member{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:10px;border:1px solid #dfe3df;border-radius:8px}.member-identity{min-width:0;display:flex;align-items:center;gap:9px}.member-identity>span:last-child{min-width:0;display:flex;flex-direction:column}.member-identity strong,.member-identity small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.member-identity small{color:#7c857e;font-size:10px}.member-avatar{width:30px;height:30px;flex:none;display:grid;place-items:center;border-radius:50%;background:#dfece4;color:#176b44;font-size:12px;font-weight:750}.member-controls{display:flex;align-items:center;gap:5px}.member-controls select{border:1px solid #cbd2cc;border-radius:6px;padding:6px 8px;background:#fff;font-size:11px}.icon-only{width:31px;height:31px;display:grid;place-items:center;border:0;border-radius:6px;background:transparent}.icon-only:hover{background:#f8e9e7}.share-empty{min-height:90px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;color:#89928c;font-size:12px}.spin{animation:spin .8s linear infinite}.dialog-overlay{position:fixed;z-index:100;inset:0;background:#101b15aa;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:fade-in .12s ease-out}.dialog-content{position:fixed;z-index:101;top:50%;left:50%;width:min(460px,calc(100vw - 32px));max-height:min(760px,calc(100vh - 40px));overflow:auto;transform:translate(-50%,-50%);background:#fff;border-radius:12px;box-shadow:0 25px 80px #0004;animation:dialog-in .15s ease-out}.dialog-wide{width:min(660px,calc(100vw - 32px))}.dialog-extra-wide{width:min(900px,calc(100vw - 32px))}.dialog-header{display:flex;align-items:flex-start;justify-content:space-between;padding:20px 22px 14px;border-bottom:1px solid #e5e8e5}.dialog-header h2{margin:0;font-size:19px}.dialog-header p{margin:6px 0 0;color:#737a75;font-size:12px;line-height:1.5}.dialog-close{border:0;background:transparent;color:#6d746f;font-size:22px}.dialog-body{padding:20px 22px}.dialog-body .warning{display:flex;align-items:flex-start;gap:8px;margin:0;padding:10px 11px;border:1px solid #e4cf91;border-radius:7px;background:#fff8df;color:#735a18;font-size:11px;line-height:1.5}.dialog-body .warning svg{flex:none;margin-top:1px}.system-metrics{display:grid;gap:20px}.metric-summary-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.metric-card{min-width:0;padding:14px 16px;border:1px solid #dfe4df;border-radius:10px;background:#f7f8f6;display:grid;gap:4px}.metric-card>span,.metric-card small{color:var(--muted);font-size:12px}.metric-card strong{font-size:20px;font-weight:650}.metric-section{display:grid;gap:10px}.metric-section h3{margin:0;display:flex;align-items:center;gap:7px;font-size:14px}.metric-section p{margin:0;font-size:13px}.metric-table-wrap{overflow-x:auto;border:1px solid #dfe4df;border-radius:9px}.metric-table{width:100%;border-collapse:collapse;font-size:12px;white-space:nowrap}.metric-table th,.metric-table td{padding:8px 10px;border-bottom:1px solid #e4e8e4;text-align:right}.metric-table th:first-child,.metric-table td:first-child{text-align:left}.metric-table tbody tr:last-child td{border-bottom:0}@media(max-width:640px){.metric-summary-grid{grid-template-columns:1fr}}.dialog-footer{display:flex;justify-content:flex-end;gap:8px;padding:14px 22px;border-top:1px solid #e5e8e5;background:#f8f9f7}.dialog-footer button{border:1px solid #cbd2cc;border-radius:7px;padding:8px 14px;background:#fff}.dialog-footer .primary{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-color:#176b44;background:#176b44;color:#fff}.dialog-footer .danger,button.danger{border-color:#b3261e;background:#b3261e;color:#fff}.citation-library-dialog{display:flex;flex-direction:column;gap:12px}.citation-library-tabs{display:flex;gap:4px;padding:3px;border:1px solid #d7ded8;border-radius:9px;background:#f4f7f4}.citation-library-tabs button{display:inline-flex;flex:1;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px solid transparent;border-radius:7px;background:transparent;color:#66716a;font-size:12px}.citation-library-tabs button:hover{background:#eaf1eb;color:#31523e}.citation-library-tabs button.active{border-color:#c8d9cb;background:#fff;color:#176b44;box-shadow:0 1px 2px #233c2a12}.citation-library-tabs button span{min-width:18px;padding:1px 5px;border-radius:999px;background:#e7eee8;font-size:10px}.citation-library-search{display:flex;align-items:center;gap:8px;padding:0 10px;border:1px solid #ccd6ce;border-radius:7px;background:#fff;color:#68766d}.citation-library-search-row{display:flex;align-items:stretch;gap:8px}.citation-library-search-row .citation-library-search{min-width:0;flex:1}.citation-add-entry-button{display:inline-flex;align-items:center;gap:5px;flex:none;border:1px solid #176b44;border-radius:7px;padding:0 11px;background:#176b44;color:#fff;font-size:11px}.citation-add-entry-button:hover{background:#125937}.citation-library-search input{min-width:0;flex:1;border:0;padding:9px 0;outline:0;font-size:12px}.citation-library-search span{flex:none;color:#8a958d;font-size:10px}.citation-tag-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.citation-tag-filters{min-width:0;display:flex;flex:1;flex-wrap:wrap;gap:5px}.citation-tag-filters button{display:inline-flex;align-items:center;gap:5px;border:1px solid transparent;border-radius:999px;padding:5px 8px;background:#f1f4f1;color:#5f6e64;font-size:10px}.citation-tag-filters button:hover{background:#e6eee8}.citation-tag-filters button.active{border-color:#9cbea6;background:#e8f2eb;color:#176b44}.citation-tag-create{display:flex;flex:none;gap:5px}.citation-tag-create input,.citation-tag-create select{min-width:0;border:1px solid #cbd4cd;border-radius:6px;padding:6px 7px;background:#fff;font-size:10px}.citation-tag-create input{width:120px}.citation-tag-create select{width:76px}.citation-tag-create button{display:inline-flex;align-items:center;gap:4px;border:1px solid #bdc9c0;border-radius:6px;padding:6px 8px;background:#fff;color:#31523e;font-size:10px;white-space:nowrap}.citation-tag-create button:hover:not(:disabled){border-color:#8eaa98;background:#edf4ef}.citation-entry-list{display:flex;flex-direction:column;gap:8px;max-height:46vh;overflow:auto;padding:1px}.citation-entry-card{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:10px 11px;border:1px solid #dce3de;border-radius:8px;background:#fbfcfb}.citation-entry-main{min-width:0;display:flex;flex:1;flex-direction:column;gap:3px}.citation-entry-heading{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.citation-entry-heading code{color:#176b44;font:650 11px/1.3 SFMono-Regular,Consolas,monospace}.citation-entry-heading>span{color:#859189;font-size:10px}.citation-entry-heading em{display:inline-flex;align-items:center;gap:3px;border-radius:999px;padding:2px 6px;background:#e8f2eb;color:#26704a;font-size:9px;font-style:normal}.citation-entry-heading em.private{background:#edf0ee;color:#69756d}.citation-entry-main>strong{overflow:hidden;color:#2b3b31;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.citation-entry-main>p{margin:0;overflow:hidden;color:#718078;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.citation-entry-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.citation-tag-pill{display:inline-flex;align-items:center;gap:4px;border:1px solid color-mix(in srgb,var(--tag-color) 35%,transparent);border-radius:999px;padding:2px 6px;background:color-mix(in srgb,var(--tag-color) 12%,white);color:#526158;font-size:9px}.citation-tag-dot{width:7px;height:7px;flex:none;display:inline-block;border-radius:50%;background:var(--tag-color)}.citation-tag-filters .citation-tag-dot{background:var(--tag-color)}.citation-entry-actions{display:flex;flex:none;flex-wrap:wrap;justify-content:flex-end;gap:5px}.citation-entry-actions button{display:inline-flex;align-items:center;gap:4px;border:1px solid #cbd5ce;border-radius:6px;padding:6px 8px;background:#fff;color:#52685a;font-size:10px;white-space:nowrap}.citation-entry-actions button:hover:not(:disabled){border-color:#8eaa98;background:#edf4ef;color:#176b44}.citation-entry-actions button.primary{border-color:#176b44;background:#176b44;color:#fff}.citation-entry-actions button.saved{border-color:#acd0b8;background:#edf7f0;color:#17623d}.citation-library-empty{min-height:150px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;color:#7b887f;text-align:center}.citation-library-empty strong{color:#526158;font-size:13px}.citation-library-empty span{max-width:360px;font-size:11px;line-height:1.45}.citation-library-notice{display:flex;align-items:center;gap:6px;margin:0;padding:8px 10px;border:1px solid #acd0b8;border-radius:7px;background:#edf7f0;color:#17623d;font-size:11px}.citation-library-notice button{display:grid;place-items:center;width:21px;height:21px;margin-left:auto;border:0;border-radius:4px;background:transparent;color:inherit}.citation-current-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.citation-current-heading>div{min-width:0;display:flex;flex-direction:column;gap:3px}.citation-current-heading strong{color:#31523e;font-size:13px}.citation-current-heading span{color:#78857c;font-size:10px}.citation-current-heading button{display:inline-flex;align-items:center;gap:4px;border:0;background:transparent;color:#176b44;font-size:10px;white-space:nowrap}.citation-edit-panel,.citation-tag-editor{display:flex;flex-direction:column;gap:9px;padding:10px 11px;border:1px solid #cbd9ce;border-radius:8px;background:#f5f9f6}.citation-edit-header{display:flex;align-items:center;justify-content:space-between;gap:8px;color:#31523e;font-size:11px}.citation-edit-header>div{min-width:0;display:flex;flex-direction:column;gap:3px}.citation-edit-header small{color:#78857c;font-size:10px;font-weight:400;line-height:1.4}.citation-edit-header button{display:grid;place-items:center;width:22px;height:22px;border:0;border-radius:4px;background:transparent;color:#68766d}.citation-edit-header button:hover{background:#e2ece4}.citation-edit-panel textarea{width:100%;resize:vertical;border:1px solid #cbd5ce;border-radius:6px;padding:8px;outline:0;font:11px/1.5 SFMono-Regular,Consolas,monospace}.citation-edit-panel textarea:focus{border-color:#176b44;box-shadow:0 0 0 2px #176b4420}.citation-edit-actions{display:flex;justify-content:flex-end;gap:6px}.citation-edit-actions button{display:inline-flex;align-items:center;gap:4px;border:1px solid #bdc9c0;border-radius:6px;padding:6px 9px;background:#fff;color:#52685a;font-size:10px}.citation-edit-actions button.primary{border-color:#176b44;background:#176b44;color:#fff}.citation-tag-choice-list{display:flex;flex-wrap:wrap;gap:7px}.citation-tag-choice-list label{display:inline-flex;align-items:center;gap:5px;border:1px solid #d4ded6;border-radius:999px;padding:5px 7px;background:#fff;color:#526158;font-size:10px}.citation-tag-choice-list input{width:auto;accent-color:#176b44}.form-stack{display:flex;flex-direction:column;gap:14px}.import-error{margin:0;padding:10px 12px;border:1px solid #e3b5b1;border-radius:8px;background:#fff3f2;line-height:1.45}.form-field{display:flex;flex-direction:column;gap:7px;font-size:13px;font-weight:650}.form-field input,.form-field select,.form-field textarea{width:100%;border:1px solid #cbd2cc;border-radius:8px;padding:10px 11px;background:#fff;outline:none}.form-field input:focus,.form-field select:focus,.form-field textarea:focus{border-color:#176b44;box-shadow:0 0 0 3px #176b4420}.upload-picker{position:relative;min-height:116px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:14px;padding:20px;border:1.5px dashed #aebbb2;border-radius:10px;background:#f6f8f6;color:#5f6b63;transition:border-color .15s ease,background .15s ease}.upload-picker:hover,.upload-picker:focus-within{border-color:#4f8a68;background:#f0f6f2}.upload-picker.has-file{border-style:solid;border-color:#83aa91;background:#edf5ef;color:#276044}.upload-picker>svg{flex:none;color:#176b44}.upload-picker-copy{min-width:0;display:flex;flex-direction:column;gap:4px}.upload-picker-copy strong,.upload-picker-copy span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.upload-picker-copy strong{color:#29382f;font-size:13px}.upload-picker-copy span{color:#758078;font-size:11px}.upload-picker>button{display:inline-flex;align-items:center;gap:6px;border:1px solid #9fb0a4;border-radius:7px;padding:8px 11px;background:#fff;color:#235c3d;font-size:12px;font-weight:650}.upload-picker>button:hover{border-color:#176b44;background:#e4efe8}.upload-picker>.upload-clear{position:absolute;top:7px;right:7px;width:26px;height:26px;display:grid;place-items:center;border:0;padding:0;background:transparent;color:#6c776f}.upload-picker>.upload-clear:hover{background:#dce9e0;color:#9d2f27}.choice-group{display:flex;flex-direction:column;gap:10px;border:1px solid #dfe3df;border-radius:8px;padding:14px}.choice-group legend{padding:0 5px;font-size:12px;color:#69716b}.choice-group label{font-size:13px}.color-picker{display:grid;grid-template-columns:repeat(2,1fr);gap:7px;border:0;padding:0}.color-picker legend{margin-bottom:8px;font-size:13px;font-weight:650}.color-picker label{display:flex;align-items:center;gap:7px;border:1px solid #dce1dd;border-radius:7px;padding:8px;font-size:12px}.color-picker label.active{border-color:#176b44;background:#edf5f0}.color-picker input{position:absolute;opacity:0;pointer-events:none}.tag-assignment-list{display:flex;flex-direction:column;gap:7px}.tag-assignment-list label,.tag-assignment-list button{display:flex;align-items:center;gap:9px;min-height:38px;border:1px solid #dce1dd;border-radius:7px;padding:8px 10px;background:#fff;color:#38423b;font-size:13px;text-align:left}.tag-assignment-list label:hover,.tag-assignment-list button:hover{border-color:#8bae9a;background:#f4f8f5}.tag-assignment-list input{width:auto;accent-color:#176b44}.checkbox-field{display:flex;align-items:center;gap:8px;font-size:13px}.checkbox-field input{width:auto}.selection-preview{max-height:130px;overflow:auto;margin:14px 0 0;padding:10px 12px;border-left:3px solid #d6a128;background:#fff8e5;white-space:pre-wrap;font:12px/1.5 SFMono-Regular,Consolas,monospace}.git-dialog{display:flex;flex-direction:column;gap:14px}.git-dialog-modal-fullscreen{top:0;left:0;width:100vw;height:100vh;max-height:100vh;transform:none;overflow:hidden;border-radius:0}.git-loading{min-height:120px;display:flex;align-items:center;justify-content:center;gap:8px;color:#6e7871;font-size:13px}.git-message{margin:0;display:flex;align-items:center;gap:7px;padding:9px 11px;border:1px solid;border-radius:7px;font-size:12px}.git-message.error{border-color:#e2b4b0;background:#fff2f1}.git-message.success{border-color:#acd0b8;background:#edf7f0;color:#17623d}.git-section{display:flex;flex-direction:column;gap:11px;padding:14px;border:1px solid #dfe4e0;border-radius:9px;background:#fbfcfb}.git-section-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px;border:0;padding:0;background:transparent;color:#607068;text-align:left}.git-section-toggle:hover{color:#176b44}.git-section-heading{min-width:0;display:flex;align-items:flex-start;gap:8px;color:#27533a}.git-section-heading>svg{flex:none;margin-top:1px}.git-section-heading>div,.git-section-heading>span{min-width:0;display:flex;flex-direction:column;gap:3px}.git-section-heading strong{font-size:13px}.git-section-heading small{max-width:650px;overflow:hidden;color:#78817b;font-size:10px;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.git-section-content{display:flex;flex-direction:column;gap:11px;padding-top:2px}.git-token-guidance{display:flex;flex-direction:column;gap:4px;padding:10px 11px;border:1px solid #c6d9cb;border-left:3px solid #28714b;border-radius:7px;background:#f0f7f2}.git-token-guidance strong{color:#225d3c;font-size:11px}.git-token-guidance span{color:#66756b;font-size:10px;line-height:1.5}.git-account,.git-repository-card{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:7px;background:#eaf3ed;color:#23563a;font-size:12px}.git-account>span{min-width:0;flex:1}.git-account button{display:inline-flex;align-items:center;gap:4px;border:0;background:transparent;color:#a3342c;font-size:10px}.git-inline-actions,.git-action-row{display:flex;align-items:center;flex-wrap:wrap;gap:7px}.git-inline-actions{justify-content:space-between}.git-inline-actions a,.git-repository-card a{display:inline-flex;align-items:center;gap:4px;color:#176b44;font-size:11px;text-decoration:none}.git-inline-actions button,.git-action-row button,.git-primary-action{display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid #bdc9c0;border-radius:6px;padding:7px 9px;background:#fff;color:#31523e;font-size:11px}.git-action-row .git-discard-button:not(:disabled){border-color:#d9aaa5;color:#9b332b}.git-action-row .git-discard-button:not(:disabled):hover{background:#fff0ee}.git-primary-action{align-self:flex-start;border-color:#176b44;background:#176b44;color:#fff}.git-repository-card{justify-content:space-between}.git-repository-card>div{min-width:0;display:flex;flex-direction:column;gap:3px}.git-repository-card strong,.git-repository-card small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.git-repository-card small{color:#6e7c73;font-size:10px}.git-count{min-width:17px;padding:1px 5px;border-radius:999px;background:#d9e9df;font-size:9px}.git-diff-section{padding-bottom:0;overflow:hidden}.git-diff-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.git-diff-controls{display:flex;align-items:center;gap:7px;flex:none}.git-diff-font-controls{display:inline-flex;align-items:center;gap:2px;border:1px solid #cbd5ce;border-radius:6px;padding:2px;background:#fff;color:#52685a}.git-diff-font-button{width:25px;height:25px;display:grid;place-items:center;border:0;border-radius:4px;padding:0;background:transparent;color:inherit}.git-diff-font-button:hover:not(:disabled){background:#edf4ef;color:#176b44}.git-diff-font-value{min-width:34px;color:#52685a;font-size:10px;font-variant-numeric:tabular-nums;text-align:center}.git-diff-fullscreen{width:29px;height:29px;flex:none;display:grid;place-items:center;padding:0!important;border:1px solid #cbd5ce!important;border-radius:6px!important;background:#fff!important;color:#52685a}.git-diff-fullscreen:hover{border-color:#8eaa98!important;background:#edf4ef!important;color:#176b44}.git-diff-section.is-fullscreen,.git-diff-section:fullscreen{position:fixed;z-index:1000;inset:0;width:100vw;height:100vh;max-height:none;box-sizing:border-box;margin:0;padding:20px!important;border:0;border-radius:0;overflow:hidden;background:#fbfcfb}.git-diff-section.is-fullscreen .git-diff,.git-diff-section:fullscreen .git-diff{flex:1;min-height:0;max-height:none;margin:0}.git-diff-section.is-fullscreen .git-diff-empty,.git-diff-section:fullscreen .git-diff-empty{flex:1;min-height:0;margin:0}.git-diff{max-height:360px;margin:0 -14px;overflow:auto;padding:11px 0;background:#202522;color:#d9e1dc;font:10px/1.5 SFMono-Regular,Consolas,monospace;tab-size:4}.git-diff span{display:block;min-width:max-content;padding:0 12px;white-space:pre}.git-diff span.addition{background:#173b28;color:#bfe8cb}.git-diff span.deletion{background:#442421;color:#f1c1bc}.git-diff span.hunk{background:#283a48;color:#b8daf4}.git-diff span.header{color:#e8cf8b;font-weight:650}.git-diff-empty{margin:0 -14px;padding:28px;background:#252a27;color:#b9c0bc;font-size:12px;text-align:center}.git-history{max-height:300px;display:flex;flex-direction:column;gap:5px;overflow:auto}.git-history article{min-width:0;display:grid;grid-template-columns:66px minmax(0,1fr) auto;align-items:center;gap:8px;padding:8px;border:1px solid #e2e6e3;border-radius:6px;background:#fff}.git-history code{color:#176b44;font-size:10px}.git-history article>div{min-width:0;display:flex;flex-direction:column;gap:2px}.git-history article strong,.git-history article small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.git-history article strong{font-size:11px}.git-history article small{color:#7b847e;font-size:9px}.git-history article>span{display:flex;gap:3px}.git-history button{width:27px;height:27px;display:grid;place-items:center;border:0;border-radius:5px;background:transparent;color:#52685a}.git-history button:hover{background:#e8f0eb;color:#176b44}.git-force-checkout{display:flex;align-items:flex-start;gap:9px;padding:11px;border:1px solid #e4b9b5;border-radius:8px;background:#fff6f5}.git-force-checkout input{margin-top:2px;accent-color:#b3261e}.git-force-checkout span{display:flex;flex-direction:column;gap:3px}.git-force-checkout strong{color:#932c25;font-size:12px}.git-force-checkout small{color:#7d6562;font-size:10px;line-height:1.45}.quick-open-dialog>label,.project-search-query{display:flex;align-items:center;gap:8px;border:1px solid #bfcac2;border-radius:7px;padding:0 10px;background:#fff}.quick-open-dialog>label:focus-within,.project-search-query:focus-within{border-color:#418060;box-shadow:0 0 0 2px #41806020}.quick-open-dialog input,.project-search-query input{width:100%;border:0;outline:0;padding:10px 0;background:transparent}.quick-open-results{max-height:420px;overflow:auto;margin-top:8px;border:1px solid #d9e0db;border-radius:7px}.quick-open-results button{display:grid;grid-template-columns:20px minmax(0,1fr) 18px;align-items:center;gap:6px;width:100%;padding:8px 10px;border:0;border-radius:0;background:#fff;text-align:left}.quick-open-results button+button{border-top:1px solid #eef1ef}.quick-open-results button.active{background:#e4efe8;color:#195f3d}.quick-open-results span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:11px/1.4 JetBrains Mono,monospace}.project-search-dialog{display:grid;gap:9px}.project-search-form{display:grid;grid-template-columns:1fr 1fr;gap:8px}.project-search-options{grid-column:1 / -1;display:flex;align-items:center;gap:14px}.project-search-options label{display:inline-flex;align-items:center;gap:5px;color:#55645a;font-size:11px}.project-search-options label input{accent-color:#28734b}.project-search-options button{margin-left:auto;display:inline-flex;align-items:center;gap:5px}.project-search-summary{display:flex;align-items:center;justify-content:space-between;min-height:22px;color:#5e6c63;font-size:11px}.project-search-summary span{display:inline-flex;align-items:center;gap:5px}.project-search-results{max-height:480px;overflow:auto;border:1px solid #d9e0db;border-radius:7px;background:#fff}.project-search-results>button{display:grid;grid-template-columns:20px minmax(0,1fr);gap:7px;width:100%;padding:9px 10px;border:0;border-radius:0;background:#fff;text-align:left}.project-search-results>button+button{border-top:1px solid #e8ece9}.project-search-results>button:hover{background:#edf4ef}.project-search-results>button>span{min-width:0;display:grid;gap:4px}.project-search-results strong{color:#31503d;font-size:11px}.project-search-results strong small{color:#7c877f;font-weight:400}.project-search-results code{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#4e5952;font:10px/1.45 JetBrains Mono,monospace}.project-search-results mark{border-radius:2px;padding:1px;background:#f2d36e;color:#4b3b0b}.history-dialog-modal{width:min(1120px,calc(100vw - 32px))}.history-dialog{display:grid;grid-template-columns:270px minmax(0,1fr);min-height:560px;max-height:min(72vh,760px);border:1px solid #d9e0db;border-radius:8px;overflow:hidden;background:#fff}.history-loading{grid-column:1 / -1;display:flex;align-items:center;justify-content:center;gap:8px;min-height:420px;color:#68736b}.history-message{grid-column:1 / -1;margin:10px}.history-timeline{overflow:auto;border-right:1px solid #d9e0db;background:#f5f7f5}.history-stats{position:sticky;top:0;z-index:2;display:grid;gap:5px;padding:10px 12px;border-bottom:1px solid #d8dfda;background:#edf3ef;color:#33483b}.history-stats header{display:flex;align-items:center;justify-content:space-between;gap:8px}.history-stats header span{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700}.history-stats header button{width:26px;height:26px;display:grid;place-items:center;border:0;padding:0;background:transparent;color:#9d332b}.history-stats header button:hover:not(:disabled){background:#f5deda}.history-stats strong{font-size:11px}.history-stats small{color:#68766d;font-size:9px;line-height:1.4}.history-stats progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;accent-color:#34845a}.history-stats.exceeded progress{accent-color:#bd483e}.history-stats .history-storage-warning{color:#9d332b}.history-timeline>button{display:grid;grid-template-columns:20px minmax(0,1fr);gap:7px;width:100%;padding:11px 12px;border:0;border-bottom:1px solid #e1e6e2;border-radius:0;background:transparent;text-align:left;color:#34443a}.history-timeline>button:hover{background:#eaf0ec}.history-timeline>button.active{background:#dfece4;box-shadow:inset 3px 0 #28734b}.history-timeline>button span{min-width:0;display:grid;gap:2px}.history-timeline>button strong,.history-timeline>button small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.history-timeline>button small{color:#68736b;font-size:10px}.history-detail{min-width:0;overflow:auto;padding:14px}.history-detail-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}.history-detail-header>div{display:grid;gap:4px}.history-detail-header small{display:flex;align-items:center;gap:4px;color:#68736b}.history-detail-actions{display:flex;align-items:center;gap:6px}.history-detail-actions button{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}.history-label{display:flex;gap:8px;margin-bottom:12px}.history-label label{flex:1;display:flex;align-items:center;gap:7px;padding:0 9px;border:1px solid #cdd6cf;border-radius:6px;background:#fff}.history-label input{width:100%;border:0;padding:8px 0;outline:0}.history-files{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:start}.history-file-list{display:flex;gap:5px;overflow-x:auto;padding-bottom:5px}.history-file-list button{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;max-width:230px}.history-file-list button span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.history-file-list button small{padding:1px 4px;border-radius:8px;background:#f2d675;color:#695414;font-size:9px}.history-file-list button.active{border-color:#418060;background:#e7f0ea}.history-file-actions{display:flex;gap:5px}.history-file-actions button{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}.history-comparison{min-height:360px;margin-top:10px;border:1px solid #d8dfda;border-radius:7px;overflow:hidden;background:#fbfcfb}.history-comparison>header{display:flex;align-items:baseline;justify-content:space-between;gap:10px;padding:8px 10px;border-bottom:1px solid #d8dfda;background:#f1f4f2}.history-comparison>header small{color:#68736b}.history-comparison pre{min-height:330px;max-height:430px;margin:0;overflow:auto;padding:11px;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.5 JetBrains Mono,Source Code Pro,monospace;color:#26342b}.history-comparison pre .history-removed{background:#f8c9c9;color:#7b2525;text-decoration:line-through}.history-comparison pre .history-added{background:#ccebd5;color:#15552c}.history-comparison-empty{min-height:360px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:#78827b}@keyframes fade-in{0%{opacity:0}}@keyframes dialog-in{0%{opacity:0;transform:translate(-50%,-48%) scale(.98)}}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:1100px){.project-grid.list-view .project-card-open{grid-template-columns:110px minmax(160px,1fr)}.project-grid.list-view .project-meta{grid-column:1 / -1}.editor-actions button{padding:8px}.editor-actions button svg+*{display:none}.editor-actions .format-action button{padding:7px 6px}.editor-actions .format-action button svg+*{display:inline}}@media(max-width:720px){.citation-library-page-shell{padding-top:24px}.citation-library-page-header{flex-direction:column}.citation-library-page-header h1{font-size:25px}.citation-library-page-header>button{align-self:flex-start}.citation-library-page .citation-library-dialog{padding:12px}.citation-library-search-row{flex-direction:column}.citation-add-entry-button{min-height:34px;justify-content:center}.citation-entry-card{flex-direction:column}.citation-entry-actions{width:100%;justify-content:flex-start}.upload-picker{grid-template-columns:auto 1fr}.upload-picker>button{grid-column:1 / -1;justify-content:center}.share-add{grid-template-columns:1fr}.shared-member{align-items:flex-start}.member-controls{flex:none}.history-dialog{grid-template-columns:1fr;max-height:none}.history-timeline{max-height:180px;border-right:0;border-bottom:1px solid #d9e0db}.history-files,.project-search-form{grid-template-columns:1fr}}
@@ -1,4 +1,4 @@
1
- import{P as Kn,r as Qn,L as un}from"./BinaryModule-Aj1vLnwf-BMr5UOlk.js";import{D as ke,S as Ne,c as ze}from"./BinaryModule-Aj1vLnwf-BMr5UOlk.js";import"./index-CISv_aup.js";import"./react-CsO_xvbl.js";var Yn=Object.defineProperty,Xn=(e,n,r)=>n in e?Yn(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,I=(e,n,r)=>Xn(e,typeof n!="symbol"?n+"":n,r);function Zn(e){switch(e){case"plaintext":return un.Plain;case"typst":return un.Typst;case"markdown":case void 0:return un.Markdown;default:return console.warn(`Unknown Harper language '${String(e)}'; using markdown.`),un.Markdown}}function On(e,n){return[e,Zn(n?.language),n?.forceAllHeadings??!1,n?.regex_mask,n?.dedup??!0,n?.isolateEnglish??!1]}class We{constructor(n){I(this,"binary"),I(this,"inner"),I(this,"disposed",!1),this.binary=n.binary,this.binary.setup(),this.inner=this.createInner(n.dialect)}createInner(n){return Kn.from(async()=>(await this.binary.setup(),this.binary.createLinter(n)))}async setup(){await this.lint("",{language:"plaintext"});const n=await this.exportIgnoredLints();await this.importIgnoredLints(n)}async lint(n,r){return(await this.inner).lint(...On(n,r))}async organizedLints(n,r){const a=(await this.inner).organized_lints(...On(n,r)),i={};for(const o of a)i[o.group]=o.lints,o.free();return i}async applySuggestion(n,r,t){return(await this.inner).apply_suggestion(n,r,t)}async isLikelyEnglish(n){return(await this.inner).is_likely_english(n)}async isolateEnglish(n){return(await this.inner).isolate_english(n)}async getLintConfig(){return(await this.inner).get_lint_config_as_object()}async getDefaultLintConfigAsJSON(){return await this.binary.getDefaultLintConfigAsJSON()}async getDefaultLintConfig(){return await this.binary.getDefaultLintConfig()}async getStructuredLintConfig(){return(await this.inner).get_structured_lint_config_as_object()}async getStructuredLintConfigJSON(){return(await this.inner).get_structured_lint_config_as_json()}async setLintConfig(n){(await this.inner).set_lint_config_from_object(n)}async getLintConfigAsJSON(){return(await this.inner).get_lint_config_as_json()}async setLintConfigWithJSON(n){(await this.inner).set_lint_config_from_json(n)}async toTitleCase(n){return await this.binary.toTitleCase(n)}async getLintDescriptions(){return(await this.inner).get_lint_descriptions_as_object()}async getLintDescriptionsAsJSON(){return(await this.inner).get_lint_descriptions_as_json()}async getLintDescriptionsHTML(){return(await this.inner).get_lint_descriptions_html_as_object()}async getLintDescriptionsHTMLAsJSON(){return(await this.inner).get_lint_descriptions_html_as_json()}async ignoreLint(n,r){return await this.ignoreLints(n,[r])}async ignoreLints(n,r){(await this.inner).ignore_lints(n,r)}async ignoreLintHash(n){(await this.inner).ignore_hashes(new BigUint64Array([n]))}async exportIgnoredLints(){return(await this.inner).export_ignored_lints()}async importIgnoredLints(n){(await this.inner).import_ignored_lints(n)}async contextHash(n,r){return(await this.inner).context_hash(n,r)}async clearIgnoredLints(){(await this.inner).clear_ignored_lints()}async clearWords(){return(await this.inner).clear_words()}async importWords(n){return(await this.inner).import_words(n)}async exportWords(){return(await this.inner).export_words()}async getDialect(){return(await this.inner).get_dialect()}async setDialect(n){const r=await this.inner;return r.get_dialect()!==n&&(r.free(),this.inner=this.createInner(n)),Promise.resolve()}async summarizeStats(n,r){return(await this.inner).summarize_stats(n,r)}async generateStatsFile(){return(await this.inner).generate_stats_file()}async importStatsFile(n){return(await this.inner).import_stats_file(n)}async loadWeirpackFromBlob(n){const r=new Uint8Array(await n.arrayBuffer());return this.loadWeirpackFromBytes(r)}async loadWeirpackFromBytes(n){const r=await this.inner,t=n instanceof Uint8Array?n:Uint8Array.from(n);return r.import_weirpack(t)}async dispose(){if(this.disposed)return;this.disposed=!0,(await this.inner).free()}}function ne(e,n){if(!e)throw new Error("Assertion failed")}class ee{constructor(n){I(this,"binary"),this.binary=n,this.binary.setup()}async serializeArg(n){var r;const{Lint:t,Span:a,Suggestion:i}=await this.binary.getBinaryModule();if(Array.isArray(n))return{json:JSON.stringify(await Promise.all(n.map(g=>this.serializeArg(g)))),type:"Array"};const o=typeof n;switch(o){case"string":case"number":case"boolean":case"undefined":return{json:JSON.stringify(n),type:o};case"bigint":return{json:n.toString(),type:o}}if(n.to_json!==void 0){const g=n.to_json();let _;const c=(r=n.constructor)==null?void 0:r.name;if(n instanceof t||c==="Lint"?_="Lint":n instanceof i||c==="Suggestion"?_="Suggestion":(n instanceof a||c==="Span")&&(_="Span"),_===void 0)throw new Error("Unhandled case: type undefined");return{json:g,type:_}}if(o=="object")return{json:JSON.stringify(await Promise.all(Object.entries(n).map(([g,_])=>this.serializeArg([g,_])))),type:"object"};throw new Error(`Unhandled case: ${n}`)}async serialize(n){return{procName:n.procName,args:await Promise.all(n.args.map(r=>this.serializeArg(r)))}}async deserializeArg(n){const{Lint:r,Span:t,Suggestion:a}=await this.binary.getBinaryModule();switch(n.type){case"bigint":return BigInt(n.json);case"undefined":return;case"boolean":case"number":case"string":return JSON.parse(n.json);case"Suggestion":return a.from_json(n.json);case"Lint":return r.from_json(n.json);case"Span":return t.from_json(n.json);case"Array":{const i=JSON.parse(n.json);return ne(Array.isArray(i)),await Promise.all(i.map(o=>this.deserializeArg(o)))}case"object":{const i=JSON.parse(n.json);return Object.fromEntries(await Promise.all(i.map(o=>this.deserializeArg(o))))}default:throw new Error(`Unhandled case: ${n.type}`)}}async deserialize(n){return{procName:n.procName,args:await Promise.all(n.args.map(r=>this.deserializeArg(r)))}}}const Vn=`var __defProp = Object.defineProperty;
1
+ import{P as Kn,r as Qn,L as un}from"./BinaryModule-Aj1vLnwf-wAe5fMVe.js";import{D as ke,S as Ne,c as ze}from"./BinaryModule-Aj1vLnwf-wAe5fMVe.js";import"./index-BLOqf18q.js";import"./react-CsO_xvbl.js";var Yn=Object.defineProperty,Xn=(e,n,r)=>n in e?Yn(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,I=(e,n,r)=>Xn(e,typeof n!="symbol"?n+"":n,r);function Zn(e){switch(e){case"plaintext":return un.Plain;case"typst":return un.Typst;case"markdown":case void 0:return un.Markdown;default:return console.warn(`Unknown Harper language '${String(e)}'; using markdown.`),un.Markdown}}function On(e,n){return[e,Zn(n?.language),n?.forceAllHeadings??!1,n?.regex_mask,n?.dedup??!0,n?.isolateEnglish??!1]}class We{constructor(n){I(this,"binary"),I(this,"inner"),I(this,"disposed",!1),this.binary=n.binary,this.binary.setup(),this.inner=this.createInner(n.dialect)}createInner(n){return Kn.from(async()=>(await this.binary.setup(),this.binary.createLinter(n)))}async setup(){await this.lint("",{language:"plaintext"});const n=await this.exportIgnoredLints();await this.importIgnoredLints(n)}async lint(n,r){return(await this.inner).lint(...On(n,r))}async organizedLints(n,r){const a=(await this.inner).organized_lints(...On(n,r)),i={};for(const o of a)i[o.group]=o.lints,o.free();return i}async applySuggestion(n,r,t){return(await this.inner).apply_suggestion(n,r,t)}async isLikelyEnglish(n){return(await this.inner).is_likely_english(n)}async isolateEnglish(n){return(await this.inner).isolate_english(n)}async getLintConfig(){return(await this.inner).get_lint_config_as_object()}async getDefaultLintConfigAsJSON(){return await this.binary.getDefaultLintConfigAsJSON()}async getDefaultLintConfig(){return await this.binary.getDefaultLintConfig()}async getStructuredLintConfig(){return(await this.inner).get_structured_lint_config_as_object()}async getStructuredLintConfigJSON(){return(await this.inner).get_structured_lint_config_as_json()}async setLintConfig(n){(await this.inner).set_lint_config_from_object(n)}async getLintConfigAsJSON(){return(await this.inner).get_lint_config_as_json()}async setLintConfigWithJSON(n){(await this.inner).set_lint_config_from_json(n)}async toTitleCase(n){return await this.binary.toTitleCase(n)}async getLintDescriptions(){return(await this.inner).get_lint_descriptions_as_object()}async getLintDescriptionsAsJSON(){return(await this.inner).get_lint_descriptions_as_json()}async getLintDescriptionsHTML(){return(await this.inner).get_lint_descriptions_html_as_object()}async getLintDescriptionsHTMLAsJSON(){return(await this.inner).get_lint_descriptions_html_as_json()}async ignoreLint(n,r){return await this.ignoreLints(n,[r])}async ignoreLints(n,r){(await this.inner).ignore_lints(n,r)}async ignoreLintHash(n){(await this.inner).ignore_hashes(new BigUint64Array([n]))}async exportIgnoredLints(){return(await this.inner).export_ignored_lints()}async importIgnoredLints(n){(await this.inner).import_ignored_lints(n)}async contextHash(n,r){return(await this.inner).context_hash(n,r)}async clearIgnoredLints(){(await this.inner).clear_ignored_lints()}async clearWords(){return(await this.inner).clear_words()}async importWords(n){return(await this.inner).import_words(n)}async exportWords(){return(await this.inner).export_words()}async getDialect(){return(await this.inner).get_dialect()}async setDialect(n){const r=await this.inner;return r.get_dialect()!==n&&(r.free(),this.inner=this.createInner(n)),Promise.resolve()}async summarizeStats(n,r){return(await this.inner).summarize_stats(n,r)}async generateStatsFile(){return(await this.inner).generate_stats_file()}async importStatsFile(n){return(await this.inner).import_stats_file(n)}async loadWeirpackFromBlob(n){const r=new Uint8Array(await n.arrayBuffer());return this.loadWeirpackFromBytes(r)}async loadWeirpackFromBytes(n){const r=await this.inner,t=n instanceof Uint8Array?n:Uint8Array.from(n);return r.import_weirpack(t)}async dispose(){if(this.disposed)return;this.disposed=!0,(await this.inner).free()}}function ne(e,n){if(!e)throw new Error("Assertion failed")}class ee{constructor(n){I(this,"binary"),this.binary=n,this.binary.setup()}async serializeArg(n){var r;const{Lint:t,Span:a,Suggestion:i}=await this.binary.getBinaryModule();if(Array.isArray(n))return{json:JSON.stringify(await Promise.all(n.map(g=>this.serializeArg(g)))),type:"Array"};const o=typeof n;switch(o){case"string":case"number":case"boolean":case"undefined":return{json:JSON.stringify(n),type:o};case"bigint":return{json:n.toString(),type:o}}if(n.to_json!==void 0){const g=n.to_json();let _;const c=(r=n.constructor)==null?void 0:r.name;if(n instanceof t||c==="Lint"?_="Lint":n instanceof i||c==="Suggestion"?_="Suggestion":(n instanceof a||c==="Span")&&(_="Span"),_===void 0)throw new Error("Unhandled case: type undefined");return{json:g,type:_}}if(o=="object")return{json:JSON.stringify(await Promise.all(Object.entries(n).map(([g,_])=>this.serializeArg([g,_])))),type:"object"};throw new Error(`Unhandled case: ${n}`)}async serialize(n){return{procName:n.procName,args:await Promise.all(n.args.map(r=>this.serializeArg(r)))}}async deserializeArg(n){const{Lint:r,Span:t,Suggestion:a}=await this.binary.getBinaryModule();switch(n.type){case"bigint":return BigInt(n.json);case"undefined":return;case"boolean":case"number":case"string":return JSON.parse(n.json);case"Suggestion":return a.from_json(n.json);case"Lint":return r.from_json(n.json);case"Span":return t.from_json(n.json);case"Array":{const i=JSON.parse(n.json);return ne(Array.isArray(i)),await Promise.all(i.map(o=>this.deserializeArg(o)))}case"object":{const i=JSON.parse(n.json);return Object.fromEntries(await Promise.all(i.map(o=>this.deserializeArg(o))))}default:throw new Error(`Unhandled case: ${n.type}`)}}async deserialize(n){return{procName:n.procName,args:await Promise.all(n.args.map(r=>this.deserializeArg(r)))}}}const Vn=`var __defProp = Object.defineProperty;
2
2
  var __typeError = (msg) => {
3
3
  throw TypeError(msg);
4
4
  };
@@ -0,0 +1 @@
1
+ import{i}from"./index-BLOqf18q.js";const n=i("Maximize2",[["polyline",{points:"15 3 21 3 21 9",key:"mznyad"}],["polyline",{points:"9 21 3 21 3 15",key:"1avn1i"}],["line",{x1:"21",x2:"14",y1:"3",y2:"10",key:"ota7mn"}],["line",{x1:"3",x2:"10",y1:"21",y2:"14",key:"1atl0r"}]]);const a=i("Minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]);export{a as M,n as a};
@@ -1,5 +1,5 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-Cjwy6IYP.js","assets/BinaryModule-Aj1vLnwf-BMr5UOlk.js","assets/index-CISv_aup.js","assets/react-CsO_xvbl.js","assets/index-DdrajKQB.css","assets/binary-Ds0O2e5F.js"])))=>i.map(i=>d[i]);
2
- import{_ as w}from"./index-CISv_aup.js";import"./react-CsO_xvbl.js";const k=/([A-Za-z][A-Za-z0-9_.:/-]*(?:\s+[A-Za-z][A-Za-z0-9_.:/-]*){0,7})\s*$/,S=new Set(["tabular","tabularx","tabulary","longtable","array","matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix"]),A=new Set(["math","displaymath","equation","equation*","align","align*","alignat","alignat*","gather","gather*","multline","multline*","flalign","flalign*","tikzpicture","axis","scope","pgfonlayer","pgfpicture","tabular","tabularx","tabulary","longtable","array","matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","verbatim","Verbatim","lstlisting","minted","comment","algorithmic","alignat"]),b=new Set(["documentclass","usepackage","RequirePackage","includegraphics","tikzset","pgfplotsset","hypersetup","lstset","definecolor","colorlet","setlength","setcounter","draw","path","fill","filldraw","shade","node","addplot","color","textcolor","colorbox","pagecolor"]),L=new Set(["label","hypertarget","ref","pageref","autoref","nameref","hyperref","index","gls","Gls","glspl","Glspl","cite","citep","citet","citeauthor","citeyear","citenum","parencite","textcite","autocite","footcite"]),z=5;let d=null,_=Promise.resolve(),x="";function c(t,i,n){n>i&&t.push({from:i,to:n})}function p(t,i,n,a){if(t[i]!==n)return null;let e=0;for(let r=i;r<t.length;r+=1){const l=t[r-1]==="\\"&&t[r-2]!=="\\";if(!l&&t[r]===n&&(e+=1),!l&&t[r]===a&&(e-=1,e===0))return{from:i,to:r+1,end:r+1}}return null}function u(t,i){let n=i;for(;n<t.length&&/[ \t\r\n]/.test(t[n]);)n+=1;return n}function E(t,i){if(t[i]!=="%")return!1;let n=0;for(let a=i-1;a>=0&&t[a]==="\\";a-=1)n+=1;return n%2===0}function P(t){return/[=,!/]/.test(t)||/\b(?:colorbar|legend|width|height|draw|fill|style|domain|samples|anchor|at|axis)\b/.test(t)}function O(t){return L.has(t)||/^cite[A-Za-z]*/.test(t)}function Z(t,i){for(let n=t.indexOf("=");n>=0;n=t.indexOf("=",n+1)){const e=Math.max(...[`
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-JmuE6_j5.js","assets/BinaryModule-Aj1vLnwf-wAe5fMVe.js","assets/index-BLOqf18q.js","assets/react-CsO_xvbl.js","assets/index-CwGialPz.css","assets/binary-CWru8eff.js"])))=>i.map(i=>d[i]);
2
+ import{_ as w}from"./index-BLOqf18q.js";import"./react-CsO_xvbl.js";const k=/([A-Za-z][A-Za-z0-9_.:/-]*(?:\s+[A-Za-z][A-Za-z0-9_.:/-]*){0,7})\s*$/,S=new Set(["tabular","tabularx","tabulary","longtable","array","matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix"]),A=new Set(["math","displaymath","equation","equation*","align","align*","alignat","alignat*","gather","gather*","multline","multline*","flalign","flalign*","tikzpicture","axis","scope","pgfonlayer","pgfpicture","tabular","tabularx","tabulary","longtable","array","matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","verbatim","Verbatim","lstlisting","minted","comment","algorithmic","alignat"]),b=new Set(["documentclass","usepackage","RequirePackage","includegraphics","tikzset","pgfplotsset","hypersetup","lstset","definecolor","colorlet","setlength","setcounter","draw","path","fill","filldraw","shade","node","addplot","color","textcolor","colorbox","pagecolor"]),L=new Set(["label","hypertarget","ref","pageref","autoref","nameref","hyperref","index","gls","Gls","glspl","Glspl","cite","citep","citet","citeauthor","citeyear","citenum","parencite","textcite","autocite","footcite"]),z=5;let d=null,_=Promise.resolve(),x="";function c(t,i,n){n>i&&t.push({from:i,to:n})}function p(t,i,n,a){if(t[i]!==n)return null;let e=0;for(let r=i;r<t.length;r+=1){const l=t[r-1]==="\\"&&t[r-2]!=="\\";if(!l&&t[r]===n&&(e+=1),!l&&t[r]===a&&(e-=1,e===0))return{from:i,to:r+1,end:r+1}}return null}function u(t,i){let n=i;for(;n<t.length&&/[ \t\r\n]/.test(t[n]);)n+=1;return n}function E(t,i){if(t[i]!=="%")return!1;let n=0;for(let a=i-1;a>=0&&t[a]==="\\";a-=1)n+=1;return n%2===0}function P(t){return/[=,!/]/.test(t)||/\b(?:colorbar|legend|width|height|draw|fill|style|domain|samples|anchor|at|axis)\b/.test(t)}function O(t){return L.has(t)||/^cite[A-Za-z]*/.test(t)}function Z(t,i){for(let n=t.indexOf("=");n>=0;n=t.indexOf("=",n+1)){const e=Math.max(...[`
3
3
  `,"[","{","]","}",",",";"].map(o=>t.lastIndexOf(o,n-1)+1)),l=t.slice(e,n).match(k);l?.index!==void 0&&c(i,e+l.index,n);let f=n+1;for(;f<t.length&&/\s/.test(t[f]);)f+=1;let s=f;if(t[f]==="{")s=p(t,f,"{","}")?.end??f;else for(;s<t.length&&!/[,\]\}\n]/.test(t[s]);)s+=1;c(i,f,s)}}function I(t,i,n){const a=/\\(begin|end)\s*\{\s*([^{}]+?)\s*\}/g;a.lastIndex=i;let e=1;for(let r=a.exec(t);r;r=a.exec(t))if(r[2].trim()===n){if(r[1]==="begin")e+=1;else if(--e===0)return a.lastIndex}return null}function R(t,i){for(const n of t.matchAll(/\\\((?:\\.|[^])*?\\\)|\\\[(?:\\.|[^])*?\\\]/g))n.index!==void 0&&c(i,n.index,n.index+n[0].length);for(let n=0;n<t.length;n+=1){if(t[n]!=="$"||t[n-1]==="\\")continue;const a=t[n+1]==="$"?"$$":"$",e=t.indexOf(a,n+a.length);e>=0&&(c(i,n,e+a.length),n=e+a.length-1)}}function $(t){const i=[],n=/[A-Za-z@]/;for(let e=0;e<t.length;e+=1){if(E(t,e)){const o=t.indexOf(`
4
4
  `,e);c(i,e,o<0?t.length:o),e=o<0?t.length:o;continue}if(t[e]!=="\\")continue;const r=e;let l=e+1;if(n.test(t[l]??""))for(;l<t.length&&/[A-Za-z@0-9:_]/.test(t[l]);)l+=1;else l<t.length&&(l+=1);const f=t.slice(e+1,l);c(i,r,l);let s=u(t,l);if(f==="begin"||f==="end"){const o=p(t,s,"{","}");if(o){c(i,o.from,o.to);const h=t.slice(o.from+1,o.to-1).trim();if(s=u(t,o.end),f==="begin"){const g=p(t,s,"[","]");if(g&&(c(i,g.from,g.to),s=u(t,g.end)),S.has(h)){const m=p(t,s,"{","}");m&&c(i,m.from,m.to)}if(A.has(h)){const m=I(t,o.end,h);if(m!==null){c(i,r,m),e=m-1;continue}}}}e=l-1;continue}if(O(f))for(t[s]==="*"&&(s=u(t,s+1));;){const o=p(t,s,"[","]")??p(t,s,"{","}");if(!o)break;c(i,o.from,o.to),s=u(t,o.end)}else{const o=p(t,s,"[","]");if(o&&(b.has(f)||P(t.slice(o.from+1,o.to-1)))&&(c(i,o.from,o.to),s=u(t,o.end)),b.has(f)){const h=f==="definecolor"||f==="colorlet"?3:1;for(let g=0;g<h;g+=1){const m=p(t,s,"{","}");if(!m)break;c(i,m.from,m.to),s=u(t,m.end)}}}e=l-1}R(t,i),Z(t,i);for(const e of t.matchAll(/(?:https?|ftp):\/\/[^\s]+/gi))e.index!==void 0&&c(i,e.index,e.index+e[0].length);i.sort((e,r)=>e.from-r.from||e.to-r.to);const a=[];for(const e of i){const r=a.at(-1);r&&e.from<=r.to?r.to=Math.max(r.to,e.to):a.push({...e})}return a}function T(t){const i=$(t),n=[];let a=0,e=0;for(const r of t){for(;e<i.length&&i[e].to<=a;)e+=1;const l=i[e];n.push(l&&l.from<=a&&a<l.to&&r!==`
5
- `&&r!=="\r"?" ":r),a+=r.length}return n.join("")}function V(t){const i=[0];let n=0;for(const a of t)n+=a.length,i.push(n);return i}function B(t){return[...new Set(t.map(i=>i.trim()).filter(Boolean))].sort((i,n)=>i.localeCompare(n)).join("\0")}async function C(t,i){const n=_.then(async()=>{if(!d)try{const[{WorkerLinter:e,LocalLinter:r},{binary:l}]=await Promise.all([w(()=>import("./index-Cjwy6IYP.js"),__vite__mapDeps([0,1,2,3,4])),w(()=>import("./binary-Ds0O2e5F.js"),__vite__mapDeps([5,1,2,3,4]))]),f=typeof Worker<"u";try{d=new(f?e:r)({binary:l}),await d.setup()}catch(s){const o=d;if(d=null,await o?.dispose().catch(()=>{}),!f)throw s;d=new r({binary:l}),await d.setup()}}catch(e){throw d=null,x="",e}const a=B(t);return a!==x&&(await d.clearWords(),t.length&&await d.importWords([...new Set(t.map(e=>e.trim()).filter(Boolean))]),x=a),i(d)});return _=n.then(()=>{},()=>{}),n}function q(t){return t.get_replacement_text()}function W(t,i,n,a,e){const r={from:a.start,to:a.end},l={from:e[a.start]??-1,to:e[a.end]??-1},f=[r,l].filter((o,h,g)=>o.from>=0&&o.to>o.from&&o.to<=t.length&&g.findIndex(m=>m.from===o.from&&m.to===o.to)===h);if(f.length===0)return null;const s=n.get_problem_text();return f.find(o=>i.slice(o.from,o.to)===s)??f.find(o=>/[A-Za-z]/.test(i.slice(o.from,o.to)))??f[0]??null}function D(t,i,n,a){const e=n.span();try{const r=W(t,i,n,e,a);if(!r)return null;const{from:l,to:f}=r;if(!/[A-Za-z]/.test(i.slice(l,f)))return null;const s=t.slice(l,f);if(!s.trim()||/[\\%$]/.test(s))return null;const o=n.lint_kind()==="Spelling"||n.lint_kind()==="Typo"?"spelling":"grammar",h=n.suggestions(),g=h.map(q).filter((m,y,v)=>y<z&&v.indexOf(m)===y);for(const m of h)m.free();return{from:l,to:f,word:s,kind:o,message:n.message(),suggestions:g}}finally{e.free()}}function G(t,i=[]){const n=T(t),a=V(t);return C(i,async e=>{const r=await e.lint(n,{language:"plaintext",dedup:!0,isolateEnglish:!1});try{return r.map(l=>D(t,n,l,a)).filter(l=>!!l).sort((l,f)=>l.from-f.from||l.to-f.to)}finally{for(const l of r)l.free()}})}export{$ as ignoredRanges,G as lintLatex,T as maskLatexSource};
5
+ `&&r!=="\r"?" ":r),a+=r.length}return n.join("")}function V(t){const i=[0];let n=0;for(const a of t)n+=a.length,i.push(n);return i}function B(t){return[...new Set(t.map(i=>i.trim()).filter(Boolean))].sort((i,n)=>i.localeCompare(n)).join("\0")}async function C(t,i){const n=_.then(async()=>{if(!d)try{const[{WorkerLinter:e,LocalLinter:r},{binary:l}]=await Promise.all([w(()=>import("./index-JmuE6_j5.js"),__vite__mapDeps([0,1,2,3,4])),w(()=>import("./binary-CWru8eff.js"),__vite__mapDeps([5,1,2,3,4]))]),f=typeof Worker<"u";try{d=new(f?e:r)({binary:l}),await d.setup()}catch(s){const o=d;if(d=null,await o?.dispose().catch(()=>{}),!f)throw s;d=new r({binary:l}),await d.setup()}}catch(e){throw d=null,x="",e}const a=B(t);return a!==x&&(await d.clearWords(),t.length&&await d.importWords([...new Set(t.map(e=>e.trim()).filter(Boolean))]),x=a),i(d)});return _=n.then(()=>{},()=>{}),n}function q(t){return t.get_replacement_text()}function W(t,i,n,a,e){const r={from:a.start,to:a.end},l={from:e[a.start]??-1,to:e[a.end]??-1},f=[r,l].filter((o,h,g)=>o.from>=0&&o.to>o.from&&o.to<=t.length&&g.findIndex(m=>m.from===o.from&&m.to===o.to)===h);if(f.length===0)return null;const s=n.get_problem_text();return f.find(o=>i.slice(o.from,o.to)===s)??f.find(o=>/[A-Za-z]/.test(i.slice(o.from,o.to)))??f[0]??null}function D(t,i,n,a){const e=n.span();try{const r=W(t,i,n,e,a);if(!r)return null;const{from:l,to:f}=r;if(!/[A-Za-z]/.test(i.slice(l,f)))return null;const s=t.slice(l,f);if(!s.trim()||/[\\%$]/.test(s))return null;const o=n.lint_kind()==="Spelling"||n.lint_kind()==="Typo"?"spelling":"grammar",h=n.suggestions(),g=h.map(q).filter((m,y,v)=>y<z&&v.indexOf(m)===y);for(const m of h)m.free();return{from:l,to:f,word:s,kind:o,message:n.message(),suggestions:g}}finally{e.free()}}function G(t,i=[]){const n=T(t),a=V(t);return C(i,async e=>{const r=await e.lint(n,{language:"plaintext",dedup:!0,isolateEnglish:!1});try{return r.map(l=>D(t,n,l,a)).filter(l=>!!l).sort((l,f)=>l.from-f.from||l.to-f.to)}finally{for(const l of r)l.free()}})}export{$ as ignoredRanges,G as lintLatex,T as maskLatexSource};
@@ -10,9 +10,9 @@
10
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka@5.2.5/400.css" />
11
11
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
12
12
  <title>TexLite</title>
13
- <script type="module" crossorigin src="/assets/index-CISv_aup.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-BLOqf18q.js"></script>
14
14
  <link rel="modulepreload" crossorigin href="/assets/react-CsO_xvbl.js">
15
- <link rel="stylesheet" crossorigin href="/assets/index-DdrajKQB.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CwGialPz.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="root"></div>
@@ -31,6 +31,7 @@ import { compileMainFile } from "./compileArtifacts.js";
31
31
  import { apiError, contentDisposition, ValidationError } from "./http.js";
32
32
  import { registerCompileRoutes } from "./routes/compile.js";
33
33
  const now = () => new Date().toISOString();
34
+ const MAX_CITATION_BIBTEX_BYTES = 512 * 1024;
34
35
  const SESSION_CLEANUP_INTERVAL_MS = 15 * 60_000;
35
36
  function escapeLikePattern(value) {
36
37
  return value.replace(/[\\%_]/g, "\\$&");
@@ -52,6 +53,97 @@ function dictionaryWord(value) {
52
53
  throw new ValidationError("自定义词格式不正确");
53
54
  return word;
54
55
  }
56
+ function citationJson(row, tags = []) {
57
+ return {
58
+ id: row.id,
59
+ citationKey: row.citation_key,
60
+ entryType: row.entry_type,
61
+ bibtex: row.bibtex,
62
+ title: row.title,
63
+ authors: row.authors,
64
+ year: row.year,
65
+ revision: row.revision,
66
+ tags: tags.map((tag) => ({ id: tag.id, name: tag.name, color: tag.color, ownerId: tag.user_id })),
67
+ ownerId: row.user_id,
68
+ ownerUsername: row.owner_username ?? null,
69
+ ownerDisplayName: row.owner_display_name ?? null,
70
+ createdAt: row.created_at,
71
+ updatedAt: row.updated_at
72
+ };
73
+ }
74
+ function citationTagsForEntries(db, entryIds) {
75
+ const result = new Map(entryIds.map((entryId) => [entryId, []]));
76
+ if (!entryIds.length)
77
+ return result;
78
+ const placeholders = entryIds.map(() => "?").join(", ");
79
+ const rows = db.prepare(`SELECT link.entry_id, tag.id, tag.name, tag.color, tag.user_id
80
+ FROM citation_library_entry_tags link JOIN citation_library_tags tag ON tag.id = link.tag_id
81
+ JOIN citation_library_entries entry ON entry.id = link.entry_id AND entry.user_id = tag.user_id
82
+ WHERE link.entry_id IN (${placeholders})
83
+ ORDER BY tag.name COLLATE NOCASE`).all(...entryIds);
84
+ for (const row of rows)
85
+ result.get(row.entry_id)?.push({ id: row.id, name: row.name, color: row.color, user_id: row.user_id });
86
+ return result;
87
+ }
88
+ function citationTagIds(db, userId, value) {
89
+ if (value === undefined)
90
+ return null;
91
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
92
+ throw new ValidationError("引用标签格式不正确");
93
+ const ids = [...new Set(value.map((item) => item.trim()).filter(Boolean))];
94
+ if (ids.length > 100)
95
+ throw new ValidationError("单个引用最多只能设置 100 个标签");
96
+ if (!ids.length)
97
+ return [];
98
+ const placeholders = ids.map(() => "?").join(", ");
99
+ const rows = db.prepare(`SELECT id FROM citation_library_tags WHERE user_id = ? AND id IN (${placeholders})`)
100
+ .all(userId, ...ids);
101
+ if (rows.length !== ids.length)
102
+ throw Object.assign(new Error("引用标签不存在"), { statusCode: 404, code: "CITATION_TAG_NOT_FOUND" });
103
+ return ids;
104
+ }
105
+ function citationTagName(value) {
106
+ return text(value, "引用标签名称", 32);
107
+ }
108
+ function citationExpectedRevision(value) {
109
+ if (!Number.isInteger(value) || Number(value) < 1) {
110
+ throw new ValidationError("引用版本格式不正确");
111
+ }
112
+ return Number(value);
113
+ }
114
+ function citationNullableText(value, name, max) {
115
+ if (value === undefined || value === null || value === "")
116
+ return null;
117
+ if (typeof value !== "string" || value.length > max)
118
+ throw new ValidationError(`${name}格式不正确`);
119
+ const trimmed = value.trim();
120
+ return trimmed || null;
121
+ }
122
+ function citationInput(value) {
123
+ if (typeof value !== "object" || value === null)
124
+ throw new ValidationError("引用条目格式不正确");
125
+ const body = value;
126
+ if (typeof body.bibtex !== "string" || !body.bibtex.trim()) {
127
+ throw new ValidationError("引用条目不能为空");
128
+ }
129
+ if (Buffer.byteLength(body.bibtex, "utf8") > MAX_CITATION_BIBTEX_BYTES) {
130
+ throw Object.assign(new Error("引用条目过大"), { statusCode: 413, code: "CITATION_TOO_LARGE" });
131
+ }
132
+ if (typeof body.citationKey !== "string" || !body.citationKey.trim() || body.citationKey.length > 512) {
133
+ throw new ValidationError("引用 key 格式不正确");
134
+ }
135
+ if (typeof body.entryType !== "string" || !body.entryType.trim() || body.entryType.length > 128) {
136
+ throw new ValidationError("引用类型格式不正确");
137
+ }
138
+ return {
139
+ bibtex: body.bibtex.trim(),
140
+ citationKey: body.citationKey.trim(),
141
+ entryType: body.entryType.trim(),
142
+ title: citationNullableText(body.title, "引用标题", 2048),
143
+ authors: citationNullableText(body.authors, "引用作者", 2048),
144
+ year: citationNullableText(body.year, "引用年份", 128)
145
+ };
146
+ }
55
147
  const tagColors = ["red", "orange", "yellow", "green", "blue", "purple", "gray"];
56
148
  function tagsForProject(db, projectId, userId) {
57
149
  return db.prepare(`SELECT tag.id, tag.name, tag.color
@@ -481,6 +573,227 @@ export async function buildApp(config, db, options = {}) {
481
573
  .run(user.id, digestToken(request.cookies.texlite_session ?? ""));
482
574
  return { ok: true };
483
575
  });
576
+ app.get("/api/citations", async (request, reply) => {
577
+ const user = requireUser(request, reply, db);
578
+ if (!user)
579
+ return;
580
+ const query = request.query;
581
+ const search = typeof query.q === "string" ? query.q.trim() : "";
582
+ const tagId = typeof query.tag === "string" ? query.tag.trim() : "";
583
+ const requestedPage = Number.parseInt(query.page ?? "1", 10);
584
+ const requestedPageSize = Number.parseInt(query.pageSize ?? query.limit ?? "60", 10);
585
+ const pageSize = Math.min(200, Math.max(1, Number.isFinite(requestedPageSize) ? requestedPageSize : 60));
586
+ const where = ["entry.user_id = ?"];
587
+ const params = [user.id];
588
+ if (search) {
589
+ where.push(`(
590
+ citation_key LIKE ? ESCAPE '\\' OR entry_type LIKE ? ESCAPE '\\'
591
+ OR COALESCE(title, '') LIKE ? ESCAPE '\\'
592
+ OR COALESCE(authors, '') LIKE ? ESCAPE '\\'
593
+ OR COALESCE(year, '') LIKE ? ESCAPE '\\'
594
+ )`);
595
+ params.push(...Array.from({ length: 5 }, () => `%${escapeLikePattern(search)}%`));
596
+ }
597
+ if (tagId) {
598
+ where.push(`EXISTS (SELECT 1 FROM citation_library_entry_tags filter_link
599
+ JOIN citation_library_tags filter_tag ON filter_tag.id = filter_link.tag_id
600
+ WHERE filter_link.entry_id = entry.id AND filter_link.tag_id = ? AND filter_tag.user_id = ?)`);
601
+ params.push(tagId, user.id);
602
+ }
603
+ const countRow = db.prepare(`SELECT COUNT(*) AS count
604
+ FROM citation_library_entries entry
605
+ WHERE ${where.join(" AND ")}`).get(...params);
606
+ const total = Number(countRow.count) || 0;
607
+ const totalPages = total > 0 ? Math.ceil(total / pageSize) : 0;
608
+ const page = totalPages > 0 ? Math.min(Math.max(1, Number.isFinite(requestedPage) ? requestedPage : 1), totalPages) : 1;
609
+ const offset = (page - 1) * pageSize;
610
+ params.push(pageSize, offset);
611
+ const rows = db.prepare(`SELECT entry.*, owner.username AS owner_username, owner.display_name AS owner_display_name
612
+ FROM citation_library_entries entry
613
+ JOIN users owner ON owner.id = entry.user_id
614
+ WHERE ${where.join(" AND ")} ORDER BY entry.updated_at DESC, entry.citation_key COLLATE NOCASE LIMIT ? OFFSET ?`).all(...params);
615
+ const tags = citationTagsForEntries(db, rows.map((row) => row.id));
616
+ return {
617
+ entries: rows.map((row) => citationJson(row, tags.get(row.id) ?? [])),
618
+ pagination: { page, pageSize, total, totalPages }
619
+ };
620
+ });
621
+ app.get("/api/citations/tags", async (request, reply) => {
622
+ const user = requireUser(request, reply, db);
623
+ if (!user)
624
+ return;
625
+ const tags = db.prepare(`SELECT tag.id, tag.name, tag.color, tag.user_id AS owner_id
626
+ FROM citation_library_tags tag
627
+ WHERE tag.user_id = ?
628
+ ORDER BY tag.name COLLATE NOCASE`).all(user.id);
629
+ return { tags: tags.map((tag) => ({ id: tag.id, name: tag.name, color: tag.color, ownerId: tag.owner_id })) };
630
+ });
631
+ app.post("/api/citations/tags", async (request, reply) => {
632
+ const user = requireUser(request, reply, db);
633
+ if (!user)
634
+ return;
635
+ const body = request.body;
636
+ const name = citationTagName(body?.name);
637
+ const color = tagColors.includes(body?.color) ? body?.color : "gray";
638
+ const existing = db.prepare("SELECT id, name, color, user_id FROM citation_library_tags WHERE user_id = ? AND name = ? COLLATE NOCASE").get(user.id, name);
639
+ if (existing)
640
+ return { tag: { id: existing.id, name: existing.name, color: existing.color, ownerId: existing.user_id }, created: false };
641
+ const tag = { id: randomUUID(), name, color, ownerId: user.id };
642
+ db.prepare("INSERT INTO citation_library_tags (id, user_id, name, color, created_at) VALUES (?, ?, ?, ?, ?)")
643
+ .run(tag.id, user.id, tag.name, tag.color, now());
644
+ return reply.code(201).send({ tag, created: true });
645
+ });
646
+ app.delete("/api/citations/tags/:tagId", async (request, reply) => {
647
+ const user = requireUser(request, reply, db);
648
+ if (!user)
649
+ return;
650
+ const { tagId } = request.params;
651
+ const result = db.prepare("DELETE FROM citation_library_tags WHERE id = ? AND user_id = ?").run(tagId, user.id);
652
+ if (!result.changes)
653
+ return apiError(reply, 404, "CITATION_TAG_NOT_FOUND", "引用标签不存在");
654
+ return { ok: true };
655
+ });
656
+ // Keep the old settings endpoint explicit so stale clients cannot accidentally
657
+ // reinterpret "settings" as a citation id. Citation libraries are always private.
658
+ app.patch("/api/citations/settings", async (request, reply) => {
659
+ if (!requireUser(request, reply, db))
660
+ return;
661
+ return apiError(reply, 403, "CITATION_LIBRARY_PRIVATE", "引用库始终为私有,仅当前用户可访问");
662
+ });
663
+ app.post("/api/citations/lookup", async (request, reply) => {
664
+ const user = requireUser(request, reply, db);
665
+ if (!user)
666
+ return;
667
+ const body = request.body;
668
+ if (!Array.isArray(body?.keys) || body.keys.length > 5000
669
+ || body.keys.some((key) => typeof key !== "string" || !key.trim() || key.length > 512)) {
670
+ throw new ValidationError("引用 key 列表格式不正确");
671
+ }
672
+ const keys = [...new Map(body.keys.map((key) => [key.trim().toLowerCase(), key.trim()])).values()];
673
+ const matches = [];
674
+ for (let offset = 0; offset < keys.length; offset += 500) {
675
+ const chunk = keys.slice(offset, offset + 500);
676
+ const placeholders = chunk.map(() => "?").join(", ");
677
+ matches.push(...db.prepare(`SELECT id, citation_key, revision FROM citation_library_entries
678
+ WHERE user_id = ? AND citation_key COLLATE NOCASE IN (${placeholders})`)
679
+ .all(user.id, ...chunk));
680
+ }
681
+ return { matches: matches.map((match) => ({ id: match.id, citationKey: match.citation_key, revision: match.revision })) };
682
+ });
683
+ app.post("/api/citations", async (request, reply) => {
684
+ const user = requireUser(request, reply, db);
685
+ if (!user)
686
+ return;
687
+ const body = request.body;
688
+ const citation = citationInput(body);
689
+ const tagIds = citationTagIds(db, user.id, body?.tagIds);
690
+ const overwrite = body?.overwrite === true;
691
+ const existing = db.prepare("SELECT id, revision FROM citation_library_entries WHERE user_id = ? AND citation_key = ? COLLATE NOCASE")
692
+ .get(user.id, citation.citationKey);
693
+ if (existing && !overwrite)
694
+ return apiError(reply, 409, "CITATION_KEY_EXISTS", "引用 key 已经存在");
695
+ const expectedRevision = existing ? citationExpectedRevision(body?.expectedRevision) : null;
696
+ if (existing && existing.revision !== expectedRevision) {
697
+ return apiError(reply, 409, "CITATION_CONFLICT", "引用已在其他位置发生修改,请刷新后重试");
698
+ }
699
+ const id = existing?.id ?? randomUUID();
700
+ const timestamp = now();
701
+ db.transaction(() => {
702
+ if (existing) {
703
+ const result = db.prepare(`UPDATE citation_library_entries SET citation_key = ?, entry_type = ?, bibtex = ?,
704
+ title = ?, authors = ?, year = ?, revision = revision + 1, updated_at = ?
705
+ WHERE id = ? AND user_id = ? AND revision = ?`)
706
+ .run(citation.citationKey, citation.entryType, citation.bibtex, citation.title, citation.authors, citation.year, timestamp, id, user.id, expectedRevision);
707
+ if (!result.changes)
708
+ throw Object.assign(new Error("引用已在其他位置发生修改,请刷新后重试"), { statusCode: 409, code: "CITATION_CONFLICT" });
709
+ }
710
+ else {
711
+ db.prepare(`INSERT INTO citation_library_entries
712
+ (id, user_id, citation_key, entry_type, bibtex, title, authors, year, revision, created_at, updated_at)
713
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`)
714
+ .run(id, user.id, citation.citationKey, citation.entryType, citation.bibtex, citation.title, citation.authors, citation.year, timestamp, timestamp);
715
+ }
716
+ if (tagIds !== null) {
717
+ db.prepare("DELETE FROM citation_library_entry_tags WHERE entry_id = ?").run(id);
718
+ for (const tagId of tagIds)
719
+ db.prepare("INSERT INTO citation_library_entry_tags (entry_id, tag_id, created_at) VALUES (?, ?, ?)").run(id, tagId, timestamp);
720
+ }
721
+ })();
722
+ const row = db.prepare("SELECT * FROM citation_library_entries WHERE id = ? AND user_id = ?")
723
+ .get(id, user.id);
724
+ const tags = citationTagsForEntries(db, [id]);
725
+ return reply.code(existing ? 200 : 201).send({ entry: citationJson(row, tags.get(id) ?? []), updated: Boolean(existing) });
726
+ });
727
+ app.patch("/api/citations/:citationId/tags", async (request, reply) => {
728
+ const user = requireUser(request, reply, db);
729
+ if (!user)
730
+ return;
731
+ const { citationId } = request.params;
732
+ const existing = db.prepare("SELECT id FROM citation_library_entries WHERE id = ? AND user_id = ?")
733
+ .get(citationId, user.id);
734
+ if (!existing)
735
+ return apiError(reply, 404, "CITATION_NOT_FOUND", "引用条目不存在");
736
+ const body = request.body;
737
+ const tagIds = citationTagIds(db, user.id, body?.tagIds);
738
+ if (tagIds === null)
739
+ throw new ValidationError("引用标签格式不正确");
740
+ const expectedRevision = citationExpectedRevision(body?.expectedRevision);
741
+ const timestamp = now();
742
+ db.transaction(() => {
743
+ const result = db.prepare(`UPDATE citation_library_entries SET revision = revision + 1, updated_at = ?
744
+ WHERE id = ? AND user_id = ? AND revision = ?`).run(timestamp, citationId, user.id, expectedRevision);
745
+ if (!result.changes) {
746
+ const exists = db.prepare("SELECT id FROM citation_library_entries WHERE id = ? AND user_id = ?").get(citationId, user.id);
747
+ if (!exists)
748
+ throw Object.assign(new Error("引用条目不存在"), { statusCode: 404, code: "CITATION_NOT_FOUND" });
749
+ throw Object.assign(new Error("引用已在其他位置发生修改,请刷新后重试"), { statusCode: 409, code: "CITATION_CONFLICT" });
750
+ }
751
+ db.prepare("DELETE FROM citation_library_entry_tags WHERE entry_id = ?").run(citationId);
752
+ for (const tagId of tagIds)
753
+ db.prepare("INSERT INTO citation_library_entry_tags (entry_id, tag_id, created_at) VALUES (?, ?, ?)").run(citationId, tagId, timestamp);
754
+ })();
755
+ const row = db.prepare("SELECT * FROM citation_library_entries WHERE id = ? AND user_id = ?")
756
+ .get(citationId, user.id);
757
+ const tags = citationTagsForEntries(db, [citationId]);
758
+ return { entry: citationJson(row, tags.get(citationId) ?? []) };
759
+ });
760
+ app.patch("/api/citations/:citationId", async (request, reply) => {
761
+ const user = requireUser(request, reply, db);
762
+ if (!user)
763
+ return;
764
+ const { citationId } = request.params;
765
+ const existing = db.prepare("SELECT id FROM citation_library_entries WHERE id = ? AND user_id = ?")
766
+ .get(citationId, user.id);
767
+ if (!existing)
768
+ return apiError(reply, 404, "CITATION_NOT_FOUND", "引用条目不存在");
769
+ const body = request.body;
770
+ const citation = citationInput(body);
771
+ const expectedRevision = citationExpectedRevision(body?.expectedRevision);
772
+ const duplicate = db.prepare("SELECT id FROM citation_library_entries WHERE user_id = ? AND citation_key = ? COLLATE NOCASE AND id != ?")
773
+ .get(user.id, citation.citationKey, citationId);
774
+ if (duplicate)
775
+ return apiError(reply, 409, "CITATION_KEY_EXISTS", "引用 key 已经存在");
776
+ const result = db.prepare(`UPDATE citation_library_entries SET citation_key = ?, entry_type = ?, bibtex = ?,
777
+ title = ?, authors = ?, year = ?, revision = revision + 1, updated_at = ?
778
+ WHERE id = ? AND user_id = ? AND revision = ?`)
779
+ .run(citation.citationKey, citation.entryType, citation.bibtex, citation.title, citation.authors, citation.year, now(), citationId, user.id, expectedRevision);
780
+ if (!result.changes)
781
+ return apiError(reply, 409, "CITATION_CONFLICT", "引用已在其他位置发生修改,请刷新后重试");
782
+ const updated = db.prepare("SELECT * FROM citation_library_entries WHERE id = ? AND user_id = ?")
783
+ .get(citationId, user.id);
784
+ const tags = citationTagsForEntries(db, [citationId]);
785
+ return { entry: citationJson(updated, tags.get(citationId) ?? []) };
786
+ });
787
+ app.delete("/api/citations/:citationId", async (request, reply) => {
788
+ const user = requireUser(request, reply, db);
789
+ if (!user)
790
+ return;
791
+ const { citationId } = request.params;
792
+ const result = db.prepare("DELETE FROM citation_library_entries WHERE id = ? AND user_id = ?").run(citationId, user.id);
793
+ if (!result.changes)
794
+ return apiError(reply, 404, "CITATION_NOT_FOUND", "引用条目不存在");
795
+ return { ok: true };
796
+ });
484
797
  app.get("/api/admin/users", async (request, reply) => {
485
798
  if (!requireAdmin(request, reply, db))
486
799
  return;
@@ -1367,8 +1680,8 @@ export async function buildApp(config, db, options = {}) {
1367
1680
  return apiError(reply, 403, "PROJECT_EDIT_FORBIDDEN", "没有编辑权限");
1368
1681
  const body = request.body;
1369
1682
  const filePath = safeRelativePath(typeof body?.path === "string" ? body.path : "");
1370
- if (!/\.(?:tex|bib|cls|sty)$/i.test(filePath)) {
1371
- return apiError(reply, 400, "FORMAT_FILE_UNSUPPORTED", "只有 .tex、.bib、.cls 和 .sty 文件支持格式化", { path: filePath });
1683
+ if (!/\.(?:tex|cls|sty)$/i.test(filePath)) {
1684
+ return apiError(reply, 400, "FORMAT_FILE_UNSUPPORTED", "只有 .tex、.cls 和 .sty 文件支持宿主机格式化", { path: filePath });
1372
1685
  }
1373
1686
  if (typeof body?.source !== "string")
1374
1687
  return apiError(reply, 400, "FORMAT_SOURCE_INVALID", "待格式化内容格式不正确");