From 3ff9146a630ba690bebe7ad4f844fcc4bb846514 Mon Sep 17 00:00:00 2001 From: Sven Steinert Date: Wed, 13 May 2026 12:19:42 +0200 Subject: [PATCH] update --- codex.md | 6 + kb-markdown-importer.zip | Bin 328347 -> 331916 bytes kb-markdown-importer/assets/css/frontend.css | 117 ++++++++++++++++ .../assets/css/themes/obyte.css | 28 +++- .../includes/Admin/ProductsPage.php | 125 ++++++++++++++++++ .../includes/Frontend/Router.php | 40 ++++-- kb-markdown-importer/includes/Plugin.php | 2 + .../includes/Repository/ProductRepository.php | 113 ++++++++++++++++ kb-markdown-importer/templates/docs-app.php | 58 ++++++++ .../templates/documentation-index.php | 8 +- kb-markdown-importer/templates/page.php | 70 ++-------- kb-markdown-importer/templates/product.php | 4 +- kb-markdown-importer/templates/version.php | 4 +- 13 files changed, 499 insertions(+), 76 deletions(-) create mode 100644 kb-markdown-importer/includes/Admin/ProductsPage.php create mode 100644 kb-markdown-importer/templates/docs-app.php diff --git a/codex.md b/codex.md index a34926b..2ccd6ab 100644 --- a/codex.md +++ b/codex.md @@ -186,6 +186,7 @@ Shortcodes: ``` `[kb_docs]` bindet die Dokumentation in eine normale WordPress-Seite ein. +Die Ausgabe ist als Doku-App aufgebaut: Startseite rechts, persistente Sidebar links. Die Sidebar zeigt alle Produkte, deren Versionen und fuer die aktive Version alle Seiten, sodass man direkt von der Portalseite in die konkrete Doku springen kann. ## 10. Admin-Einstellungen @@ -194,10 +195,13 @@ Backend-Menue: ```text Knowledgebase Uebersicht + Produkte Synchronisation Einstellungen ``` +Unter `Produkte` koennen importierte Produkte verwaltet werden. Admins koennen Namen und Slugs korrigieren oder ein fehlerhaft importiertes Produkt inklusive der zugehoerigen Doku-Seiten in den Papierkorb verschieben. + Einstellungen: - GitLab Base URL @@ -239,7 +243,9 @@ Es gibt keine Renderer-Modus-Einstellung mehr. Markdown wird direkt im Plugin ve - Bilder aus `images/` werden importiert und im HTML ersetzt. - Interne `.md`-Links funktionieren. - `/docs/` zeigt die Dokumentationsuebersicht. +- `[kb_docs]` zeigt eine Startseite mit persistenter Produkt-, Versions- und Seitennavigation. - `/docs/{product}/{version}/` zeigt die Startseite. +- Im Backend koennen Produkte bei fehlerhaften Importen verwaltet und entfernt werden. - Synchronisation dupliziert unveraenderte Seiten nicht. - Importfehler werden im Backend-Log sichtbar. diff --git a/kb-markdown-importer.zip b/kb-markdown-importer.zip index 21c36518b1d1ab506f3f6667c58465975a28145c..f001bc1b6e2e627fd6b810d88f20cfb3e9996361 100644 GIT binary patch delta 14183 zcmZX51ymf%*7X1b1ef6M?(P=c-QC?iz~JsSKyV4}!7X@jcXtgQ2>$23x88l<{okzB z)9dWs)z#gnyN=YUFJTz51sI6Ra!^1Fz+XQY(|W`YuxJJ*)%u5;nYoG306;i40Kfq5 z4FVFUh{K|Sy0p(6D|t}gwo2>NHDrrhgGJQRr7QhYMg!qAA;_U*Ix253_+iZK9Ti%n z)?_vKnvZULEW2HDx5%@vT8Fqr>F%bWf{9~AwOhz2S&?SaA_<3D;P+&wv$$}E1&$#1 zBIoY{iRLb>K)Hm25^K{G;;;9?{^#0Jwk}b~vPZ2TkA&cDICqsSUmN37+heK5oloOq zl8Nm>jgG!A{kP+dqi=gBcc(z+A+Z4ovB74m*(;(pp;l@#j- zlx+U)YyrFprM;}uiDvRwN#{WyU4UZwIY#Km%PEu|BBESurdAT--$Th%2uWvzIj4J7 z1G{~oQB>Z@Z%CIGm@?>^hGOn_l_(SsWWtHC@(aay9Mn}ct~a=aF~%~rq5+T~_(X1X zu{kUY!oo==I&HYg^K2+rwF1+&L|G;j!=!8j*>Mr5>IIpa;uSrn&7@O7&|o4L6OZ9-HYNGy z>+I%w1hkAcf)H$oL0rjaJaM$BE#}}B3-rlLPuj_rmJbcAGuX6Fh*jK3%p$eOtOsl@ z%UjAlj>d3KbXQu#E3-F7y!=EL%i#qO-+a`RDow)WWolq=iRTZo`1mqxs(34R zOutNJGg(&yl~!9b`}Rj=8|(TEB;AF)Ee*5Iu1}-cS+tv{sX#E-8C<7O3&rWL*p} z?0YK~KlHYdV-BHw?A05zo=D~<0PvJuBwsW>G{oy>j5xM=j?HQGKdh+M{>p?X89?e# zy`z3olsp*cYtJyA6Z}NS%xM}@q~)!d)EK598aSZA`YBIM{A@Wj^I9xxYQ3KZgg@#< zrb+5)+7YG*uatAS_rgSspsO-_PO-L=+AC60_UIjKetNR9Z&hiB5^hG8yy;SD^v<9x+-(wUD^nGJnPsuvz(p8;46vVSkzE5T) zW<=mko6FyLa@etbZ$Uh=chp7zP3UGMPTDD9m=&5z6Kw_ve-W^9YroCm|Jw2H-_S4S z(ZPH(UkJN0-eJoUh%ZoUqqsLz@j+7)Z_)(lm0jBCh#m4wF8*Hccag)&jWdrtoU3*i zWlfb#ui~P~?xRz`=y2Jh8|Neb%S#QYX3)kes_jRx46xhAim(QSU~|KO;x2vkGYZroHSB z4IlgFe86$iEfR2fdPKv`^L|2FB;Z#5|17^WT#Jw!a8x z^4NncjFTM|mo60d$erb_jFOgM)W-Sa9dCjLeX9a{A?tS=t{79kf?6R4yPM}nS}|`J zYp6HEGHJAJM=LMOY}zW9d`2beH=%|-t#ZUv5ID^g@6IrOR)IUhI^2klpJk+E)F`bb z7%4HW5w*J#Emh#FMYN*4W6o@$S9k5rbx*Q;$ZK(5SZG`SXP*E(I-^?N}aPmU^~PYCJJQ zgnOOLm8ieS%EEWTlo-#A_`PX@;?n)}qW8Eei(Bl!Sf8}1o|u0HIYbMp&PAb9sxU)m zj&orfsdlk6z_cZqV7*FPH>=szg-#bzsbe9YOz)#%e(7NL^17_Gx+)(p-_`1`k$3?m z=F?d2dnv3FHC^VR`F|kwe$v48Q;$@6`4lxhI^adc28w?TH9H}OrpwazGx~@i`XFsR z^ZCO}Gy^wOIj9A*<@qLD{Bwgrx+U^0+U96cykLDh-<8Z!+QBmK@S06402wv_r~ zY(mvow3maA>`|oUf1)EDO^RoGonj&=;PQ4@dhGise7uvg0A{>gKpdjhZ5!M3^F*qNVPib zhIAhzL<>7XnWGtbRajX)$LrrWS;Q?-VOcMOySEH@^2MP>Ll6eq2krRQ=TY2-!YEC# z%0DI~G$j*RT`3OVwf|~=XNL`Ml7b_bMcWyJ+_9ZCKC3|$psl}QMVHCRqLAJ;c#V(l z19OnFqAL*mT5QIjmsI^|iLY0dXRVNn)UhBtA1cQRB6vd!m;^H4vJZq>(lVp)fOPqO zwXA993qTwX1sh(byG*A#cPv0X`jxK*eUS0FV5h?r!S)Sen?>E=M1l;FHA7zHuVGAR zJ8@&Qg3@&GP%M#Dl0XqpSYxDRT~+b0Tn6LXnG#p^C+&J64`;eX{+ewLko2|EK3q`x za0p2Y%9+KL8bVwg#Ap4?n}Fx>Fq)A=uoabSi{!Hev%d!Duv?s(*;%LkQXEA39&U23 zeUiFw<;8)8$5v1CP)|VX>oL(v^C9`HPG;8BYNzcab`?N!0(wG()6`6cLfUu_g0ZwV zlJB>0xe;;aIhE4RUOzHJwd z;iuvnL)aANV^VQxfBNHzn~1SFWuOE)h0mYn@8i#Tz&A{}BKk(uxw%iURRxgZ#5WwX~uDH{8)^{|p?zEiazlJ^%o%v;Y82iU|Y` zn57vA2ObUu(n5HGuS0xB0=lnuND69Tt=nFa4pOa@pyQZ)Q!RpQqM~mr^FIQzS{DI$) zKinbXB#v$wboz-;L)@b4!;(u~%K0T(6lPpMa|#9fsr!rqEn4plB1kAI^p=qS{L|Z| zV3a6)++y}%D0!f+tb2bWbPP&>u*ek{{$3hG0^QvV!k3yhuLvS$#|?2hFP~?R00(E) zekh2FI>*W-UmLwBc$qjpY17e~(d%{i@W+z;^vFn*4sinaO9XPxTP5S!(p=i(A}pAu zEEk0DSIr?%c|9R47T*$H6ip zzsI%3{paIPH~3Y5tq2$dYXnoQ*QZ&d^r6KyQo#)Me6#alrJ-;pHxb}M-X2Y<^<*i! z?qSPPVSwsYr8MZhp(I}_PgQTaMOUYjR}EjA&IAO(MLH0<4S|BbKR|x?fNCbwA3GOK zBH{rPGdH1&AH#3}2j?eE$*!6`hpQuF_NchayYiw45crml~71epKfeHSYw}8GE zBgZ6iXo~gd>vbWEF#_`vuRR}-^ z)pnv6_Z!x(MY4dq@AsAuElCx)dy5mu|zPKd{4q|Zg%_f$MXOxx(MynqQ zGyf=-Eu6_#skv2%(@-85TwPuqEm!#YLt_Mp4^rNzjY_e0SSM}z6?Y#V?X?a{_>3Vj zN94C_NxlUF&7gT^x{s7i4`L&HaPn6o+Eq9cw7-)M3yoZ^jT6%Q`dl}26zBKe!v&8% z%@w}iWJI5XY5!nS$}Tk|Tvu}U9>3REmEx89#P^E-@hNS|j-qzg92)j5crxIjF&LKT$f6n5`dVEy3H$Nq9m>O(YSO&)MAbVsSeY zhl-RtZb=!yNQNg~L)sz8*1ifM>D11czxsUzNxB;AvRXF5td`p#g3e}Scal)$TnTq0 zouR7-I)g6%c{Cg0NyWKehpqpd zID!I5w|DpWIxhWM8n1SX>6N@9UB)_r;*uDf_>2~&fi*8_yEj*7qMkhMHGsaLSY2HH z6dpqBHwK|r>^IHn4A)nOL^y!dj{Ip&ln9D`zwfdKlb1>2=VfG|V@+uXsk8XI>*o+^ z?70MQu>xE$JW{AXASas>j=Hwkx7H?v9*PGvhh$f;$ot@rG~nECapx(D$XlR#K+L(T}}JA4O(v#RZ0#=osr zwWULy^FJK|P|Wy?2HSDY?G5-b^hJafER|cv?UihbC55!7E7A`^Q zsx4558!eh*z*3Q{I&D%aQJK0Ya-I7rC_@lK~k^KA{etraz{=h@xGIM4Ld91LENZzhtYtUDD`o6R&$X6bL z!b9h6y_vCLsIR{>hT`q(;`zGcr|U$PApa%lV5RYo?(g$e99HWV}J*g+A(2&E;Yiw99x= z?M0_~GgtNR5t;I<94j4@rciZgWc`lewd%$djt0Nal@44ZIwo<^|d^RU`c~$P# zmLXlL^=?hs(*3J+&NjapT*rgZg_iLUsroAFVF!_1=;q#9+YE&AT6i9IR#E8RF9L(cCZFynP>NHdK zxc{7f3}K8Jju_r^W9}SrNWy|bmKow07_v>$wq1hGIJupfFecGK{%Q=Dn0M$UFYNjY zL}7FgHj8!lj4gnT9ws}-`+^5=5vt+)`esOPNacA8W5pP1j@ug+M{2UZ-bjh~Muhr; zaUk#!jx}B9MJJW5UQDJ?)OGs$IIAjkJFOV(MO;sE?L|S$DXSB0Ym>!-E5)1gL7w<1 zwQ@kbNX0JxSpYK?A+nc(qsd3ohTydTB-@9XG@6`R9f*X&EgRHp))#F&tvtJAnx$)@pw7N2}Vd+*)H4skga zF$u87(U1J0uLwVVe#F4p0$mQfgC0aOmQ6;TnTGFU-KKy6BK;tr`7KfY9)8)P+a!b5 zNBFv4T48J)DZcpm5wB!p)1&Cx&-rv4e%kiIy^;DYg#N@zQ&j0G;ubiM2kyW7>&y~* z%7&o)1G7Kv86D@?)k*n}-sn7Na%UCUMGq?EH5*zMaMV#yUQ?~Ieqss@1HCLOR_-rx z`vz4UGCA`VdnG+%`?sE8O9|d+5m4JZ@Evo+|6oT)u#qD25^&p6zk&*^{Qf!AT(^;P zD4ne4wt(XLx#i(5ZmvFkp!nD{Qa|~m%7-_%jUYf~7v4%z=Z!Dx3v^WR`3t-nHaXBq zLfjaxu8q1CZ)#&jzsre}{p2T_*AVL6;PNDCh*R)S!=!hVHBcpmYu0)0kPQ6Dp1!}m zyJx8)JsF3u@UZ*wz;@{I3PXbybdSpqetrJz6~(pJ1>Chu1VrY_kF|t*#*tV1zn`vm zoVoaS*ida>6~mmL-XuO4wO4vpR=W%iR{UydgA5IjFBpvG)jPk-X?cxuk>J zV}}qu#;3oyZWqLypHLUi>YX}kY?AJ{PSu_N&?D68v-Bs%4j}X=X=$P8i#%v||An_x zcqHNZMPI1SlRG~d4r$|fN18Kt11%0biPuhg+60b zSm-&6g{9Y;b3j0+)B(V?jeA;oc;Sq6i@;p@nQne}oYdTeod@Y##U6LQUWP->JQsF2 zSx*vx0_xewvx%*b4Q|y~Te6AM4o1tN}7C3^aiTfS>?U04u)0kHMj)cfUT|c|G}{IfmAR z#;#D?$1~C0_7MxgXt6y1{Ppt11SK1Ka?e(Ws4w>SXnX+Mvho`_8-K1at;8#BheSqT zYzIw|n`tKN!=X;A}K?>{%tU?yT zy-iNQY!n_iYnsHflr-#4^p0?Nf-q>LNl7yzyblu`#yK_shS(cLMd)VShvIFyQ^8v- zlWl~tXAwu#7wGQHVlMG<-C$6LnqvlPl~nx>2IP9YTFSTR#_(x;>V-t2h(B?zAnBT& z#6e=|r)TprmqFxmYwj|{GiPjV@fx12pvY42Gkv+2cUg#yOL;TEL!;V(P^?Z5!U`N? zdZ-F{5DDLESp?UZ6yB{uIx<=Vv#U3p`2}->8kx=gC^J)^g&LAjk)#9#``NG&NdK_eh z&RHHkZ{eG$(mwv1>_FRhW7y)yw0nq*u9h~KA`tJalw{!wSxg%ftU^;`k+b!U4GWoM zWtr?47IpXYJ-@~Z5hAGCQ1tbClWu%rtxP$;WcIq4kmG>GoB?m&HM{vTl=GNfpiW2! zeV`zYrzVd^8@^$VL4ZI<_9NJ;$B(C@9a&gE*X`{|h~;{B7y;*zw$t0|b*~O7Hz~G= z9{`VOLG5_pfC;RFnKO5Xha#>>N=i{4U=j6mxG$xRYH#pTUDD-PyqKwNV zk&A|Nej^kt3jkg5hg-)Q4K(S^i|nG)v0c*aM|SyQT>vG)b0m6NVJ!8)Je~;&oz41q zu?P>O@`o}7HBa@e&l0@-;R=(4>@|$Hbo5Y)nYTQ255;f^?n^RcH0~+F%Gy*G(ZKI3 zEw=?Hb@s`+lBMpjh@3p&E`ec z7)zXxC+VTwH=EWq{+n}9QFmr?jLLI2xErwBj1{rPXgYS?yGB+^&3%$YjU_F5(J&D| z9a6|6R*d5Hkj>^HZfIUhx}dw0Zuz%6+)eSXWdpXt1zJFqm$TS1f~02z;CZi}2vjok z+Nfcdl{15MZ1mI`ZKl>(cpM1^A=o}MJ-bE4fy-#I^jeh>flK=fvXIJjetrYVm0{op zG!yZ`vu7SN>XLhyJ4r?x<^*?FR0U=Ai0;5C;(&8F82ri*U8yb;( z9SvV8;tl#(B6H7c%-hSib>*z2$1R{1eWk$i#co7b3i4`|#pe6n6kU*X0(A;>@cF)d zqlgPhxiUEY_ojyH&s}TnzBx}eJbhABOjqVPle#7bFr3&KNk(g^-$Q>1WgjGXme)E5 zVcufu(*kdUK+-X08G2zJ6LIxOmE{daq70S1jw}dPT(QJa2@e5hmN+@(9QFY*r>V+b z$hm}DKgRi)ld(vbfP9i6EC_|DgFsq=Qk@fr4OG)}+HJ6&UdGoprtSwGb7*^s*->>* zASp^Y)WToM$SqhHu5VBsK}7mh;Zfm$px1!Z9QSl`K4_u03vpN++vE$LH_SUG7lXx3JZ0~&9m)k(t&04+?! z)OyNRS3%WC&q7&zhn{wxIsfa!yFWC=IbDzB7Y>@P{S}4qjd;8Sr6QuT2P*w_*lz2* zN)iry)oLkkA=W)xTe}Dg1<234Brm8oz95k6enh9`x85%~9?d(hQj)6tQg()Y8f{b$ z5~tPYZ8{j1`y-+*eg3I!)RV&wsjydG(KLovG=H({418ixLIR zD>CCB5BFf7AZ08+;c2r=uQX^Cn-LZx|HPrKdmikOBL2R^?kYwIqbB`&fT$)donZEu z5m6a1Mc$MRG3e$k{S2n=1kt8v9c-zg@3%DU+zS#8K1i3&|D>l0mIA`n^q{Yn8Zq}F zjFLj!j})z`B{>9s25x{xw6*$w6t02nVy%BZ^N%ztuRG2YB{u185A49Cp;?{&zIze; zjsQVok1V(Rb8f8)9%(SoKUwI)dE6M{SiCr29~3WLXchaKtP3SR0S!+^N=lyuHL_B` z?a(T4ZF&1H^)lnzMHiC6rp=pQrGa_0vYUnFTzn9@7%|u`BI>_oYdRF`K5h?Mwh53s z3@U&8>FrbtJ`z+~kl-YTYNq3n`iB zinXSv-dWCBPyK3@EErYJT#o0R{F3NJoLr-+lTWL9LmmMO)9n=(=($NN$90}cox~c- z$}QaS(S?K*K<>5~nMEOmh?qPO&-&n(XvPj6ji{3D-Cdeczl|7)an*qvGCBNou z>fQQ)7)l&8XHk8E>fe)0#j8suuP@=-aSP2It#eBihB)AJY)nrzPXQ=UVoX|brlCUW z%#y$ucQBB0D}1gx1t;uWh3<=i1g+?)Fau&Tx;oL;8MtA z>7A7BL1xGPkGpehf6U~i5yr!ip`0N?(nZF9P zXSvPfT6gNOkM2rsU?y6g>@d-;;zf%FPE*=M#q(~0qA79ZtVQ41$`naRn7Jibv}2V{ zgsW%l)ZQGd%fB^w`Nds(o^@vq{G8tKwpkQ;#<`P#fu;C@!~69lwmub!Db(O9F1FT0 z(*=Cc)A5vR<44G7z7mPM)~tggK9m}!39)U(lz_`eLVtq zMmmKOc_QRD*p}}Nz*068DAQ_vq)@^2$Un2?KF9zt1Op@&T?lfYcVD42HfVw}s=B^PwI@Q?(r~S$1lnUpoN@|xd z)A*n#V^>em#PH>~qEMTT`bkENn)kCot!!s>^%4C;?> zS*ttA(L`tT(WtRNN6b`&A?>TVmFm(^cm)G-JUojUt)|bBpxmfs%QJr-_bT76?ytRz zx@;z@>hQYsgb}4)=ZsI-zY)n?&!`k!Y`&VEOAgQ|D-%|Uhc7nxYi{*Ag$25BlheUqth z+=vxzqN_1_69ek&s+=+$iM7i&$C=T|A7$iSzqml@=nRhS`JV9_kG#_1lAWeFDBb^* z@@#9rR~M~Z&{R=Enp5>|^x%8aT(^RZKj&lKc{EFb#r=~b0<=^`2GY|kZn zJ%(V{?a!S_fdV&G#mRC}N8Ah$#~AS*oaKlYn_w@^8q4PWRCiWb$lNrY2qJDP)B_hx z)ks)>Q(@b{PbZpP*J^nGKv)XlJ?-|SfBJjDA`R5v16saRdFNVv(bWh=GMnJnUV zBRW!YTQlVchD0dH)phn;*-&#$c4&M!=V&bea~sr>o^6YkE=?xD$1N#hDgx8T5a-J$ z?6I;q?yny?=)zdFu|hcd;F(>i(k!Hm!*+SULI@W$1bzom&&g-%PE7E2-ULo(Iu0r( zn1bRRP%B#I9Bw>WY|ve~GT1Y-?e2oH(y)|{BlLpD1tDk3CB;ek)@31NWyhtDBIQD< zV5z>$e&ODH3*h$d8uTE1#6@+Z`7X9+lW-F+i~0LNnaUeYwbCwwlK18-y$~KRVdq{M zC)yW9M<-)r8x*%KYt}&J#B~_ zQsn>WDwN(fb&mbyb$afFzL#kNGGjm)eHHCDqwnEZ;@uMxMwAT9d7YP-)ERKmhwHn|rzVwM>Zi8MD&}Zk254~f0_QYWiF=Iq zQ0$YbHi#;WZ#NMWaJ`Y2By{D`JE;+=iTqx!pKg9XQ{VM&^#FQ4KC)zvD>ID1NkLDg zu%##EA3!h!QBzQ#Z$nM-nsk9+P@hm7?%>=+T|I?Xvshx8rXh>~j$2$VXA2baKi~#1 z;Oeu9yhM|S$-MW<022VrL3d7wQ)cmCidJr&F8I45!dqucKvT%O9J>UHv&2{f-?DrNIU@dJ+aj~GKHM79w~@K6K{SK| z@j`h`!H|k}ROC>n9TILa43)n4IfQ8LL0u@l=dZ4Ywdl8B8-9V5FIx%CSe{wmrmGc$ z+*7|*XL*OS+0!q>pLf_~C!H@`FWSH3x-_Fv5~@!DVNyT%1o^=(@fFWid4}Wq1v{16 zhFz5+*Z8qb4{aEHSu2=*Q*dq$z+$0PfY6V&k6bVQfi|=-T%+f^=n8vOU!Ch|NuS@S z>vAbllC!g`G13Eq$i}?sZ|4dF9UL~JlGmSbEYVCTc=~ZYtZ2~9_c)r{n5+|Mkai=t z$3dm5Bq88hXp_}up;zVR&8Oj((^oo(&J)u4l;8jKoL{7*1A8hA7lJ}ty5di#1wKr5 zs;?<%tEbJ|eVpipIpxni{t9qJnqyXe9aYr7w{ALoXFCRcxU%g(z0r3)>s*VL8(M%& zqK&6W!dQ}caB8fT0Ev!hy@cwRGI%V^tn{YcrL#-+Ccb(^+?FD=Mg8_E%tJ%K{+j9H zS?GA@T;MBvn^afUjXwT;1KT39F2HWaj^y_0C50cj@rEU$X%poY3o~>h;#tHc_9h8dP$K!)4lH>!mEkzg7N2b{JB`DKG zC4zY=TsWEt@2hd%c~mM=%{^?zdYfEK87nZyip8=Hx1ZH%p9sG%_i`#DnK{;dFt%Lt z>LmBFZ$i*MSCN}lH|KnZrY{q9h(AVAU*#(tvGwT)rU+`lDpb0__tK^=iS{`D6#^Vk0itA0A$>mGo)HJo&q3_!@BuWxK7@lxF8o z^u;izei1;aOU61zFH*35fRg)WpBn^%(pEoQyKKWk9GIQ4V|_6pc`)4L^3pP#IbMYe z*ti6>o^QVO_agh46cX+xgw>ln9&M7I1P@u0M3L2_Qza8>WhmD8Yrd^B@xxR(R!%R<kq5W zlt-}$7>~cUlDeEl?suqf7(gSTeqDF#8|^4xB8b^9d(qbb_3QA?exu9t)GqOrY1hw- zGusyiP|vf*MXvTsHdqR>yJ^;Ohr@+C#cg3f0b(7BohgLaHT?4EDDs$BzLuu$QMh5LFx`8{|E z5B|%lft4wG0ulWj7aUicqpr-Fo@-+1e&cE>#s(CkWP|sG19Z+RyqZ>7jKnG#n{X z?vwOb_5h_ghSyrKrZ`4ZSL?6|H_~dz^m&JS@Hc7n9ww-&{vaMBhG|XK(FdbjTfQ}L zx?NrTQv0j93a4f#3&}3UNw$rIb8&b#S$_ahxP-&UQc7{OnXH!~RGwA{Qxa7F zvc#Qof_{TpT?Mj`M2w@b0`G^5>?;OR@{Y^FeV#X?^IrmMaJ*yb`X@Hq(U66(KN`jr zxD`3h{ju^gv{3tXj5!CEhjhb|ibs~;#!2I}2J`+6&@z1q zbc94!`J(~(Dp`2HKN<-6uLdwdIch3@z6~6|2Q5G8-VaR;ON(FK?@avh?Jr5QYVq)j zDC*Dhg#+rFzR-s_sX{6lD$ngLIST0sG7-Oo>Mo>lH`m;4u6-!?iSx>NDP{@fS8XlMe#(e& z_;nSt!xHkNhyAxGt6bNB1gKK2=t9@fw6NG?r+UO-&yJCfBox1Y%Piwg8)4nhlXbjS zvF9VTzj+eYvpu*%OnEI;St@=y&D%Jb(L7^%&B6m7ts(Ejq#r3?w0dGLjqwBnIF=i*#C` z?|=yq$p2iX11|gplqdOT={Eqt@~?G0|M}ZT1kd0DiT)IWV}M2Ofpn1EXnz3|;KX~N z0+baaWXcW#A@~v>2=f;L3ub13!~_#R{2_7>f_q3^>0UwioQzvv|3@ZUfVNX3MI3+umuT#$Q-{}v(>{&cAH z2t*|LpR$a9oJk`2Uwrict`Wq2~lr8(WVDJPK zfb_2ZxA12+4h}r<1SBQ-=Ug2C!1|BA<7@xse+2V9|9K4b9e*Qm;J9a?5TteY|3U7b z|MVj8{r?~aFF+m$9`N^9ASu}Y6^H~Lc=_{0=!X7H`4>X{`Ul~k_!|*}WS{&?Fn}bU z`fE=O&L91wJcTzPDR!zuYPSfa@PE%O3u;6eszoAqN1c|3Qj>{6iuIdqO~Cko?oU004CV zU`jpzi}}aYAb=Mkpy?o?U;hOlT>;_2jF8ZXB>x;7|Nr(j@BgCvLPCE;rUlRfWKaMA LMuAkAD&f0xWb#+%oa3>iYftU#BQzYq?-YChI%ajRS;kXDSIACUH*gNf!%D#9Y>8wwZ1 zgu^MY>QmIL#!({9zS3W~R=&(l?FaS1;_4#(j>!Onv?>p~^5`jHT#XJsR}TgI@j8NiaT z+lq6E*Q4gI#|f>dJHsfFz#Vmvc^-mie3HL#|6RayJc#P~a z`~j-PH49a7WU#=SU@1kiSV2jMl+J~0G@lmWO=0QuSnDRAZ0WZ)qTC-L4PSKMJv<)F zG$(y=e%ox)#NpeR#$ROq9SOcsALHbp2bLU=l*d(p&tzj{@bx!|A_k_yPme{H_3BHgW*F7g2kyF z+;;6AySaKUn7L!Tsz&d$EZH@amyP!sXtR@m-6H9hLeS$l z#zA5Aa}p!b5Dr4C**%tPSM_U@NS&y$j&q1liYDKac8O8_lUYOpiehkkQ)v?U=^F%<&2<(?s*P^Sl4%69W zq&{~axFuh$qy80&a}9y`s|50zV>4GPtpH5TJpAU=4|KqdhKxq?%ZE85FN4uqi>6sh zF>UGck9d6_SIHdQ?{J!``0{Q}l%lLaTe#8^7jSj}oJasz!09$nr|SnP+z+B9vZQBmOvCL00emf584 zN?a~5>#gtIrs#lmGhEEEeO~Y+u5};i+C@BZH*tPL!58J~sUjac80VV`fi%_IaRoQ1 z7uA{!)o+(dFJTlNttj5ExrbS;ZJnKcvjWgdfzK{fhX&hr=Ht0_bpq#_HBPwqdsh}_ zPV2NIybm# z!;Ve;^pXr|)}_vUs&Mg#&$+HVm*6P*M!L^JQ?%+qrf#n*=xXm` zUdv#o*fb7xXA@CXI2HR-p|zFP=B&=pG9w*5lx7TsXUc^5pzg0BciwDm?UGj*eS7%?D4(D)KVbzPLYJ#Q5tyUnj~Ar0*u!FFBQi1BsD-94)E45p_oKE zDZNJ$>0F+(A2Os~KuQGoLM5$Z+u!1f?6Y#c;oo)6#ag1~1V1FqZE7co7`7yQM@dmP zG}Wm7%2mLkk0N<|G0iyej^fgMJKx;aSC(v}m;d4(10glQ1ud(Z@@-gv)zyCyMq1t2m`5~UCP{^1<>2hY z^?>CJwYTMU`B+Dwa6%=4h(jO8+^n|hk5D|&u5D<=V10dlanU}u0|?b`Y(`%C#>dnF zv(iDW$4o||MV>ASQ{j^KAz1dPcTrvr64wDG?>#?J*FyoInK@6k| zXbGRiiLdG{zDQ5Y_)!~t!!G5ID9y#0KQq>y)H$lq;+MhZwaQf4WWqo#ZZO?7>&Cx; zH1pAAWd^x>>b?=BC3hi*oHgu@D#6OpX%sR%ltYb>o9)a{MwzLVKhcAZ7E}4DC7df? z(Y#)9T72SgYqNqpm(5sg(KzR7#bxY%KRXJ5l!Tp5B-5 zi>?~!iWf^1F}$v+cdzj~H+)yYcD9$9+2RiNiEt-lnzw1bXmPy@2M0y`u@Hpi3N4zq zvcOySiPIMNXZx?0BRBlw7R!gz1;W zp$nE}2u?5AVpIlLpPYo?+X++;Z*kCq@!QYsSptY`R+9$-iv(MXZp@{4T2L~h-5`S* zi96t*j~e0)4I&Iw#|Tx&fO@@vuNQQKItEO;%U`jGDY!Lu2Oa?Epa%fxA?)KY6p$7W z2oa(b0z(E?Sn`&F4oI;~N4a*U#_4Y!0Jl?=;>n~i7owb?di};+AddN%<+(934)%dm zO}56tX22iF(rD#WFBYhgwi1w?9QRV^anL|X;9cH|FNY5D3&l=~(=!_JCE6hw#&n1E z8djC6FdGap)fR*b7DSZTqHoZmx+Og6Q`h7<-I;?^%s%u9HQC6BSM&QTQf_$nqdvsF zcqy91iHcPlv2VbiC1UEI+YWBFuHB+P76u5SOT@#hX!)&558+w%nA{OVXGzkJy@i7f z;Yt6LGboLxO-!HCQ$l{zuUgPRq%*AlQUki+#&U6SF`F%(N_d&iodQRjKYecES-Usd z4LAT>ef7pxGpDpasVh+AE|qoKmCpNCf`))Zl$XHP)1&m8G&1$#X5v_?^2@ShR*oz! z)CQI*#fG-g_WQ&rm&za9g?GBoWKEI4jw~>mwRt}sl|p0(}wr{y&pak;m3?w z7^pHVtrnKKCM`1tMX4|@wb#Cn={XQ;RW4Ii-!vsc`GX9`*SQ71s9xkHK*^R0K zgH&CL-fSl5?8u6kxQeQ9SY$BFeh@WHMffa(N4-_%s0p#{D@dhO?cQI%9ivZe7GQ6+k`z5bg`Ff>G?G@k ziZALuVQJh^KBrs=90MYR9Oy%|#Ap2v;NJz~-YS+S(YFn$(rl`*#oQ+w{7L)zPx%(c zoKbi|vWr?NjHlsIoUBZJa1STqQ@NTY^%3;m zc7^oY3hEgz`CYAa?NnHmNU-Zjx*ty_5@Rw?6 zXRlQq8s_rXF?EzULi2sALh)Gly!2U$1Z{0P+pkBcnOv;-pX?c2L14R8A5$7}N5lez zNE{Q)B$#sHar8Hs{_vYH*dMN-9*r>(VPE3iU}5^vi+>qya1&F!h(M-ZyvE9`kDBud zlR?Vtz41!2p;yZ3Bd9ZsUm2|w{g0NF>}CMEN1q8uimlMny}z)%uWqR%qtwL-STR;R zp*OMOJsUNyRtzF1;sP_$^&7!ns&}K}blTYsH56TCIkX74Oe2#ys-2CuKpOc3nmcyM z&aM{*e}zsReq0$f8yYM+nzd)1f)ean*_e%%q}jJetN1WZQZ@JKDotqx#qBVzY0EA@ z4_UldKh8FFze&ugej}RDExsK-uKAA07yPdIZEMSKzn^P{`eR_BtkJXL1>p&sZ49b@ z5%OOmubV~habLH&n#;?Dqx;8;K&nA_lax1GhSJ2&J|r_NVWy-$OTTC?qxTJBV;F-S z1cwpJaS9k6*ZXGX>@!D;!hZ>9+0{vte$fv;I7UQtS8?;O+@gf76H)BUWM{fd0MuiQ=6q`JfBU`%aNgFD-Dybqd41T z$V>O`S=^nJDkECH)nweP)Y@6iu zDPsMJ;{OBeB(JB2yTjj@Up5Z7Z8(!xdm}5Q68FP(V}s$o#KpkjCy|7{ksC_mO9kqx zTj{3|BO1#+>Rxd`rMi2ROE#oHfTd@}DR!)+cff)l(F{rsR^ZY8qdf3MzIy0&1(qUYmalO)41)qbYm^r)mH3Ya)>ajwubb54iCxy}hrhl+UopQ;(^qrbkRYqIrU z4SJug0)j&li|! zTns*gNkXmA@%y&bd=GB#1(HLd=g`x&S$!O1#XvA}uOVyKXO3?lbxU7S zmeKGOmF{6ot1SD`w1VMw@ACM#S0eR2=r{of;ko9~LR7fmm;AYPGn19X-UFGXwi>uV z%Gh8ugVUfwBZ&R?&s(QQ=af{`iYBh5;l(Sv5HVx5X7PcAMUTBEcn0I@kyZacQ- z*84AZ{N@E^>mcT=P^(50_G}VPd=xzLp+raGks>J6;2n(;BNEkhlHm5>PHAv;{x;*Y z76d0ejtTt^3H>b;e;vjVI&)HjBHem>9on!5lMl@Y^XyIhY_;TO$49=Fyx$IrxNA{x zXUh{Dm6|iA`0>I{hsF7C=7fzeiz8VXCmaW>5X_CXga+@vWPFSk^ujOAPjvVg_zltD z*Ra!xq@n@#+4^*V3{(D00-+E*Xvek*g_!HFs9~$5GV={x>^xmwMHVXk^BK~LWr?-hfD^w*pM)$n`!jc|2gOPOI#|aGN>guNGX<(`0c={W#HB(QEX&7iO zM82~T2T#$eSV6(nVwGrCcY4WYO7-P}<^f}8 z6w41B*#@g?(t-W4@*E^@xG{2mTDMg8!=KQjA6!UB#bH|MP7l;0*`-x-j|8VlDY({S zXM5c&kL|5Fsl}ytRBh*h&+qP{un!Ze!i)j-TPV^hLK0rl=)~got>N#;fd-5yWwN4% z=W`PtCJGw<3#q;ou!|SewMUCVE^O(1854d1=kq(|0d?KCnfP}q-gN#Dr9d=$QP?UM zUcd9z8N!yQsc@kh#L*Znczx1T!s;y-k8>*qN}9uhvGN$&wuc+Az{4-NC^c}|`~tIbpS{X!YH9auSfFNm!%%QaU|)GpNS?e5Em#`+WN z>Ggas|9EqeJnn!WfXV9H297TV? zH7H=wDC1FV3c3_!f z@{tkoBVK}XD-Fm(4Y$9Hlha)%DqKI&TQIXsvmKZ)ycpqH*1_{~9(*gGhN~=PbY43~ z9;r?6j?WtR7Mhw}YBU`IDCh#$k<1-^C-?MQj20*>^~F@~Uh_r5a`i3o>hyhc1G5G2Rr za7j~3VHB|Qi}`t4`sHdhur60ZBVC1rghrLGHM^GY_GQ76yvwMY zfv|5=j&ghF7gIMyuV z6$U3w=cfni*9-dJU=yP3@C~DEbVmO$ENm_l%I!9$_5aM^?h%K^)I}>1I87eg7V3w# zv!g?@S&$oU>@9S7mA4_D+ zU2S{2;Ov!?XGTmnjyBpXxEHFlRdmoaA)KQ19h@(eL*BE(22{VTGIAYgZ}m$~)eH(Htd{OnJ6@;Ir6+!cWFwp<2rGwiH5- z{Q@j=(B%_v-Xow6w0BeQ`-a_)wh<-dvx)FuBA+_U$gRlwdlClR=g5!4aM2>@+cnLH zw92TN5=(B#wi00(4Oi2b8|lwVe5SO&Bq={N?@w)+TSoAb!QT@z(uT9FZUwK{RiPoN zmJ(U8U0YeuKZDJ6oErpJD%pSHsd8%%ueMBR(_|0jEz4kh>p^Q?GGhooebv|# zv{*OyWhjQ1Rj1d&OHx1hMK}MPqlD39w-;7O73N3FQ-^-Go!%w>OA{{d!p2=Gw?$_B zyJRztJ7bi<-F~|(*EiE-L{fUlB}n7=Dw*E4pvOF-?GnjRpJ+?#d;r*7P^ddIQp(Tp ztICpKItbnG-T3gYQ?tTN!6C#j;>JnF$#~lAoyVhs?er~djJFt_Sp;a8 zC>^?42fS!Z-$MjF)8G8+_o% z7gOtG>gQ{Yqk#49pxn?}x9jrk+8v}Ps8ta&!KjDKsg)eT4hf60NTX&3yU*g2n#Xqk64dd+Jw}Tg!4DluUV*u%{xz7t>cCfejTGFlJDHF?J2>EEM(1 z*giC*;}+%}I5|<@CXfv~!q1Qt#cUC#B6A~g2zZCQ_KOWO= zMxGo^iln{2qsevAeK z_X{LWvE3L_f$Q_`cHly9uV}p z4ZPY*{oebU^-6u+3erI+u3#u3!@pq=z#7B$TRhk+4@_D!sxl=wxw=d7>=JlW`u?~p z1^(}4=WsC&pb{by-7ZdBy`@sjXrQsLDcJ|TPA-WjAH;OOmje}ALMRKe!Ou?(Kf&I) z7{Bk6JCNp@XRP1kvb_Ju?L~VlZdK%|prD^=vpP*}rc$d}Ta^w}6Xyp}KN`apt@{|^Zqm$#4G!?cP-6r6g#bbQq=@ZlP8`_obDxMfMXwuTX z7v2q~27W`Q12ZqlQ54WS5+bjf21XMe8(2*Lp5w!`ks)mf8m1^1G;~+T@CDzRx@gh4 z1g`S*eEabc2RVz}KM6&-7r`)L6O4Brs{XCT97BW0M_1$#yPl@^p1%}4v&Mn*(x)Tq* zk@4(uEz#m4ro;=(rQqEdIkea z`SoCZioM1fz5H!TC=0HQBkJ#p>hV@Nu}GHT+Tf>bR1u~KB}~_y4#1X?LO>O^Q4{L9 z=vJC#ade?rBKauq&&6ZOOr)&G@x5}QvqG5?ZqqLKpZM-pn+{TRLXNKVm!cm6lD-o} zxs4z>iKXGYLw8YI)MT!WBd&3Cz8I#pl;`j$BDvv1J=(VW%7yhpv6fmzN6Jx2qPnebSN6$$h0LYq&h{$(Ddl+PjWU3G4c0?{$)}eYZp84ZgXcN*)hK4 znU$7d_WLHcfRq1-G6nZrpR0 zC3Hg=*%j5B?(zQn!T(v$@G-dx++Mx255(^Uh8EI-210_wKEM!xxtv1!=)O*=1? zw@DAhC=-7*ihkn`F3@tBnK2p8JL@&+0tL+$(ppQV2g-S@@RXS{5^K@Z;P0nMW#qZs z8Q|ZlD36zgq~*z0Q*(b3UkW}f{@jvnUK4ZWQn)^v?z&`9E zT#MD|x~oF=KG1k!z9$kfFN|Ko*94we4|)RqOh|3u>>Ek9@uZGCWnkR;E>ZW+O5LZf zx}a}tYmtLadWIv{@0JkwhEw!Pt=2Cv^WX+*M_hT`xJG<%b&QitY9EkJ7yX^%bj4E1 zsHHs{?;(c#XpW*ygK~~ZgvO_;i-z<^Pk*N+N6|op5b7%!vIfy77CR%w^U&lb=GrtPEG zm|%w3(C9d7d`Igvy;M`H=Z`g^)gpqHR;V^9cM>~GH8(x$5}XD%60$PAtNPJw6fzS+ zf3VDG_Q!UnK)vaYM~nhcG32O~olpEM-BKM*$6zSC(GU4jo5B@@g_xEa<>cUEP}^%J zJp?6sU;KP;)-{4c+MP6!h5UMp-ySs&Jll8_vig>5KVBU-BgJh!4cFXB@5R`htrg!! zGQlcQwY+aC!QE`wYap~I(Ec&zo*|jp0!fJchWB0lvn5knBYm_Yg$Pww{vu(Zy z#)h+)F@YHq8C#9@l1FpKF1x%50VP=|AR;#4Kc#C80#KmXzXO{87Oz3(fgm~Z|5GjU zSGC)J{(6ugB7rcZuZ1Jn5I1NLBk%{>UjU>78l(!n#{`5ZlfY0x_+UUEfb~QWe;AM{ zu*m;UOA0Xzgu#a3gI<+90{=!#Kp?`u4E|5n`aib*Y9oUnHLxJ^KS^vZU_Laq@&Z-JvNepvY7 zNIT*yALBj$LMV|y+`xg}zY&Kq7%WH>(knyXAn}N=47~zykToO_FR;A-9}zf+FfxcA zs6X^CV!Z diff --git a/kb-markdown-importer/assets/css/frontend.css b/kb-markdown-importer/assets/css/frontend.css index 0f8ee0e..09e02e8 100644 --- a/kb-markdown-importer/assets/css/frontend.css +++ b/kb-markdown-importer/assets/css/frontend.css @@ -15,6 +15,114 @@ padding: 24px 20px 48px; } +.kb-docs-app { + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + gap: 32px; + align-items: start; +} + +.kb-app-sidebar { + position: sticky; + top: 24px; + max-height: calc(100vh - 48px); + overflow: auto; + border: 1px solid var(--kb-border); + border-radius: 8px; + padding: 16px; + background: var(--kb-surface); +} + +.kb-app-sidebar__brand { + margin-bottom: 14px; + font-size: 18px; + font-weight: 700; +} + +.kb-app-sidebar__brand a { + color: var(--kb-text); + text-decoration: none; +} + +.kb-app-sidebar__search { + margin-bottom: 16px; +} + +.kb-app-sidebar__search .kb-search { + padding: 0; + border: 0; + box-shadow: none; + background: transparent; +} + +.kb-app-sidebar__search .kb-search h2, +.kb-app-sidebar__search .kb-search-results { + display: none; +} + +.kb-app-nav, +.kb-app-nav ul { + margin: 0; + padding: 0; + list-style: none; +} + +.kb-app-product { + padding: 10px 0; + border-top: 1px solid var(--kb-border); +} + +.kb-app-product:first-child { + border-top: 0; +} + +.kb-app-product__link, +.kb-app-version-list a, +.kb-app-page-list a { + display: block; + border-radius: 6px; + color: var(--kb-text); + text-decoration: none; +} + +.kb-app-product__link { + padding: 8px 10px; + font-weight: 700; +} + +.kb-app-version-list a { + padding: 6px 10px 6px 22px; + color: var(--kb-muted); + font-size: 14px; +} + +.kb-app-page-list a { + padding: 5px 10px 5px 36px; + color: var(--kb-muted); + font-size: 13px; + line-height: 1.35; +} + +.kb-app-product__link:hover, +.kb-app-version-list a:hover, +.kb-app-page-list a:hover, +.kb-app-product.is-active > .kb-app-product__link, +.kb-app-version-list li.is-active > a, +.kb-app-page-list li.is-active > a { + background: var(--kb-accent-soft); + color: var(--kb-accent); +} + +.kb-app-main { + min-width: 0; +} + +.kb-docs-home > h1, +.kb-docs-product > h1, +.kb-docs-version > h1 { + margin-top: 0; +} + .kb-product-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); @@ -195,6 +303,15 @@ } @media (max-width: 780px) { + .kb-docs-app { + grid-template-columns: 1fr; + } + + .kb-app-sidebar { + position: static; + max-height: none; + } + .kb-doc-layout { grid-template-columns: 1fr; } diff --git a/kb-markdown-importer/assets/css/themes/obyte.css b/kb-markdown-importer/assets/css/themes/obyte.css index 42e919a..7535710 100644 --- a/kb-markdown-importer/assets/css/themes/obyte.css +++ b/kb-markdown-importer/assets/css/themes/obyte.css @@ -68,12 +68,38 @@ } .kb-product-card, -.kb-search { +.kb-search, +.kb-app-sidebar { border-radius: var(--kb-radius); border-color: var(--kb-border); box-shadow: var(--kb-shadow); } +.kb-app-sidebar { + background: rgba(255, 255, 255, 0.96); +} + +.kb-app-sidebar__brand a { + font-family: var(--kb-font-strong); + color: var(--kb-text); +} + +.kb-app-product__link, +.kb-app-version-list a, +.kb-app-page-list a { + border-radius: 11px; +} + +.kb-app-product__link:hover, +.kb-app-version-list a:hover, +.kb-app-page-list a:hover, +.kb-app-product.is-active > .kb-app-product__link, +.kb-app-version-list li.is-active > a, +.kb-app-page-list li.is-active > a { + background: rgba(0, 167, 230, 0.12); + color: var(--kb-primary-shade); +} + .kb-product-card h2 a { color: var(--kb-text); text-decoration: none; diff --git a/kb-markdown-importer/includes/Admin/ProductsPage.php b/kb-markdown-importer/includes/Admin/ProductsPage.php new file mode 100644 index 0000000..a01cd3a --- /dev/null +++ b/kb-markdown-importer/includes/Admin/ProductsPage.php @@ -0,0 +1,125 @@ +allWithStats(); + ?> +
+

