From 4f039ecb2bc95ba498edb4fc3a3eaa5ccfd95684 Mon Sep 17 00:00:00 2001 From: Marc Laventure Date: Tue, 19 Mar 2024 07:32:54 -0700 Subject: [PATCH] feat: add scalar api client (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * feat: add scalar api client * fix: pr feedback * feat: add scalar react client package, remove vueary * feat: add scalar proxy * Move dependencies to package * Format once * Rename prop * Use react context and simplify lazy loading * Add loading state * Format * Move styles * bun install * bun * fix: remove extraneous api-reference package * feat: make operation functional with spec in client * fix: column bug and http request type wrapping * fix: add scalar api reference package * Format * bun install * fix: scrolling in scalar modal * Lazy load operations data in component * fix: change dependency from api-reference to oas-utils to reduce size * bun install * Log error in the worker proxy * Cleanup * Comment "mode" * Without credentials and referrerPolicy --------- Co-authored-by: Samy Pessé Co-authored-by: Amrit --- bun.lockb | Bin 438869 -> 469177 bytes packages/react-openapi/package.json | 6 +- .../react-openapi/src/InteractiveSection.tsx | 4 + .../react-openapi/src/OpenAPICodeSample.tsx | 22 +- .../react-openapi/src/OpenAPIOperation.tsx | 72 ++-- packages/react-openapi/src/OpenAPISpec.tsx | 10 +- .../react-openapi/src/ScalarApiButton.tsx | 163 +++++++++ src/app/~scalar/proxy/route.ts | 79 +++++ .../DocumentView/OpenAPI/OpenAPI.tsx | 1 + .../DocumentView/OpenAPI/scalar.css | 325 ++++++++++++++++++ src/middleware.ts | 2 +- 11 files changed, 638 insertions(+), 46 deletions(-) create mode 100644 packages/react-openapi/src/ScalarApiButton.tsx create mode 100644 src/app/~scalar/proxy/route.ts create mode 100644 src/components/DocumentView/OpenAPI/scalar.css diff --git a/bun.lockb b/bun.lockb index cfd1fe63c75805a0689045c3685bee01c5d01a0f..6469f28a4b85a38e13d5146f603f650a891b824b 100755 GIT binary patch delta 22680 zcmeHvXIKgqJ9=lWyq{a3ooH#f=JJE-o~ z?@n!v%RdE8f9lJbRIm5jYy9#h=U=zodExHxOp_jMIQT0o7^5e*yl6+pd;?;J50|6N z;bcW6NyMUv7|tY!D?xl5glZ5@5GBN8o{PRl9 z1H?Tb-WI|dh^s)D0&!Ic%SXxd+l}UO+H*KWRs@|fIZ2ulA(e8v*mI~1axomrFae|^ zA!m#%<0?NYW7aZ=cLMwegq2gwSKkci_`fG7*%m{A|nvE~v8G2dtiv4-IgVtPF2Vfr3_na5TD zj)qNy&=A755TfB%L%{$74!(jS0s&z%m(|g61(D2<1{!||p$_O>1{_^}5%jR2yHN9X z5T1h&J(Vnpl_r1}5=Dtgv5_23M4E{5NJP9e0}sB1%M5z~;aJSL6Eb2SfVMS@6eq-f zjg%2Sg%E3W6+$d%%R~-U<7AOUDmF`y9-;G9b&=&4WyB_-^VSP{3>cEwQcfriYGlT#uZzZ~nMXWIF6`+k zyv*r1|B2HSuVYpVRF<7`oYm{{>`ytSYn{Fyds1n6E8nTdyM1L(g|&O%MsB;Yrfa9E z8`Or)y89*6cE7%Vb;Rpk>vU8c*Xngr=yk5(r$%|W@08cKrj+k^oPG6_^0rii6$fw6 z`sUtI{-){7c@dvPem{sC8`QTX2|73IzwSLT=4r^0@Bp>?+I5Bz6W_j*{+8#QiSoVl zY=6alkIXLp?c(|AB{LLC6mDD|ZLToY*7a*2@AIViyz;`?JMuO4JGWXf{Ari;zWXlGpL_T4T_;Suj>yh^Z9|hjaYO5J2S8=!TbKHbwQMt3H z3|_NkS^qWJmbzxe;|FdK?OhUfSsY*K*2d}E!6zrbc?HSs_m3J;Wi08CaK2sl%j+K! zNjarm253(fcmD8n-H!6f!rfXrVV?@72X^-9y}e=nnX`+(=@`hDj3~8zGVQs0`Ez$} za!18Nn2TJXKXQtMi4KZaAqI0smRwQYLv!v2Q z(GH4xA?8D+i6%R!Xu>pO0Wo>#zr3QlT+ zKBTA&pyxkSp9AXn57Yr>MzkE~Xw9NJ57VN{VT#HCy0KMBA6+hmwk!)R(1j@#E8DUP zp@6p1tx{A5&;e8xWVKyG)mHH?O~8fH9aOr)=ynAiP~mhf61|t{gaV2NQ96ohF!{>- zE2?o&83{2bOoJS4NTD59mSqRPloN`|20JL~!K~#4O7c`J7GmgN4k_Yu9aJvC+=wj% z^C?GAB=n*P)Iwnjw?ZZO$$3BFO&W0E^FjyVz zz`qAEhoXCroO!TX45G{z!x1(%w@3);%MeF(Qs~BYQ0xGUi5(`ALVJjMKvbq%_ufHq z3dDS6)V>aCbr3^Cv3b!T6{;CjS12mLLL#dH)-D!e*pHz}6@YOKoyaK?op4apiD39e z*Blf>A!ZAzd~{`xB8X9@05|&=E>01dL;I_UHpLbVlO8K_8KlX-2FjL2!NE?7JXoFl zvAm+Pkq#yF?lNX8e%RGgYE$nP_JlN47!fDgGxNasE!0| z(0KF!TLI``$iXGce7FvB&!S1~KyIVr;22p}1t`|0d{huO%YeYqD};a8AO;v|6(Vj( z;~3(^U<@ISh+cHu0zw>9R&;0sAs^zsA;cd$#QaXQUS9<`cftbt0RX2#91jRl!Hdp_ zA*K(35Qnorgji4j9R|^12!!~<5YB2iNf2VbWI7yAr(?(iJQYGLCqn_wo$!}IE3iYH zL(x5`m`$f+i18c<(F6GqVnMU%bPO>*2SO~UfDRWzi1`-L;Zg|EL#0dss{p{7t)Ugx zLx>*PLC1H~VL60o*bzEj0U;8dh7b!r10m+S3?cs5A@X0PaSTzPsHOo7(c+sBqT+2@ z;SQb74l!d5jsGdcn%<|=*&*`Q0*>W8h7fqF|L=ChnYYWh<+sb^eoZ2txQ@zj$dB!GuIACxuGT{wB0h3|Re08R8r@C1>!og~ z<@}Hi4_Ef9aH$UtzFD7C_)PF_-qw{B!Q1l!LawFg`&EEpTux*AMhHh^Z>N8})j6L^ z^OkIkuG)O$O@^3T{<%J~&&P}~zq2XaLr+BJ?)eT=PKW$f(X!mRuX>HOR*lKEu(ROwg0pAb2w-pS#*|$FGUS8{VyG-~Da-c_te~-G97mUq7T%RYE|a z3n$;uz38UtQC|01uP<%8)~DNTH_4~pJv=0@zr5Td8he>#-x#)iPc=V|`F>tCds?Ht zQQhfnR%bt%DqQH9o4DoCugpH*W~dn+@06Ppt-bZa;nm#~K9OH``c<}%SW$R>M#g2c zCw`UVgGgO{c}*fVI4snl&|$T-euLIauOHv{r`FAWDlFeR_U)_6#S@l1%s9x~9`Uts zPs+nHU4IT&)aqFH^m?h0{D-Ul12WWm%r0b^HNyI!B?FWev3LV{u&6OVp0(Yb6P-hE8{gh#eJP@Ij*@d|-d@wH!}moy zcN{Og^4U%0=Zsx>rseC`1RhgUo9D~d*K23yvHfJ%s9$Q*{4MD#S;RqXgZn)UdHcRH ze~rJF$y>EO3IpU7+zU!=&g}E3JJN8CP%m=-V(XEVU0-{2|B)>}rs?@>UDCI6?EO68 ztVP!`SL{CP-DeR8vx%eT1--uaA?2a!=35(Hm-jvXd3e>Rgq0tztLH^tIyJrBk+zuxb94D8!#P{jZlx6TDbg7`yUnjHeXVk?9uU9Dx$yf@U}P3U!#n3-)`kW7W{CVhXe(iuAR-hhvrS;S#%VqcAQ2GK#65BINIKd7zHM*4HC13yfn zIOzP9>lc>0Zi+Dex~6LNZ?(#6k5=YcHp*xFj=tKVX7TMW`cH|>Gm=?(QWmj@O}zN+ z{k79Sg!}coWu*MJ%VB5zd#g`8FjT5K#DAMEHYp0$ugiY-#l`RKfckGI-&xhx^}7`r z?K0Q)&beE0T@6Q0%VY6|vw0h*+&>WJ7-i)_$eq6Vz`LyLOG5u_yCq|{?e;v_|80TQ z%*Oa|U!Qb`sdeAS4fKsxwQ1-OnrD%)GOK9U)nlV`v`ksV5p3eG@AnR_SF9Sd>iIEk z%a3p0XH4}RzuZl3a?S{U2mhr}(;_Eqc-k)fTJo$%&yxmf9=SCH#&vfcJZoN{&KTs-$f^D!}kn%A2O)fV1f86j`XtFb zyH-)orsC8K&zkh)+0U!b{K(BctNbfHeVf(z@eNB1R%B+l7bLTIV|dN;4~J*6f4<)@ zyQ0GQTVs1GFY6oQ>^{6?#fL3Z$K`OlaR&@aovJkH-rd_#s*=9ly5}Da^j9f)sHbf4 zqh!cL7dP9f?ccMAW7!7xUsxNSW!d=c#;f9w{GC2++b@f2Ge)KVOTEp;96~d{JpX%G z$FG~ly;OJ}p7UT;~fZ zvsP9*smJ`96yWA=uz11v_2aX;7<}ck431~>+7v476b=r5;agv!O`d7Ipj1LUIct35 z+k-%lQ|&csPnK-W3otq45PbL7;H7;9JN(PTst>vdeK!q0wp0J9eTPS1Si}izV&|E& z<1=TMXFc<2xFG*QEOkA(F!w@`((4le+l@%$ujdLr#+8#N{PV56%gx|MMgMYZf!=r} z(~?z0W>u8j(LU!{#EER;`$LpNa#xhwL~d0~)T!B0zhs~DmE%J;ss0q*dz3pgG|q2UUk}_ z%rwElqcn-5vEcxx>x1cfF7_AOot4b>n!0bjscEt6kyT^X{E|L3c(ow%soM9zfx_?4 zS;Wa~;>WRZI7A@Ux|GeFd&&R*ndk)+=sc*@*^{2XtN2z8g4DlTJu}ER3 zy+#}2TilJpU`_wdZU*;EqBZ2-^<@!{XA>V!{-S3Tw|b#M&9hn2&(>e}*3YRQ{>n5b zr#5cGyQTg;%>dFW}T-s_%W?=cO!z1;VBnjN%?_;LQl_N6RdNi#3O;SF%L z_BE*6zC^3w;99N4@yEl2^4q7599q1s-={-UI&7&dNj$fGVM^KP496!8?;30Ryh*sX z$#`vteY0ou_!6Batz{8Qn~6ER?v;(pOR`gAb2EGeL9T%v>tpj~EbDme%KKOkQ;D^% zoVVhd5tE4c$#(r`o;H1V{cKzR`irSc)}IN zY4oUnf8xc~UMjK5#W`D-990m$StI9v=|arfxAHgC@0w`Kw;yDrQK=QT`l7J&zR{|F z=eq2C!{SXP*u7 zY2jX_vg;d-zyCV7G%P$W_wencioB@9Pad*})7ZTA#nySpwPOTcBaiR9JKf-erR1e3 zGh4XApIc8j++X~7d-;{v{oN{Go_wmHetXBUiamDwM&1c?AaA)md^OAG;>o8h;&e9g zFSQ^a%~v-2HuzS%m?syl6~~-BQ=;y^`)cvZY6a5y;`g3$r#EZ#c3C%~ zdR3I7ZZ`Qb06Fqa!j{!T|0~FNhP8oiG!*M=kF#S<=eg`zDg;59lqQ_ zwM*fNkgnUWpZnB_MV!ed4%oflrd{gxxECAyi0<9*Kk2oJTJX&Dw04Ch0|ocHeMs|> zOg|IpGjW%$!{v5!$FFyZVREpZR{w;9BOhJs zJMYlA4|D2P$D0>uolbty)~U{_QSDwBoDwbe)V;mg@6Zgl4Gu;70%~uJ;9gsknY>MO zjm0~O&8rg?byfVSgy_=ndQm{kvl!KcWqvQCYZqOLHRmMP+^$mJykh)e(a6jXK@ZBj z9aE>jHthIw+&AOW5h{;%W(9P*Mm0Z@*Z{XM}Hp`-$?0d`LCJ#Zx1Aja<>|M`PKe$2R|onO&)n?&Gj#3vwn0rwm&g{kLAQJ+R_1s>fYwKA87j| zZR*FaLJjk5o$ExO>z7|GsGPK$d;K}DM^&cgr%q?{2ff(JK2gbH8ysX}c~Ij>=8L?V zv&X)?@qHoL9NORSP3SS5CsXqxw&dn7SiMe*XxQ=g&;uQdj?>%*bUC}bV6_J^_orxP ze(lmS-8(Fcr?QEE^bK8*9=5jh#Dd~9bI&!6J0E(OAF7LJD?MW6n{ifep1}3y*;PLi z(?(s{@-)g+`O>HQK`HBgJx~nfR%ciF4%1@s64`9xlPO>OPv1YGc9CkYS+?KD)tx?R zQ4{p3y@Tg3wYF2ft*V)xcJQ=`eT1dt^KIx<8H0 zyL;#&(?jwLkB(i<{kdeprM$cYTQ%;vtQk0N3DICE$0wEb5Bts9-u-QlwK3hqn-`t- zB*y#5J)EE*ZEs;Rcdv>FZn8Ix=jm+X*Nes<69GJ-8siJYVi7Xjya86rLK>T8PyL6==LbL!hWr&OKDw)&i=jN`m2dJhfQoXDMaDK z$1P{pdhS>BwKi(3qeq_(WyqDc(+;E=3BA&@6ew;P#{mym|Is|u{9P(xT z@5rksYj-bAyO8#b8>OYHwX^RouXs+lbK3J|3x0KOr?kMQ_)UbQL3*F~wa->>im%6& zRV?CMHgUs1KjrHa{CXMu9vi#un`cmwUq0u@pLQnoK0TI4{OG{FVRWnhW0YrEXZgoTSuEmNY~njxdM{2$ zig<8KFY{~o^6j4KJBYnq%K8m#qg$O56uw8S^H)@AD(YE^@)5ov8mpd6IB}Ks~F5WKNo zufO5eD^n&;y!CE*>=nH>pVM8o39`K}pSY0GfADdy>nz^c3@=`P@V030&Zu~Ax7S5eNJuY&2t|f_ejBM&i9^s?|uK#$?S`HLbd8K&X8$v=Pk&SFih#A z6jFHJ-nql+V;Sbe>kZ*LVQq&jtHRG)??mPxTe-9X6;w`9(7?!I{S< zp6%-_RPLQ~;f_UCTm8Fnz1%M+M61l5zPfnEKH;X4AcJu)1lzm!a@zY?vO~vI{eu0v zMt!Z#UIq{9LNj;$nsI`?+RbP4-VSR!xlhLFIfe7X77e<;NyRF(=vu0@&(y<&oAu2Y zrCXZ{dd%|cprMoGd`NBKn@isx4=TI)VTYTvY{0dQn{{@b;ptaXpD17x{|2;;zBmDYTY4|(^Xm1`uO;1{X|jkHvWchN(pr&vebYYWw+(N)87*ww>~!Mc z3dd11G_qeRl=3GT>Ej340bLktoj);I@9pCfUTpiho5Ps1M%zhVOXci)U<+^BwC0S+_@D7@j6K)843@ zpWZ*y`{e^ma`(H{XNjxB@@=lK=A`;Bed`f(uIgRbqx;)f#EaO(i}@B>!bJS3 zTEC6feo?{j8wZHBrqgbm*_CbFBXnk@Q0eDl*CWYVA_LW`+ub9BHi!3BdGD8#kGXf~>6MzituE`bfA^=!d%c!k6sH86$jusgwC5=;zq=l* zevjhlK2E$?KV;;#lRfi>nsr(?@VW0k`>V;lS;R}&1|R+Ev3vft2e+rK(~s#imN)ba zvG3;iEs^7(zWgUYc=>^+ z!n~Rn9-C;MK!#b!_abYD$ww6FXY=?5qu`yg@)3jO4Y_=@)3*GIP+mh06(Xt3%*$cE zFq5c|1-;~>DLuz>p_RN7w>&FdUUUt$EB}A|%K{rMi}bQP@Gao6b~|!vy8O_#we+;G zdl`=$5yV#~i@bSu1Q)D_*2FjHs2|4hStJU)k4XLG0mJ8(C|zU_M?mL;0N=W!er;$B zAG#XRm;#OA6JdD&hx#ef7(Bcq@CiNjQ=&n9HrfUM!LKci;Zs{fWPqPCjlnB~95WhI z0Spa>7a}?4G}fNhgBR~Ox-_OrWB9y$1RP`e95ot*H}yF9zz4n2fezsfO3pZ}4*b+< zOcjokXsjb(=oNgh?gq#B)1dV_z_AY|!cUXN)Zy5V#dX-o^SNK}HKkjAv(m|)&tFaQt@gBKt< z)%a8uenzw+w)qIa(B;N}A-68bSQ_g=V_gBWq+Q(;F!f!NZRIgRN9W-r5_{Q<;E z2|*kI#5S}642>}WSqI11UiP$}Asj2wgbp;;4X}^2TYCeB24V;NL}QM$o-ts1Xv~Si z!2jI=ET=(dI&%-e_R&~BIzM*JhQ*w!921}|}OVnOidNn`K|4`(Qh9BkLYG=`&V1dVyo zm*}f#OXnU-ZTa;3vuq!ePJk#!JB}b2*9xYhXIBI0bXe0G(eMJ`;Vmc z;1wMXm(Dv1Fl-WdLx{tpF+Z6efp7M36lstde()+6=Lg-!{xl)H^2GU0WBA|`3E^!b z&Jh|51Pnb4FQ0La0)`zAAF|=->JM^?*28;|=wKI+T>@x-{7Gp-S2%X36;o+!0AM)I z@t00x1L3$69Cw2d?>8Z#8%Spw!&^m&xr3YltFZ4(q_IJ8e3I@9lkmm_Dtdq{q(Qv9 zf|w^r6pi7H5yS?AEP$$GpJ4LBD|wt^OoSiakHEtrAYW;(P6Z6T1@8fJz5|BMidXBX zM+}93B&~>7+Nd}T#FEDFVipyLgLJ1cybeXo2LxYv$9Ba_NW?~f7}9!p`G=SskaWOYAjFFu#QZ=40CR;9uT!v%$AARU*h;{# zBK{yaE8=f8op&r8Q$WtJ}vUbpVVc7r9(S_`!K@ni*X>1pbg##v~vE6`S z?IJ)X(%4>FFA}f?P%s)$PGe#?E~c@4G!_NeCCE$l|NS%=4d7(}!yr5W7;?veOv3~C z9j5hg;+X*$8g+!m;^6oeU`TwF&N~i{uKO_n0?`K10l^cDt{{5l6a08X z&q1MvaD>2f5?VX5lA>l1V|)^7$gcL8YBiJ7924ZC~&lsGDVrVdy4ktl~(<;u@IEND;=^(g*;j_gFAekT;AhA_$U?1BnMophG4f;&?nBBpC$DnaGf*K_mr)QD#D14gYk^HUbGTj?&uD{}jhG zf+0t17-b~F6^*GSL!1p59y2)ODO{|Wyy&4ES`QaC)JIP-c`!tbE|1_~%^4#wVI~M> zUTCnnLO=wrEvVc8veo0@nE@eF7NeKD^2hWv&%=08)9j0ZEg!q4)-$QS|p3v4?w_vjL3Rx+BMh9Ojl}-4y>N zVs^wadzqS3&cRfn1hp`1GFJe}yjceKB^^532R zmr7Z2$c6(vFL@jq_nr9KgdK<3D#>he17}*9JD6M9bC?}}%w{c2v809@vxkq_eMRN5 zH-}3oW|tqcKZ{DSG>5Zw=3WxBMGR8x%n2CTaQa1W5o9)ZL6W^}#50=-nQdT@A{)va zW*;N70}Uq$Tbw;O%uYvUj~b@fQz^{uPiEhq92d&(gX142LD`(cZ27}X*3eTq|6$T- zJx{4N%~P#?3KI?b6bfRGOBj{}j7w$?V)m*syA0*HbHON>d~wSqn#gG}Z&@Q9v#*ue zk%%3XN@!gWl+zw|QKw3lg|nQ08vT#%va4w)s&h*4RYMc2jDnAZ!$*Qf$q8 z!vz}9b^=;vPa&0pvn_|&!^-SFY))Zz$G#)zjg3?ibS^6nv%!|x8%Y-gbj+SyW;Z33 z0@H~VhuK`qY>R{xD{upt&1|=2HcHYdz$V+6OK`H7J(X0FE!xfO!DV(&QU$??fm6M) zvR%5&{z^!}RRaE){lK!VoHsU5c_=S1TZ);@ppaq-iw@k=fM%txTw1m(wH`71lbM~R z*nw=I7h7_e9m~vKQ;N>oytjSR9%g3uDV+jC;xBn@tvN(%cSAu?3f8r?yPH$~Y@D6B z{eNgYX8$y^^%abRbsNS%oCU&$y0*}PnBB3EWGy=pVE5Ffj!GHFY@cQ}zCs?_?53{8 z?pahGW@|RHsTT7Pvbr+6xS9R8&2+8()11=UKg}usA`j7IH?x79+4>7~(B*LA{1*=+Y8YXk$#^3MZ|8WDdSV3fO=o$oEi7nQ>53k0I{gJJCAlr_GJGW|J z0yEh*C$+Y_Ipyzz@_%49@#hw&mN4qm2VfktX`LPs&`bVhr=wc@k3Gv8#y{LhgIOJ} zT(X#5_RS-L`Pc%pPrliNKhq)GzcP-%F%Fsj?$kdn$y6TZOApK*{bsZOSRu*_tw#jW zoT+t-Qz=wWWj-yzd=!B40<}#2(F>Gu%x5W>4+>Cus8Q4+4*?%BppvK-|NE5jubfKz z^zV6^O(107QD`>d?+x)sr{ap-^vw_U!Xj(d*6xOKTCT1{)?X$DVEyO3zrVpHn)ZOm-)~M^N9*7i8@zjK8eD7%tDUa9-f0M z1I-_=WvELT=Cdly2Qet)tnnHH@3f!^Tg7C%hB;S-kYbJ3KhS~xURUO8EX;Q_Tg?l2p6#p8_nEtK9l28h!IQjJRrQDKl2S&VX~CyvNb?}Rrc&-f ze3$`uE02;sL3|hLwp96iIFcuR!?3_M-tVP-Kyo`dt1Wyl0?kW__k3}}@8bYVsaTQ* z*JC%9UUzw#GhbB~(p4bceXyq8``^92dqNx@a%T19U+=#(aJbYJ;$0y=X6MMysToFz zJg6@6PyFJhDSTPJQ5E9q5FfFSOuVcc*vr8Xfc5}9eEZ{I&)px>tRW6h&9mO+Rzw}? z;&f`!QJL++9&KjUg!0R4LIoK(PPZCMZQd45% zrR2!%g2!EH?IzU1%%bVo%#!@j$TuV_6Zpz-zheG8zM%(0)0!UgwT8^7+j!KtAj~vb$`QE`rh+kwhHnf`<`F3CXeXVu@K~QbJ2W8WlkSl2o{hlmK0j zR-oEN0<}(z6idjkM82*orPl%%OQ7UdNNX#aik3=AQ6j&KajWvB$iY&Q$OuSoion3J zmFa9vF!ZlQMI=ecx~cpwy<3(AFuJO#FpRS~!bRcYcncgKRE4c%6{ft1^`s71`sNn! z8juB*$&4CQ5;CiT-&NbCnTK+aEX@q=){zDC_y&C_T9&_on-yaf&_a=^Q_Jjhm6-gk zYlLl2b==sr*c9@^a6uOX87X#V#3o?jO%Ykyl!7y>lLB2i8%y#EDKH@ZF_?gi)z7JW%T%w85sM<@#ZqZ%EcR!#G*yb7 zS0YZ9h*MG~n1xSB5lfkYfbE!+2xGGeNr@3BG^ayevxuZfu|`6yM3N+7vLT$16e)@) zT~G6M^<@LS2?W!cj;XOnislJ~16o=JN?@BAb!V>+S}vPG&8W|&*ju`!h!UkKqKI*n zR$8niB^635a~J52_(K9sMyr8iaZ$qY-pnFnfXYb3u*|`7sy}IxzN}1&)~Jb=rHQ<( zsF@A9Jy~E#u-$;(kvWI1b~6BVhjo^+qZzairKTjI+vCM4Viy)3FG`FygB!{Jz+zGo z;#+_OYQ{;E5?i1M;=6UN!BkOni*mrLEelSI6{owzi!))Mfn5neD~rU$M#sd1q|otr zi3@b1_}G-8pvP1iEe5B?CrQkbVFjh_e`?M!f$C2(kC`WmGg3%l9KQ<@ zCk6MxeH=6Dt`aQS(0Z29&@si*6m$Si6RfCtoD>n+(gzVsaYUq4WCj{y2~^K)oOnXA zC{{x0{5c6S#iXPpOC2p>@J5q!EBLDVcv}k0z<}zveQeN-SZPv1k|a4MHiDX)gw1i_ z0QDAe%tu}^5eU2gqoP>B)|H2qt@I?{(3;(7*dCyZftkIzHJ+b?74U!|o*H!NHW64o z^8Qr5o(>&J6eZxS%!-+T6+HyqIy1Q-wTZ#ZmQ0?`@21_{D3AtTqjb=F_S6zPhLw&p zep8d9MOLl5HFh0QVr0wi8=IKIu9;LUl0?LiAC?Mqb(-j4@k0~~rw*X>Z54b&73%&I zpj1eTJOsvUgX4i35-|WL*^wV|1%|?AP8>|w@ll071T`yi$4$PWs;qm*tjG7W57QcakgL>@faB4u|VtYO4T7wlqQl!NMe(jDUOp053<85T|SEN|q!l_%RA6NHH_IrcKN}76vd~#zsO!?AijYAT2OsnX zuM1-YLV|UMg@kZ^VJ5p$rCfxMg{2nh;&8L@_*8L9Qc_9`b5523ABc+-rHGig?Bp0v zig0L~Ma5>IKiF|tZgx=VG#RBx`)kLyc-}V1hF{`ZOMx>GOH0FL`B&z_2Tw&eRMvm_zgl zr9+lx@&$%190F=B;$SDWijIRb;aVjDzrzIhYAYzwC$I@pv)-hYj=+e_d(ZDc?#$(P zqtrC%bK7Q3>QooIJzhL25dwYkstNpizeUi2yh>tpo4|zJSpnr(2nB7+U3UmNa{nK; C-V2xj delta 2878 zcmbu>Yfu$s7zgm(vwMJZ6ca^7#6XaXm58Bo@kT0$fO+5usBsmAV>bn*yyZcd5gYrU z9rc|t$6%UZ%y4j+S}m2Cl19zEflZc;T9(sjO=DI%iS>Ux|GxFDch1B6?C(7L?(X~U z+2zQEh)XxZw%htv>J2+U?Q9N?p1(P}3T!SJo}Btnz4XY&(6Wu`#yV{I15>w&*5Lj^)YGSA{xO z;HwOjYQEOG6vuvSO~IDUSJ{x_xQeZ5{;jSQ+o*iCr&fRHcrD+*N04tvPO{Zkr#D1T zms5xg(li}?6nbg@)!X3F_})Yx$NG5dg3`DB&oP=WAJwhPM{N%_s$B&}u3o6F7Z}BA zZMrc~-7YZl^kP3GOf`b7-`&Q|BGuApEVHh48kK#%mMuxXx3>(jSK-nIsq3A_eCOdj zIY&(kEmZA3bDsB8!+&+dAE~Cvwp@)w7_A3}&!dQ-o#cKpS`6(J&5rF@+G(21P~17M?R7iqX(w0!?HYrV8!STRh; z@wGnUAm9sf>S&i}k0D=4`x1taHW)F7172ob2+Sp4qJ2#pg1m_K4Q(jwsN76wSHy69 zEfjH*_$^RoVHn~J?J7-{C{4aZyUqbeAkU`wP7sWGvzX+5?*0fEKMEz3dx@I86KdQ61(# z9(aT(mnMY?hyyemOb#**QA{&f_XKPy&Ca?fVg0fLS^(`SL_cOwmTNHU{Xa7*F2gF z`4X5cR}M_pI3H0$D}%`u7a$^$ORM0%ZsbuWe*dzrmBfX}<>@5rwUkzfyoS?X1(UQ0 z(T^+1LSN0gV&r>xbv3j_us3lsazP%L{7M!he#9Vhtd~rzzf9K>guJPwz094TLoRPB zX)9?Z$XC-=!Q|>n5gTZAtSf`<#DV1mR@2In@20JxRmlA(Z!~H3#7gAnVRBNhz~t+t zh$hypV_g+&3rtRGJ#88CuV5AEH*nwO$mI3>QNPC_pV>Hn+3X!0>YOLdX%^W>M{xyn4gd-vlqpdOf%v?i{wCY>UZo~4t zYpyYrYrnZxpJ4UuH*|S{RgvxX`Kqza9 zySZ$0N045YnwXS`=k)dxx$Ur|)3cnuXzq!t7^{F$WCaP<_W>~Z8 awp*$@V4+Ke4i3DbTFL_gtk(FzF#R9EIaCP% diff --git a/packages/react-openapi/package.json b/packages/react-openapi/package.json index d10c570c7..432128cae 100644 --- a/packages/react-openapi/package.json +++ b/packages/react-openapi/package.json @@ -2,10 +2,12 @@ "name": "@gitbook/react-openapi", "exports": "./src/index.ts", "dependencies": { - "openapi-types": "^12.1.3", + "@scalar/api-client-react": "^0.2.4", + "@scalar/oas-utils": "0.1.1", "classnames": "^2.5.1", + "flatted": "^3.2.9", "json-pointer": "^0.6.2", - "flatted": "^3.2.9" + "openapi-types": "^12.1.3" }, "devDependencies": { "@types/json-pointer": "^1.0.34" diff --git a/packages/react-openapi/src/InteractiveSection.tsx b/packages/react-openapi/src/InteractiveSection.tsx index a0050c47b..7761deeae 100644 --- a/packages/react-openapi/src/InteractiveSection.tsx +++ b/packages/react-openapi/src/InteractiveSection.tsx @@ -30,6 +30,8 @@ export function InteractiveSection(props: { header: React.ReactNode; /** Body of the section */ children?: React.ReactNode; + /** Children to display within the container */ + overlay?: React.ReactNode; }) { const { id, @@ -40,6 +42,7 @@ export function InteractiveSection(props: { defaultTab = tabs[0]?.key, header, children, + overlay, toggleOpenIcon = '▶', toggleCloseIcon = '▼', } = props; @@ -120,6 +123,7 @@ export function InteractiveSection(props: { {tabBody} ) : null} + {overlay} ); } diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index 0489ac543..d7afb222f 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -1,9 +1,11 @@ import { OpenAPIV3 } from 'openapi-types'; + +import { CodeSampleInput, codeSampleGenerators } from './code-samples'; +import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation'; +import { generateMediaTypeExample } from './generateSchemaExample'; import { InteractiveSection } from './InteractiveSection'; import { getServersURL } from './OpenAPIServerURL'; -import { CodeSampleInput, codeSampleGenerators } from './code-samples'; -import { OpenAPIOperationData } from './fetchOpenAPIOperation'; -import { generateMediaTypeExample } from './generateSchemaExample'; +import { ScalarApiButton } from './ScalarApiButton'; import { OpenAPIContextProps } from './types'; import { noReference } from './utils'; @@ -72,7 +74,19 @@ export function OpenAPICodeSample(props: { } }); - return ; + async function fetchOperationData() { + 'use server'; + return toJSON(data); + } + + return ( + } + /> + ); } function getSecurityHeaders(securities: OpenAPIOperationData['securities']): { diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index 493341b59..7787e5c67 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -1,12 +1,13 @@ import classNames from 'classnames'; import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation'; -import { OpenAPIServerURL } from './OpenAPIServerURL'; -import { OpenAPIClientContext, OpenAPIContextProps } from './types'; -import { OpenAPICodeSample } from './OpenAPICodeSample'; -import { OpenAPISpec } from './OpenAPISpec'; -import { OpenAPIResponseExample } from './OpenAPIResponseExample'; import { Markdown } from './Markdown'; +import { OpenAPICodeSample } from './OpenAPICodeSample'; +import { OpenAPIResponseExample } from './OpenAPIResponseExample'; +import { OpenAPIServerURL } from './OpenAPIServerURL'; +import { OpenAPISpec } from './OpenAPISpec'; +import { ScalarApiClient } from './ScalarApiButton'; +import { OpenAPIClientContext, OpenAPIContextProps } from './types'; /** * Display an interactive OpenAPI operation. @@ -25,39 +26,40 @@ export function OpenAPIOperation(props: { }; return ( -
-
-

{operation.summary}

- {operation.description ? ( - - ) : null} -
- - {method.toUpperCase()} - - - - {path} - + +
+
+

{operation.summary}

+ {operation.description ? ( + + ) : null} +
+ + {method.toUpperCase()} + + + + {path} + +
-
- -
-
- -
-
-
- - +
+
+ +
+
+
+ + +
-
+ ); } diff --git a/packages/react-openapi/src/OpenAPISpec.tsx b/packages/react-openapi/src/OpenAPISpec.tsx index d8d0f9bd9..8ee8e31b7 100644 --- a/packages/react-openapi/src/OpenAPISpec.tsx +++ b/packages/react-openapi/src/OpenAPISpec.tsx @@ -1,14 +1,15 @@ 'use client'; import { OpenAPIV3 } from 'openapi-types'; + +import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation'; import { InteractiveSection } from './InteractiveSection'; import { OpenAPIRequestBody } from './OpenAPIRequestBody'; import { OpenAPIResponses } from './OpenAPIResponses'; import { OpenAPISchemaProperties } from './OpenAPISchema'; -import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation'; +import { OpenAPISecurities } from './OpenAPISecurities'; import { OpenAPIClientContext } from './types'; import { noReference } from './utils'; -import { OpenAPISecurities } from './OpenAPISecurities'; /** * Client component to render the spec for the request and response. @@ -18,7 +19,9 @@ import { OpenAPISecurities } from './OpenAPISecurities'; */ export function OpenAPISpec(props: { rawData: any; context: OpenAPIClientContext }) { const { rawData, context } = props; - const { operation, securities } = fromJSON(rawData) as OpenAPIOperationData; + + const parsedData = fromJSON(rawData) as OpenAPIOperationData; + const { operation, securities } = parsedData; const parameterGroups = groupParameters((operation.parameters || []).map(noReference)); @@ -55,7 +58,6 @@ export function OpenAPISpec(props: { rawData: any; context: OpenAPIClientContext context={context} /> ) : null} - {operation.responses ? ( ) : null} diff --git a/packages/react-openapi/src/ScalarApiButton.tsx b/packages/react-openapi/src/ScalarApiButton.tsx new file mode 100644 index 000000000..11d0ea4db --- /dev/null +++ b/packages/react-openapi/src/ScalarApiButton.tsx @@ -0,0 +1,163 @@ +'use client'; +import { + Cookie, + getHarRequest, + getParametersFromOperation, + type TransformedOperation, + getRequestFromOperation, + Query, + Header, +} from '@scalar/oas-utils'; +import React from 'react'; + +import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation'; + +const ApiClientReact = React.lazy(async () => { + const mod = await import('@scalar/api-client-react'); + return { default: mod.ApiClientReact }; +}); + +const ScalarContext = React.createContext< + (fetchOperationData: () => Promise) => void +>(() => {}); + +/** + * Button which launches the Scalar API Client + */ +export function ScalarApiButton(props: { + fetchOperationData: () => Promise; +}) { + const { fetchOperationData } = props; + const open = React.useContext(ScalarContext); + + return ( +
+ +
+ ); +} + +/** + * Wrap the rendering with a context to open the scalar modal. + */ +export function ScalarApiClient(props: { children: React.ReactNode }) { + const { children } = props; + + const [active, setActive] = React.useState(null); + + const proxy = '/~scalar/proxy'; + + const open = React.useCallback( + async (fetchOperationData: () => Promise) => { + setActive({ operationData: null }); + const operationData = fromJSON(await fetchOperationData()); + setActive({ operationData }); + }, + [], + ); + + const onClose = React.useCallback(() => { + setActive(null); + }, []); + + const request = React.useMemo(() => { + const operationData = active?.operationData; + + if (!operationData) { + return null; + } + + const operationId = + operationData.operation.operationId ?? operationData.method + operationData.path; + + const operation = { + ...operationData, + httpVerb: operationData.method, + pathParameters: operationData.operation.parameters, + } as TransformedOperation; + + const variables = getParametersFromOperation(operation, 'path', false); + + const request = getHarRequest( + { + url: operationData.path, + }, + getRequestFromOperation(operation, { requiredOnly: false }), + ); + + return { + id: operationId, + type: operationData.method, + path: operationData.path, + variables, + cookies: request.cookies.map((cookie: Cookie) => { + return { ...cookie, enabled: true }; + }), + query: request.queryString.map((queryString: Query) => { + const query: typeof queryString & { required?: boolean } = queryString; + return { ...queryString, enabled: query.required ?? true }; + }), + headers: request.headers.map((header: Header) => { + return { ...header, enabled: true }; + }), + url: operationData.servers[0]?.url, + body: request.postData?.text, + }; + }, [active]); + + return ( + + {children} + {active ? ( +
+
+
+ + {request ? ( + }> + + + ) : ( + + )} +
+
onClose()} className="scalar-app-exit">
+
+
+ ) : null} +
+ ); +} + +function ScalarLoading() { + return
Loading...
; +} diff --git a/src/app/~scalar/proxy/route.ts b/src/app/~scalar/proxy/route.ts new file mode 100644 index 000000000..269820fb4 --- /dev/null +++ b/src/app/~scalar/proxy/route.ts @@ -0,0 +1,79 @@ +import { NextRequest, NextResponse } from 'next/server'; + +type ProxyRequest = { + url: string; + method: string; + headers: Record; + data: Record; +}; + +export const runtime = 'edge'; + +/** + * Taken from https://github.com/scalar/scalar/tree/main/packages/api-client-proxy + */ +export async function POST(req: NextRequest) { + const requestBody: ProxyRequest = await req.json(); + + const isGetOrHeadRequest = ['get', 'head'].includes(requestBody.method.trim().toLowerCase()); + const body = isGetOrHeadRequest + ? null + : requestBody.data + ? JSON.stringify(requestBody.data) + : null; + + // Default options are marked with * + try { + const response = await fetch(requestBody.url.trim(), { + // *GET, POST, PUT, DELETE, etc. + method: requestBody.method.trim(), + // no-cors, *cors, same-origin + // mode: 'cors', // Not supported on Cloudflare Workers + // *default, no-cache, reload, force-cache, only-if-cached + cache: 'no-cache', + // include, *same-origin, omit + // credentials: 'include', // Not supported on Cloudflare Workers + headers: requestBody.headers, + // manual, *follow, error + redirect: 'follow', + // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + // referrerPolicy: 'no-referrer', // Not supported on Cloudflare Workers + // body data type must match "Content-Type" header + body, + }); + + const headers: Record = {}; + + const proxyHeaders = [...response.headers]; + proxyHeaders.forEach(([key, value]) => { + if (['access-control-allow-origin'].includes(key.toLowerCase())) { + return; + } + + headers[key] = value; + }); + + const text = await response.text(); + + return NextResponse.json({ + statusCode: response.status, + // TODO: Do we need body? + // body: … + data: text, + headers: { + ...headers, + 'X-API-Client-Content-Length': text.length, + }, + // TODO: transform cookie data + cookies: response.headers.get('cookies'), + }); + } catch (error) { + console.error( + 'Scalar API Client Proxy Error', + (error as Error).stack ?? (error as Error).message ?? error, + ); + return NextResponse.json({ + data: 'Scalar API Client Proxy Error', + }); + } +} diff --git a/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 9111e6079..a5220dd9b 100644 --- a/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -12,6 +12,7 @@ import { BlockProps } from '../Block'; import { PlainCodeBlock } from '../CodeBlock'; import './style.css'; +import './scalar.css'; /** * Render an OpenAPI block. diff --git a/src/components/DocumentView/OpenAPI/scalar.css b/src/components/DocumentView/OpenAPI/scalar.css new file mode 100644 index 000000000..17e934494 --- /dev/null +++ b/src/components/DocumentView/OpenAPI/scalar.css @@ -0,0 +1,325 @@ +.light .scalar-modal-layout, +.light .scalar { + --theme-color-1: color-mix( + in srgb, + rgb(var(--primary-base-300, 180 180 180)), + rgb(var(--dark-base, 23 23 23)) 96% + ); + --theme-color-2: color-mix(in srgb, var(--theme-color-1), transparent calc(100% - 100% * 0.72)); + --theme-color-3: color-mix(in srgb, var(--theme-color-1), transparent calc(100% - 100% * 0.4)); + --theme-color-accent: #007d9c; + + --theme-background-1: rgb(var(--light-base, 255 255 255)); + --theme-background-2: color-mix( + in srgb, + rgb(var(--primary-base-800, 30 30 30)), + var(--theme-background-1) 96% + ); + --theme-background-3: color-mix( + in srgb, + rgb(var(--primary-base-800, 30 30 30)), + var(--theme-background-1) 90% + ); + --theme-background-accent: #007d9c1f; + --theme-code-language-color-supersede: var(--theme-color-1); + --theme-code-languages-background-supersede: var(--theme-background-1); + --theme-border-color: color-mix( + in srgb, + var(--theme-color-1), + transparent calc(100% - 100% * 0.08) + ); + + --theme-color-green: #0a6355; + --theme-color-red: #dc1b19; + --theme-color-yellow: #ffc90d; + --theme-color-blue: rgb(var(--primary-color-500, 52 109 219)); + --theme-color-orange: #ff8d4d; + --theme-color-purple: #8250df; + + --theme-scrollbar-color: rgba(255, 255, 255, 0.24); + --theme-scrollbar-color-active: rgba(255, 255, 255, 0.48); +} +.dark .scalar-modal-layout, +.dark .scalar { + --theme-color-1: color-mix( + in srgb, + rgb(var(--primary-base-700, 70 70 70)), + rgb(var(--light-base, 255 255 255)) 100% + ); + --theme-color-2: color-mix(in srgb, var(--theme-color-1), transparent calc(100% - 100% * 0.64)); + --theme-color-3: color-mix(in srgb, var(--theme-color-1), transparent calc(100% - 100% * 0.4)); + --theme-color-accent: #50b7e0; + + --theme-background-1: rgb(var(--dark-base, 22 22 22)); + --theme-background-2: color-mix( + in srgb, + rgb(var(--primary-base-200, 200 200 200)), + var(--theme-background-1) 92% + ); + --theme-background-3: color-mix( + in srgb, + rgb(var(--primary-base-200, 200 200 200)), + var(--theme-background-1) 88% + ); + --theme-background-accent: #8ab4f81f; + --theme-code-languages-background-supersede: var(--theme-background-1); + --theme-border-color: color-mix( + in srgb, + var(--theme-color-1), + transparent calc(100% - 100% * 0.08) + ); + + --theme-color-green: #56b6c2; + --theme-color-red: rgb(245 124 97); + --theme-color-yellow: #edbe20; + --theme-color-blue: rgb(var(--primary-color-400, 93 138 226)); + --theme-color-orange: #d19a66; + --theme-color-purple: #5203d1; + + --theme-scrollbar-color: rgba(0, 0, 0, 0.18); + --theme-scrollbar-color-active: rgba(0, 0, 0, 0.36); +} +.scalar-modal-layout, +.scalar { + --theme-font: initial; + --theme-font-code: var(--font-mono); + + --theme-paragraph: 16px; + --theme-small: 14px; + --theme-mini: 13px; + --theme-micro: 12px; + + --theme-bold: 600; + --theme-semibold: 500; + --theme-regular: 400; + + /* Font sizes for interactive applications (not rendered text content) */ + --theme-font-size-1: 24px; + --theme-font-size-2: 16px; + --theme-font-size-3: 14px; + --theme-font-size-4: 13px; + --theme-font-size-5: 12px; + + --theme-line-height-1: 32px; + --theme-line-height-2: 24px; + --theme-line-height-3: 20px; + --theme-line-height-4: 18px; + --theme-line-height-5: 16px; + + --scalar-app-header-height: 35px; +} +.scalar input::placeholder { + color: var(--theme-color-3); +} +.scalar .scalar-app-header { + width: 100%; + z-index: 1000; + padding: 6px 12px 6px 12px; + border-radius: 0.25rem 0.25rem 0 0; + font-size: 14px; + height: var(--scalar-app-header-height); + display: flex; + align-items: center; + flex-shrink: 0; + gap: 6px; +} +.scalar .scalar-api-client { + max-height: calc(100dvh - (100px + var(--scalar-app-header-height))) !important; + border-radius: 8px; +} +.scalar-api-client__close { + appearance: none; + border: none; + outline: none; + display: flex; + align-items: center; + background: transparent; + color: var(--theme-color-1); + font-size: var(--theme-small); + font-weight: var(--theme-semibold); +} +.scalar-api-client__close:hover { + cursor: pointer; +} +.scalar .scalar-app { + background: var(--theme-background-3); + height: calc(100dvh - 100px); + max-width: 1280px; + width: 100%; + margin: auto; + opacity: 0; + animation: scalarapiclientfadein 0.35s forwards; + z-index: 1002; + position: relative; + overflow: hidden; + border-radius: 8px; + display: flex; + flex-direction: column; +} +@keyframes scalarapiclientfadein { + from { + transform: translate3d(0, 20px, 0) scale(0.985); + opacity: 0; + } + to { + transform: translate3d(0, 0, 0) scale(1); + opacity: 1; + } +} +.scalar .scalar-app-exit { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.62); + transition: all 0.3s ease-in-out; + z-index: 1000; + cursor: pointer; + animation: scalardrawerexitfadein 0.35s forwards; +} +.scalar .scalar-app-exit:before { + content: '\00d7'; + font-family: sans-serif; + position: absolute; + top: 0; + right: 0; + font-size: 30px; + font-weight: 100; + line-height: 50px; + right: 12px; + text-align: center; + color: white; + opacity: 0.6; +} +.scalar .scalar-app-exit:hover:before { + opacity: 1; +} +@keyframes scalardrawerexitfadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +.scalar-container { + overflow: hidden; + visibility: visible; + position: fixed; + bottom: 0; + left: 0; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1001; + display: flex; + align-items: center; + justify-content: center; +} + +.scalar .url-form-input { + min-height: auto !important; +} + +.scalar .scalar-container { + line-height: normal; +} +.scalar .scalar-app-header span { + color: var(--theme-color-3); +} +.scalar .scalar-app-header a { + color: var(--theme-color-1); +} +.scalar .scalar-app-header a:hover { + text-decoration: underline; +} +.scalar-activate { + width: fit-content; + margin: 0px 0.75rem 0.75rem auto; + line-height: 24px; + font-size: 0.75rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +} +.scalar-activate-button { + display: flex; + gap: 6px; + align-items: center; + color: var(--theme-color-blue); + appearance: none; + outline: none; + border: none; + background: transparent; +} +.scalar-activate-button { + padding: 0 0.5rem; +} +.scalar-activate:hover .scalar-activate-button { + background: var(--theme-background-3); + border-radius: 3px; +} + +.scalar-app-loading { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} +.scalar .request-method { + white-space: nowrap; +} +/* Use :where to lower specificity to 0 */ +.scalar .custom-scroll { + overflow-y: auto; + scrollbar-color: transparent transparent; + scrollbar-width: thin; + -webkit-overflow-scrolling: touch; +} +@supports (-moz-appearance: none) { + .scalar .custom-scroll { + padding-right: 12px; + } +} +.scalar .custom-scroll:hover { + scrollbar-color: rgba(0, 0, 0, 0.24) transparent; +} +.dark .scalar .custom-scroll:hover { + scrollbar-color: rgba(255, 255, 255, 0.24) transparent; +} +.scalar .custom-scroll:hover::-webkit-scrollbar-thumb { + background: var(--theme-scrollbar-color, var(--default-theme-scrollbar-color)); + background-clip: content-box; + border: 3px solid transparent; +} +.scalar .custom-scroll::-webkit-scrollbar-thumb:active { + background: var(--theme-scrollbar-color-active, var(--default-theme-scrollbar-color-active)); + background-clip: content-box; + border: 3px solid transparent; +} +.scalar .custom-scroll::-webkit-scrollbar-corner { + background: transparent; +} +.scalar .custom-scroll::-webkit-scrollbar { + height: 12px; + width: 12px; +} +.scalar .custom-scroll::-webkit-scrollbar-track { + background: transparent; +} +.scalar .custom-scroll::-webkit-scrollbar-thumb { + border-radius: 20px; + background: transparent; + background-clip: content-box; + border: 3px solid transparent; +} +@media (pointer: coarse) { + .scalar .custom-scroll { + padding-right: 12px; + } +} diff --git a/src/middleware.ts b/src/middleware.ts index 9c5b6277b..c159ee8c4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -29,7 +29,7 @@ import { waitUntil } from './lib/waitUntil'; export const config = { matcher: - '/((?!_next/static|_next/image|~gitbook/revalidate|~gitbook/image|~gitbook/monitoring|~gitbook/static).*)', + '/((?!_next/static|_next/image|~gitbook/revalidate|~gitbook/image|~gitbook/monitoring|~gitbook/static|~scalar/proxy).*)', skipTrailingSlashRedirect: true, };