+

+ + + + + + + + + + + + + + + + + + term_id; + $versions = array_map(static fn (\WP_Term $version): string => $version->name, (array) $item['versions']); + ?> + + + + + + + + + +
+ + + + +
+ term_id); ?> + + + +
+
+ term_id); ?> + + + + +
+
+
+ update( + $termId, + sanitize_text_field(wp_unslash((string) ($_POST['product_name'] ?? ''))), + sanitize_title(wp_unslash((string) ($_POST['product_slug'] ?? ''))) + ); + + if (is_wp_error($result)) { + add_settings_error('kb_markdown_products', 'update_failed', $result->get_error_message(), 'error'); + return; + } + + add_settings_error('kb_markdown_products', 'updated', __('Product saved.', 'kb-markdown-importer'), 'success'); + return; + } + + if ('delete' === $action) { + check_admin_referer('kb_markdown_delete_product_' . $termId); + $trashPages = ! empty($_POST['trash_pages']); + $result = $repository->deleteProduct($termId, $trashPages); + + if (is_wp_error($result)) { + add_settings_error('kb_markdown_products', 'delete_failed', $result->get_error_message(), 'error'); + return; + } + + add_settings_error('kb_markdown_products', 'deleted', __('Product deleted.', 'kb-markdown-importer'), 'success'); + } + } +} diff --git a/kb-markdown-importer/includes/Frontend/Router.php b/kb-markdown-importer/includes/Frontend/Router.php index 120d490..a20a3b7 100644 --- a/kb-markdown-importer/includes/Frontend/Router.php +++ b/kb-markdown-importer/includes/Frontend/Router.php @@ -143,11 +143,7 @@ final class Router public static function shortcodeDocsIndex(): string { - return (new TemplateLoader())->capture('documentation-index', [ - 'products' => self::productsWithVersions(), - 'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'), - 'url_builder' => UrlBuilder::class, - ]); + return (new self())->captureRoute('index'); } public static function shortcodeDocsApp(array $atts = []): string @@ -188,12 +184,12 @@ final class Router $atts = shortcode_atts(['product' => ''], $atts, 'kb_product_index'); $router = new self(); - return $router->captureProduct((string) $atts['product']); + return $router->captureRoute('product', sanitize_title((string) $atts['product'])); } private function renderIndex(): void { - echo $this->captureIndex(); + echo $this->captureRoute('index'); } private function captureIndex(): string @@ -207,7 +203,7 @@ final class Router private function renderProduct(string $productSlug): void { - echo $this->captureProduct($productSlug); + echo $this->captureRoute('product', $productSlug); } private function captureProduct(string $productSlug): string @@ -229,7 +225,7 @@ final class Router private function renderVersion(string $productSlug, string $versionSlug): void { - echo $this->captureVersion($productSlug, $versionSlug); + echo $this->captureRoute('version', $productSlug, $versionSlug); } private function captureVersion(string $productSlug, string $versionSlug): string @@ -257,7 +253,7 @@ final class Router private function renderPage(string $productSlug, string $versionSlug, string $pageSlug): void { - echo $this->capturePage($productSlug, $versionSlug, $pageSlug); + echo $this->captureRoute('page', $productSlug, $versionSlug, $pageSlug); } private function capturePage(string $productSlug, string $versionSlug, string $pageSlug): string @@ -301,13 +297,35 @@ final class Router private function captureRoute(string $route, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string { - return match ($route) { + $content = match ($route) { 'index' => $this->captureIndex(), 'product' => $productSlug ? $this->captureProduct($productSlug) : $this->captureIndex(), 'version' => ($productSlug && $versionSlug) ? $this->captureVersion($productSlug, $versionSlug) : $this->captureIndex(), 'page' => ($productSlug && $versionSlug) ? $this->capturePage($productSlug, $versionSlug, $pageSlug) : $this->captureIndex(), default => $this->captureIndex(), }; + + return $this->captureShell($content, $productSlug, $versionSlug, $pageSlug); + } + + private function captureShell(string $content, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string + { + $activePages = []; + + if ($productSlug && $versionSlug) { + $activePages = $this->pagesForVersion($productSlug, $versionSlug); + } + + return (new TemplateLoader())->capture('docs-app', [ + 'content' => $content, + 'products' => self::productsWithVersions(), + 'active_product_slug' => $productSlug, + 'active_version_slug' => $versionSlug, + 'active_page_slug' => $pageSlug, + 'active_pages' => $activePages, + 'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'), + 'url_builder' => UrlBuilder::class, + ]); } private function render404(): void diff --git a/kb-markdown-importer/includes/Plugin.php b/kb-markdown-importer/includes/Plugin.php index f7fcfc8..f9157b0 100644 --- a/kb-markdown-importer/includes/Plugin.php +++ b/kb-markdown-importer/includes/Plugin.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace KbMarkdownImporter; use KbMarkdownImporter\Admin\SettingsPage; +use KbMarkdownImporter\Admin\ProductsPage; use KbMarkdownImporter\Admin\StatusPage; use KbMarkdownImporter\Admin\SyncPage; use KbMarkdownImporter\Frontend\Router; @@ -119,6 +120,7 @@ final class Plugin ); add_submenu_page('kb-markdown-importer', __('Overview', 'kb-markdown-importer'), __('Overview', 'kb-markdown-importer'), 'manage_kb_docs', 'kb-markdown-importer', [StatusPage::class, 'render']); + add_submenu_page('kb-markdown-importer', __('Products', 'kb-markdown-importer'), __('Products', 'kb-markdown-importer'), 'manage_kb_docs', 'kb-markdown-products', [ProductsPage::class, 'render']); add_submenu_page('kb-markdown-importer', __('Synchronization', 'kb-markdown-importer'), __('Synchronization', 'kb-markdown-importer'), 'sync_kb_docs', 'kb-markdown-sync', [SyncPage::class, 'render']); add_submenu_page('kb-markdown-importer', __('Settings', 'kb-markdown-importer'), __('Settings', 'kb-markdown-importer'), 'manage_kb_docs', 'kb-markdown-settings', [SettingsPage::class, 'render']); } diff --git a/kb-markdown-importer/includes/Repository/ProductRepository.php b/kb-markdown-importer/includes/Repository/ProductRepository.php index 535ec47..00c2f2a 100644 --- a/kb-markdown-importer/includes/Repository/ProductRepository.php +++ b/kb-markdown-importer/includes/Repository/ProductRepository.php @@ -16,4 +16,117 @@ final class ProductRepository return is_wp_error($term) ? 0 : (int) ($term['term_id'] ?? $term); } + + public function allWithStats(): array + { + $terms = get_terms([ + 'taxonomy' => 'kb_product', + 'hide_empty' => false, + 'orderby' => 'name', + 'order' => 'ASC', + ]); + + if (is_wp_error($terms)) { + return []; + } + + $items = []; + + foreach ($terms as $term) { + $pageIds = $this->pageIdsForProduct((int) $term->term_id); + $versions = []; + + foreach ($pageIds as $pageId) { + $pageVersions = wp_get_object_terms($pageId, 'kb_version'); + + if (is_wp_error($pageVersions)) { + continue; + } + + foreach ($pageVersions as $version) { + $versions[$version->slug] = $version; + } + } + + uasort($versions, static fn ($a, $b): int => strnatcasecmp($b->name, $a->name)); + + $items[] = [ + 'term' => $term, + 'page_count' => count($pageIds), + 'versions' => array_values($versions), + ]; + } + + return $items; + } + + public function update(int $termId, string $name, string $slug): \WP_Term|\WP_Error + { + $name = trim($name); + $slug = sanitize_title($slug ?: $name); + + if ('' === $name || '' === $slug) { + return new \WP_Error('kb_product_invalid', __('Product name and slug are required.', 'kb-markdown-importer')); + } + + $updated = wp_update_term($termId, 'kb_product', [ + 'name' => $name, + 'slug' => $slug, + ]); + + if (is_wp_error($updated)) { + return $updated; + } + + foreach ($this->pageIdsForProduct($termId) as $pageId) { + update_post_meta($pageId, '_kb_product_slug', $slug); + } + + $term = get_term((int) $updated['term_id'], 'kb_product'); + + return $term instanceof \WP_Term ? $term : new \WP_Error('kb_product_missing', __('Product could not be loaded after update.', 'kb-markdown-importer')); + } + + public function trashProductPages(int $termId): int + { + $count = 0; + + foreach ($this->pageIdsForProduct($termId, ['publish', 'draft', 'private', 'pending', 'future']) as $pageId) { + if (wp_trash_post($pageId)) { + ++$count; + } + } + + return $count; + } + + public function deleteProduct(int $termId, bool $trashPages): true|\WP_Error + { + if ($trashPages) { + $this->trashProductPages($termId); + } + + $deleted = wp_delete_term($termId, 'kb_product'); + + if (is_wp_error($deleted)) { + return $deleted; + } + + return true; + } + + private function pageIdsForProduct(int $termId, array $postStatus = ['publish']): array + { + $query = new \WP_Query([ + 'post_type' => 'kb_doc_page', + 'post_status' => $postStatus, + 'posts_per_page' => -1, + 'fields' => 'ids', + 'tax_query' => [ + ['taxonomy' => 'kb_product', 'field' => 'term_id', 'terms' => $termId], + ], + ]); + + return array_map('intval', $query->posts); + } } diff --git a/kb-markdown-importer/templates/docs-app.php b/kb-markdown-importer/templates/docs-app.php new file mode 100644 index 0000000..83e902c --- /dev/null +++ b/kb-markdown-importer/templates/docs-app.php @@ -0,0 +1,58 @@ + +
+ +
+ +
+
diff --git a/kb-markdown-importer/templates/documentation-index.php b/kb-markdown-importer/templates/documentation-index.php index 98a8563..fa9c212 100644 --- a/kb-markdown-importer/templates/documentation-index.php +++ b/kb-markdown-importer/templates/documentation-index.php @@ -1,14 +1,14 @@ -
+

-
+
-

name); ?>

+

name); ?>

    @@ -19,4 +19,4 @@ defined('ABSPATH') || exit;
-
+ diff --git a/kb-markdown-importer/templates/page.php b/kb-markdown-importer/templates/page.php index 91d8dc7..2b0fc8a 100644 --- a/kb-markdown-importer/templates/page.php +++ b/kb-markdown-importer/templates/page.php @@ -1,59 +1,17 @@ '; - foreach ($nodes as $node) { - $target = (string) ($node['target'] ?? ''); - $label = (string) ($node['title'] ?? ''); - $href = ''; - - if ($target) { - $slug = preg_replace('/\.md(#.+)?$/', '', basename($target)) ?: basename($target); - $slug = in_array(strtolower($slug), ['doku', 'index'], true) ? '' : sanitize_title($slug); - $href = $url_builder::page($product_slug, $version_slug, $slug); - } - - echo '
  • '; - if ($href) { - printf('%s', esc_url($href), esc_html($label)); - } else { - echo '' . esc_html($label) . ''; - } - $render_nav((array) ($node['children'] ?? [])); - echo '
  • '; - } - echo ''; -}; ?> -
    - - -
    + diff --git a/kb-markdown-importer/templates/product.php b/kb-markdown-importer/templates/product.php index 2a84464..12a54f5 100644 --- a/kb-markdown-importer/templates/product.php +++ b/kb-markdown-importer/templates/product.php @@ -1,7 +1,7 @@ -
    +

    name); ?>

    @@ -13,4 +13,4 @@ defined('ABSPATH') || exit; -
    + diff --git a/kb-markdown-importer/templates/version.php b/kb-markdown-importer/templates/version.php index 449f43a..9f47458 100644 --- a/kb-markdown-importer/templates/version.php +++ b/kb-markdown-importer/templates/version.php @@ -1,7 +1,7 @@ -
    +
    +