Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers
// ==UserScript== // @name JHS-enhance // @namespace JHS-enhance // @version 3.3.17 // @author xiebro,fireinrain // @description Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com // @include https://javdb*.com/* // @include https://*javbus*/* // @include https://*javsee*/* // @include https://*seejav*/* // @include https://javtrailers.com/* // @include https://subtitlecat.com/* // @include https://www.aliyundrive.com/* // @include https://www.alipan.com/* // @include https://115.com/* // @match https://javdb.com/* // @match https://www.javbus.com/* // @exclude https://*javbus*/forum/* // @exclude https://*javbus*/*actresses // @exclude https://*javsee*/forum/* // @exclude https://*javsee*/*actresses // @exclude https://*seejav*/forum/* // @exclude https://*seejav*/*actresses // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/js/md5.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/viewer.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/system.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/named-register.min.js // @require data:application/javascript,%3B(typeof%20System!%3D'undefined')%26%26(System%3Dnew%20System.constructor())%3B // @connect xunlei.com // @connect geilijiasu.com // @connect aliyundrive.com // @connect aliyundrive.net // @connect ja.wikipedia.org // @connect beta.magnet.pics // @connect jdforrepam.com // @connect cc3001.dmm.co.jp // @connect cc3001.dmm.com // @connect www.dmm.co.jp // @connect www.dmm.com // @connect api.dmm.com // @connect special.dmm.co.jp // @connect adult.contents.fc2.com // @connect fc2ppvdb.com // @connect 123av.com // @connect u3c3.com // @connect u9a9.com // @connect btsow.lol // @connect sukebei.nyaa.si // @connect javstore.net // @connect javbest.net // @connect missav.live // @connect jable.tv // @connect www.av.gl // @connect jav.rs // @connect javtrailers.com // @connect javdb.com // @connect javbus.com // @connect supjav.com // @connect 115.com // @connect jianguoyun.com // @connect 127.0.0.1 // @connect * // @grant GM_addStyle // @grant GM_info // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant window.close // @run-at document-idle // ==/UserScript== (a=>{if(typeof GM_addStyle=="function"){GM_addStyle(a);return}const A=document.createElement("style");A.textContent=a,document.head.append(A)})(" html #layuicss-skinlayercss{display:none;position:absolute;width:1989px}.layui-layer-shade,.layui-layer{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%}.layui-layer{-webkit-overflow-scrolling:touch}.layui-layer{top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;box-shadow:1px 1px 50px #0000004d}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px #0003}.layui-layer-load{background:url(data:image/gif;base64,R0lGODlhJQAlAJECAL3L2AYrTv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAAJQAlAAACi5SPqcvtDyGYIFpF690i8xUw3qJBwUlSadmcLqYmGQu6KDIeM13beGzYWWy3DlB4IYaMk+Dso2RWkFCfLPcRvFbZxFLUDTt21BW56TyjRep1e20+i+eYMR145W2eefj+6VFmgTQi+ECVY8iGxcg35phGo/iDFwlTyXWphwlm1imGRdcnuqhHeop6UAAAIfkEBQoAAgAsEAACAAQACwAAAgWMj6nLXAAh+QQFCgACACwVAAUACgALAAACFZQvgRi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwXABEADAADAAACBYyPqcsFACH5BAUKAAIALBUAFQAKAAsAAAITlGKZwWoMHYxqtmplxlNT7ixGAQAh+QQFCgACACwQABgABAALAAACBYyPqctcACH5BAUKAAIALAUAFQAKAAsAAAIVlC+BGL3Z3IlxUmUuhtR2LzHhsiEFACH5BAUKAAIALAEAEQAMAAMAAAIFjI+pywUAIfkEBQoAAgAsBQAFAAoACwAAAhOUYJnAagwdjGq2amXGU1PuLEYBACH5BAUKAAIALBAAAgAEAAsAAAIFhI+py1wAIfkEBQoAAgAsFQAFAAoACwAAAhWUL4AIvdnciXFSZS6G1HYvMeGyIQUAIfkEBQoAAgAsFwARAAwAAwAAAgWEj6nLBQAh+QQFCgACACwVABUACgALAAACE5RgmcBqDB2MarZqZcZTU+4sRgEAIfkEBQoAAgAsEAAYAAQACwAAAgWEj6nLXAAh+QQFCgACACwFABUACgALAAACFZQvgAi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwBABEADAADAAACBYSPqcsFADs=) #eee center center no-repeat}.layui-layer-ico{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANIAAAA9CAYAAADYizcVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAACAASURBVHic7Z15fFvVmfe/92qzJMtrvCQ2jp09TuwsQCAkISwJDVAoW0hombaQhgKlnU5pmc60A+3M0FK6dxhoGghLC0PCEjJAIW8gISQhwWSzHcfO4tix41heZNmSLFnS1T3vH7IUy1qdmPfTd8a/z8cfS/ee5/zuvbq/e855znOeKwkhBEkg3FYC1j0Eug6i2upQHU0ITycAkjEfOaMMOXcWmrz5aAoXIZkKk1WZEs563ezstVLV10WNy8bJfgedPg8A+XojU8wZVKbnsiAzj6VZhUwwmEaFV7Xb8NXV4D95DKWliYC1DbW3FwA5KwtNYRHakjJ0U6ajn1WJnJ07KrzCd5aAYyeqqwrVXYM6cBLhH7zOunzktCnIpkrk9AVoMpYi6SeMCq/D4aCpqYmWlhasVis2mw2n0wmAxWIhNzeXwsJCSkpKKCsrIyMjY1R41Z5ufLWH8R8/ir+5kUB7G2pvDwByVg6a8UXoSiejm1aOvmIucs64UeG1+vvY42rkQH8zRz1nOeXtplMJnm++1sIkwzjKjRO42FzKovTJFOoyk9YpJRKSam9AaXoLpfld1J6jKR2knFOOtvRGtGW3IGfPSPHUIlHXb2dTRxObO5updfakZFNhyeHW/FLuLChjljn7vHiV1tMM7N2Jt2oPyummlGy0E8swLFhE2sKlaC+aeF68qrsOxbaJQM9m1P7alGxkcwWanFvR5t6JbJp1XrwdHR3U1NRQV1dHe3t7Sjbjx49n1qxZVFZWUlBQcF68SksTA7u2M7D3Y5TmxpRstKWTSVt4JWlLrkFbUnZevA0DVt6yH+Sd3hqOes6mZFNunMAXsyq5JXs+M9LiNxBxhCTwNzyPv/4lVFstctZUNIWLkPPno8kuR7KUIBmyEV47wtlCwH4UtfMgAese1N4TAMi5FehmfhXdjHsAKaWDFsAfzzSwvq2eQw4bM8xZLM0pZEFGPhXp2ZQZLeRoDfQoXpo8TmpddqocnezssdLQH2wx5mXksrZoJvcXz0iRFRACz7Z38XzwV/xNJ9EWlaArr0A3ZQbaiWVo8guQ0zNQXQ4CnR0op5vwn2zAf7QWpa0FAF3ZFIzLbsC4/EaQUmUWKB1/xN+xHtV1CNk0AzljKZr0BcimCqS0MiRtDkLpQQw0obprCbiqUB07Ud0NweucPg9dwVq0Bfenfp2FoKqqik8//ZS2tjby8/MpKyujpKSEwsJCsrOzMZlMuN1u7HY7VquVlpYWmpqa6OwMtpBFRUVcdtllLFiwACnV8xUC93tv4dn6Nv7G42gvmoh+9jx002aiLZ2EpmACssWC6nQS6DiL0nwK//F6fEcOobSeDl7nydMwfuEmTNffkvJ1Fgie797DS92fUOM+w9S0AhalT2G+uZRZxvGU6HPJ1pqwK25afDbqPO0c7G9mj+skJwY6AKg0FfPVcVdwz7hFSDGuc5SQhKcTX+1/4K95GjlvLrpJt6It+xJS+kXJD9jTiXJyE/5Tm1G7DgdPvPJB9BXfRjLmJ7S1+jz88nQtv2mu4ZLMPFYVTGJlQRkT09KT8lp9Hv7SfpKNHafY39cFwPdKK/nBxAoK9caEtmqvnf7/3oT77TfQTZ5G2hVXYVh4JZq8xMcbsh34+EMGPvkIf+NxAEw33Y755juRsxK3isJvxX/2l/jbfoNsuQRt7iq0uSuRDMlbNeG3onT9BcW2EdW5HwBd0ffQTfgBki5xt9rpdLJz50527dpFcXExc+bMoaKiguzs5K240+nk0KFDVFdXc+bMGQCWLFnC0qVLsVgsCW1Vew/9b75C/+ZX0U2dSdqSa0hbfDWa/OTDANXeg2fHVgZ2bcd/oh4A862rMd/2ZeTsnIS2nX4Hf+j8kKc7djDXVMJt2fP5UvZcLtIntgvZburZz5v2gxx2Bx+YDxZczXfyryVfF9m91fzkJz/5SeiL8HTiO/AE/rpn0c28F8MlP0I78UYkffI+IoCkM6MpWICm6CpQ/ajdh1A7PgO/C03ePCSdOaad1efhscYDPNVSxwMXzeTxyZdwS/5EsrT6lHjTNTquyCpgeU4RPqGy39HN3t4OnAE/l2bmka7RxbRTe+24Nr2E570tmL5wE+l33YNhwRXI5tjHGXW+aUZ008vRV86HgIK/8Tj+4/WIAQ+6ydOR0mKLWPit+FofQ2l/Ct34B9Bf9DjanFuQtFmp8WrS0ViuQJu1HPChuvajOvciVCea9EuRNLEfPk6nk23btvHJJ5+wcOFCVqxYwaxZszAaEz9sQjAYDEycOJGpU6eiqipnzpyhpaUFr9dLcXExBoMhpp1q78H18nO433kD0w23YvnqWtIWXolsTv6QBJCMRvQzKzDMuxQUBf+JBvwNRxAeN7qpM5HiHH+n38HP29/j2a6PuTdvMT+e8EVuzKokU5Pa+Zo1Bhakl3FVxnT8IsAhdwuf9TfjVL3MN5Vg1pw73yFCEvgO/QLl+CvoL34Ew6WPIhnzUiKMOnFDFtqS5aDRoXYdINC5HySBtuhqhnc/BPCTU4d4oe04j06+mCemXkpBklYkHrJ1Bm4YV4JO1lDV18Xevk6EJHFdblF0YywErtdeYmDH/yF95d2kf3kNcub5ja1kczqGeQuQtFr8J4/hP34UEBgqL47R/RD4z/yEQNcL6C96FH3JE0i68xtrSNpsNNk3IMk61P4qVMdekASarOuIus5CsG3bNvbv38+yZctYsWIF6emp3cjDYTQamT59OhqNhtbWVlpaWhBCMHXq1OhunhC4XtmA54O/YrnrXixfvx85K3lrEAtyugXDJQuRtLqgg6LhCCCCApOG31eCJ6zv8YrtUx4Zfz2PFd1Eni5xqxkPWRoTyzPL0UlaDvSfZn9/E6okuDpjRribFxaSv+F5fJ/9DP3Fj6Cf8z1S7W/Hh4SmcCFIEDi7C7XjMyRTHppx8yJK/fFMAz8++RmPTr6YH5XNGQVWuDK7EIHE9p6z7O3toMBg4tKMSI+PZ9u7uP7rBdJX3o35ti+PYFwTj1hCP7MCEPiOHMZ/vB5NVg66ydMiiikdf8R3+sfoL3oUXdGPGJXrnHElIAj0bUd17kXWFyCnXxpRqqqqiq1bt7Js2TKuueaa1Mc18VglibKy4KC/sbGRlpYWMjIyKC4ujijnfu8tXH9ej+WuezGv+uroXOfZc5AE+GoO4G84gpydg27qzIhiz3fv4fGz7/LI+Ot5uPC6mOOaEdEicUX6ZASwy3mcz/qbyddlMM9UAgwKSbU34Nv/ONrSL2K49FGS/biuZ3PxHXwS38En0U6+HSkt/hNGU3g5wtON2n0I0W9FU3g5kjF4U9f12/lx435uzS/liamXXvAtNRRLsgvp9HvY7+jmrK+fxdmF5A+2dErrafpffYG0yxaR/uU1ET+u0taC0tqcUt8dVcV7sArZaAp3L/QzZiMcffgbj6PabehmzEbODHbZVHcdvtYfo829FX3JEyS6zsJ3FqVzA0rX86h9HyB87UhpU5Dk2N0nTcYSUDpRXfsR/rNoLIuRdMFxXkdHB1u3bmX27NmsWLEioYjcbjdbtmzh/fffp6GhAbPZTG5ufPd+aWkp/f39nDlzBofDQWlpabilU1qacP15PWkLr8Ty9fsvXERDoJ9VidrXi/9EA2qPLTgNMdijaBiw8vjZd7gxq5LHim66YBENxcL0SXQrLg65W7D6+7g8fTLjtOnIAErTWyBr0JXfy4U/IYdDQlf5EHLeXFRbbZBrEJs6mtBIMg8Wl48Ka31/L58OOhsk4AcTK7kkM49DDhubOs65swf27gRZxviFmyJ+XLXXjvvtN+h/4xUC7WeS8nmrD9D/2p/xbH+fQHfn4OlKmG5eiW7yNPxNJ4Ncg1Bsm0DSoC14kETXOdCzGc/hcnxN30Wx/gn/2T/gPbkGz+FyAn3b41hJ6Cb8ANlyCarrUJBrEDU1NUiSxMKFCxOKqKWlhccff5yOjg5mzJiBTqfjxRdf5NVXX41rI0kSV155JcXFxbS1tVFTUxPeN7BrO8gaTDfeGlNEgY52+t94hYGd2xCKP2q/6ujD/e5m3O9uRnU5hxNjvv3L6KbOxN94PMg1iLfsB5GRWTNuyaiKCIIt07cLrmWuqYQa9xnesh8EQBZuK0rzu+gm3YqcPTNJNecH2TIR3aRbAVCa30W4rZz1utnc2cyqgknMTj+/sclQ1Lrs3Fb9AbdWb2NfX/CmLjNaWFUwCYDNnc2c9bpR7Ta8VXtIu+IqtBeVRtThqz2E58P38NUewvHc0wQ6rXH5fEcO43pxHf7G4/Rv/i8Gtm8N79PkF5J2xVUAeKv2oNptwcnWns1oc1chm2bHrVftP8xAw+0IpS+4QdaDHHSWCO8ZBupXoHrqY9pKhjK0uauAoBiF7ywOh4O6ujrmzJmTdN5nw4YNXHXVVXzrW9/ihhtu4O677+ZHP/oRtbW11NfH5gTIyclhzpw5ANTV1eFwOFB7uhnY+3Fw3mfipBgnqjKwcxvu/36N/rc24a8/ElXEe2Af/Vs24v7v1/Dt3xu1X1MwnrQl1wAwsPdj1J5urP4+3umt4bbs+cw0jo+y2Ww/xEXVPyDn4HeYWP2PbO2ri9ivCJVvn36FcQf/npyD32H5sd/QrbgiykzU53Jb9nwA3umtwervQw5Y94DqR1v2pbgXajSgnXInctZU1J6jBKx72Nlrxa+qrCw4v8m1oTjstLGy5kMa+ntp97r59rFzF/3u8VOYYc6i1tnDzl4rvroaUAIYFl4ZfYxlUzBcthgAX/V+nM8+FVNMvrpqnM89hdLWgqTTY7zmevRzL4kok3bltWiLSlBON+GrqyHg2AnCjzZ3ZcJz8bX+M0EXjIy+9BeY5jdhmn8KXfEjgASqH3/rv8S11+bdjWyagdpfS8Cxk6amJgKBABUVFQl5jx8/TiAQYPny5RHbTSYTc+bMYdu2bQnt582bR35+Pu3t7TQ1NeGrPQwBhbTFV8c+z5oDuP7reQK2Lvwn6nG99CfU/nM3rNLajOvP6wmcPYPS1oLzz+tRWpuj6jFe/QW0F01EaW7EV3uYPa5G/CLAl7LnRnOKAOu6dtIf8ALgDHh4qvPDiDINA+28bNuHSnBW6EB/M+/1RYv8zpxLmJpWwFHPWfa4GpEDXQeDYT0pzBNdCCRjPprCRQAEug5S1dfF0pzClOaJEuGw08ZdtTs4Njgha9Ho+M5F52b6C/VGluYExztVfV34Tx5DV14Rc55IW1xCxpqHSBsUmfdQVVBMHedm/X1HDuN87j9RzrQg6Q2YbriF9LvXoJsW2ZrLWdnoyoM3r//kMVRXFXLG0sTzRKoX1fUZAJrsZegmPIKkn4CkL0Zf8gtkY/ChE3Dui1uFpCtEzlgarM5VRUtLC2VlZUnnibq7uzHHcfsXFxfjdrsT2lsslrDzoaWlBf/xo+hnz4s71gx0dyH8/iHfOxFOR/i72tdHoKvz3Hd7D6o9OspFzs5BPzvowPIfP8qB/mYWpU+JOU8kI6GXNBHb9JI24rt22H4AXYxt+boMFqVPAYJik1VbHXL+/JgnO9oI8ai2OmpcNhZkxJ70PODo5pi7L2l9Na4e7q77KBzVkK7R8Uz5Yv5u/JSIciGeGpcNpaUJ3ZT4oUtydg6Wex4g7fIlwKCYnn8Gpa0V39EanBueRmltRtLpMd14G+Y7/w5JH9sBEOJRWppQ3TVo0hckPB+h9CB83QBRghNKD0KxJ7QPIcSjumuwWq2UlJSkZHehCPFYrVb8zY1RD5eh0M+ei27S1PB3w+VL0OSd63pqJ5aStnDJuf0XX4Z2mAc0hBCPv7mRo56zzDeXxiynlWT+vmA5BYOxc0X6bB4u/EJEmelpBfx94bKweJZnzmJFZuwQrBDPUc9ZtKqjCU12edwTHk2EeFRHEyctDiqGjY0EsNNu5a7a7YzTG3ijchnTTLEng2tcPdx95CPqnMGby6LRsW7WEu4qiO6Ph3hO9jsIWNvQTkzcnZSzc7Hc8wBCCLyf7sZ7YB+qsw/hHQiKSKvFdMMtmG+/K66IgDBPwNqGOnAS2ZS4eyVpMzFMeRZEANk8J7xddVXhbbwX4Q+eqyZzWeLjH+RRB05is9koLBydIOJkCPHYbDYC7W1oS2OMjQahKZxA1g//De+hKiSTmbTLFoPm3JNftmSS8cDDGC6+HKGqpC1cEncCN8QTaG/jlDebWTHGRiFcmzGD96d/l5MDXcw0FjJBFzkJLiHx2ISbuSlzDv2qnwXmiRjk2BP6IZ5T3m60wtOJZPl/88QK8QhPJ50+D2XGyAkyu9/LQ8f2YPW6sXrdrKrdzqsV1zB9mJhqXD3cVbODo/3BGytDq+NP5VeyKs54K8TT6fOg9vaiyU8+ASrnjCPj3gdxpRnx7NyG/3hwsC2nWzCuuBnzzSuRDGkJ6wjxqL29CH8nUlqS8aBsQpu/JmKTv/23+JoehsE+u2ycgn7ikwmrCfEIfydOpzNpt+73v/89TqcTt9vNr3/966j9Ho8nvO+aa65h3rx5MWohzON0OlF7e9AUJI5O10woxjShOO5+OTsn6FlNghCP2ttDp+KkRJ84Gn+iPpeJScrMNycP1QrxdCpOtACS4cK9ZqlgOE+ONvJpbtJoebC4nEeOf0p/QOGww8aq2u28Mvtqys3BJ8dhp41VNds5Ptj1y9TqWT9rCSvz49+kw3nk9NSWAcg54zBcuhDP7u0QCATPwZxO2uVLkIzJl2wM55G0I5vRVz3H8DV9L/xdm/dl9GVPIWkT/17DeUymxMfa3t7OqlWrkh7Pzp07qa+vjyuk4Txykvi70cJwnmzt6CynSYahPLJkzEd4U+t7XyhCPJIxn3y9kR7FG7E/TdbwYPFMfjfjCkya4CCw2mHj7iM7OOVxUt/fy+raHWERZWh1rCtPLCIgzJOvNyJnZaG6HAnLh+Crq6Z/86thEQGoPTZcr/2ZgDV5GH6IR87KQtLlI5TUloSEILzN4c/agq9jmPpyUhEBYR5Jl4/FYknqKJAkiXnz5oX/AoEAW7Zs4eDBgxHbkwWmhngsFgtyVg6q05mw/GghxCNn5ZCvtWBXEp/vaCHEk6+1oJUzyhDOFqS00VmclgjCGYyglTPKmGLOoMnjZJwuunv0jQnTCAiVf2jYi0cNcMhh47aaD3ArCicGRWTR6Hhm5uK43bmhaPIEL/QUcwaawiICnR3IlsSBuL4jh3E+O+ji1hswLrsB1dnHwK7teD/dA34/lnu/haYgfn880BkMwdcUFiGnTUEMNCGlj2BxmiSHP2qyvpCgYCTEQHDyWU6bQm5uLna7Pa5HLhb27dtHTk4OJ06cSP1YAbs9+KDMzc1FM76IQMdZ5IzUAp4h2DVTXU5QFNDpkDMyk/5OAIGO4ENNM76ISYZxtPhs5GpTP9/zRYvPBsAkwzi0cu4sAvajyHmxm+tYSP+G7byIA/bg4kA5dxaV6bnUuuxcmhE7MPabRTOQkfjusb24AwrVjnOcGVodfyyP7ViIhVpX8AeuTM9FW1KGcropKgZuKHx11Tif/Q+UtlaQ5aBj4bYvIwY84PczsG8X3kOfIZ79DzK+8e24YgotDtSWlCGbfKju2qgYuIQQyrnPgdSf7qo7uDhQNlVSWFiI1WqNioFLhOuuu44NGzYwffr0lG0g6K2DoNNBhwel+VRUDFzUsTr7GNizE1/1AZRTJ1B7exBCIMky8ri84JKLhUvQz78MSRt70K80nwJAVzqZcuME6jzt4Ri4oWgYsPKh4yhr85ZGucHjoS/g4Xcd21honsJ1mZFOuTpPcFqk3DgBWZM3H7XzYEqVXihCPJq8+SzIzKPK0Zmw/Nqi6fx6+uVR29eNQERAmGdBZh66KdPxn2yIW9bfcATn+kERaTSYb7oD821fRjIaw65xw2WLQAh81QdwrP9DxDxTRF2DPLop05HTFxBwVaV8zACy+WLSyt8nbdZWNJmpt0ghHjl9ASUlJbS0tIyId9q0aVRWVnLbbbeNyC7EU1JSgm5aedhBEwuqy4l7yyZs3/0GjqeeZGDXhwRsXaDXB1sxnY6A9Syebe9i/9cfYv/JD/DVHopZV4hHN62ci82lHOxvjlnursZ1/MuZt7jz5B855E58TVwBL6/2fMaKY7/l99YP+H7rJmxKf0SZEM/F5lK0msJFKI2bEa7WlCdlXc+e6waaVn6KnDklQekghKuVgHUPck45msJFLNVksNHayOkBV8JJ2fuLZmD1evhp4wEA/lS+hNUjENHpARc7e6xUWHJYmlWIflYmA5/sJNDVGTUp66urxrHu98E4O40W8xdvw7zy7gjvnJwzjow1D+GUNQzs/RhfzUEcT/8a47UrSLvynFs60NWJ/2gt2oll6GdVIpmno9g2IrynU1q8B8ExjvA2IwJONJnLkxsAwnsa1bEzuBQ9Yyll+nSqq6ux2+1xvXdCiKh4OlmWee+99yK2Wa3W8KTrcNjtdpqamhg/fjxlZWXolaKgODqtUZOy/lMncP7p9/iODC7+nDkbw/zL0JdXIueOQzKkIbwDBLo68NUewrt3F77D+7E3HCH97rWYbznnGAl0WvEdOYS2dDL6irksStex2X6AVl9P1KTsV3Ov4FfWrXzsPMbyYye4KWsOV1umM8M4nkyNEa+qcNrXQ427lb/21VI/uBx9nNbCA/lXYdGcuw9afT3scZ2k3DiBRemT0UqmQuTc2ShNW9BVPJToN7ogKE1bUHtPoJ//AyRTIROAOZZcXuto4vsTE8+v/GTSPNJkDTk6A2uLRtbdeK2jiYb+Xh6dPD+YHMVgQls6Ce/ejzHdfEdEWeHpR+3uDM4T3bwS8x1fQdJFLy6Us3Ox3PstkKSgKDvOojoiHRjevR+jtLVgXnl3ODmKbJqDYnsN3YTvp3Tsgb4P8DbeD4Cky0Ob97WkNortNVR3A7qLHkXSTyBDH8yzUFtby5VXRodFAVx77bXYbMm765MnT+b666+Pua+2tpbOzk6WLVsWTo6iLZvCwO4dmG+7K6KscDrw1deiGV9E+sq/w3DZ4nCE/FBoiydimLcA0/Iv0v/GK7i3vYPa0x1RZmD3DpTW06TfdQ9yzjgKgdnGIrbYD/NQwTURZf+hcDmLLVN5sv19PnQcZYv9EFvsh9BIMmmyDkWoeNVz0RZZWhM3Z83lOwXLmGSIHNtusR/mxEAHj4xfQaEuM+j+1pbdgq/6d2js9Z9L4Kpqr8d/ajNybgXaslvC2+8sKOOJ5mqOuOxJA1d/WFo5Yt4jLjsbO04xLyOXO4c4JdIWLqX/rVdRWpsjAlf1FRdjWfttVFs3puu/FFNEIchZ2aR/ZQ1ybh6GOfPRzTgXiKq0NjPwyUfoyqaQtnBpeLs29078Z59AdR9JGLga5kibipRWAoF+ZFN07NhwqO4jKLaNyOnz0ObeGd5eWVnJRx99REdHR8zA1eHxdSNFR0cH1dXVFBUVUVl57ndKW3IN/a/9BeX0qYjAVf2sOeT89FfIWTloSycnrV8zvgjLfd/BuOz6iHqU06cY2LU9mCJgyTnR3JI9n99Zt1HvaY8KXL3UXMprU+5nj6uRrX1H2O6op83Xi08oaCWZLF0ms4wTuDZjJsszypmSFh19U+9p5037QSpNxdwyGLyKGISv/jkxsPv7QghVjC5UMbD7+8K5Pkf46p+L2vt0a714oH7358AqxAP1uwX/Z714urU+ar9769vCsf4PQqijzKyqwrH+D8J6x3Lh3vp21G6/9WnhPfWASPk6B9xCVfpSIRbeUw8I1x6E3/p01N59+/aJzZs3C3WUz1dVVbF582bxyCOPiH379kXt73/3TdH3n7/6XK5z33/+SrTfuEj0v/tm1O7nunaJh1s2CjXJdVaFKuxKv2ga6BZnvHYxoPqTln+4ZaPIPvBt8VzXrvD2sH9VN+Me0KUR6BjZgDgZfNW/xV+/AV3lg4MZhSJxf/EMjBode3o7RpX3Z03VPNNaz/dKK7m/ODq2zrj8RtDrB5eFjx76N/8X7q1vY7rp9iDHMGgL7gfZSMC5J7UKZSOSJvkEsr/tZ/jbn0FX9L3BjEKRWLBgAVqtdsSOh2TYsWMHe/fuZcmSJSxYEB1LaLr+FiSDAV9DdAT1haB/059x/3Uz5ltXBzMKDcM94xaRJuuociVOqyYhkaUxUWrIpUifhWFYEOtw/Ma6jQ1du3mw4GruGbfo3I4ItXl7hepzjeS5kFC73sO/Fq4XisXArn8Qqrsjbkm73yucim+UWIX491OHRfqHL4j7ju4S7V53/LIup1A98fePjFgVrjdeFh133yz61v1OBOw98Yv67UJVnKPDK1ThO/Pvov/TdDHQeJ9Qfe1xS7rdbuH1ekeHVVXFhx9+KH784x+LN954QzgcjrhlA6N9nV99UVhvXyb6/uNJEeixxS3aq7iFKzAwOrRCFb9q3yqKDn1ffPf0q6LDF9lLSJggkoAXNPGDMuNBtdfjP7oh3BKlko5rKLxqAIOcmp9/KI647Dx95mi4JUolHddQCL8fSRd7riIRlNZmPFvfDrdEqaTjioDqhThLyBOauY+gdDwdbolSSccVcdyKglab+AkcCx0dHezduzfcEqWSjmsozvs6nz4VXDE72BKlko5rKLxCSdrixEK9p53nuneFW6JY6bjiC0lV8DdsgIAv9bx2rlaUpi34T20GNTDiBJEQXKH49JkGfGog5bx2pwdcvNbRxMaOUwSEOvIEkQCBAO6tb4OipJzXLtDViXfvxwx88hGo6nkkiASEgr/jaVB9qee1855Gsb2GYtsIIjDiBJEAqqqyd+/e8KK/VPLa2e12amtrqa6uRggx8gSRELzO725GKP6U89oFOq0M7N4RXE6uBkacIBKC99Vz3bvwq4GU89q1+nrYYj/Mm/aDqKgjSxA5FKGUxartCJKxIKVMq8i6UUtZXO20UWgwppRpVSfLo5ayWGk+FVyYl0KmVbSaUUtZrLqrFPi7jAAADFNJREFUkXSFKWVaRdKNWsri9vZ20tPTU8q0qtFoRi1lsdJ0Ejk7N6VMq2i0o5ay+IinjXxtRkqZVnWS5kJSFkdiLIn+WBL9sST6iZGSkMYwhjEkxshHXmMYQ2zEHLAkGz+pqroUWAyUAIVAAOiSJKkJ2C1J0u5RPs7PhVsaa5DG8HkilpCEEHOFEA8At0qSlDAvthCiDdgMPCPL8gVP+n1e3GNCGsOoI14rJIQoEUI8IEnSD8+nXiHEYwRv6q7zsP1cuceENIZRR5xWaJkQ4jVJkiKiU7u6uqitraW3txeXy4UkSZjNZnJycqisrCQnJ2d4Pe3AalmWP071eOJxd/R5+bRFwTYgc6ZPBWDaOJk8k+DyEh2mNN3weuJyjwlpDKOO4UJSVfVrkiS9MHRbTU0NH3zwQfjFZfEwYcIErrvuOmbMiJxKEULcLMvy28mOJRb3p80DPLvfT1VLdJrkEEw6iWum6vnGJTrKxkUGL8fiHhPSGEYdQ4WkquoXJEl6P/Td6XTy8ssv09zcPKI6p06dyl133RWRYEUIsTRRyzScu6df4dFtHnadii+g4dBp4OuXGLn/cgNazbml/8O5x4Q0hlFHSEiqqpYBtZIkmQHa2tp48cUXcThSSz4zHLm5uaxZsybc3RNCnJYkaY4kSVHZRAe5qyVJsgA0dfv4zn97aOkNDC+aEhaV6vj1F00Y9ZqY3HJC6zGM4cLwQEhEDofjgkQEwcSTzz33XDhbkSRJE4EHEnBbAGz9Ab61xX3eIgLY0+znh+95CKgiJveYkMbwuUAIcbEkST8Iff/LX/5yQSIKwWazsWnTpqGbfq6qakSug+HcP3zPTdugM+FC8FGjj2erBmJyX/CE7B0b2/5ZkliSvOQ5CMGuN1YX/wxg2pRL/lkgjcheQuw6duKzn4W/D+mT37HxTOwMGTHw+qriqNRJ06ZekrL9seOfpZ566X8ZBl3NABw+fDjmOqgJEyZw9mz8/IB6vR6TyUTvYHhWCA0NDRw/fpxp08KZoB4AHo7Fve24L6ZTYXqehmNd8Vsok04iM02i3RkpwOeqBrh1toH89HAb9ADwsLayYrEEPFJTu/sXcWsFKisW/yPwZE3t7ohBlSSxUpZ4WBWklKNLlshV4ddAUAgSK0E8rKoiNXtZymWo/TBIEsnXZCeEdIH2YwCQJCmce/nDDz+M2j937lxWr15NY2MjL7zwAn5/5M1eWFjImjVrMJlM/PSnP8Xn80Xs37ZtW1hIg1xhIQ3lfmqPJ4r7hhl6fn5DOlUtfh56y4VXifQTTB2n4Y+3W8hMk7nyaTtu/7n9XkWwbp+Hf1lmjuDWAo8Aj1dWLJZranf/PNZFqaxY/E/Avw1+jRKcKvx1r68qi1rieturZ8fJcqA2sqwyFyL98xqNqDtx4kCUfVlZ5TitVl87rOxcNTDabxUcw2hCVdVwEojm5ma6uqLnMBsbG3E4HEyePJmvf/3rEWLKz89n7dq1mM1mTpw4ESUigNbWVqxWayhxf6aqqgtkWa4ayl3drtBsj251DrUpdPerLCjR8dQt6RFimpSrYf3KDLKNEntP+yNEFML7x3w8cpUJg1Y6x01QGP8C/KyyYvFPhxsNbvvZYJmErdZwvLl6Qvfrd140XlY0V0tC6peEFJ0qJgGammq6T5zYP14I+WoJqV8iub0QzE30l8w+oKpzE/2N5Pj/F2Nx6EO8bK1Op5N169ZFiEmn05Gfn883v/lNzGYzp06d4qWXXopLMqzuxcP+s+90bDd3u1Nl7evOCDEZtBKTcjVsuDMoov1nFP5+iyumvcsrONoRIdDF2pra3QA/r6xYnAY8WlmxmJra3Y9BWESPAv8ar7VKhttfPTtTyIF3VJVvSDKvMEJv+6RJl86UUd8RSN8A8Uqy8q+vKq5OtH/lpsTvhm1sPJDQftrUEWRK/d+LcM60WK1RCDabjXXr1vHNb36TyZMns2bNGvLy8sIiev7556O6fEMxrO7pw/7TaIs/BjplC7D2dSfr77CwoETHutstlOZowiJ68E1nVJdvKBq6FOYVhV0M08Ofamp3P1ZZsRiCYgqllAyJ6LG4NSbAqk2nywME3lFV1ryx+qIdd2xqHZH99EmXlKuyeEdI0poTJz7bMXXqJTHLpeJgiOVYCCEVB8PxE/vHHAspQpKk8II0lyv2Uz0Em83G+vXrWbt2LaWlpQApiShG3YXDuW39iT11Q8UUEkUqIgKwuyP2F0Z47QbF1M+5Ltw/1tTuTvwynjhY+XrLrIAqvS0GRQQgVLE41dXB06dfPEsN8DaDIgLQKPJiNNFPmTEHw//fkCQpwvMqy6MzKxM4j1gDzXkOvy/4iAf0hiWv31ka5SgQqrx0qIgA3lhd0hhVDu2ShoZoR4OqykuHigigoakqyn4Mf3sQQoTfYJ2enjjnRl5eHmvXrsVisdDc3IzT6aS0tJQ1a9agS5IgZVjd1uHc48yJb+/SbA3r77AwziyHHRDzirSsu90SciTERbYpYr81okUaMib6x8FNv6isWGxO1LUzeL33r3ytrYghYw9VVatev7P46YRHMghJBO6fOuWSoshtVB0/8VlK9mP4m8Sx0Ie8vPhLfvLy8rjvvvuwWCzh7lxWVhb33XdfWEwbNmyI6bWLUfexYf+ZlBM/E1Vptobn7gyKKNSdK8qUw928dbdbePBNZ0yvXch+KHdYssMcC08Odun+leCYKcqbF4Ik8RWEqEewD8E+wCNL0nVxzyCqAvEVCaleIO8TyPskJI+QR2A/hr9FhFeWTp06NWaBkGCGisjv99PV1cWf/vSncMv0ta99LW5Xb1jdu4f9Z+HE2C3aeIscJSKvIsJjplDL9PsvpSPHaJgMWon5RRFt0G65smJxaJ4oyrEw+Dkkpn8adEZEQaBseW1V8cbXVhVvVFV1Z8xCCSBr1S0nT1ZtPHmyaqOKGLH9GP62IEnS9tDnkpKSqDVFAEVFRVgsFhobG6McCyExORwOSktLY+beKyoqGvqS6T5ZlquGc8+ZoKUoM1qEMwu0jDPLVLX4oxwLITF1ulTmFelIi9HFu2GGfmjXr0+W5SotwW7cvwH/HMvFPeiAGBgsozLCuaS/NaQyl5QIY3NJySFJUkBV1b2SJC2UZZlly5YNj4+jrq6O3/zmN3HXI3V1dfHLX/4So9EYs2s3NPG/EOKFmNwSPLTIxD/9NdJzuP2kj1tf7ONUHPf4KVuAL27oIzNNiura6TRw74Jzr3cJcWuBJwEShQjV1O7+eWXFYjVUdjgCssHwdxuaDAADGlnHYIRsaNtQuGSDQaMOd0tqDUVFiw0AsvDoxKAHJ7QtEn5DMEfF+SHZPFMyJJtnGkMYDwOfQDAcaM+ePbS1tUUUSLaoz+/3x3SBT5o0afhCv2fica+Yruflg1qOWJWIAvFEFIJXEXS6osdHX5mXRklWxPjoGQDtYOxc0lYmgdDOatXAzgHzYPOrCoTE6wADZm3Uq/G0agAkaWt4g5DOBpTATpMxeGIimIzmdQCTcSDKPqAAEluHbx/D3xZkWd47tFVavXo1zzzzTNIXQydDRkYGq1evDn8XQjwuy/KxoWUiuCX47c3p3PWyg+4k80rJsKBEx0OLIhYWhrn/RyzsW7mpbU6yMolaosmTL05qH6sl+p9w7T4PDFnYNweokiRJD8GFfS+88EI4+eRIkZWVxb333kt+fjCdtBDCBsyRZblteNlB7k8lSTIAHO1Q+PZbrvMW07wiLb+92UK2UYrJ/T9CSGP428Kwpea3S5L0euh7b28vL730UsLlE7FQWlrKV77ylYhk/UKIFbIsx+2dDOfu7lf5/jsuDrUp8Uxi4o5KAz+82oxuSI9uOPeYkMYw6kgl+clnn33Gjh076OnpSVhXXl4ey5cvj3gTIIAQYpUsy5vimCXk3n7Sx/pPBzjakVhQV03W8+AVRqbnRc5HxeIeE9IYRh2x0nGpqnoDsEWSIt+r0tbWxtGjR3E6nXR2diLLMuPGjSMzM5NZs2YNdXEDIITolSRppSRJH6R6PPG4z/YpfHI6QH3nOUEZdRKzCzRcVqIl2xQloLjcY0Iaw6gjXoJIVVXLCeZSOK+3fgshnpAk6RlJkkb82sHPm3tMSGMYdaSQ73shwSXad0iSlOxNcH1CiNcHb+IDF3psnxf3mJDGMOoYyYvHBle0Lia4DKJckqSAEOIY0AbslmV5e8IKLgCjyf1/AfzC3EiJBPQWAAAAAElFTkSuQmCC) no-repeat}.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a,.layui-layer-btn a{display:inline-block;*display: inline;*zoom: 1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position: absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layui-layer{border-radius:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}to{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}to{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translate(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translate(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translate(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translate(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translate(-100%) rotate(-120deg)}to{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);transform:translate(0) rotate(0)}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translate(-100%) rotate(-120deg)}to{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);-ms-transform:translateX(0px) rotate(0deg);transform:translate(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:rollIn;animation-name:rollIn}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.layer-anim-05{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes shake{0%,to{-webkit-transform:translateX(0);transform:translate(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translate(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translate(10px)}}@keyframes shake{0%,to{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translate(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translate(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translate(10px)}}.layer-anim-06{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes bounceOut{to{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceOut{to{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.2s;animation-duration:.2s}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#f8f8f8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right: 0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2e2d3c;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2d93ca}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:0 -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right: -18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 10px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:0 6px;padding:0 15px;border:1px #dedede solid;background-color:#f1f1f1;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#4898d5;background-color:#2e8ded;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:#0009;color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-page .layui-layer-btn,.layui-layer-iframe .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:none}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:none;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(data:image/gif;base64,R0lGODlhPAAYALMPAPPJp/HYxfSwkf50Df5TD/+HAPe5bvqHR/twOviZavyrMu/m3f9EAP9mAP+ZAO7u7iH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6QUNDRERBRjMxRTIwNjgxMTg4QzZERjVGQ0I0MDI1NDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTlDMEI5MzU5RTY2MTFFMTkxRDY5NkVCMDBGOTBFRUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTlDMEI5MzQ5RTY2MTFFMTkxRDY5NkVCMDBGOTBFRUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpENzU2N0YwMTMyMjA2ODExODhDNkRGNUZDQjQwMjU0OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQ0NEREFGMzFFMjA2ODExODhDNkRGNUZDQjQwMjU0OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkFAA8ALAAAAAA8ABgAAATy8MlJq7046827/2AWCEdpCkGodkHSvHCcLGttLUOsw0NqW4EDYog4+CyInbJxwAQMiqjCcMQkGNgsNmFxLZXcisFBLpMNGYF2zRBQAt9vFWCuOwCXAJt9FMSXbhILdnY0FQd7a00SB39Kiw9jhGZoFQSJWgQTjksTCpNmChaYa5ucOxOgdaOkWaanMamqZRaXrZoSObAvA56zDqKHrQyQjbtME5KqlW/DRwbHDcwBv1UTV5hhEnDHVcqEzF2J2te75BLflBsCCFoIgRU4pwOGFQCfZQp4PxUBSX8IrPELscDLkhkDB7bQxQthwocQI0rMEAEAIfkECQUADwAsAAAAADwAGAAABOjwyUmrvTjrzbv/ILcAiWCUQKh+y9G88DsIa40JcQ4ji30JCIZQeAhcAgOdMvEYKZ5PQA8UHFoZtEpSuVQ4vmCHwuhJXK+EqcTA5YbfYrUmcD4jKNt2rAB/GzoHdWdTAXo6fXByGASCV1k4hjADiG8pG41XTA9skS98lGB/l5hDmgmdnqChHIykWBKQnZOqDpYaVaRkAKgvtA6KFwCudxO8DZ+UCh64grYPp7yqwBgLzFYEWRNIvAlecGMhAq1DCGQVu7JNBn7THQDvAObBeVwJ7T4gLW2i+P0PAQkOCBQoxZ/BgwgTYogAACH5BAkFAA8ALAAAAAA8ABgAAATV8MlJq7046827/2AojmMgHE2aIslCckAcb8Kg3umQvFhCMEAgYXcR4HCFpEJhCPAkiKAUiLAEbMdGwcHtcg28w3R8qKCyA6/aASABxvC2JJBNrdcuURQ+rUqMWVt3XmAhC3xweWdHg2oKIm+IU3KLOI1qkJKTEpU3l14iAZpSTg+dKp9cjyKjQRMGdYKXhSFio2VzdQ2ppSEBP5IEvQ9YR7J3tCICmgIVALrHhE+/cMIXCboDxwpy0z5BQ8POxUcD3U88CwnkKQni6E8yMXnw9fb3+BURACH5BAkFAA8ALAAAAAA8ABgAAATe8MlJq7046827/2AojqQUCEeaJkC5CUgcJ0G2JE2u60jrVgICY0gcIhaXQ25QaDp1vt8jUKwaLbhGwcHtcgeNQe23EFqrAkogt/W6cwlp4mwlICW4gXvvyI1LZnRFaRJgfHtghCRUglUIJjmHbgUNBy4AjY4SAGGSXpSWJZiZRI8PnHqeXKAujKQMpmsNqquVP4GZioa0iT8Hrwx/Sqmefj8BuHRxE5xanmChPwKZRxVKznxgDX8/AAh00RQL18S1YYpSDzffpVEXAjvxB9zpiwnaOQfo9fz9/v8ApUQAACH5BAkFAA8ALAAAAAA8ABgAAATO8MlJq7046827/2AojmS1AIkhJEBQasFBMDRDHC62JE3v97iXJVEr1hKThUHBdBQGv99gIZwIjFiG4AFweL/eQtSHqD4Cs2yR0AW7xeOGoXpQYwvuPHQ8FabtNA15g3ENWyUBgEUDg3mFBy8AijV4jWB7UZAlkpMMlZZemD+aJImdjKBej0KdDIKpDoWHJXWdn42iPzmIrQagcGOkL0SKSG15wFF9ZgJ/WLMBvpeFCLtmMVhBOjxxB1RmJgDiAN8bAQkH6Qcs4O3u7/Dx4BEAIfkECQUADwAsAAAAADwAGAAABMrwyUmrvTjrzbv/YCiOV5AgKCosZIsFCCPPc5KZQ6M3QxK4lgWBRuMVFEgDS5LYOXc24CRGlBUc2CwW8BA8vw2BtFuVDbRoByAHdg6WrmG1kU63v9EWoMy41rVsdzoDQAllBH9oBYJOPy2GVYiJWYuMOlyPh5OUlpcuApqbDpWWjiQBfH6TgYxSVER0m50NeS2gZap1BrOmLpBVuVpcTYy1QLdzuQamAqxuYmMTMGUJcBYmeL3REgAJ3gkC2hgLAOUA1tvp6uvs7R8RACH5BAkFAA8ALAAAAAA8ABgAAATs8MlJq7046827/2AojpmQICgqBGSLCQgjz3OyZEFy7LvgXoDZoEAsDBoyxGWRaDifzgHgNwkQGA2HdqstyBKWA3TsZFEPDAJ37RjIzBIBmTy4uQKyAnuNPEwWR3NjPi4CWHtrXgQTcoJjAz8xA4hrbxJijmNwIzF6lFtXUw+YmU+iJJ2foAyipKUNp5wMk6oOV2aupZsihlmqioyvUT94DJ6USEoSAcINYJGHlF6sFE2lA7sjxQ3HXNN+FAu5ZIRUAlcEtF1IDAh2FQaC2FQUATE0NOVACYENAyv0LMCgQeBAtoAIEypcyLDhhAgAIfkECQUADwAsAAAAADwAGAAABODwyUmrvTjrzbv/YCiOWiAgTJoiSUC+V0KodEokZWIIugtPAYNCUSgMZjWV4BI4NJ7QxsH3Mjiu2OsgqaJKFoOoeLB4AbJoR4PLQFQQ4rh7tEin2QyqIM5fiqx2WVtcfg9hfGMjCoFZBWxzC4h8Xh6MaI8SAJJxACKWWZgPmptRnSGLn45ccwGkUWUhgJaDSTgSh6QDIwGfDkhJeq4Ntn+zbAQUYLmwI7Joa2ymQLhxA5QiAKhXBdBcBxcLcHEHzD+Zv0kEhRcACQfvCdLmFAEytdfzZgD7AOX5/wADCrwQAQAh+QQJBQAPACwAAAAAPAAYAAAE6fDJSau9OOvNu/8gFiQkKSxh+gkE474uEqj0tSBw/gq1twCKYBCAOuiOhJllkRg0no0BDxRQOK5YhyJx7BIsAKj4ifgsrNlsodtNUBbOsdjgMaTvA7buOznI5UoaC3eELXowSgF/cm4bAIR3DYcwUwKLYwMcdpBZkpMujQaXY5qcnZ+gEgmjYhyPpleen42WrFEcg7AOqC4AEoq2Bx1onGu8FAi2vriwvAzCE2Gj0B1VhApGqEkVAnFyByhmm1gGKDifUxULfmN0PerZbATLGD8A9+8ZLF0y+f4PAhAIFJgg0L+DCBMqrBABACH5BAkFAA8ALAAAAAA8ABgAAATz8MlJq7046827/2AWCEhpCkGodkHCvHCcpGtdBUSswwRt3waFUGHwVRC7JAOBaR2eh9nG4KhaqwaLS5lMaBvgMNiLAVzPDgAlwOUaBeJ4Q3BZoNGLiaCtpEsWcnJGElR3V1kSSHw7TBIJgXFkFAqGVwoTi0oTA5BiAxaVZ5iZOxOdcaChVqOkMaanYRaUqpcSOa0vBJuwDZ8VhaGID4q4jQ+PsJITAaoOPlu4kgG8g4TBa7gvRsiQyr+GwhMHuAcWBpDhFwCzVQpqFgu3iwR5FgAHYgfvP2vEXAj7+K1YMI7LgXoCf7SQx4CAlIQQI0qcmCECACH5BAkFAA8ALAAAAAA8ABgAAATe8MlJq7046827/2AXACQZnuCSEEzrEkmAzpjAunhLCDRmKI5g0LC4BHJIl+wRSByeh1hKSBUWKYtbMkdQNb7gb+IDrFIVlcR2iwi7GzwOwGwGTI7r5Pu91NDpEwJ5SAR7bmMaC39mVweDOYZuAxsBi1VLCI84kW6UllSYmi6cYZ6fQaGiDKRfkxunQRNqqoWkiBoGpwaBqi2sfRmKn1cPWb21hreUZWYKwA+zogkGhrsguVVEF5maCBIAB2EHdicLAecBxBYL3HkI6j0zgmvK8T1NWgwwz/b9/v8AMUQAACH5BAkFAA8ALAAAAAA8ABgAAATY8MlJq7046827/+AXACQQhCi4CATjvoSwpFww2nMWtG/vEieaZQFwGI8ORbCy4Pl6hMZhmlimFMiswypBPKGNsDicoBm02pxE8IWN3w0BaoHWGijOLxzO5RTrWRMBbT97b2UgWIBIQQmEDIZvAyGKi0aNj5FvlJZHmISaYyFnnVtrmaENkyCknRMAj1GhiB90nXcTeU+pfRytgGoPjoSyhrQqlWgATLo+xW+4NH9ZCsGCzT5vB8tCEkQG4Aa9EwtehAjW3TQLCdgJ6erdAgn0CXLx+Pn6+xwRACH5BAkFAA8ALAAAAAA8ABgAAATM8MlJq7046827/2AojuSzCAeiqsJSaoChzIoBaALB7DyfvJaAwkEsEhWBS6LHIDQaTt0PKFkMjViFiwLoOZ/gJ0FAfVyx2Ypu9w2HB9QAeu5ISgQ9t75BfhnoaAYTCDxte08HQIBzE2sMh25wL4toE3mQYYqURpY8mJmTm0WdO59gQH+ighKEbKaJLwCiDjd3l5h9L2eAChQBjoZ7di9ylMMSS4WHBFNUVnRaSkx6DM1lDwC7CgBbFwCOTAy51yQLCeDM3eTr7O3u7yMRACH5BAkFAA8ALAAAAAA8ABgAAATO8MlJq7046827/2AojiQVCEmaCEGpLYbizI5iLFlwMHzPHy1XJSCjGRVBU6JBYDZ8PEJSuCgaj7jJssHtMqGIrKt6LdeyAq/660sIHwaz2fBYrNdQxnRkldMUD1t3XQRQAi4BfmYBA4NqYIiKZQGOjz4IkZJGlJVdkCULmkYLjZ1Pl0KiNIGmhT6HLnGidJyVeXsioaJZgneuPZhviZJicIO/UcVjfVgWAAeWl7hjskY3GQsIeTxhbxcLAeEByhjPCOcIBwDe7O3u7/DxEhEAIfkECQUADwAsAAAAADwAGAAABMHwyUmrvTjrzbv/YCiOWGACgbcALEpygeHMtGNoAsLsPCG8GEBt6FAAEofkIRE48J68A7ASIBILjay2QYB6f9PHQmElDrbarveZmsrKRLR2/USE4dazXE1ntElVeENYcg19O2CAgoOFhocJQIGLM4RyhwyQL5KTlWiXiSSTNHpofHR/JG+TjZcEYQuinWmfYQ9CgrJZpmtStQ+bRAYAB1sHAAKHoLUxVjcZOV4+vhYxBtYACx0CCdwJytPg4eLj5B8RACH5BAkFAA8ALAAAAAA8ABgAAATI8MlJq7046827/2AojtgCGKgBLGSbLYojz7LClgnC7AyS3C7KgkacJQKWAIHHZBCQQQmsSG0khMsmkwB0GajUQkMg1WmbiO5oCKYOGsjE+Xz1tt3WR3a+DX7vRGIDAXxnUCQxgDRiDQCFWgAuiYoyjI6PTJEtk5SChJg8hyMAlDNvV3uPBEFspQ1wD3KgdZKlYnULqXNcUa2ADQYUn3xPURIBnFQHmkJmWmnGE1NUNi+yTD/RFQEGCt4KBqIvAOQr2ufo6errIhEAIfkECQUADwAsAAAAADwAGAAABLTwyUmrvTjrzbv/YCiO2QIYSqoYxkK+GODMdG1owoHsyCHAF0NtSLtZAgiGcqlEBIATGXEKqCwIzCyD4IIqplNFJanNIqBSMLEqAZTL7NdXTRRLDm/tAUgHT7B5TAR8fUQTgVqEhTWHiEyKizN/jkqDMHORdg9kjmcwaYtxApQMPzALkTNdEpx5nkBCi0YTAYgEcbCFsxQLrUwIq1APAXS4Fjm/AsHCDwsoQy3M0tPU1dbXGBEAIfkECQUADwAsAAAAADwAGAAABNbwyUmrvTjrzbv/4BWMZGiCC+CsrKMYyyljQWuvSqAFQA/os0kgcSjejoqYRYBgOJ8EQDDRqDYKR6Ql8ew+EzKBtTHIZqUTgHfNQKPGDXNWQWmyuwgTdSzPAgN3a0AfA2NYfTcGEgKBXgIhcIeILYoPXI1fkIaTNpWXmAxgIIVWZZwsaIygDI8ge1Wmpw5/qwyDHgFwsisUBKAEJ69XspUSapitJgabk0kVB40HQQAHsJy3ab5eUUEWCwpyzhkCCeUJbt0VCwZZMOnvxgbyBujw9vf4+RcRACH5BAUFAA8ALAAAAAA8ABgAAATi8MlJq7046827/2AWjGRogovirKyjAMspY0Zrr0owe0Fy/IeE7gG4GRWxS4LAaDIIiaQn0ahaq0KjVmEJMJ1gwpAjuJob2jSgggC7GYjO4nwdpLcUwfst4FDpVQV3WlJfe04EHAOAVYNaaw8Bh29jGIyNjjcGEgCTbpAZl2iZNptEnmCgGIuMpKUSkqhNlUqXgq4roIaTiRsBtrgrUgmyCR1/dHa4XBMLu28EUhsGgAYqrrReh2IgAAdXB2sLrqYWS24H0juR13flFwsA8jDr8DVq9fkB9ywKBur5AgocSPBCBAA7) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(data:image/gif;base64,R0lGODlhJQAlAJECAL3L2AYrTv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAAJQAlAAACi5SPqcvtDyGYIFpF690i8xUw3qJBwUlSadmcLqYmGQu6KDIeM13beGzYWWy3DlB4IYaMk+Dso2RWkFCfLPcRvFbZxFLUDTt21BW56TyjRep1e20+i+eYMR145W2eefj+6VFmgTQi+ECVY8iGxcg35phGo/iDFwlTyXWphwlm1imGRdcnuqhHeop6UAAAIfkEBQoAAgAsEAACAAQACwAAAgWMj6nLXAAh+QQFCgACACwVAAUACgALAAACFZQvgRi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwXABEADAADAAACBYyPqcsFACH5BAUKAAIALBUAFQAKAAsAAAITlGKZwWoMHYxqtmplxlNT7ixGAQAh+QQFCgACACwQABgABAALAAACBYyPqctcACH5BAUKAAIALAUAFQAKAAsAAAIVlC+BGL3Z3IlxUmUuhtR2LzHhsiEFACH5BAUKAAIALAEAEQAMAAMAAAIFjI+pywUAIfkEBQoAAgAsBQAFAAoACwAAAhOUYJnAagwdjGq2amXGU1PuLEYBACH5BAUKAAIALBAAAgAEAAsAAAIFhI+py1wAIfkEBQoAAgAsFQAFAAoACwAAAhWUL4AIvdnciXFSZS6G1HYvMeGyIQUAIfkEBQoAAgAsFwARAAwAAwAAAgWEj6nLBQAh+QQFCgACACwVABUACgALAAACE5RgmcBqDB2MarZqZcZTU+4sRgEAIfkEBQoAAgAsEAAYAAQACwAAAgWEj6nLXAAh+QQFCgACACwFABUACgALAAACFZQvgAi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwBABEADAADAAACBYSPqcsFADs=) no-repeat}.layui-layer-loading .layui-layer-loading2,.layui-layer-ico16{width:32px;height:32px;background:url(data:image/gif;base64,R0lGODlhIAAgALMAAP///7Ozs/v7+9bW1uHh4fLy8rq6uoGBgTQ0NAEBARsbG8TExJeXl/39/VRUVAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAAACwAAAAAIAAgAAAE5xDISSlLrOrNp0pKNRCdFhxVolJLEJQUoSgOpSYT4RowNSsvyW1icA16k8MMMRkCBjskBTFDAZyuAEkqCfxIQ2hgQRFvAQEEIjNxVDW6XNE4YagRjuBCwe60smQUDnd4Rz1ZAQZnFAGDd0hihh12CEE9kjAEVlycXIg7BAsMB6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YEvpJivxNaGmLHT0VnOgGYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHQjYKhKP1oZmADdEAAAh+QQFBQAAACwAAAAAGAAXAAAEchDISasKNeuJFKoHs4mUYlJIkmjIV54Soypsa0wmLSnqoTEtBw52mG0AjhYpBxioEqRNy8V0qFzNw+GGwlJki4lBqx1IBgjMkRIghwjrzcDti2/Gh7D9qN774wQGAYOEfwCChIV/gYmDho+QkZKTR3p7EQAh+QQFBQAAACwBAAAAHQAOAAAEchDISWdANesNHHJZwE2DUSEo5SjKKB2HOKGYFLD1CB/DnEoIlkti2PlyuKGEATMBaAACSyGbEDYD4zN1YIEmh0SCQQgYehNmTNNaKsQJXmBuuEYPi9ECAU/UFnNzeUp9VBQEBoFOLmFxWHNoQw6RWEocEQAh+QQFBQAAACwHAAAAGQARAAAEaRDICdZZNOvNDsvfBhBDdpwZgohBgE3nQaki0AYEjEqOGmqDlkEnAzBUjhrA0CoBYhLVSkm4SaAAWkahCFAWTU0A4RxzFWJnzXFWJJWb9pTihRu5dvghl+/7NQmBggo/fYKHCX8AiAmEEQAh+QQFBQAAACwOAAAAEgAYAAAEZXCwAaq9ODAMDOUAI17McYDhWA3mCYpb1RooXBktmsbt944BU6zCQCBQiwPB4jAihiCK86irTB20qvWp7Xq/FYV4TNWNz4oqWoEIgL0HX/eQSLi69boCikTkE2VVDAp5d1p0CW4RACH5BAUFAAAALA4AAAASAB4AAASAkBgCqr3YBIMXvkEIMsxXhcFFpiZqBaTXisBClibgAnd+ijYGq2I4HAamwXBgNHJ8BEbzgPNNjz7LwpnFDLvgLGJMdnw/5DRCrHaE3xbKm6FQwOt1xDnpwCvcJgcJMgEIeCYOCQlrF4YmBIoJVV2CCXZvCooHbwGRcAiKcmFUJhEAIfkEBQUAAAAsDwABABEAHwAABHsQyAkGoRivELInnOFlBjeM1BCiFBdcbMUtKQdTN0CUJru5NJQrYMh5VIFTTKJcOj2HqJQRhEqvqGuU+uw6AwgEwxkOO55lxIihoDjKY8pBoThPxmpAYi+hKzoeewkTdHkZghMIdCOIhIuHfBMOjxiNLR4KCW1ODAlxSxEAIfkEBQUAAAAsCAAOABgAEgAABGwQyEkrCDgbYvvMoOF5ILaNaIoGKroch9hacD3MFMHUBzMHiBtgwJMBFolDB4GoGGBCACKRcAAUWAmzOWJQExysQsJgWj0KqvKalTiYPhp1LBFTtp10Is6mT5gdVFx1bRN8FTsVCAqDOB9+KhEAIfkEBQUAAAAsAgASAB0ADgAABHgQyEmrBePS4bQdQZBdR5IcHmWEgUFQgWKaKbWwwSIhc4LonsXhBSCsQoOSScGQDJiWwOHQnAxWBIYJNXEoFCiEWDI9jCzESey7GwMM5doEwW4jJoypQQ743u1WcTV0CgFzbhJ5XClfHYd/EwZnHoYVDgiOfHKQNREAIfkEBQUAAAAsAAAPABkAEQAABGeQqUQruDjrW3vaYCZ5X2ie6EkcKaooTAsi7ytnTq046BBsNcTvItz4AotMwKZBIC6H6CVAJaCcT0CUBTgaTg5nTCu9GKiDEMPJg5YBBOpwlnVzLwtqyKnZagZWahoMB2M3GgsHSRsRACH5BAUFAAAALAEACAARABgAAARcMKR0gL34npkUyyCAcAmyhBijkGi2UW02VHFt33iu7yiDIDaD4/erEYGDlu/nuBAOJ9Dvc2EcDgFAYIuaXS3bbOh6MIC5IAP5Eh5fk2exC4tpgwZyiyFgvhEMBBEAIfkEBQUAAAAsAAACAA4AHQAABHMQyAnYoViSlFDGXBJ808Ep5KRwV8qEg+pRCOeoioKMwJK0Ekcu54h9AoghKgXIMZgAApQZcCCu2Ax2O6NUud2pmJcyHA4L0uDM/ljYDCnGfGakJQE5YH0wUBYBAUYfBIFkHwaBgxkDgX5lgXpHAXcpBIsRADs=) no-repeat}.layui-layer-tips{background:none;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:5px 10px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px #0003;background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow: hidden}.layui-layer-tips i.layui-layer-TipsT,.layui-layer-tips i.layui-layer-TipsB{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsR,.layui-layer-tips i.layui-layer-TipsL{top:1px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476a7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#bbb5b5;border:none}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#c9c5c5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92b8b1}.layui-layer-iconext{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABkCAYAAAAv8xodAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNS8xMS8xNNdPc0oAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAWb0lEQVR4nO2de2wU172Av5l92t4Hti/YQHmVmFcSE7+WR0h0uRRVVC2pKldpCFJQEJAiFwg4diikgLEghEhpVLVQNblBSFEUVYoi+hc0FQoE87B52JQAiQnBJDY2wfix633O7P1jdwbbxe9d7yx3PgnJc87iM2M+js+cx+8nhMNhdHQeFcRE34COTizRhdZ5pNCF1nmk0IXWeaQwPqxQEIRYtpEKOABL9NoLtAKhWDbSDRF4EsgAbEBHtL3LAIN9CV6+fHlSvS0fOXLkof9oj8pzDJZ499A2YCxgWbJkiSUnJ8cApADZgCEO7ZmAImAyYFuxYsV4Iv+ZpgIFcWhPR2PEU2gbkAlQU1Mz67PPPvvvq1ev/s/f/va3LCIyp8e4PROQD4wrLi7Oam9vP/Dhhx/+9f79+3/Kz8+3AxPi0KaOxoiX0KrM169ff6KgoGAKgMFgEF944YWZ0c9Y+vrLw8BEpAceV1xcnHX48OE3HQ7HRIAxY8ZM2bdv39Lo5zJi2KaOBomH0D1knjFjxsTulX6/Pxj9Uo5Re4rMY1esWDH+8OHDb6akpGR2/4Db7fZEv4zXuF1HI8Ra6H5lDgQC0ttvv/1l9NIfg/Z6yPzee+/t6S1zY2PjpdWrV/8retkagzZ1NEwshR5Q5j179pzfu3dvOxBg5HINKHNTU1Ody+WqbG1tDQENQOcI29TROA+dthsGGYAd+pd5165d94nIfHeE7Q1K5qKioorvv/8+ANwGakfYZlwIh8MEg0F1OtFoNGIwxGMCKL4Eg0G6urqQJAlRFElNTcVsNo/6fcSih84A7BaLRRiCzCMZy5qAQoYm86URtBc3FJnNZjMzZswgKyuLQCCAJEmJvrUhEQwGcbvdZGdn88ILL5CTk0NnZ2dCnmOkPbQqc11d3eMPk3n37t01lZWVbcRW5v966aWXJh44cKAyWWWWZZlgMIjD4aCoqIj09HRkWeb48eO0tbUlTS+tyDxz5kz2799Peno6LS0trFq1Cr/fT2pq6qjez0iE1mUeJorM6enpFBUVkZaWRigUIhgMEgwGB/4GGiEQCOB2u5kzZw779u0jPT0yze/xeJAkKdYrzoNiuELrMg8TSZIIBoNkZWVRWFiI2WwmHA7j8/k4d+4cnZ2dCRl7DhWlZ37iiSd46623cDgcANy5c4c33ngDj8eD0+kc9fsajtC6zMMkGAwiSRKTJk0iLy9PHVZ4PB7Onj1LR0cHZrMZUdT2njGlZ87Ly2P//v1YrVYAmpqaKC0tpaGhAbvdnpBh01CFHm2ZzURmMx4ZmadNm0Zubi6iKCIIAm1tbZw5cwav14vJZNK8zH6/H7fbTWFhIXv27Okh85YtW7h9+zY2mw2TyZSQ+xuK0KrMly9ffiInJ2dC98pEyNzY2HjJ5XJVJoPMoVCI2bNnM2vWLCCyo/Hu3bucPXuWUCiE2WxOyJhzKCgyL1y4kMrKSiyWyO6Fb7/9li1bttDS0pJQmWHwQidU5oMHD+6xWq09NhYlg8zKtJwsy+Tm5vLYY48RDocRRZGmpiaqq6uRZRmTyaR5mX0+Hx6Ph2eeeYbdu3er0jY0NFBaWqoJmWFwQg8kc2j37t3ndZl70l3mvLw8pk6dSjgcxmAw8O2333LhwgVEUUyKF0C/36/KXFFRoUpbX19PaWkp9+7dw263YzTGap1u+Ax0B7rMwyAcDhMIBDAajbhcLsaPH48sy4iiyNdff01tbS1GozHhvdlgUHrmpUuXsmPHDvU3ybVr1ygtLaW9vV0zMkP/QusyDwNljtlsNlNUVMS4ceNUma9evcrVq1eTSma3282yZcvYvn27KvP169cpKyujo6NDUzJD30Kn8UDmJ3NycsZ3r4yDzABzeURkTklJweVykZGRQTgcRhAEamtr+eabbzCZTJoSoC+UnvnnP/85W7duVWWuq6ujtLQUn8+XsKm5/ujrJ2sDuHDhwpxRktkJZC9evDg92WV2OBy4XC5sNhuyLCPLMpcuXaKhoQGj0ZgUMnu9XtxuN7/61a947bXX1PLa2lrKy8s1KzP0LbR18uTJ4uzZs3vMM0uSJMdBZohuOy0rK1vUW+bm5uZ/a11mgFAohM1mY8GCBaSmpiJJEj6fj9raWpqbmzGZTJoUoDfBYBCPx/MfMl+8eJGtW7fS1dWlWZmhn912zc3N4WCvjQUGg0H85S9/mR29NEf/xAIvQGNj43/skU5PT//xypUrp0YvHUQO2WoOWZZxOp04HA4kScJgMNDS0sKdO3cQRVGzAvQmGAwyZswY1q5dq5bduHGD9evX4/F4cDgcmn6WvoTu8vv94YMHD16WJKnHUamCgoIpNTU1s6KXY4mEKRgprQAlJSWn6+vrP+9eYTabUysqKirKy8tnEBmaFKFBqUVR5M6dO9y+fRuj0YgkSUyePJk5c+aoG4+SAYPBgMfj4cqVK2rZhAkT+NnPfoYsywQCgQTe3cD0JXQrENq4ceMPf/7zny+OgtR+4JbX65Vzc3PfSUapTSYTsixz7tw5Ghoa1F5s5syZ5Ofnq2NsraM8x86dO6mpqQEgJSWF7du3s3z5ctxuN16vN8F32Td9CS0BzYyu1HUksdSCIKiLJDU1Ndy4cQODwUA4HGbatGkUFhYC9DidokVEUcThcODz+Xj99deprq4GIs9XXl5OcXExbrebrq6uBN/pw+lvJ0wIXeoho2wwunTpEteuXUMURWRZZtKkSbhcLkRR1LzUBoMBu91OIBDg97//PefOnVPrNm/ezPPPP4/H49Gk1ANt7dKlHgbKXPOVK1e4fPkygiAgyzLZ2dksXLgQq9WaFFI7HA78fj9btmzhxIkTat2mTZt48cUXNSn1YPYq6lIPA5PJhMlk4uuvv+bChQuqvJmZmcyfP5/U1FQCgQCyHKvwJLFHGX4IgsCOHTt6SF1SUsKqVavweDx4PJ5+vsvoMtjNt7rUw0BZ4r516xbV1dWEQiHC4TBOp5OFCxfidDrVDUxaRZFalmXeeOMNjh8/rtatW7eOl19+ma6uLs1IPZTd5LrUw8BgMGAymWhsbOTs2bP4/ZH4OmlpaSxcuJCxY8eqm/+1iiiK2O12ALZv386xY8fUujVr1rBu3To8Hg9utztRt6gy1OMRutTDwGAwYDabaWlpoaqqSh13WiwWXC4X2dnZSSO10Whk9+7dHD16VK1btWoVJSUlmuiph3PeR5d6GCh7n9vb2zl16hSdnZ0IgoDRaKSoqIgpU6aoJ1u0ijL8EEWRHTt2cOTIEbVu5cqVbNiwIeE99XAPsOlSDwNRFDGZTHg8Hqqqqrh37x6CIGAwGMjPz2f69OlIkqRpqQVBwG63Y7Va2bt3L59++qlat2LFCkpLS+nq6kqY1CM5kalLPQyUntrn81FVVUVzc7N6MDY3N1cNOKNllOGHxWJh3759fPLJJ2pdcXExr776Kj6fLyHL5CM9YqxLPQwEQVCXmM+cOUNDQwMQ2bap5d65O0pPrUj90UcfqXW/+MUvyMjISMizxOLMvC71MFCkFgSB6upqTp06xcmTJ+no6EiKPdPwQOqUlBTeffddDh48yHfffcfHH39MW1tbQk7lxCoIxHCkHmkE/6FKnTvC9uKCsgBz7949fD5fUgSa6Y4gCDgcDiwWC4cOHWLdunUcOHAAs9mc1ELDIKU+fvz4tOilPQZtDkrq4uLiLGAcGk1JocxVJ0Ogmb5wOp2MGzcOg8FAdnY2NpstIfcR65/egFIXFRVNjn4ZqxwrA0q9fv36Z6KXmhRaJ3bEozvoV+qOjg5l5j2Wm4P7lbq+vv776JeJX8rSiSvxevtQpM7auHHjD62trTWrV6+e5vP5gqWlpV9HPxOLHCvdqQPwer1TcnNz3zly5MjN3Nxc16VLl86sXbv2dLQ9PcfKI47wsC2MMQxLZSTyAtj77KEXaIlVI73IBaY8pPwk0KZnktU2I80kG+/5oRDQRORwq5VIKjcf8f3VX0fkdHg2kTFza/Qe2uLYpo5GiHcPrTm0vKleZ+T010ObiaQStkavfUR6uViPfbujJJ13EOnFlaTzo74W/Kj8qn5UnmOw9DXLYQbGA9a8vDxTXl6eiYjY2TwQPNYUEEky74gmnbcRSUJfRCSKv47OgPQl9BiAo0ePTjl//vzi8+fPL/7nP/+pLIhkEXup04EJ+fn59vv37//pww8//Gt7e/uBbgsi+ehS6wyCvoROcTqdwpIlS2YIUX7yk5/M+Pzzz6dH62MtdQbAvn37lo4ZM2YKgMPhmHj48OE3dal1hkKfCys+nw9Jknpsl3r22Wcfi5PUIeiRZB6AlJSUzF5SF6BLrdMP/YYC++CDD/4d7jUt8Oyzzz526tSpx6KXsZK6FWD16tX/amxs7BGMUZE6Oq4eiy61Tj/0FwpMeuWVV+6+//77tb2lXrhw4fQYS90JNLS2toZcLldlU1NTXffKlJSUzPfee2+PLrXOQAwUCkxas2ZN8/vvv1/be5NRHKSuBW5///33gaKioopklFqSJPx+P36/n0AgkLRz3oFAgLa2NlpbW2lvb0+KmHwK/W1OCtJN6g8++KBuEFKPdBP9JZJUakmSCAQCZGVlkZOTg8lk0nx0pIchSRJut5ucnBx+85vfkJWVhdvtThqpB9ptFwTuMHipxzH6UheiAaklSSIzM5Onn36auXPnsmjRIjU+nNbPCHbH7/fjdDrZu3cvJSUlHDhwgJycnKSRerChwAaUuqqqKid6OWpSv/TSSxOB/0IjUgeDQfx+P6FQCKfTyfz580lPT9d8dKTuCIKAJElq7JDMzEz279/PrFmz6OzsTNr40L0ZUOoFCxb8eLSlPnDgQKVWpDYajbjdbk6fPo3f7yccDpOamsqCBQsYN24cgUBA04FkFMxmM263mz/84Q+0tEQ2RGZmZvL222/z+OOPa76nHmooMF3qPlBibrS2tlJVVaVGEDKbzcybN48f/ehHBINBTcsAD0Lp3rx5k82bN9Pc3AxEjli9++67PPXUU5ruqYcTCiyhUj9snlprUnd0dPDFF1/Q0dGhBpIpKChg6tSpSJKkealNJhM2m41bt26xefNm7ty5A4DVauXNN98kPz8ft9utxunTEsMNBZYwqV0uV+UgpU5IzmFFap/PxxdffMEPP/yAIAgIgsBTTz3FzJkzkyLniiJ1Q0MDJSUl3Lp1CwCbzcZbb73FvHnz8Hg8mpN6JKHAtC51AQmSWom5EQwGOX36tJoJC2DOnDk8+eSThEIhzc9VK1I3Nzfz2muvcfv2bQA1DNjTTz+N2+3G5/Ml+E4fMNJQYANKfe7cuVGV+uDBg3u0IrXZbEaWZU6fPq0mEgqHw+Tk5JCXl6cmEtK61Ha7naamJjZu3Mg333wDRN4NKioqWLRokaZ66liEAutX6qKiolGV2mq1pmtFaniQc6Wmpob6+no1PcW0adOYN28eoihqvqc2Go3Y7Xbu3r3Lpk2buH79OhAJB7x3716WLFmimZ46VqHAdKn7Qcki2z2RUDgcZuLEicybNw+z2az5BRhF6ra2NsrKyvjqq6+AyDvDrl27+OlPf4rH40m41LEMBTZUqUcaaGaoUs8dYXsjQomMdPXqVerq6hAEgXA4TFZWFvPnz1cTCSWD1Pfv3+d3v/sdly9fBiLDq+3bt7Ns2bKESx3rUGADSt0tFJgjBm32kPq777670L1SkXrx4sXpRI6POWPQ5rBRktffuHGDCxcuIEkSsiyTkZHBokWLcDgcmpdamaf2er2UlZVRVxdZGhBFkW3btiU8OWc8QoH1kLr3B+bNm6fEzIhVKDBV6vnz5+9pbm7+d/dKq9WaXlZWtih6mRmjNoeNkkiooaGB6upqvF4vsixjt9uZP38+NptN8yF1Fak9Hg/l5eXU1tYCkZ66rKyM5557Do/Hk5CpyXiEAnMABovFIixZsiS7d6XX61Veh2O1DpwSbZOVK1dOTU9P/3HvDzQ2NioRkzSR09dgMCCKIk1NTbS0tGAwGJAkCafTidPp1HQPraDkMXS73fz2t7/lxo0bQETqdevWqRm+RptYC50B2C0Wi3D16tXcadOm9RA6EAgEKysrlazosRhopRA5Fe4sLy+fUVFRUWE2m3vEnq6vr/+8pKTkdPRSE6HAlFwqc+bMYfLkyUiShNFo5Pbt2zQ1NSVNBFLlRXbZsmVMmDBBLf/yyy/p6upS852PJrGMnDSgzK+//nrNO++800Ektsf9EbY3KJlzc3Pf8Xq9MnCL+MYUGRTKGDk/P5+pU6ciyzIGg4GGhgY1QWci4ioPFa/Xi8fj4bnnnqOsrEwNTlRTU8POnTuRZTkhzxEroYcq890Rtjccmf9jPD+ahMNhdWxcWFjIpEmTVJlv3LhBXV2dmn9F6yjp237961+zefNmtby6upqtW7cSCATUbFmjTSyEzgRsQ5R5JOPnpJQ5GAxiMBgoLCxk/PjxyLKMKIpcu3aNK1euqC+LWkeR+fnnn2fTpk1q+blz59i2bRuBQAC73Z6Q4QaMXOhMwOZ0OoWLFy/qMj8ERWar1UpRUREZGRnIsowgCFy+fJmvvvoq6WR+8cUXKSkpUctPnDjBtm3b1PQUiXwHGInQuswDoOzVSEtLw+VyMWbMGMLhMOFwmIsXL3Lr1i1MJlNSJAnyeDx0dXWxatUq1q1bp5afOHGCHTt2aEJmGL7QuswDoMjsdDpxuVykpaWp4+jz58/T2NioLolrHUXml19+mTVr1qjlx48fZ+fOnQCakBmGJ7Qu8wAo2WDHjh1LYWEhFktkDcnv91NTU0NLS0vSyOx2u+nq6uKVV15h1apVavmxY8fYtWuXuhyuBZlh6ELrMg+AciJl/PjxFBQUqGPjrq4uzp49S1tbW9KkblN65pKSElauXKmWHz16lMrKSs3JDEMTWpd5AEKhEKFQiClTpjB37lxEUUQQBDo6Ojhz5gwejydpZFZ65g0bNrBixQq1/MiRI+zZsweLxYLD4dBccPzBCq3LPAChUAhJkpg+fTq5uQ9yfN67d0/ds5EseQgVmUtLSykuLlbLP/30U/bt24fVasVms2lOZhic0LrMg0CWZdLT01WZlb0a1dXVSJKE2WzWpAC9CQQC+P1+Xn311R4yf/LJJ+zfvx+LxYLdbtfsswwktC7zEAiFQni9XlJSUmhoaOD8+fM9cnonA6FQiIyMDJYvX66WffTRR/zxj38kJSVF0zJD/0LrMg8Bo9FIZ2cnJ0+eJC0tTd1FlwwLJt0xmUy0tbXx8ccfs3TpUv7xj39w6NChpJAZ+hY6jdGVGSL5BZNSZugZvsDn8yXNtFxvlJM1f/nLX/j73/9Oa2ur+gKYDPQltA2gqqpqzijJnAGMKy4uzkpGmRVEUUyKl76BsNlsavL5sWPHJvhuhkZfP32r0+kUZs+ePbF7YZxkhmiOlfXr1z+TrDLraIO+hA62t7eHOzo6upSCOMoM0cyy3ZLME73WZdYZEn0J7QcoLy+/ePPmzeabN2/e2bBhw9k4yQyRkyT+tWvXnj527Nj/trS0fPnZZ58d0mXWGSp9pUYWiIQa6J1mwgf8QGxlVhgDPPOQ8pjKrCev1zbxSl4fJpKOwsGD09l+oGMkjQ1AG3CSSAZbJen8HUZ+VEvn/xEP7aF1dJKV5J9j0tHphi60ziOFLrTOI4UutM4jhS60ziPF/wHgvbfirVNMHAAAAABJRU5ErkJggg==) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:220px;height:30px;margin:0 auto;line-height:30px;padding:0 5px;border:1px solid #ccc;box-shadow:1px 1px 5px #0000001a inset;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px #0006}.layui-layer-tab .layui-layer-title{padding-left:0;border-bottom:1px solid #ccc;background-color:#eee;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;cursor:default;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-layer-tab .layui-layer-title span.layui-layer-tabnow{height:43px;border-left:1px solid #ccc;border-right:1px solid #ccc;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.xubox_tab_layer{display:block}.xubox_tabclose{position:absolute;right:10px;top:5px;cursor:pointer}.layui-layer-photos{-webkit-animation-duration:1s;animation-duration:1s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display: inline;*zoom: 1;vertical-align:top}.layui-layer-imguide,.layui-layer-imgbar{display:none}.layui-layer-imgprev,.layui-layer-imgnext{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:none;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px}.layui-layer-imgnext{right:10px;background-position:-5px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:#000c;background-color:#000 \\ ;filter:Alpha(opacity=80);color:#fff;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-size:0}.layui-layer-imgtit *{display:inline-block;*display: inline;*zoom: 1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} "); System.addImportMap({ imports: {"jquery":"user:jquery","localforage":"user:localforage","toastify-js":"user:toastify-js","blueimp-md5":"user:blueimp-md5","viewerjs":"user:viewerjs","qrcodejs":"user:qrcodejs"} }); System.set("user:jquery", (()=>{const _=jQuery;('default' in _)||(_.default=_);return _})()); System.set("user:localforage", (()=>{const _=localforage;('default' in _)||(_.default=_);return _})()); System.set("user:toastify-js", (()=>{const _=Toastify;('default' in _)||(_.default=_);return _})()); System.set("user:blueimp-md5", (()=>{const _=md5;('default' in _)||(_.default=_);return _})()); System.set("user:viewerjs", (()=>{const _=Viewer;('default' in _)||(_.default=_);return _})()); System.set("user:qrcodejs", (()=>{const _=QRCode;('default' in _)||(_.default=_);return _})()); System.register("./__entry.js", ['jquery', 'localforage', 'toastify-js', 'blueimp-md5', 'viewerjs', 'qrcodejs'], (function (exports, module) { 'use strict'; var $$1, localforage$1, Toastify$1, md5$1, Viewer$1, QRCode$1; return { setters: [module => { $$1 = module.default; }, module => { localforage$1 = module.default; }, module => { Toastify$1 = module.default; }, module => { md5$1 = module.default; }, module => { Viewer$1 = module.default; }, module => { QRCode$1 = module.default; }], execute: (async function () { var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var _a; const scriptRel = /* @__PURE__ */ (function detectScriptRel() { const relList = typeof document !== "undefined" && document.createElement("link").relList; return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload"; })(); const assetsURL = function(dep) { return "/" + dep; }; const seen = {}; const __vitePreload = function preload(baseModule, deps, importerUrl) { let promise = Promise.resolve(); if (deps && deps.length > 0) { let allSettled2 = function(promises) { return Promise.all( promises.map( (p) => Promise.resolve(p).then( (value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }) ) ) ); }; document.getElementsByTagName("link"); const cspNonceMeta = document.querySelector( "meta[property=csp-nonce]" ); const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute("nonce")); promise = allSettled2( deps.map((dep) => { dep = assetsURL(dep); if (dep in seen) return; seen[dep] = true; const isCss = dep.endsWith(".css"); const cssSelector = isCss ? '[rel="stylesheet"]' : ""; if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) { return; } const link2 = document.createElement("link"); link2.rel = isCss ? "stylesheet" : scriptRel; if (!isCss) { link2.as = "script"; } link2.crossOrigin = ""; link2.href = dep; if (cspNonce) { link2.setAttribute("nonce", cspNonce); } document.head.appendChild(link2); if (isCss) { return new Promise((res, rej) => { link2.addEventListener("load", res); link2.addEventListener( "error", () => rej(new Error(`Unable to preload CSS for ${dep}`)) ); }); } }) ); } function handlePreloadError(err) { const e = new Event("vite:preloadError", { cancelable: true }); e.payload = err; window.dispatchEvent(e); if (!e.defaultPrevented) { throw err; } } return promise.then((res) => { for (const item of res || []) { if (item.status !== "rejected") continue; handlePreloadError(item.reason); } return baseModule().catch(handlePreloadError); }); }; var __defProp2 = Object.defineProperty, __typeError = (msg) => { throw TypeError(msg); }, __publicField2 = (obj, key, value) => ((obj2, key2, value2) => key2 in obj2 ? __defProp2(obj2, key2, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj2[key2] = value2)(obj, "symbol" != typeof key ? key + "" : key, value), __privateMethod = (obj, member, method) => (((obj2, member2, msg) => { member2.has(obj2) || __typeError("Cannot " + msg); })(obj, member, "access private method"), method); const currentHref$1 = window.location.href, isJavDb$1 = currentHref$1.includes("javdb"), isJavBus$1 = currentHref$1.includes("javbus") || currentHref$1.includes("seejav") || currentHref$1.includes("bus") || currentHref$1.includes("javsee") || "javbus" === (() => { try { const _alt = $(".hidden-xs").attr("alt"); return _alt ? _alt.trim().toLowerCase() : void 0; } catch (_e) { return void 0; } })(), isSearchPage = currentHref$1.includes("/search?q") || currentHref$1.includes("/search/") || currentHref$1.includes("/users/"), Status_RUNNING = "RUNNING", Status_SUCCESS = "SUCCESS", Status_FAIL = "FAIL", Status_LOADING = "LOADING", Status_FILTER$1 = "filter", Status_FAVORITE$1 = "favorite", Status_HAS_DOWN$1 = "hasDown", Status_HAS_WATCH$1 = "hasWatch", NO$1 = "no", YES$1 = "yes", qualityOptions = [{ id: "video-mhb", quality: "dmb_w", text: "旧视频源-中画质宽版 (404p)", canSelect: false }, { id: "video-mhb", quality: "sm_s", text: "旧视频源-低画质 (240p)", canSelect: false }, { id: "video-mhb", quality: "dm_s", text: "旧视频源-中画质 (360p)", canSelect: false }, { id: "video-mhb", quality: "dmb_s", text: "旧视频源-中画质 (480p)", canSelect: false }, { id: "video-mhb", quality: "mhb_w", text: "旧视频源-高画质宽版 (404p)", canSelect: false }, { id: "video-mmb", quality: "mmb", text: "中画质 (432p)", canSelect: true }, { id: "video-dm", quality: "dm", text: "低画质 (1000kbps)", canSelect: true }, { id: "video-dmb", quality: "dmb", text: "中画质 (1500kbps)", canSelect: true }, { id: "video-mhb", quality: "mhb", text: "高画质 (576p)", canSelect: true }, { id: "video-hmb", quality: "hmb", text: "HD (720p)", canSelect: true }, { id: "video-hhb", quality: "hhb", text: "FullHD (1080p)", canSelect: true }, { id: "video-hhbs", quality: "hhbs", text: "FullHD (1080p60fps)", canSelect: true }, { id: "video-4k", quality: "4k", text: "4K (2160p)", canSelect: true }, { id: "video-4ks", quality: "4ks", text: "4K (2160p60fps)", canSelect: true }]; let detailPageCss$1 = ""; window.location.href.includes("hideNav=1") && (detailPageCss$1 = "\n .navbar-default {\n display: none !important;\n }\n body {\n padding-top:0px!important;\n }\n "); const javBusStyle = ` <style> .top-bar { z-index: 12345689 !important; } ${detailPageCss$1} .masonry { height: 100% !important; width: 100% !important; padding: 0 15px !important; } .masonry { display: grid; column-gap: 10px; row-gap: 10px; grid-template-columns: repeat(4, minmax(0, 1fr)); align-items: start; } .masonry .item { top: initial !important; left: initial !important; float: none !important; background-color: #c4b1b1; position: relative !important; } .masonry .item:hover { box-shadow: 0 .5em 1em -.125em rgba(10, 10, 10, .1), 0 0 0 1px #485fc7; } .masonry .movie-box { width: 100% !important; height: 100% !important; margin: 0 !important; overflow: inherit !important; } .masonry .movie-box .photo-frame { height: auto !important; margin: 0 !important; position: relative; } .masonry .movie-box img { max-height: 500px; height: 100% !important; object-fit: contain; object-position: top; } .masonry .movie-box img:hover { transform: scale(1.04); transition: transform 0.3s; } .masonry .photo-info span { display: inline-block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .photo-frame .mheyzo, .photo-frame .mcaribbeancom2 { margin-left: 0 !important; } .avatar-box { width: 100% !important; display: flex !important; margin: 0 !important; } .avatar-box .photo-info { display: flex; justify-content: center; align-items: center; gap: 30px; flex-direction: row; background-color: #fff !important; } footer { display: none !important; } .video-title { white-space: normal !important; height: 75px; display: -webkit-box !important; -webkit-box-orient: vertical; -webkit-line-clamp: 3; } </style> `; let detailPageCss = ""; window.location.href.includes("hideNav=1") && (detailPageCss = "\n .main-nav,#search-bar-container {\n display: none !important;\n }\n \n html {\n padding-top:0px!important;\n }\n "); const javdbStyle = ` <style> ${detailPageCss} .navbar { z-index: 12345679 !important; padding: 0 0; } .navbar-link:not(.is-arrowless) { padding-right: 33px; } .sub-header, #footer, .app-desktop-banner, div[data-controller="movie-tab"] .tabs, h3.main-title, div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(3), div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(2), div.video-detail > div:nth-child(4) > div > div.tabs.no-bottom > ul > li:nth-child(1), .top-meta, .float-buttons { display: none !important; } div.tabs.no-bottom, .tabs ul { border-bottom: none !important; } .movie-list .item { position: relative !important; } .video-title { white-space: normal !important; height: 80px; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; } .main-tabs, .tabs { overflow-x: hidden; flex-wrap: wrap; justify-content: flex-start; } .main-tabs ul, .tabs ul { flex-wrap: wrap; flex-grow: 0; } .toolbar { display: flex; } </style> `; const mainCss = ` <style> /* 全局通用样式 */ .fr-btn { float: right; margin-left: 4px !important; } .menu-box { position: fixed; right: 10px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; z-index: 1000; gap: 6px; } .menu-btn { display: inline-block; min-width: 80px; padding: 7px 12px; border-radius: 4px; color: white !important; text-decoration: none; font-weight: bold; font-size: 12px; text-align: center; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); border: none; line-height: 1.3; margin: 0; } .menu-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); opacity: 0.9; } .menu-btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .do-hide { display: none !important; } .main-tab-btn { border-bottom: none !important; border-radius: 3px !important; height: 30px; margin-left: 5px !important; } .jhs-icon { width: 16px; height: 16px; vertical-align: middle; margin-right: 5px; } .tool-box .jhs-icon { width: 1.5rem; height: 1.5rem; } .tabulator { margin: 0 !important; } .tabulator .tabulator-row .action-cell-dropdown { overflow: visible !important; } .tabulator .tabulator-row.tabulator-selectable:hover { cursor: default !important; } .tabulator .tabulator-col.tabulator-sortable[aria-sort="ascending"] .tabulator-arrow { border-bottom-color: #337ab7 !important; } .tabulator .tabulator-col.tabulator-sortable[aria-sort="descending"] .tabulator-arrow { border-top-color: #337ab7 !important; } .tabulator-responsive-collapse { border-top: none !important; } .tabulator-responsive-collapse table { margin-left: 50px !important; } .tabulator-cell { height: auto !important; } .tabulator .tabulator-cell { white-space: normal !important; text-overflow: clip !important; } .tabulator-tableholder { overflow-x: hidden !important; } .jhs-scrollbar::-webkit-scrollbar, .content-panel::-webkit-scrollbar, .tabulator-tableholder::-webkit-scrollbar, .has-navbar-fixed-top::-webkit-scrollbar, .layui-layer-content::-webkit-scrollbar { width: 6px; height: 6px; } .jhs-scrollbar::-webkit-scrollbar-track, .content-panel::-webkit-scrollbar-track, .tabulator-tableholder::-webkit-scrollbar-track, .has-navbar-fixed-top::-webkit-scrollbar-track, .layui-layer-content::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } .jhs-scrollbar::-webkit-scrollbar-thumb, .content-panel::-webkit-scrollbar-thumb, .tabulator-tableholder::-webkit-scrollbar-thumb, .has-navbar-fixed-top::-webkit-scrollbar-thumb, .layui-layer-content::-webkit-scrollbar-thumb { background: #888; border-radius: 10px; } .jhs-scrollbar::-webkit-scrollbar-thumb:hover, .content-panel::-webkit-scrollbar-thumb:hover, .tabulator-tableholder::-webkit-scrollbar-thumb:hover, .has-navbar-fixed-top::-webkit-scrollbar-thumb:hover, .layui-layer-content::-webkit-scrollbar-thumb:hover { background: #555; } .jhs-setting-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; border-radius: 12px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); z-index: 999999; width: 600px; max-height: 80vh; display: flex; flex-direction: column; } .jhs-setting-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid #e2e8f0; } .jhs-setting-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #1e293b; } .jhs-setting-body { flex: 1; overflow-y: auto; padding: 20px; } .jhs-setting-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 16px 20px; border-top: 1px solid #e2e8f0; } .jhs-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.3); z-index: 999998; } .jhs-preview-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.85); z-index: 2147483647; display: flex; overflow: auto; cursor: zoom-out; backdrop-filter: blur(5px); } .jhs-preview-img { border-radius: 4px; margin: auto; cursor: zoom-in; max-width: 95vw; max-height: 95vh; object-fit: contain; display: block; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); } .jhs-preview-img.zoomed { max-width: none; max-height: none; cursor: zoom-out; } .jhs-preview-toolbar { position: fixed; top: 20px; right: 20px; display: flex; gap: 8px; z-index: 2147483648; background: #fff; padding: 8px 12px; border-radius: 8px; border: 1px solid #e5e7eb; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .jhs-preview-btn { padding: 7px 16px; background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; } .jhs-preview-btn:hover { background: #e5e7eb; border-color: #9ca3af; } .jhs-preview-btn.active { color: #fff; background: #2ecc71; border-color: #2ecc71; } .jhs-loading-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); z-index: 2147483649; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; } .jhs-loading-spinner { text-align: center; } .jhs-loading-spinner .spinner { width: 40px; height: 40px; margin: 0 auto 12px; border: 3px solid rgba(255, 255, 255, 0.3); border-top-color: #fff; border-radius: 50%; animation: jhs-spin 0.6s linear infinite; } @keyframes jhs-spin { to { transform: rotate(360deg); } } .jhs-preview-toast { position: fixed; top: 80px; left: 50%; transform: translate(-50%, -20px); z-index: 2147483650; padding: 10px 24px; border-radius: 8px; font-size: 14px; font-weight: 600; font-family: -apple-system, BlinkMacSystemFont, sans-serif; color: #fff; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; pointer-events: none; } .jhs-preview-toast.show { opacity: 1; transform: translate(-50%, 0); } .jhs-preview-toast-error { background: rgba(220, 38, 38, 0.9); box-shadow: 0 4px 16px rgba(220, 38, 38, 0.4); } .jhs-preview-toast-info { background: rgba(59, 130, 246, 0.9); box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4); } .magnet-4k-highlight { background-color: #fff5f5 !important; } .magnet-4k-highlight .name { color: #f40 !important; } #magnets-content .item[data-rank] { position: relative !important; } #magnets-content .item[data-rank] .buttons { position: absolute !important; bottom: 0; right: 0; margin: 0 !important; display: flex; gap: 4px; align-items: center; } #magnets-content .item[data-rank] .name { display: block; padding-right: 260px; } .msf-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.3); z-index: 999997; } .msf-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; border-radius: 10px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2); z-index: 999998; width: 380px; display: flex; flex-direction: column; } .msf-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid #e5e7eb; background: #f9fafb; border-radius: 10px 10px 0 0; } .msf-title { font-size: 15px; font-weight: 600; color: #1e293b; } .msf-close { font-size: 22px; color: #9ca3af; cursor: pointer; padding: 0 6px; line-height: 1; transition: color 0.2s; } .msf-close:hover { color: #ef4444; } .msf-body { padding: 18px; display: flex; flex-direction: column; gap: 16px; } .msf-section { display: flex; flex-direction: column; gap: 8px; } .msf-section-title { font-size: 13px; font-weight: 600; color: #6b7280; } .msf-sort-list { display: flex; flex-direction: column; gap: 6px; } .msf-sort-item { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; cursor: default; transition: all 0.2s; user-select: none; } .msf-sort-item:hover { border-color: #cbd5e1; background: #f1f5f9; } .msf-sort-item.msf-dragging { opacity: 0.5; border-color: #3b82f6; background: #eff6ff; } .msf-sort-item.msf-drag-over { border-color: #3b82f6; background: #eff6ff; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); } .msf-drag-handle { color: #cbd5e1; font-size: 16px; cursor: grab; line-height: 1; letter-spacing: -2px; transition: color 0.2s; } .msf-drag-handle:active { cursor: grabbing; } .msf-sort-item:hover .msf-drag-handle { color: #94a3b8; } .msf-sort-label { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #374151; cursor: pointer; flex: 1; } .msf-sort-checkbox { margin: 0; cursor: pointer; width: 16px; height: 16px; } .msf-sort-hint { font-size: 12px; color: #9ca3af; margin-left: 4px; } .msf-filter-list { display: flex; flex-wrap: wrap; gap: 10px; } .msf-filter-label { display: flex; align-items: center; gap: 6px; padding: 8px 14px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 14px; color: #374151; cursor: pointer; transition: all 0.2s; user-select: none; } .msf-filter-label:hover { border-color: #cbd5e1; background: #f1f5f9; } .msf-filter-checkbox { margin: 0; cursor: pointer; width: 16px; height: 16px; } .msf-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 18px; border-top: 1px solid #e5e7eb; } .msf-btn { padding: 8px 22px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; } .msf-btn-cancel { background: #f3f4f6; color: #374151; } .msf-btn-cancel:hover { background: #e5e7eb; } .msf-btn-confirm { background: #3b82f6; color: #fff; } .msf-btn-confirm:hover { background: #2563eb; } </style> `; const subtitleModalCss = ` <style> .pdb-sub-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 999999; display: flex; align-items: center; justify-content: center; } .pdb-sub-modal { background: #fff; border-radius: 10px; width: 85vw; max-width: 1100px; height: 85vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 8px 40px rgba(0, 0, 0, 0.2); } .pdb-sub-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; border-bottom: 1px solid #e5e7eb; background: #f9fafb; flex-shrink: 0; } .pdb-sub-search-wrap { display: flex; align-items: center; gap: 10px; flex: 1; } .pdb-sub-title { font-weight: 600; font-size: 14px; color: #374151; white-space: nowrap; } .pdb-sub-input { flex: 1; max-width: 400px; padding: 8px 14px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s; } .pdb-sub-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .pdb-sub-btn { padding: 8px 18px; background: #3b82f6; color: #fff; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; transition: background 0.2s; white-space: nowrap; } .pdb-sub-btn:hover { background: #2563eb; } .pdb-sub-115-wrap { display: flex; align-items: center; gap: 8px; margin-left: 16px; flex-shrink: 1; min-width: 0; max-width: 360px; } .pdb-sub-checkbox-group { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-width: 0; } .pdb-sub-checkbox-label { display: flex; align-items: center; gap: 4px; font-size: 13px; color: #374151; cursor: pointer; white-space: nowrap; } .pdb-sub-checkbox { margin: 0; cursor: pointer; } .pdb-sub-close { font-size: 26px; color: #9ca3af; cursor: pointer; padding: 0 8px; line-height: 1; transition: color 0.2s; } .pdb-sub-close:hover { color: #ef4444; } .pdb-sub-body { display: flex; flex: 1; overflow: hidden; } .pdb-sub-content { flex: 1; overflow-y: auto; padding: 12px; } .pdb-sub-preview-wrap { width: 45%; display: flex; flex-direction: column; border-left: 1px solid #e5e7eb; background: #1e1e1e; } .pdb-sub-preview-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid #333; color: #d1d5db; font-size: 13px; flex-shrink: 0; } .pdb-sub-preview-status { font-size: 12px; color: #6b7280; } .pdb-sub-textarea { flex: 1; background: #1e1e1e; color: #2ecc71; border: none; padding: 14px; font-family: Consolas, Monaco, monospace; font-size: 13px; line-height: 1.5; resize: none; outline: none; overflow-y: auto; } .pdb-sub-table { width: 100%; border-collapse: collapse; font-size: 13px; } .pdb-sub-table thead { position: sticky; top: 0; z-index: 1; } .pdb-sub-th { padding: 10px 12px; background: #f3f4f6; color: #374151; font-weight: 600; text-align: left; border-bottom: 2px solid #e5e7eb; white-space: nowrap; } .pdb-sub-tr { transition: background 0.15s; } .pdb-sub-tr:hover { background: #f9fafb; } .pdb-sub-tr.is-previewing { background: #eff6ff; outline: 2px solid #3b82f6; outline-offset: -2px; } .pdb-sub-td, .pdb-sub-td-lang, .pdb-sub-td-ext, .pdb-sub-td-provider { padding: 9px 12px; border-bottom: 1px solid #f3f4f6; vertical-align: middle; } .pdb-sub-td { word-break: break-all; } .pdb-sub-td-lang, .pdb-sub-td-ext { text-align: center; white-space: nowrap; } .pdb-sub-td-actions { padding: 9px 12px; border-bottom: 1px solid #f3f4f6; text-align: center; white-space: nowrap; } .pdb-sub-provider { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 600; } .pdb-sub-provider--xunlei { background: #dbeafe; color: #1d4ed8; } .pdb-sub-provider--subtitlecat { background: #fef3c7; color: #92400e; } .pdb-sub-action-btn { padding: 4px 12px; border: none; border-radius: 4px; font-size: 12px; cursor: pointer; margin: 0 2px; transition: opacity 0.2s; } .pdb-sub-btn-preview { background: #e0f2fe; color: #0369a1; } .pdb-sub-btn-preview:hover { background: #bae6fd; } .pdb-sub-btn-download { background: #dcfce7; color: #166534; } .pdb-sub-btn-download:hover { background: #bbf7d0; } .pdb-sub-btn-upload { background: #ede9fe; color: #5b21b6; } .pdb-sub-btn-upload:hover { background: #ddd6fe; } .pdb-sub-msg { text-align: center; padding: 40px 20px; color: #6b7280; font-size: 14px; } </style> `; function insertStyle(css) { if (css) if (css.includes("<style>")) document.head.insertAdjacentHTML("beforeend", css); else { const style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); } } isJavBus$1 && insertStyle(javBusStyle); isJavDb$1 && insertStyle(javdbStyle); insertStyle( "\n<style>\n .a-normal, /* 白色 */\n .a-primary, /* 浅蓝色 */\n .a-success, /* 浅绿色 */\n .a-danger, /* 浅粉色 */\n .a-warning, /* 浅橙色 */\n .a-info /* 灰色 */\n {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px 14px;\n margin-right: 10px;\n border-radius: 6px;\n text-decoration: none;\n font-size: 13px;\n font-weight: 500;\n transition: all 0.2s ease;\n cursor: pointer;\n border: 1px solid rgba(0, 0, 0, 0.08);\n white-space: nowrap;\n }\n \n .a-primary {\n background: #e0f2fe;\n color: #0369a1;\n border-color: #bae6fd;\n }\n \n .a-primary:hover {\n background: #bae6fd;\n }\n \n .a-success {\n background: #dcfce7;\n color: #166534;\n border-color: #bbf7d0;\n }\n \n .a-success:hover {\n background: #bbf7d0;\n }\n \n .a-danger {\n background: #fee2e2;\n color: #b91c1c;\n border-color: #fecaca;\n }\n \n .a-danger:hover {\n background: #fecaca;\n }\n \n .a-warning {\n background: #ffedd5;\n color: #9a3412;\n border-color: #fed7aa;\n }\n \n .a-warning:hover {\n background: #fed7aa;\n }\n \n .a-info {\n background: #e2e8f0;\n color: #334155;\n border-color: #cbd5e1;\n }\n \n .a-info:hover {\n background: #cbd5e1;\n }\n \n .a-normal {\n background: transparent;\n color: #64748b;\n border-color: #cbd5e1;\n }\n \n .a-normal:hover {\n background: #f8fafc;\n }\n</style>\n" ); insertStyle(mainCss); insertStyle(subtitleModalCss); const isEligibleDmmCoverCode = (value) => { const raw = String(value || "").trim().toUpperCase(); if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(raw)) return false; const code = raw.replace(/\s+/g, "-").toUpperCase(); if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(code) || /^FC2-/.test(code)) return false; if (/^\d{6}[-_]\d{2,3}$/.test(code)) return false; if (/^(DUGA|MYWIFE|HEYZO|PACO|10MU|1PONDO|CARIBBEAN|CARIB|TOKYO|GACHI|REAL|JUKU|AKA|NTR)-/.test(code)) return false; return true; }; window.$ = window.jQuery = $$1; if (typeof $$1.trim !== "function") { $$1.trim = function(text) { return text == null ? "" : (text + "").trim(); }; } if (typeof jQuery !== "undefined" && typeof layer === "undefined") { await __vitePreload(() => module.import('./layer-npqWxmx4-BZXOsKge.js').then((n) => n.l), void 0 ); } window.localforage = localforage$1; window.Toastify = Toastify$1; window.md5 = md5$1; window.Viewer = Viewer$1; window.QRCode = QRCode$1; window.currentHref = currentHref$1; window.isJavDb = isJavDb$1; window.isJavBus = isJavBus$1; window.isSearchPage = isSearchPage; window.Status_RUNNING = Status_RUNNING; window.Status_SUCCESS = Status_SUCCESS; window.Status_FAIL = Status_FAIL; window.Status_LOADING = Status_LOADING; window.Status_FILTER = Status_FILTER$1; window.Status_FAVORITE = Status_FAVORITE$1; window.Status_HAS_DOWN = Status_HAS_DOWN$1; window.Status_HAS_WATCH = Status_HAS_WATCH$1; window.NO = NO$1; window.YES = YES$1; const HAS_GM = typeof GM !== "undefined"; const NEW_GM = ((scope, GM2) => { if (typeof GM_info === "undefined" || GM_info.scriptHandler !== "Tampermonkey" || compareVersions(GM_info.version, "5.3.2") < 0) return; const GM_xmlhttpRequestOrig = GM_xmlhttpRequest; const GM_xmlHttpRequestOrig = GM2.xmlHttpRequest; function compareVersions(v1, v2) { const parts1 = v1.split(".").map(Number); const parts2 = v2.split(".").map(Number); const length = Math.max(parts1.length, parts2.length); for (let i = 0; i < length; i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 > num2) return 1; if (num1 < num2) return -1; } return 0; } function GM_xmlhttpRequestWrapper(odetails) { if (odetails.redirect !== void 0) { return GM_xmlhttpRequestOrig(odetails); } if (odetails.onprogress || odetails.fetch === false) { console.warn("Fetch mode does not support onprogress in the background."); } const { onload, onloadend, onerror, onabort, ontimeout, ...details } = odetails; const handleRedirects = (initialDetails) => { const request = GM_xmlhttpRequestOrig({ ...initialDetails, redirect: "manual", onload: function(response) { if (response.status >= 300 && response.status < 400) { const m = response.responseHeaders.match(/Location:\s*(\S+)/i); const redirectUrl = m && m[1]; if (redirectUrl) { const absoluteUrl = new URL(redirectUrl, initialDetails.url).href; handleRedirects({ ...initialDetails, url: absoluteUrl }); return; } } if (onload) onload.call(this, response); if (onloadend) onloadend.call(this, response); }, onerror: function(response) { if (onerror) onerror.call(this, response); if (onloadend) onloadend.call(this, response); }, onabort: function(response) { if (onabort) onabort.call(this, response); if (onloadend) onloadend.call(this, response); }, ontimeout: function(response) { if (ontimeout) ontimeout.call(this, response); if (onloadend) onloadend.call(this, response); } }); return request; }; return handleRedirects(details); } function GM_xmlHttpRequestWrapper(odetails) { let abort; const p = new Promise((resolve, reject) => { const { onload, ontimeout, onerror, ...send } = odetails; send.onerror = function(r) { if (onerror) { resolve(r); onerror.call(this, r); } else { reject(r); } }; send.ontimeout = function(r) { if (ontimeout) { resolve(r); ontimeout.call(this, r); } else { reject(r); } }; send.onload = function(r) { resolve(r); if (onload) onload.call(this, r); }; const a = GM_xmlhttpRequestWrapper(send).abort; if (abort === true) { a(); } else { abort = a; } }); p.abort = () => { if (typeof abort === "function") { abort(); } else { abort = true; } }; return p; } GM_xmlhttpRequest = GM_xmlhttpRequestWrapper; scope.GM_xmlhttpRequestOrig = GM_xmlhttpRequestOrig; const gopd = Object.getOwnPropertyDescriptor(GM2, "xmlHttpRequest"); if (gopd && gopd.configurable === false) { return { __proto__: GM2, xmlHttpRequest: GM_xmlHttpRequestWrapper, xmlHttpRequestOrig: GM_xmlHttpRequestOrig }; } else { GM2.xmlHttpRequest = GM_xmlHttpRequestWrapper; GM2.xmlHttpRequestOrig = GM_xmlHttpRequestOrig; } })(globalThis, HAS_GM ? GM : {}); if (HAS_GM && NEW_GM) GM = NEW_GM; var _StorageManager_instances, setItem_fn, saveFilterItem_fn; _StorageManager_instances = /* @__PURE__ */ new WeakSet(); setItem_fn = async function(key, data) { key === this.favorite_actresses_key && window.clean_cacheFavoriteActressList(); key === this.blacklist_car_list_key && window.clean_cacheBlacklistCarList(); key === this.setting_key && window.clean_cacheSettingObj(); key === this.car_list_key && window.clean_cacheCarList(); await this.forage.setItem(key, data); }; saveFilterItem_fn = async function(items, storageKey, itemName) { let itemList; if (Array.isArray(items)) itemList = [...items]; else { itemList = await this.forage.getItem(storageKey) || []; if (itemList.includes(items)) { const errorMsg = `${items} ${itemName}已存在`; show.error(errorMsg); throw new Error(errorMsg); } itemList.push(items); } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, storageKey, itemList); return itemList; }; let StorageManager = class _StorageManager { constructor() { obj = this, (member = _StorageManager_instances).has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var obj, member, value; __publicField2(this, "car_list_key", "car_list"); __publicField2(this, "filter_keyword_title_key", "filter_keyword_title"); __publicField2(this, "filter_keyword_review_key", "filter_keyword_review"); __publicField2(this, "setting_key", "setting"); __publicField2(this, "blacklist_key", "blacklist"); __publicField2(this, "blacklist_car_list_key", "blacklist_car_list"); __publicField2(this, "favorite_actresses_key", "favorite_actresses"); __publicField2(this, "highlighted_tags_key", "highlighted_tags"); __publicField2(this, "forage", localforage.createInstance({ driver: localforage.INDEXEDDB, name: "JAV-JHS", version: 1, storeName: "appData" })); __publicField2(this, "cacheCarList", null); __publicField2(this, "cacheBlacklistCarList", null); __publicField2(this, "cacheFavoriteActressList", null); __publicField2(this, "cacheSettingObj", null); if (_StorageManager.instance) throw new Error("StorageManager已被实例化过了!"); _StorageManager.instance = this; } async getCarList() { if (this.cacheCarList) return utils.copyObj(this.cacheCarList); this.cacheCarList = await this.forage.getItem(this.car_list_key) || []; return utils.copyObj(this.cacheCarList); } async getCar(carNum2) { return (await this.getCarList()).find(((item) => item.carNum === carNum2)); } _handleSingleCar(carParam, carList) { let { carNum: carNum2, url, names, actionType, publishTime, starId } = carParam; if (!carNum2) { show.error("番号为空!"); throw new Error("番号为空!"); } if (!url) { show.error("url为空!"); throw new Error("url为空!"); } url.includes("http") || (url = window.location.origin + url); names && (names = names.trim()); let carData = carList.find(((item) => item.carNum === carNum2)); if (carData) { names && (carData.names = names); url && (carData.url = url); publishTime && (carData.publishTime = publishTime); carData.updateDate = utils.getNowStr(); } else { let nowStr = utils.getNowStr(); carData = { carNum: carNum2, url, names, status: "", createDate: nowStr, updateDate: nowStr, publishTime }; starId && (carData.starId = starId); carList.push(carData); } switch (actionType) { case Status_FILTER: if (carData.status === Status_FILTER) { const msg2 = `${carNum2} 已在屏蔽列表中`; show.error(msg2); throw new Error(msg2); } carData.status = Status_FILTER; break; case Status_FAVORITE: if (carData.status === Status_FAVORITE) { const msg2 = `${carNum2} 已在收藏列表中`; show.error(msg2); throw new Error(msg2); } carData.status = Status_FAVORITE; break; case Status_HAS_DOWN: carData.status = Status_HAS_DOWN; break; case Status_HAS_WATCH: carData.status = Status_HAS_WATCH; break; default: const msg = "actionType错误, 请联系作者更正: " + actionType; show.error(msg); throw new Error(msg); } } async saveCar(carData) { const carList = await this.forage.getItem(this.car_list_key) || []; this._handleSingleCar(carData, carList); await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList); await this.removeNewVideoList([carData.carNum]); } async updateCarInfo(carParam) { let { carNum: carNum2, url, names, actionType, publishTime, remark } = carParam; if (!carNum2) { show.error("番号为空!"); throw new Error("番号为空!"); } if (!url) { show.error("url为空!"); throw new Error("url为空!"); } names && (names = names.trim()); const carList = await this.forage.getItem(this.car_list_key) || []; let carData = carList.find(((item) => item.carNum === carNum2)); if (!carData) { const msg = "数据不存在: " + carNum2; show.error(msg); throw new Error(msg); } carData.names = names; carData.url = url; carData.remark = remark; carData.updateDate = utils.getNowStr(); switch (actionType) { case Status_FILTER: carData.status = Status_FILTER; break; case Status_FAVORITE: carData.status = Status_FAVORITE; break; case Status_HAS_DOWN: carData.status = Status_HAS_DOWN; break; case Status_HAS_WATCH: carData.status = Status_HAS_WATCH; break; default: const msg = "actionType错误, 请联系作者更正: " + actionType; show.error(msg); throw new Error(msg); } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList); } async saveCarList(carRecords) { if (!carRecords || !Array.isArray(carRecords) || 0 === carRecords.length) { show.error("记录列表为空!"); throw new Error("记录列表为空!"); } const carList = await this.forage.getItem(this.car_list_key) || []; for (const record of carRecords) try { this._handleSingleCar(record, carList); } catch (error) { throw error; } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, carList); } async removeNewVideoList(carNumList) { const favoriteActressesList = await this.getFavoriteActressList(); let hasAnyListBeenModified = false; const updatedActressList = favoriteActressesList.map(((actress) => { if (!actress.newVideoList || !Array.isArray(actress.newVideoList)) return actress; const newFilteredVideoList = actress.newVideoList.filter(((carNumString) => { const isRemoved = carNumList.includes(carNumString); if (isRemoved) { clog.log("移除关联女优新作品", actress.name, carNumString); hasAnyListBeenModified = true; } return !isRemoved; })); return { ...actress, newVideoList: newFilteredVideoList }; })); hasAnyListBeenModified && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedActressList); } async removeCar(carNum2) { const carList = await this.getCarList(), initialLength = carList.length, updatedList = carList.filter(((car) => car.carNum !== carNum2)); if (updatedList.length === initialLength) { show.error(`${carNum2} 不存在`); return false; } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, updatedList); return true; } async batchRemoveCars(carNumList) { if (!carNumList || 0 === carNumList.length) throw new Error("未传入参数"); const carList = await this.getCarList(), initialLength = carList.length, carNumsSet = new Set(carNumList), updatedList = carList.filter(((car) => !carNumsSet.has(car.carNum))), removedCount = initialLength - updatedList.length; if (0 === removedCount) return removedCount; await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, updatedList); return removedCount; } async getBlacklist() { return await this.forage.getItem(this.blacklist_key) || []; } async addBlacklistItem(item) { let { starId, name: name2, allName, role, movieType, url } = item; if (!starId) throw new Error("缺失starId"); if (!name2) throw new Error("缺失name"); if (!role) throw new Error("缺失role"); const blacklist = await this.getBlacklist(), existData = blacklist.find(((item2) => item2.starId === starId)); if (existData) { existData.url = url; existData.role = role; existData.movieType = movieType; clog.log("更新黑名单演员信息", existData); } else { const info = { starId, name: name2, allName: allName || [name2], createTime: utils.getNowStr(), role, movieType, url }; blacklist.push(info); clog.log("增加黑名单演员信息", info); } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, blacklist); } async updateBlacklistItem(item) { if (!item || !item.starId) throw new Error("参数不全"); const filterActorActressInfoList = await this.getBlacklist(), waitUpdateObj = filterActorActressInfoList.find(((i) => i.starId === item.starId)); if (!waitUpdateObj) throw new Error(`未找到黑名单演员信息:${item.name} ${item.starId}`); item.checkTime && (waitUpdateObj.checkTime = item.checkTime); item.lastPublishTime && (waitUpdateObj.lastPublishTime = item.lastPublishTime); await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, filterActorActressInfoList); } async deleteBlacklistItem(starId) { const blacklist = await this.getBlacklist(), updatedList = blacklist.filter(((item) => item.starId !== starId)); blacklist.length !== updatedList.length && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_key, updatedList); } async getBlacklistCarList() { if (this.cacheBlacklistCarList && this.cacheBlacklistCarList.length > 0) return utils.deepFreeze(this.cacheBlacklistCarList); this.cacheBlacklistCarList = await this.forage.getItem(this.blacklist_car_list_key) || []; setTimeout((() => { utils.deepFreeze(this.cacheBlacklistCarList); }), 0); return this.cacheBlacklistCarList; } async batchSaveBlacklistCarList(carDataList) { const carList = await this.getCarList(); let blacklistCarList = await this.getBlacklistCarList(), hasChanged = false, copyBlacklistCarList = null, carNumList = []; for (const carData of carDataList) { let existBlacklistCarData = blacklistCarList.find(((item) => item.carNum === carData.carNum)); if (existBlacklistCarData) { console.log("已在鉴定记录中", existBlacklistCarData); continue; } let existCarData = carList.find(((item) => item.carNum === carData.carNum)); if (existCarData) console.log("已在鉴定记录中", existCarData); else { copyBlacklistCarList || (copyBlacklistCarList = utils.copyObj(blacklistCarList)); this._handleSingleCar(carData, copyBlacklistCarList); clog.log(`屏蔽演员番号: <span style="color: #f40">${carData.names} ${carData.carNum}</span>`); hasChanged = true; carNumList.push(carData.carNum); } } if (hasChanged) { if (!copyBlacklistCarList) throw new Error("程序异常, 黑名单番号数据对象为空!"); await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, copyBlacklistCarList); await this.removeNewVideoList(carNumList); } } async removeBlacklistCarList(starId) { const filterActorActressCarList = await this.getBlacklistCarList(), newList = filterActorActressCarList.filter(((car) => car.starId !== starId)); newList.length !== filterActorActressCarList.length && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, newList); } async batchRemoveBlacklistCars(carNumList) { if (!carNumList || 0 === carNumList.length) throw new Error("未传入参数"); const blacklistCarList = await this.getBlacklistCarList(), initialLength = blacklistCarList.length, carNumsSet = new Set(carNumList), updatedList = blacklistCarList.filter(((car) => !carNumsSet.has(car.carNum))), removedCount = initialLength - updatedList.length; if (0 === removedCount) return removedCount; await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.blacklist_car_list_key, updatedList); return removedCount; } async getFavoriteActressList() { if (this.cacheFavoriteActressList) return utils.copyObj(this.cacheFavoriteActressList); this.cacheFavoriteActressList = await this.forage.getItem(this.favorite_actresses_key) || []; return utils.copyObj(this.cacheFavoriteActressList); } async addFavoriteActressList(actressList) { const favoriteActressesInfoList = await this.getFavoriteActressList(); let resultCount = 0; for (const actress of actressList) { let { starId, name: name2, allName, avatar, lastCheckTime, lastPublishTime, actressType } = actress; if (!starId) throw new Error("缺失starId"); if (!name2) throw new Error("缺失name"); allName || (allName = [name2]); const uncensoredText = "(無碼)"; if (!actressType) { actressType = name2.includes(uncensoredText) || allName.some(((element) => element.includes(uncensoredText))) ? "uncensored" : "censored"; } name2 = name2.replace(uncensoredText, ""); allName = allName.map(((n) => n.replace(uncensoredText, ""))); let favoriteActresses = favoriteActressesInfoList.find(((item) => item.starId === starId)); if (favoriteActresses) { if ((!favoriteActresses.avatar || !favoriteActresses.avatar.includes("https")) && avatar) { clog.log(avatar); favoriteActresses.avatar = avatar; clog.log(`<span style="color: #f40">补全女优头像: ${name2}</span>`); resultCount++; } if (!favoriteActresses.actressType && actressType) { favoriteActresses.actressType = actressType; clog.log(`<span style="color: #f40">补全女优类别: ${name2} ${actressType}</span>`); resultCount++; } if (favoriteActresses.name.includes(uncensoredText)) { favoriteActresses.name = name2; favoriteActresses.allName = allName; clog.log(`<span style="color: #f40">更正女优名字: ${name2} ${allName}</span>`); resultCount++; } continue; } const nowStr = utils.getNowStr(); favoriteActressesInfoList.push({ starId, name: name2, allName, avatar, lastCheckTime, lastPublishTime, createDate: nowStr, updateDate: nowStr, actressType }); clog.log(`<span style="color: #f40">同步JavDB已收藏的演员: ${name2}</span>`); resultCount++; } resultCount > 0 ? await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, favoriteActressesInfoList) : clog.log("信息已记录, 无需要进行同步收藏的演员"); return resultCount; } async removeFavoriteActress(starId) { const favoriteActressesInfoList = await this.getFavoriteActressList(), initialLength = favoriteActressesInfoList.length, updatedList = favoriteActressesInfoList.filter(((car) => car.starId !== starId)); if (updatedList.length === initialLength) { clog.error(`移除演员失败, ${starId} 不存在`); return false; } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedList); return true; } async updateFavoriteActress(actress) { const favoriteActressesInfoList = await this.getFavoriteActressList(), { starId, name: name2, allName, avatar, lastCheckTime, newVideoList, lastPublishTime, actressType, remark } = actress; if (!starId) throw new Error("缺失starId"); let favoriteActresses = favoriteActressesInfoList.find(((item) => item.starId === starId)); if (!favoriteActresses) { clog.error("未找到演员信息", starId, name2); return false; } name2 && (favoriteActresses.name = name2); allName && (favoriteActresses.allName = allName); avatar && (favoriteActresses.avatar = avatar); null != actressType && (favoriteActresses.actressType = actressType); lastCheckTime && (favoriteActresses.lastCheckTime = lastCheckTime); newVideoList && (favoriteActresses.newVideoList = newVideoList); lastPublishTime && (favoriteActresses.lastPublishTime = lastPublishTime); remark && (favoriteActresses.remark = remark); favoriteActresses.updateDate = utils.getNowStr(); await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, favoriteActressesInfoList); } async getHighlightedTags() { return await this.forage.getItem(this.highlighted_tags_key) || []; } async setHighlightedTags(highlightedTags) { return await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.highlighted_tags_key, highlightedTags); } async saveTitleFilterKeyword(keywords) { await __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_keyword_title_key, "标题关键词"); if (Array.isArray(keywords)) return null; const favoriteActressesList = await this.getFavoriteActressList(); let hasAnyListBeenModified = false; const updatedActressList = favoriteActressesList.map(((actress) => { if (!actress.newVideoList || !Array.isArray(actress.newVideoList)) return actress; const newFilteredVideoList = actress.newVideoList.filter(((carNumString) => { const isRemoved = carNumString.startsWith(keywords); if (isRemoved) { clog.log("移除关联女优新作品", actress.name, carNumString); hasAnyListBeenModified = true; } return !isRemoved; })); return { ...actress, newVideoList: newFilteredVideoList }; })); hasAnyListBeenModified && await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedActressList); } async getTitleFilterKeyword() { return await this.forage.getItem(this.filter_keyword_title_key) || []; } async getReviewFilterKeywordList() { return await this.forage.getItem(this.filter_keyword_review_key) || []; } async saveReviewFilterKeyword(keywords) { return __privateMethod(this, _StorageManager_instances, saveFilterItem_fn).call(this, keywords, this.filter_keyword_review_key, "评论关键词"); } async getSetting(attribute = null, defaultVal) { this.cacheSettingObj || (this.cacheSettingObj = await this.forage.getItem(this.setting_key) || {}); let settingObj = utils.copyObj(this.cacheSettingObj); if (null === attribute) return settingObj; const value = settingObj[attribute]; return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || "" === value.trim() || isNaN(Number(value)) ? value : Number(value) : defaultVal; } async saveSetting(settingObj) { settingObj ? await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.setting_key, settingObj) : show.error("设置对象为空"); } async saveSettingItem(key, value) { if (!key) { show.error("key 不能为空"); return; } let settingObj = await this.getSetting(); settingObj[key] = value; await this.saveSetting(settingObj); } async importData(jsonData) { await this.forage.clear(); const importPromises = []; for (const key in jsonData) { const value = jsonData[key], setPromise = __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, key, value); importPromises.push(setPromise); } await Promise.all(importPromises); } async exportData() { const dataMap = {}; await this.forage.iterate(((value, key) => { dataMap[key] = value; })); if (0 === Object.keys(dataMap).length) throw new Error("没有可导出的数据"); return dataMap; } }; const apiUrl = "https://jdforrepam.com/api"; function buildSignature() { const curr = Math.floor(Date.now() / 1e3), stored_sign = localStorage.getItem("jhs_jdsignature"); if (stored_sign) { const parts = stored_sign.split("."); if (3 === parts.length) { if (curr - parseInt(parts[0]) <= 300) return stored_sign; } } const sign = `${curr}.lpw6vgqzsp.${md5(`${curr}71cf27bb3c0bcdf207b64abecddc970098c7421ee7203b9cdae54478478a199e7d5a6e1a57691123c1a931c057842fb73ba3b3c83bcd69c17ccf174081e3d8aa`)}`; localStorage.setItem("jhs_jdsignature", sign); return sign; } const javDbApi = { getReviews: async (movieId, pageNum = 1, pageSize = 20) => { let url = `${apiUrl}/v1/movies/${movieId}/reviews`, headers = { jdSignature: await buildSignature() }; return (await gmHttp.get(url, { page: pageNum, sort_by: "hotly", limit: pageSize }, headers)).data.reviews; }, searchMovie: async (keyword) => { let url = `${apiUrl}/v2/search`, headers = { "user-agent": "Dart/3.5 (dart:io)", "accept-language": "zh-TW", host: "jdforrepam.com", jdsignature: await buildSignature() }, params = { q: keyword, page: 1, type: "movie", limit: 1, movie_type: "all", from_recent: "false", movie_filter_by: "all", movie_sort_by: "relevance" }; return (await gmHttp.get(url, params, headers)).data.movies; }, getMovieDetail: async (movieId) => { let url = `${apiUrl}/v4/movies/${movieId}`, headers = { jdSignature: await buildSignature() }; const res = await gmHttp.get(url, null, headers); if (!res.data) { show.error("获取视频详情失败: " + res.message); throw new Error(res.message); } const movie = res.data.movie, preview_images = movie.preview_images, imgList = []; preview_images.forEach(((item) => { imgList.push(item.large_url.replace("https://tp.spfcas.com/rhe951l4q", "https://c0.jdbstatic.com")); })); return { movieId: movie.id, actors: movie.actors, duration: movie.duration, title: movie.origin_title, carNum: movie.number, score: movie.score, releaseDate: movie.release_date, watchedCount: movie.watched_count, imgList }; }, related: async (movieId, page = 1, limit = 20) => { let url = `${apiUrl}/v1/lists/related?movie_id=${movieId}&page=${page}&limit=${limit}`, headers = { jdSignature: await buildSignature() }; const res = await gmHttp.get(url, null, headers), dataList = []; res.data.lists.forEach(((item) => { dataList.push({ relatedId: item.id, name: item.name, movieCount: item.movies_count, collectionCount: item.collections_count, viewCount: item.views_count, createTime: utils.formatDate(item.created_at) }); })); return dataList; }, getMagnets: async (movieId) => { let url = `${apiUrl}/v1/movies/${movieId}/magnets`, headers = { jdSignature: await buildSignature() }; return (await gmHttp.get(url, null, headers)).data.magnets; }, playback: async (period = "daily", filter_by = "high_score") => { let url = `${apiUrl}/v1/rankings/playback?period=${period}&filter_by=${filter_by}`, headers = { jdSignature: await buildSignature() }; return (await gmHttp.get(url, null, headers)).data.movies; }, login: async (username, password) => { let url = `${apiUrl}/v1/sessions?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}&device_uuid=04b9534d-5118-53de-9f87-2ddded77111e&device_name=iPhone&device_model=iPhone&platform=ios&system_version=17.4&app_version=official&app_version_number=1.9.29&app_channel=official`, headers = { "user-agent": "Dart/3.5 (dart:io)", "accept-language": "zh-TW", "content-type": "multipart/form-data; boundary=--dio-boundary-2210433284", jdsignature: await buildSignature() }; return await gmHttp.post(url, null, headers); }, top250: async (type = "all", type_value = "", page = 1, limit = 40) => { let url = `${apiUrl}/v1/movies/top?start_rank=1&type=${type}&type_value=${type_value}&ignore_watched=false&page=${page}&limit=${limit}`, headers = { "user-agent": "Dart/3.5 (dart:io)", "accept-language": "zh-TW", host: "jdforrepam.com", authorization: "Bearer " + localStorage.getItem("jhs_appAuthorization"), jdsignature: await buildSignature() }; return await gmHttp.get(url, null, headers); }, buildSignature, markDataListHtml: (movies) => { let moviesHtml = ""; movies.forEach(((movie) => { moviesHtml += ` <div class="item" id="${movie.id}"> <a href="/v/${movie.id}" class="box" title="${movie.origin_title}"> <div class="cover "> <img loading="lazy" src="${movie.cover_url.replace("https://tp.spfcas.com/rhe951l4q", "https://c0.jdbstatic.com")}" alt=""> </div> <div class="video-title"><strong>${movie.number}</strong> ${movie.origin_title}</div> <div class="score" id="score_${movie.id}"> </div> <div class="meta"> ${movie.release_date} </div> <div class="tags has-addons"> ${movie.has_cnsub ? '<span class="tag is-warning">含中字磁鏈</span>' : movie.magnets_count > 0 ? '<span class="tag is-success">含磁鏈</span>' : '<span class="tag is-info">无磁鏈</span>'} ${movie.new_magnets ? '<span class="tag is-info">今日新種</span>' : ""} </div> </a> </div> `; })); return moviesHtml; } }; var _GM_openInTab = /* @__PURE__ */ (() => typeof GM_openInTab != "undefined" ? GM_openInTab : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); class Utils { constructor() { __publicField2(this, "intervalContainer", {}); __publicField2(this, "mimeTypes", { txt: "text/plain", html: "text/html", css: "text/css", csv: "text/csv", json: "application/json", xml: "application/xml", jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", pdf: "application/pdf", doc: "application/msword", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", xls: "application/vnd.ms-excel", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ppt: "application/vnd.ms-powerpoint", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", zip: "application/zip", rar: "application/x-rar-compressed", "7z": "application/x-7z-compressed", mp3: "audio/mpeg", wav: "audio/wav", mp4: "video/mp4", webm: "video/webm", ogg: "audio/ogg" }); __publicField2(this, "timers", /* @__PURE__ */ new Map()); __publicField2(this, "insertStyle", ((css) => { if (css) { -1 === css.indexOf("<style>") && (css = "<style>" + css + "</style>"); $("head").append(css); } })); __publicField2(this, "layerIndexStack", []); Utils.instance || (Utils.instance = this); return Utils.instance; } importResource(url) { let tag; if (url.indexOf("css") >= 0) { tag = document.createElement("link"); tag.setAttribute("rel", "stylesheet"); tag.href = url; } else { tag = document.createElement("script"); tag.setAttribute("type", "text/javascript"); tag.src = url; } document.documentElement.appendChild(tag); } openPage(url, title, shadeClose, event) { if (!url) throw new Error("未传入url"); shadeClose = shadeClose ?? true; if (event && (event.ctrlKey || event.metaKey)) { _GM_openInTab(url.includes("http") ? url : window.location.origin + url, { insert: 0 }); return; } let finalUrl = url; url.includes("/actors/") || url.includes("/star/") || (finalUrl = url.includes("?") ? `${url}&hideNav=1` : `${url}?hideNav=1`); layer.open({ type: 2, title, content: finalUrl, scrollbar: false, shadeClose, area: this.getResponsiveArea(["85%", "90%"]), isOutAnim: false, anim: -1, success: (layero, index) => { this.setupEscClose(index); } }); } _handleGlobalEscKey(e) { if ("Escape" !== e.key && 27 !== e.keyCode) return; if (0 === this.layerIndexStack.length) return; const topLayerIndex = this.layerIndexStack[this.layerIndexStack.length - 1], $layer = $(`#layui-layer${topLayerIndex}`); let viewerExists = false; if ($layer.find(".viewer-container").length > 0) viewerExists = true; else { const iframe = $layer.find(`#layui-layer-iframe${topLayerIndex}`)[0]; if (iframe && iframe.contentDocument) try { $(iframe.contentDocument).find(".viewer-container").length > 0 && (viewerExists = true); } catch (error) { clog.warn("无法检查跨域 iframe 内的 .viewer-container"); } } if (!viewerExists) { this.layerIndexStack.pop(); typeof layer !== "undefined" && layer.close(topLayerIndex); } } setupEscClose(layerIndex) { var _a2; if (!this._boundHandler) { this._boundHandler = this._handleGlobalEscKey.bind(this); $(document).off("keydown.globalLayerEsc"); $(document).on("keydown.globalLayerEsc", this._boundHandler); } -1 === this.layerIndexStack.indexOf(layerIndex) && this.layerIndexStack.push(layerIndex); const $iframe = $(`#layui-layer-iframe${layerIndex}`), eventNamespace = `keydown.layerEsc${layerIndex}`; try { const iframeDocument = null == (_a2 = $iframe[0]) ? void 0 : _a2.contentDocument; if (iframeDocument) { if ("yes" === $iframe.attr("data-esc-bound")) return; $(iframeDocument).off(eventNamespace); $(iframeDocument).on(eventNamespace, this._boundHandler); $iframe.attr("data-esc-bound", "yes"); } } catch (e) { clog.error("iframe监听失败 (跨域或未加载完毕):", e); } } closePage() { storageManager.getSetting("needClosePage", "yes").then(((needClosePage) => { if ("yes" !== needClosePage) return; parent.document.documentElement.style.overflow = "auto"; [".layui-layer-shade", ".layui-layer-move", ".layui-layer"].forEach((function(selector) { const elements = parent.document.querySelectorAll(selector); if (elements.length > 0) { const elementToRemove = elements.length > 1 ? elements[elements.length - 1] : elements[0]; elementToRemove.parentNode.removeChild(elementToRemove); } })); window.close(); })); } loopDetector(condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = true) { const uuid = Math.random(), start = (/* @__PURE__ */ new Date()).getTime(), stopAndRun = (shouldRun) => { clearInterval(this.intervalContainer[uuid]); shouldRun && after && after(); delete this.intervalContainer[uuid]; }; this.intervalContainer[uuid] = setInterval((() => { const timeElapsed = (/* @__PURE__ */ new Date()).getTime() - start; condition() ? stopAndRun(true) : timeElapsed >= timeout && stopAndRun(runWhenTimeout); }), detectInterval); } rightClick(container, targetSelector, callback) { let containerElement; "string" == typeof container ? containerElement = document.querySelector(container) : container instanceof HTMLElement && (containerElement = container); if (!containerElement) { console.warn("rightClick(), 容器无效或未提供,将使用 document.body 进行全局委托。"); containerElement = document.body; } "string" == typeof targetSelector && "" !== targetSelector.trim() ? containerElement.addEventListener("contextmenu", ((event) => { const targetElement = event.target.closest(targetSelector); targetElement && callback(event, targetElement); })) : console.error("rightClick(), 必须提供有效的 targetSelector。"); } q(event, msg, fun, cancelFun) { let x, y; if (event) { x = event.clientX - 130; y = event.clientY - 120; } else { x = window.innerWidth / 2 - 120; y = window.innerHeight / 2 - 120; } let confirmIndex = layer.confirm(msg, { offset: [y, x], title: "提示", btn: ["确定", "取消"], shade: 0, zIndex: 999999991 }, (function() { fun && fun(); layer.close(confirmIndex); }), (function() { cancelFun && cancelFun(); })); } alert(event, msg, yesFun) { let offset; event && (offset = [event.clientX - 200, event.clientY - 120]); let confirmIndex = layer.alert(msg, { offset, shade: 0, zIndex: 999999991 }, (function() { yesFun && yesFun(); layer.close(confirmIndex); })); } getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) { let now; now = dateString ? new Date(dateString) : /* @__PURE__ */ new Date(); const year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, "0"), day = String(now.getDate()).padStart(2, "0"), hours = String(now.getHours()).padStart(2, "0"), minutes = String(now.getMinutes()).padStart(2, "0"), seconds = String(now.getSeconds()).padStart(2, "0"); return `${[year, month, day].join(dateSplitStr)} ${[hours, minutes, seconds].join(timeSplitStr)}`; } formatDate(date2, dateSplitStr = "-", timeSplitStr = ":") { let targetDate; if (date2 instanceof Date) targetDate = date2; else { if ("string" != typeof date2) throw new Error("Invalid date input: must be Date object or date string"); targetDate = new Date(date2); if (isNaN(targetDate.getTime())) throw new Error("Invalid date string"); } const year = targetDate.getFullYear(), month = String(targetDate.getMonth() + 1).padStart(2, "0"), day = String(targetDate.getDate()).padStart(2, "0"), hours = String(targetDate.getHours()).padStart(2, "0"), minutes = String(targetDate.getMinutes()).padStart(2, "0"), seconds = String(targetDate.getSeconds()).padStart(2, "0"); return `${[year, month, day].join(dateSplitStr)} ${[hours, minutes, seconds].join(timeSplitStr)}`; } getHourDifference(date1, date2) { const timestamp1 = date1.getTime(), timestamp2 = date2.getTime(), differenceInHours = Math.abs(timestamp2 - timestamp1) / 36e5; return Math.floor(differenceInHours); } download(data, fileName) { show.info("开始请求下载..."); const fileExtension = fileName.split(".").pop().toLowerCase(); let blob, mimeType = this.mimeTypes[fileExtension] || "application/octet-stream"; if (data instanceof Blob) blob = data; else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) blob = new Blob([data], { type: mimeType }); else if ("string" == typeof data && data.startsWith("data:")) { const byteString = atob(data.split(",")[1]), arrayBuffer = new ArrayBuffer(byteString.length), uintArray = new Uint8Array(arrayBuffer); for (let i = 0; i < byteString.length; i++) uintArray[i] = byteString.charCodeAt(i); blob = new Blob([uintArray], { type: mimeType }); } else blob = new Blob([data], { type: mimeType }); const url = URL.createObjectURL(blob), a = document.createElement("a"); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); setTimeout((() => { document.body.removeChild(a); URL.revokeObjectURL(url); }), 100); } smoothScrollToTop(duration = 500) { return new Promise(((resolve) => { const start = performance.now(), startPosition = window.pageYOffset; window.requestAnimationFrame((function scrollStep(timestamp) { const elapsed = timestamp - start, progress2 = Math.min(elapsed / duration, 1), easeInOutCubic = progress2 < 0.5 ? 4 * progress2 * progress2 * progress2 : 1 - Math.pow(-2 * progress2 + 2, 3) / 2; window.scrollTo(0, startPosition * (1 - easeInOutCubic)); progress2 < 1 ? window.requestAnimationFrame(scrollStep) : resolve(); })); })); } simpleId() { return crypto.randomUUID().replace("-", ""); } isUrl(urlString) { try { new URL(urlString); return true; } catch (_) { return false; } } setHrefParam(key, val) { const newUrl = new URL(window.location.href); newUrl.searchParams.set(key, val); window.history.pushState({}, "", newUrl.toString()); } getUrlParam(url, key) { const searchString = url.split("?")[1]; if (!searchString) return null; const regex = new RegExp(`(?:^|&)${key}=([^&]*)`), match = searchString.match(regex); let value = ""; match && match[1] && (value = decodeURIComponent(match[1].replace(/\+/g, " "))); return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || "" === value.trim() || isNaN(Number(value)) ? value : Number(value) : value; } reBuildSignature() { return javDbApi.buildSignature(); } getResponsiveArea(defaultArea) { const screenWidth = window.innerWidth; return screenWidth >= 1200 ? defaultArea || this.getDefaultArea() : screenWidth >= 768 ? ["70%", "90%"] : ["95%", "95%"]; } getDefaultArea() { return ["85%", "90%"]; } isMobile() { const userAgent = navigator.userAgent.toLowerCase(); return ["iphone", "ipod", "ipad", "android", "blackberry", "windows phone", "nokia", "webos", "opera mini", "mobile", "mobi", "tablet"].some(((keyword) => userAgent.includes(keyword))); } copyToClipboard(type, text) { navigator.clipboard.writeText(text).then((() => show.info(`${type}已复制到剪切板, ${text}`))).catch(((err) => console.error("复制失败: ", err))); } htmlTo$dom(html2) { const parser = new DOMParser(); return $(parser.parseFromString(html2, "text/html")); } addCookie(cookieStr, options = {}) { const { maxAge = 604800, path = "/", domain = "", secure = false, sameSite = "Lax" } = options; cookieStr.split(";").forEach(((cookie) => { const trimmed = cookie.trim(); if (trimmed) { const parts = trimmed.split("="); if (parts.length >= 2 && parts[0].trim()) { let cookieParts = [`${parts[0].trim()}=${parts.slice(1).join("=")}`]; maxAge > 0 && cookieParts.push(`max-age=${maxAge}`); cookieParts.push(`path=${path}`); domain && cookieParts.push(`domain=${domain}`); secure && cookieParts.push("Secure"); sameSite && cookieParts.push(`SameSite=${sameSite}`); console.log("document.cookie = '" + cookieParts.join("; ") + "'"); document.cookie = cookieParts.join("; "); } } })); } isHidden(el) { const element = el.jquery ? el[0] : el; return !element || (element.offsetWidth <= 0 && element.offsetHeight <= 0 || "none" === window.getComputedStyle(element).display); } time(label = "default", unit = "s", precision = 2) { if (this.timers.has(label)) { const timer = this.timers.get(label), elapsedTime = performance.now() - timer.startTime; let formattedTime, unitLabel; if ("s" === timer.unit) { formattedTime = (elapsedTime / 1e3).toFixed(timer.precision); unitLabel = "秒"; } else { formattedTime = elapsedTime.toFixed(timer.precision); unitLabel = "毫秒"; } this.timers.delete(label); return `${label}: ${formattedTime}${unitLabel}`; } this.timers.set(label, { startTime: performance.now(), unit, precision }); } sleep(ms = 1e3) { return new Promise(((resolve) => setTimeout(resolve, ms))); } genericSort(arr, sortRules, emptyLast = true) { if (!Array.isArray(arr) || 0 === arr.length) return []; if (!Array.isArray(sortRules) || 0 === sortRules.length) return [...arr]; const sortedArr = [...arr], safeDateConvert = (value) => { if (value instanceof Date) return value; if ("string" == typeof value) { const date2 = new Date(value); if (!isNaN(date2.getTime())) return date2; } return value; }; return sortedArr.sort(((a, b) => { for (const rule of sortRules) { const { key, order = "asc" } = rule; let valA = a, valB = b; if (null != key) if ("function" == typeof key) { valA = key(a); valB = key(b); } else { valA = a && "object" == typeof a ? a[key] : void 0; valB = b && "object" == typeof b ? b[key] : void 0; } const dateA = safeDateConvert(valA), dateB = safeDateConvert(valB); let comparison = 0; const aIsNull = null == valA, bIsNull = null == valB; if (aIsNull && bIsNull) return 0; if (aIsNull) return emptyLast ? 1 : -1; if (bIsNull) return emptyLast ? 1 : -1; comparison = dateA instanceof Date && dateB instanceof Date ? dateA.getTime() - dateB.getTime() : "number" == typeof valA && "number" == typeof valB ? valA - valB : "string" == typeof valA && "string" == typeof valB ? valA.localeCompare(valB) : String(valA).localeCompare(String(valB)); "desc" === order && (comparison *= -1); if (0 !== comparison) return comparison; } return 0; })); } async retry(fun, tryCount = 3) { let runCount = 0; for (; runCount < tryCount; ) try { const result = await fun(); runCount > 0 && clog.debug(`[重试] 成功,共发起 ${runCount + 1} 次。`); return result; } catch (e) { let errorString = String(e); errorString.startsWith("Error: ") && (errorString = errorString.replace("Error: ", "")); if (errorString.includes("Just a moment") || errorString.includes("重定向") || errorString.toLowerCase().includes("404 page not found") || errorString.toLowerCase().includes("404 not found")) throw e; runCount++; if (runCount === tryCount) { clog.debug(`[重试] 达到最大重试次数 (${tryCount}),最终失败:`, e); throw e; } clog.debug(`[重试] 准备第 ${runCount + 1} 次重试, 错误信息: ${errorString}`); } } copyObj(data) { return JSON.parse(JSON.stringify(data)); } deepFreeze(obj) { if (null === obj || "object" != typeof obj || Object.isFrozen(obj)) return obj; const propNames = Object.getOwnPropertyNames(obj); for (const name2 of propNames) { const value = obj[name2]; value && "object" == typeof value && this.deepFreeze(value); } return Object.freeze(obj); } } class AsyncQueue { constructor() { this.queue = Promise.resolve(); } addTask(fun) { this.queue = this.queue.then((() => fun())).catch(((e) => { console.error("执行异步队列任务失败:", e); })); } async waitAllFinished() { return this.queue; } } class GmHttp { async get(url, params = {}, headers = {}, noRedirect) { return this.gmRequest("GET", url, null, params, headers, noRedirect); } post(url, data = {}, headers = {}) { headers = { "Content-Type": "application/json", ...headers }; let jsonData = JSON.stringify(data); return this.gmRequest("POST", url, jsonData, null, headers); } postForm(url, data = {}, headers = {}) { headers || (headers = {}); headers["Content-Type"] || (headers["Content-Type"] = "application/x-www-form-urlencoded"); let body = ""; data && Object.keys(data).length > 0 && (body = Object.entries(data).map((([key, value]) => `${key}=${value}`)).join("&")); return this.gmRequest("POST", url, body, null, headers); } postFileFormData(url, data = {}, headers = {}) { headers || (headers = {}); const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`; headers["Content-Type"] = `multipart/form-data; boundary=${boundary}`; let body = ""; data && Object.keys(data).length > 0 && (body = Object.entries(data).map((([key, value]) => `--${boundary}\r Content-Disposition: form-data; name="${key}"\r \r ${value}\r `)).join("")); body += `--${boundary}--`; return this.gmRequest("POST", url, body, null, headers); } async downloadFileInChunks(url, headers = {}) { const httpTimeout = await storageManager.getSetting("httpTimeout", 5e3), httpRetryCount = await storageManager.getSetting("httpRetryCount", 3); clog.log("正在获取文件大小..."); let fileSize, mimeType; try { const sizeResponse = await utils.retry((() => new Promise(((resolve, reject) => { _GM_xmlhttpRequest({ method: "GET", url, headers: { ...headers, Range: "bytes=0-0" }, timeout: httpTimeout, onload: resolve, onerror: (e) => reject(new Error("网络错误:无法获取文件大小")), ontimeout: () => reject(new Error("超时:获取文件大小")) }); }))), httpRetryCount); if (206 !== sizeResponse.status && 200 !== sizeResponse.status) throw new Error(`请求文件大小失败,状态码: ${sizeResponse.status}`); { const rangeHeader = sizeResponse.responseHeaders.match(/content-range:\s*bytes\s*\d+-\d+\/(\d+)/i), typeHeader = sizeResponse.responseHeaders.match(/content-type:\s*([^\s;]+)/i); if (rangeHeader && rangeHeader[1]) fileSize = parseInt(rangeHeader[1], 10); else { if (!sizeResponse.responseHeaders.match(/content-length:\s*(\d+)/i) || 200 !== sizeResponse.status) throw new Error("无法从响应头中获取文件总大小,服务器可能不支持 Range 请求。"); { const lengthHeader = sizeResponse.responseHeaders.match(/content-length:\s*(\d+)/i); fileSize = parseInt(lengthHeader[1], 10); clog.warn("服务器返回 200 状态码,可能不支持 Range 请求。将尝试完整下载。"); } } typeHeader && typeHeader[1] && (mimeType = typeHeader[1]); clog.log(`文件总大小:${(fileSize / 1024 / 1024).toFixed(2)} MB, MIME 类型: ${mimeType || "未知"}`); } } catch (e) { clog.error("获取文件大小失败:", e.message); throw e; } if (!fileSize || fileSize <= 0) throw new Error("获取到的文件大小无效或服务器拒绝提供大小信息。"); const numChunks = Math.ceil(fileSize / 1048576), chunkPromises = [], downloadedChunks = new Array(numChunks); clog.log(`文件将被分为 ${numChunks} 块进行下载 (每块约 ${1 .toFixed(2)} MB)`); for (let i = 0; i < numChunks; i++) { const start = 1048576 * i, rangeHeader = `bytes=${start}-${Math.min(start + 1048576 - 1, fileSize - 1)}`, chunkPromise = await utils.retry((() => new Promise(((resolve, reject) => { const currentHeaders = { ...headers, Range: rangeHeader, Accept: "application/octet-stream" }; _GM_xmlhttpRequest({ method: "GET", url, headers: currentHeaders, timeout: httpTimeout, responseType: "arraybuffer", onload: (response) => { if (206 === response.status || 200 === response.status) if (response.response instanceof ArrayBuffer) { downloadedChunks[i] = response.response; clog.log(`成功下载第 ${i + 1}/${numChunks} 块 (${rangeHeader})`); resolve(); } else reject(new Error(`第 ${i + 1} 块响应不是 ArrayBuffer。`)); else reject(new Error(`第 ${i + 1} 块请求失败,状态码: ${response.status}`)); }, onerror: (error) => reject(new Error(`第 ${i + 1} 块网络错误: ${error.error}`)), ontimeout: () => reject(new Error(`第 ${i + 1} 块超时。`)) }); }))), httpRetryCount); chunkPromises.push(chunkPromise); } try { await Promise.all(chunkPromises); clog.log("所有分块下载完成,开始合并..."); } catch (e) { clog.error("分块下载过程中发生错误:", e.message); throw e; } const finalBlob = new Blob(downloadedChunks); finalBlob.size !== fileSize && clog.warn(`警告:合并后的 Blob 大小 (${finalBlob.size}) 与预期文件大小 (${fileSize}) 不匹配!`); return await finalBlob.text(); } async gmRequest(method, url, data = {}, params = {}, headers = {}, noRedirect = false) { if (params && Object.keys(params).length) { const queryString = new URLSearchParams(params).toString(); url += (url.includes("?") ? "&" : "?") + queryString; } const httpTimeout = await storageManager.getSetting("httpTimeout", 5e3), httpRetryCount = await storageManager.getSetting("httpRetryCount", 3); data || (data = void 0); return await utils.retry((() => new Promise(((resolve, reject) => { _GM_xmlhttpRequest({ method, url, headers, timeout: httpTimeout, data, onload: (response) => { try { noRedirect && response.finalUrl !== url && reject("请求被重定向了,URL是:" + response.finalUrl); if (response.status >= 200 && response.status < 300) if (response.responseText) try { resolve(JSON.parse(response.responseText)); } catch (e) { resolve(response.responseText); } else resolve(response.responseText || response); else { clog.error("请求失败,状态码:", response.status, url); if (response.responseText) try { const errorData = JSON.parse(response.responseText); reject(errorData); } catch { reject(new Error(response.responseText || `请求发生错误 ${response.status}`)); } else reject(new Error(`请求发生错误 ${response.status}`)); } } catch (e) { reject(e); } }, onerror: (error) => { clog.error("网络错误:", url); reject(new Error(error.error || "网络错误")); }, ontimeout: () => { reject(new Error("请求超时: " + url)); } }); }))), httpRetryCount); } } class PluginManager { constructor() { this.plugins = /* @__PURE__ */ new Map(); } register(pluginClass) { if ("function" != typeof pluginClass) throw new Error("插件必须是一个类"); const instance = new pluginClass(); instance.pluginManager = this; const lowerName = instance.getName(); if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`); this.plugins.set(lowerName, instance); } getBean(name2) { return this.plugins.get(name2); } async processCss() { const failedCssLoads = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => { try { if ("function" == typeof instance.initCss) { const css = await instance.initCss(); css && utils.insertStyle(css); return { name: name2, status: "fulfilled" }; } return { name: name2, status: "skipped" }; } catch (e) { console.error(`插件 ${name2} 加载 CSS 失败`, e); return { name: name2, status: "rejected", error: e }; } })))).filter(((r) => "rejected" === r.status)); failedCssLoads.length && console.error("以下插件的 CSS 加载失败:", failedCssLoads.map(((p) => p.value.name))); } async processPlugins() { const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => { try { if ("function" == typeof instance.handle) { await instance.handle(); return { name: name2, status: "fulfilled" }; } } catch (e) { clog.error(`插件 ${name2} 执行失败`, e); return { name: name2, status: "rejected", error: e }; } })))).filter(((r) => "rejected" === r.status)); failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map(((p) => p.value.name))); } } class BasePlugin { constructor() { __publicField2(this, "pluginManager", null); __publicField2(this, "settingSvg", '<svg t="1760926954860" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4947" width="200" height="200"><path d="M511.099222 365.825763c-80.7786 0-146.26579 65.482515-146.26579 146.259556 0 80.7786 65.48719 146.259556 146.26579 146.259556 80.777041 0 146.259556-65.480957 146.259556-146.259556C657.358779 431.308278 591.876263 365.825763 511.099222 365.825763L511.099222 365.825763zM511.099222 585.215097c-40.391637 0-73.136012-32.742816-73.136012-73.129778 0-40.391637 32.742816-73.129778 73.136012-73.129778 40.386962 0 73.129778 32.738141 73.129778 73.129778C584.229 552.472281 551.486184 585.215097 511.099222 585.215097L511.099222 585.215097zM511.099222 585.215097M900.893017 568.24369l-26.451395-15.268032c3.065451-27.021784 3.138697-54.472139 0.077922-81.822754l26.373473-15.225955c69.953678-40.391637 93.920921-129.844512 53.533959-199.799749-40.390079-69.95212-129.839837-93.925596-199.799749-53.533959l-26.373473 15.225955c-22.153219-16.330888-45.963059-29.99217-70.896534-40.843585l0-30.545416c0-80.777041-65.48719-146.259556-146.26579-146.259556-80.7786 0-146.259556 65.482515-146.259556 146.259556l0 30.515806c-12.377127 5.421811-24.587501 11.55583-36.562551 18.473743-11.97505 6.917913-23.396854 14.420242-34.277879 22.432179l-26.431136-15.258682c-69.958353-40.391637-159.406553-16.424395-199.79819 53.533959C27.378272 326.082437 51.343956 415.535311 121.299193 455.922273l26.449837 15.275825c-3.063892 27.020226-3.137139 54.465905-0.077922 81.822754l-26.373473 15.224397c-69.953678 40.391637-93.920921 129.841395-53.533959 199.799749 40.391637 69.95212 129.839837 93.920921 199.79819 53.533959l26.375032-15.224397c22.153219 16.32933 45.963059 29.984378 70.896534 40.843585l0 30.537624c0 80.7786 65.48719 146.26579 146.26579 146.26579 80.777041 0 146.259556-65.48719 146.259556-146.26579l0-30.515806c12.377127-5.415577 24.587501-11.55583 36.567226-18.467509 11.97505-6.917913 23.398412-14.420242 34.277879-22.432179l26.423343 15.258682c69.959912 40.391637 159.408111 16.418162 199.799749-53.533959C994.813938 698.085085 970.848254 608.635327 900.893017 568.24369L900.893017 568.24369zM891.096666 731.474653c-20.198936 34.982294-64.923035 46.962019-99.900654 26.770875l-63.331869-36.567226 0 0 0 0-7.988562-4.611422c-18.134004 18.450366-39.024886 34.787489-62.516805 48.353705-23.49971 13.559983-48.091888 23.482568-73.129778 29.964118l0 9.222846 0 0 0 65.828489 0 7.301289c0 40.391637-32.742816 73.136012-73.136012 73.136012-40.386962 0-73.129778-32.742816-73.129778-73.136012l0-7.402588 0-65.72719 0 0 0-9.300768c-50.682014-13.090892-97.855981-39.682547-135.652816-78.232109l-7.983886 4.606747 0 0-63.331869 36.567226c-34.977618 20.191144-79.706394 8.206743-99.900654-26.770875-20.192702-34.977618-8.206743-79.701718 26.770875-99.899095l6.341291-3.657657 0 0 64.972905-37.516316c-14.487254-52.005129-13.929333-106.151555 0.073247-156.593569l-8.057133-4.650384 0 0-63.331869-36.567226c-34.982294-20.192702-46.963578-64.923035-26.770875-99.900654 20.192702-34.97606 64.923035-46.962019 99.900654-26.763083l6.324148 3.649866 0 0 64.996282 37.528784c18.132445-18.450366 39.024886-34.790606 62.516805-48.353705 23.493477-13.559983 48.085654-23.485685 73.129778-29.964118l0-9.229079L437.960093 153.739276l0-7.309082c0-40.385404 32.742816-73.129778 73.129778-73.129778 40.391637 0 73.129778 32.744375 73.129778 73.129778l0 7.404147 0 65.72719 0 9.307001c50.686689 13.086217 97.862215 39.684106 135.657491 78.232109l48.487732-27.997368 22.828023-13.176607c34.977618-20.192702 79.701718-8.212977 99.89442 26.763083 20.198936 34.982294 8.212977 79.706394-26.764641 99.900654l-30.822819 17.79738-32.50905 18.769847 0 0 0 0-7.983886 4.605189c14.488813 52.009805 13.929333 106.159347-0.077922 156.599803l64.979139 37.511641 0 0 6.414537 3.701294C899.303409 651.772936 911.289368 696.498594 891.096666 731.474653L891.096666 731.474653zM891.096666 731.474653M197.330785 324.240361c-1.932465 3.232203-3.824411 6.497135-5.649343 9.785442L197.330785 324.240361 197.330785 324.240361zM197.330785 324.240361M830.515443 690.133926l-5.655577 9.804144C826.793889 696.699632 828.685835 693.433143 830.515443 690.133926L830.515443 690.133926zM830.515443 690.133926M505.297151 146.430195l11.304921 0C512.835324 146.369416 509.067017 146.374091 505.297151 146.430195L505.297151 146.430195zM505.297151 146.430195M516.898176 877.740444l-11.31583 0C509.350653 877.796547 513.125193 877.796547 516.898176 877.740444L516.898176 877.740444zM516.898176 877.740444" fill="#272636" p-id="4948"></path></svg>'); __publicField2(this, "editSvg", '<svg t="1760920692801" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3545" width="200" height="200"><path d="M1013.929675 128.26571a143.759824 143.759824 0 0 1 10.44409 53.858738 84.576649 84.576649 0 0 1-5.836403 30.308339 92.870485 92.870485 0 0 1-18.635533 29.284408 1314.726599 1314.726599 0 0 1-24.983901 24.574329c-7.372299 7.06512-13.82306 13.311095-19.249891 18.737926-6.143582 6.143582-12.082378 11.672806-17.406817 16.382886L720.266444 82.598415c9.317766-8.601015 20.478607-18.942712 33.277737-31.02509s23.448006-21.604931 31.946628-28.67005a102.085858 102.085858 0 0 1 68.193763-22.731255c11.263234 0.307179 22.116896 2.047861 32.560985 5.222045 10.546483 3.071791 19.659463 6.655547 27.441334 10.546483 16.280493 8.601015 34.301667 23.550399 54.063524 45.052936 19.864249 21.502538 35.120812 43.82422 46.076867 67.272226z m-907.20231 570.943576l32.560986-33.38013c17.099637-17.509209 38.397389-39.216533 64.098041-64.917186l84.986221-85.395793 94.303987-94.815953 250.350976-251.477299L850.817567 389.163169 600.46659 640.640468l-93.177663 94.815953c-31.02509 30.410732-58.978389 58.364031-83.859898 83.655111-24.779115 25.29108-45.360116 46.17926-61.743001 62.562146a504.797674 504.797674 0 0 1-55.804206 50.274981c-10.239304 7.884264-20.581 14.130239-31.537055 18.737926a507.152714 507.152714 0 0 1-47.715156 19.86425 1609.311367 1609.311367 0 0 1-131.063087 42.185931c-20.478607 5.426831-35.837563 8.908194-45.974474 10.546483-20.88818 2.35504-34.813633-0.819144-41.981145-9.42016-6.860333-8.601015-8.805801-22.93604-5.73401-43.312254a396.261054 396.261054 0 0 1 11.058448-47.305584c5.836403-20.683394 12.082378-42.185931 18.635532-64.40522 6.553154-22.219289 13.003916-42.697897 19.249891-61.435822 6.143582-18.635533 11.263234-31.537055 15.15417-38.602176 4.607687-10.853662 9.829732-20.785787 15.666135-29.796373a192.49891 192.49891 0 0 1 25.086294-29.796374z" fill="#FF9500" p-id="3546"></path></svg>'); __publicField2(this, "deleteSvg", '<svg t="1760921450746" class="jhs-icon icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4530" width="200" height="200"><path d="M761.086847 36.028779s309.754321-147.538628 424.952209 231.50509c2.047962 6.570546 71.337359 253.862013-220.838618 415.139055-12.970429 7.167869-267.515096 145.746661-370.339877 341.327076 0 0-90.963666-205.649563-393.379455-351.566888-6.399883-3.071944-304.549083-156.583796-163.751664-487.2444 3.669266-8.533177 163.666333-336.20717 466.423449-99.411511l24.575549 27.391498L387.931021 324.279495l237.648977 159.570408-109.139333 145.746661L625.579998 849.069874l-30.719437-205.820227 166.226286-169.81022-216.486698-168.103585L761.086847 36.028779z" fill="#F4382E" p-id="4531"></path></svg>'); __publicField2(this, "checkSvg", '<svg t="1760921633527" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5603" width="200" height="200"><path d="M924.928 544A413.76 413.76 0 0 1 544 924.736v3.264h-64v-3.2A413.696 413.696 0 0 1 99.072 544H96v-64h3.072A413.696 413.696 0 0 1 480 99.2V96h64v3.2a413.76 413.76 0 0 1 380.928 380.8h3.072v64h-3.072z m-64-64A350.016 350.016 0 0 0 544 163.2V288h-64V163.2A350.016 350.016 0 0 0 163.072 480H288v64H163.072A350.016 350.016 0 0 0 480 860.8V736h64v124.8a350.016 350.016 0 0 0 316.928-316.8H736v-64h124.928zM512 544a32 32 0 1 1 32-32 32 32 0 0 1-32 32z" fill="#333333" p-id="5604"></path></svg>'); __publicField2(this, "actressSvg", '<svg t="1760926744637" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1948" width="200" height="200"><path d="M265.950168 668.467036V209.809493A209.809493 209.809493 0 0 1 475.759661 0h40.949536A209.809493 209.809493 0 0 1 726.564189 209.809493v440.435" p-id="1949"></path><path d="M916.558657 825.861124a193.463804 193.463804 0 0 0-137.442564-155.83573l-186.001889-45.795231-10.487631-124.293214H424.106373L412.231008 624.025416l-170.623063 44.44162a193.452429 193.452429 0 0 0-133.666108 154.698244L76.410695 1023.192384h871.189985z" fill="#FFE7D9" p-id="1950"></path><path d="M668.472724 265.682859c68.431223-29.187919 96.140409 100.349111 5.20969 151.774902z" fill="#FFCFB5" p-id="1951"></path><path d="M676.378259 334.421203c1.137487-99.814492-38.674561-172.158671-38.674561-172.15867l-59.740822 11.920865a493.805894 493.805894 0 0 1-80.761583 9.099896 493.669396 493.669396 0 0 1-80.761583-9.099896l-59.683948-11.88674s-39.812048 72.344179-38.776934 172.15867l-1.080613 92.05683c5.209691 56.271486 92.4777 121.381247 195.022161 119.163147 61.196805 0.034125 165.59537-51.573665 165.59537-119.197272z" fill="#FFE7D9" p-id="1952"></path><path d="M322.198905 274.703131c-68.419848-29.187919-96.140409 100.349111-5.209691 151.774902z" fill="#FFCFB5" p-id="1953"></path><path d="M297.390311 812.461526H742.034014a38.458438 38.458438 0 0 1 38.458438 38.458439V1020.325917H258.931873V850.90859a38.458438 38.458438 0 0 1 38.458438-38.447064z" fill="#FFD527" p-id="1954"></path><path d="M690.539973 92.284327c-20.645391 84.287793-275.613121 235.323328-424.589805 117.525166l104.955934-95.548915 139.399042-64.529643z" p-id="1955"></path><path d="M285.321573 383.708519h33.624119v177.118114h-33.624119zM675.855015 383.708519h33.624118v177.118114h-33.624118z" fill="#FFD527" p-id="1956"></path></svg>'); __publicField2(this, "newSvg", '<svg t="1760926857487" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3954" width="200" height="200"><path d="M508.330667 733.994667c-11.008-7.338667-13.44-17.109333-7.338667-29.333334 28.117333-37.888 41.557333-98.986667 40.341333-183.317333v-165.013333c0-14.656 7.338667-23.210667 21.994667-25.664 37.888-1.216 82.496-5.504 133.845333-12.842667 13.44-2.432 21.376 3.072 23.829334 16.512 1.216 12.224-4.266667 19.562667-16.512 21.994667a1787.093333 1787.093333 0 0 1-113.664 11.008c-6.101333 0-9.173333 3.669333-9.173334 10.986666v84.330667h135.68c12.224 1.237333 18.944 7.957333 20.16 20.181333-1.216 10.986667-7.936 17.109333-20.16 18.346667h-36.672v223.658667c-1.216 12.202667-7.936 18.944-20.16 20.16-11.008-1.216-17.109333-7.957333-18.346666-20.16V501.162667h-60.48v18.346666c1.216 92.885333-13.44 161.92-44.010667 207.146667-6.101333 12.224-15.893333 14.677333-29.333333 7.338667z m-131.989334-282.325334c-1.237333 0-2.453333 0.618667-3.669333 1.834667h45.824a522.666667 522.666667 0 0 0 16.512-31.168c7.317333-12.224 12.224-20.778667 14.656-25.664 6.122667-11.008 15.274667-14.677333 27.52-11.008 9.770667 6.122667 12.202667 14.058667 7.317333 23.829333-4.906667 9.792-13.44 24.448-25.664 44.010667h49.493334c9.770667 1.216 15.274667 6.72 16.512 16.490667-1.237333 11.008-6.741333 17.109333-16.512 18.346666h-82.496a12.437333 12.437333 0 0 1 3.669333 9.173334v38.485333h69.653333c9.792 1.216 15.296 6.72 16.512 16.490667-1.216 11.008-6.72 17.130667-16.512 18.346666h-69.653333v108.16c0 34.218667-15.274667 51.946667-45.845333 53.162667h-16.490667a195.157333 195.157333 0 0 1-20.16 1.834667c-12.224 0-19.562667-6.72-22.016-20.16 1.237333-12.224 7.338667-18.944 18.346667-20.16 2.432 0 6.101333 0.597333 10.986666 1.834666h11.008c15.893333 0 23.829333-8.554667 23.829334-25.685333v-98.986667H314.026667c-11.008-1.216-17.109333-7.338667-18.346667-18.346666 1.237333-9.770667 7.338667-15.274667 18.346667-16.490667h75.157333V497.493333c0-3.669333 1.216-6.72 3.669333-9.173333h-89.813333c-11.029333-1.216-17.130667-7.317333-18.346667-18.325333 1.216-9.770667 7.317333-15.274667 18.346667-16.490667h56.810667c-3.669333-1.216-6.72-4.266667-9.173334-9.173333-1.216-1.216-3.050667-4.266667-5.482666-9.173334a758.336 758.336 0 0 0-14.677334-23.829333c-4.885333-9.770667-3.050667-17.706667 5.504-23.829333 11.008-3.669333 19.562667-1.216 25.664 7.338666 2.453333 2.432 6.122667 7.338667 11.008 14.656 6.101333 8.554667 9.770667 14.08 10.986667 16.512 4.906667 9.770667 2.453333 18.346667-7.317333 25.664z m-60.501333-71.509333c-9.792-1.216-15.274667-7.317333-16.512-18.346667 1.237333-9.749333 6.72-15.253333 16.512-16.490666h75.157333c-3.669333-12.202667-7.338667-21.973333-10.986666-29.333334-1.237333-12.202667 3.648-19.541333 14.656-21.973333 12.224-2.453333 21.397333 1.216 27.52 10.986667 0 1.216 0.597333 3.669333 1.813333 7.338666 4.906667 15.872 9.173333 26.88 12.842667 32.981334h60.48c11.008 1.237333 17.130667 6.741333 18.346666 16.512-1.216 11.008-7.338667 17.109333-18.346666 18.346666h-181.482667z m-14.677333 311.68c-8.533333-6.122667-10.986667-14.08-7.338667-23.829333a1659.648 1659.648 0 0 0 33.002667-66.005334c4.906667-9.792 12.224-12.842667 22.016-9.173333 9.770667 4.906667 13.44 12.224 10.986666 21.994667-3.669333 6.122667-9.173333 17.728-16.490666 34.837333-8.554667 15.893333-14.677333 27.52-18.346667 34.837333-4.885333 8.554667-12.821333 11.008-23.829333 7.338667z m201.664-25.664c-9.770667 4.885333-18.346667 2.432-25.664-7.338667a1138.56 1138.56 0 0 1-27.498667-44.010666c-4.885333-8.533333-3.050667-16.490667 5.504-23.829334 9.770667-3.669333 18.346667-1.216 25.664 7.338667l14.677333 21.994667c6.101333 9.770667 10.389333 17.109333 12.821334 21.994666 4.906667 8.554667 3.050667 16.512-5.504 23.850667z" fill="#333333" p-id="3955"></path><path d="M675.328 117.717333A425.429333 425.429333 0 0 0 512 85.333333C276.352 85.333333 85.333333 276.352 85.333333 512s191.018667 426.666667 426.666667 426.666667 426.666667-191.018667 426.666667-426.666667c0-56.746667-11.093333-112-32.384-163.328a21.333333 21.333333 0 0 0-39.402667 16.341333A382.762667 382.762667 0 0 1 896 512c0 212.074667-171.925333 384-384 384S128 724.074667 128 512 299.925333 128 512 128c51.114667 0 100.8 9.984 146.986667 29.12a21.333333 21.333333 0 0 0 16.341333-39.402667z" fill="#333333" p-id="3956"></path></svg>'); __publicField2(this, "refreshSvg", '<svg t="1760926993643" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5942" width="200" height="200"><path d="M511.966722 0a511.966722 511.966722 0 1 0 179.828311 32.445891l-22.46254 59.964102A447.970882 447.970882 0 1 1 511.966722 63.99584a31.99792 31.99792 0 0 0 0-63.99584z" fill="#333333" p-id="5943"></path><path d="M649.2378 9.151405A30.909991 30.909991 0 0 1 671.316364 0h193.267438a31.99792 31.99792 0 0 1 31.357962 31.99792c0 17.662852-13.759106 31.99792-31.357962 31.99792H703.954243v160.629559a31.99792 31.99792 0 0 1-31.99792 31.357962 31.485953 31.485953 0 0 1-31.99792-31.357962V31.357962c0-8.511447 3.647763-16.318939 9.343392-21.950573z" fill="#333333" p-id="5944"></path></svg>'); __publicField2(this, "blacklistSvg", '<svg t="1761386375897" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1936" width="200" height="200"><path d="M513.199827 65.667605c-246.537999 0-446.399933 199.861934-446.399933 446.399933 0 246.553349 199.861934 446.399933 446.399933 446.399933 246.553349 0 446.399933-199.846584 446.399933-446.399933C959.599759 265.529539 759.753175 65.667605 513.199827 65.667605zM513.199827 894.697075c-211.320916 0-382.629537-171.322947-382.629537-382.628514 0-94.183056 34.029024-180.417069 90.461291-247.080352l165.389818 165.389818c4.320399 39.651069 26.816762 73.840752 58.981323 94.068446-72.189136 27.369348-123.517151 97.156784-123.517151 178.936345l337.541643 0 100.846826 100.846826C693.608709 860.664981 607.375719 894.697075 513.199827 894.697075zM805.362956 759.14175 697.264982 651.0448c-16.556071-58.332547-60.10082-105.306394-116.275213-126.601396 35.888372-22.570042 59.752896-62.511729 59.752896-108.032482 0-70.436212-57.108672-127.542838-127.542838-127.542838-48.218188 0-90.184999 26.765597-111.865787 66.245773L266.120498 219.900316c66.663282-56.432267 152.897296-90.461291 247.079328-90.461291 211.304544 0 382.628514 171.308621 382.628514 382.629537C895.82834 606.244454 861.796246 692.476421 805.362956 759.14175z" fill="#272636" p-id="1937"></path></svg>'); __publicField2(this, "removeSvg", '<svg t="1761958343616" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" width="200" height="200"><path d="M405.312 736a32 32 0 0 1-32-32V448a32 32 0 0 1 64 0v256a32 32 0 0 1-32 32zM650.688 448a32 32 0 0 0-64 0v256a32 32 0 1 0 64 0V448z" fill="#333333" p-id="1940"></path><path d="M514.816 53.312h-2.752c-21.504 0-39.808 0-55.04 1.408a116.672 116.672 0 0 0-45.568 12.352c-5.76 3.008-11.2 6.528-16.32 10.496a116.608 116.608 0 0 0-30.144 36.352c-7.552 13.248-15.168 29.888-24.128 49.536l-17.856 39.232H128a32 32 0 0 0 0 64h32V832A160 160 0 0 0 320 992h384a160 160 0 0 0 160-160V266.688h32a32 32 0 0 0 0-64h-190.912l-20.992-43.264c-9.152-18.944-16.896-35.008-24.576-47.744a116.608 116.608 0 0 0-30.208-35.072 117.376 117.376 0 0 0-16.064-10.112 116.608 116.608 0 0 0-44.736-11.84c-14.784-1.28-32.64-1.28-53.696-1.28zM800 266.688V832a96 96 0 0 1-96 96H320A96 96 0 0 1 224 832V266.688h576z m-166.016-64h-240.64l5.184-11.456c9.664-21.184 16.064-35.2 22.016-45.568a54.144 54.144 0 0 1 13.568-17.28 53.312 53.312 0 0 1 7.424-4.8 54.144 54.144 0 0 1 21.312-5.12c11.968-1.088 27.328-1.152 50.624-1.152 22.72 0 37.76 0 49.344 1.088 11.072 0.96 16.704 2.752 20.928 4.928 2.56 1.28 4.992 2.88 7.36 4.608 3.776 2.816 7.808 7.168 13.504 16.64 6.016 10.048 12.608 23.488 22.528 43.968l6.848 14.08z" fill="#333333" p-id="1941"></path></svg>'); __publicField2(this, "copySvg", '<svg t="1749017229420" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9184" width="200" height="200"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667z m0 810.666666c-204.8 0-373.333333-168.533333-373.333333-373.333333S307.2 138.666667 512 138.666667 885.333333 307.2 885.333333 512 716.8 885.333333 512 885.333333z" fill="#666666" p-id="9185"></path><path d="M512 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 1 0-85.333334 0Z" fill="#666666" p-id="9186"></path><path d="M341.333333 512m-42.666666 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9187"></path><path d="M682.666667 512m-42.666667 0a42.666667 42.666667 0 1 0 85.333333 0 42.666667 42.666667 0 1 0-85.333333 0Z" fill="#666666" p-id="9188"></path></svg>'); __publicField2(this, "titleSvg", '<svg t="1747553289744" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7507" width="200" height="200"><path d="M959.8 150.8c0-2.3-1.9-4.2-4.2-4.2H253.3c-2.3 0-4.2 1.9-4.2 4.2v115.9c0 2.3 1.9 4.2 4.2 4.2h702.3c2.3 0 4.2-1.9 4.2-4.2V150.8z" fill="" p-id="7508"></path><path d="M126.4 208.8m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7509"></path><path d="M851.5 453.7c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.7-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V453.7z" fill="" p-id="7510"></path><path d="M126.4 512m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7511"></path><path d="M851.5 756.9c0-2.1-1.8-3.9-3.9-3.9H252.9c-2.1 0-3.9 1.8-3.9 3.9v116.6c0 2.1 1.7 3.9 3.9 3.9h594.7c2.1 0 3.9-1.7 3.9-3.9V756.9z" fill="" p-id="7512"></path><path d="M126.4 815.2m-62.2 0a62.2 62.2 0 1 0 124.4 0 62.2 62.2 0 1 0-124.4 0Z" fill="" p-id="7513"></path></svg>'); __publicField2(this, "carNumSvg", '<svg t="1747552574854" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3539" width="200" height="200"><path d="M920.337035 447.804932c-6.067182-6.067182-10.918677-11.643178-16.985859-17.71036l48.536436-30.334889-42.469254-109.207238-121.340579 12.134365c-6.067182-6.067182-6.067182-12.134365-12.134365-18.201547-12.134365-12.134365-18.201547-24.267706-24.267706-30.334889-24.26873-36.402071-30.334889-42.469254-54.603619-42.469254H339.116511c-18.201547 0-24.267706 6.067182-54.603619 42.469254-6.067182 6.067182-12.134365 18.201547-24.267706 30.334889 0 0-6.067182 6.067182-12.134365 18.201547l-115.27442-12.134365-48.536436 109.207238 51.090608 24.378223c-6.067182 6.067182-30.334889 34.660404-30.334889 34.660405l-15.542998 22.280446-12.282744 17.018605c-6.067182 12.134365-5.064342 10.868535-5.064342 29.070082v224.480635c0 36.402071 18.201547 60.670801 54.603618 60.670801h115.273397c36.402071 0 54.603619-24.267706 54.603619-54.603619v-18.201547h424.693562v18.201547c0 30.334889 18.201547 54.603619 54.603618 54.603619h115.273397c36.402071 0 60.670801-24.267706 60.670801-60.670801V539.300786c0-42.469254 0.685615-46.662763-11.44875-64.863287-4.731768-6.744611-11.94403-16.196891-20.101827-26.632567z m-35.186383-78.381161l-30.334889 18.201547-12.134365-12.134365c-6.067182-8.899694-12.134365-12.134365-12.134365-18.201547l42.469254-6.067183 12.134365 18.201548z m-533.899776-97.072873h339.755054l78.871325 103.140055H272.378527l78.872349-103.140055zM175.305655 357.290429h36.402071c-6.067182 6.067182-6.067182 12.134365-12.134365 18.201547l-18.201547 6.067183-18.201547-12.134365 12.135388-12.134365z m667.375743 394.35765h-54.603619V678.843936H242.043638v72.804143H132.837424V527.167444c0-12.134365-0.041956-20.662599 1.216711-23.556508 1.258667-2.89391 9.955746-16.924461 21.193695-29.173437l35.722596-38.276768h639.576607l21.917172 20.938891c6.067182 6.067182 21.847587 21.366633 25.712615 28.732392 7.621585 9.996678 6.973832 10.999518 13.041014 23.133883v242.682182h-48.536436zM242.043638 533.234627h133.474944v60.670801H242.043638v-60.670801z m412.559197 0h133.474944v60.670801H654.602835v-60.670801z" p-id="3540"></path></svg>'); __publicField2(this, "downSvg", '<svg t="1747552626242" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4551" width="200" height="200"><path d="M641.6 660l-8.64-64 32-4.32a211.2 211.2 0 0 0-26.72-420.32 215.36 215.36 0 0 0-213.12 192 94.56 94.56 0 0 0 0 11.52v41.28h-64V384v-7.04a153.12 153.12 0 0 1 0-19.52A279.84 279.84 0 0 1 636.16 108H640A275.2 275.2 0 0 1 673.28 656z" fill="#333333" p-id="4552"></path><path d="M490.4 446.24l-7.52-39.84a182.4 182.4 0 0 1 107.52-162.88l29.12-13.28L646.08 288l-29.12 13.28a117.92 117.92 0 0 0-70.08 101.28l6.24 30.4zM392.96 652.32h-78.72A202.24 202.24 0 0 1 256 256l30.72-9.12 18.24 61.28-30.72 9.12a138.24 138.24 0 0 0 39.68 270.72h78.72zM479.2 512h64v320h-64z" fill="#333333" p-id="4553"></path><path d="M510.4 908l-156.32-147.68 43.84-46.4 112.48 106.08 112.8-106.08 43.84 46.56-156.64 147.52z" fill="#333333" p-id="4554"></path></svg>'); __publicField2(this, "handleSvg", '<svg t="1749106236917" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2628" width="200" height="200"><path d="M838 989.48a32 32 0 0 1-22.5-9.22L519.3 687.6 207.48 980.8a32 32 0 0 1-54-23.32V136.52A98.54 98.54 0 0 1 252 38.1h519.6A98.52 98.52 0 0 1 870 136.52v820.96a32 32 0 0 1-32 32zM252 102.1a34.46 34.46 0 0 0-34.42 34.42v746.96L498 619.84a32 32 0 0 1 44.42 0.56L806 880.88V136.52a34.46 34.46 0 0 0-34.4-34.42z" p-id="2629"></path><path d="M648 604.92a28 28 0 0 1-16.46-5.34l-112.84-82-112.84 82a28 28 0 0 1-43.08-31.32l43.1-132.64-112.84-82a28 28 0 0 1 16.46-50.66h139.48L492 170.34a28 28 0 0 1 53.26 0l43.1 132.64h139.48a28 28 0 0 1 16.46 50.66l-112.84 82 43.1 132.64A28 28 0 0 1 648 604.92z m-129.3-150a27.86 27.86 0 0 1 16.46 5.36l59.58 43.28-22.76-70a28 28 0 0 1 10.02-31.28l59.58-43.3H568a28 28 0 0 1-26.64-19.34l-22.76-70-22.76 70a28 28 0 0 1-26.62 19.34h-73.64l59.58 43.3a28 28 0 0 1 10.16 31.3l-22.76 70 59.58-43.28a28 28 0 0 1 16.46-5.32z" p-id="2630"></path></svg>'); __publicField2(this, "siteSvg", '<svg t="1749107903569" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12439" width="200" height="200"><path d="M882.758621 133.674884C882.758621 59.84828 822.91034 0 749.083736 0 675.25715 0 615.40887 59.84828 615.40887 133.674884 615.40887 163.358402 625.152318 191.656395 642.813352 214.773283L670.872117 193.336726 648.314739 166.170836 253.911693 493.666092 276.469054 520.831982 302.371681 496.834595C277.256669 469.725608 241.995388 453.990153 204.295574 453.990153 130.46897 453.990153 70.62069 513.838433 70.62069 587.66502 70.62069 661.491624 130.46897 721.339904 204.295574 721.339904 255.555319 721.339904 301.619094 692.208675 324.036714 647.136344L276.646223 663.002394 706.082022 877.440106 721.856794 845.849335 690.37312 829.861888C680.932829 848.452414 675.940882 869.068818 675.940882 890.325116 675.940882 964.15172 735.789162 1024 809.615766 1024 883.442353 1024 943.290633 964.15172 943.290633 890.325116 943.290633 874.050807 940.36533 858.125365 934.723584 843.16446L868.645076 868.0826C871.294817 875.109252 872.669943 882.595452 872.669943 890.325116 872.669943 925.14899 844.439623 953.37931 809.615766 953.37931 774.791892 953.37931 746.561571 925.14899 746.561571 890.325116 746.561571 880.245089 748.902894 870.575616 753.340487 861.836782L769.436089 830.140063 737.631567 814.258564 308.195769 599.820853 276.554929 584.02108 260.805279 615.686903C250.212352 636.984797 228.494795 650.719214 204.295574 650.719214 169.4717 650.719214 141.241379 622.488894 141.241379 587.66502 141.241379 552.841163 169.4717 524.610842 204.295574 524.610842 222.12269 524.610842 238.680594 531.99985 250.566444 544.829369L273.29589 569.363385 299.026432 547.997855 693.429478 220.502616 719.514606 198.84265 698.930882 171.900169C690.596687 160.991373 686.029559 147.727007 686.029559 133.674884 686.029559 98.85101 714.25988 70.62069 749.083736 70.62069 783.90761 70.62069 812.137931 98.85101 812.137931 133.674884 812.137931 148.208022 807.249885 161.899255 798.379608 172.996785L853.543883 217.089695C872.331935 193.584128 882.758621 164.379366 882.758621 133.674884ZM749.083736 196.729062C729.149334 196.729062 710.818745 187.460449 698.930882 171.900169L642.813352 214.773283C667.922573 247.639305 706.904064 267.349751 749.083736 267.349751 790.225902 267.349751 828.357809 248.599782 853.543883 217.089695L798.379608 172.996785C786.455411 187.915034 768.530291 196.729062 749.083736 196.729062ZM337.970441 587.66502C337.970441 553.551854 325.093782 521.360666 302.371681 496.834595L250.566444 544.829369C261.309069 556.424898 267.349751 571.526356 267.349751 587.66502 267.349751 597.565263 265.091478 607.069184 260.805279 615.686903L324.036714 647.136344C333.156105 628.801148 337.970441 608.540036 337.970441 587.66502ZM809.615766 756.650249C758.753986 756.650249 712.986006 785.330865 690.37312 829.861888L753.340487 861.836782C764.027215 840.791658 785.603302 827.270938 809.615766 827.270938 836.08553 827.270938 859.461862 843.730308 868.645076 868.0826L934.723584 843.16446C915.252259 791.529949 865.714547 756.650249 809.615766 756.650249Z" fill="#389BFF" p-id="12440"></path></svg>'); __publicField2(this, "videoSvg", '<svg t="1749003664455" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1952" width="200" height="200"><path d="M825.6 153.6H198.4C124.5 153.6 64 214.1 64 288v448c0 73.9 60.5 134.4 134.4 134.4h627.2c73.9 0 134.4-60.5 134.4-134.4V288c0-73.9-60.5-134.4-134.4-134.4z m-138.2 44.8l112 112H706l-112-112h93.4z m-156.8 0l112 112H526.7l-112-112h115.9z m-179.2 0l112 112H347.5l-112-112h115.9zM108.8 288c0-41.4 28.4-76.1 66.7-86.3l108.7 108.7H108.8V288z m806.4 448c0 49.4-40.2 89.6-89.6 89.6H198.4c-49.4 0-89.6-40.2-89.6-89.6V355.2h806.4V736z m0-425.6h-52.5l-112-112h74.9c49.4 0 89.6 40.2 89.6 89.6v22.4z" p-id="1953"></path><path d="M454 687.2l149.3-77.6c27.5-13.8 27.5-53 0-66.8L468 472.2c-31.2-15.6-68 7.1-68 42v139.6c0 27.8 29.2 45.8 54 33.4zM444.8 512l134.4 67.2-134.4 67.2V512z" p-id="1954"></path></svg>'); __publicField2(this, "screenSvg", '<svg t="1750691468062" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2693" width="200" height="200"><path d="M288 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64v-576a64 64 0 0 0-64-64h-448m0-64h448a128 128 0 0 1 128 128v576a128 128 0 0 1-128 128h-448a128 128 0 0 1-128-128v-576a128 128 0 0 1 128-128z" fill="#4078FD" p-id="2694"></path><path d="M416 352m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" fill="#FE9C23" p-id="2695"></path><path d="M352 732.448a32 32 0 0 1-32-32v-160a32 32 0 0 1 44.224-29.568l130.112 53.632 153.952-169.984a32 32 0 0 1 55.712 21.472v284.448a32 32 0 0 1-32 32z m0-32h320z" fill="#4078FD" opacity=".2" p-id="2696"></path><path d="M672 416l-169.088 186.656-150.912-62.208v160h320V416m0-32a32 32 0 0 1 32 32v284.448a32 32 0 0 1-32 32h-320a32 32 0 0 1-32-32v-160a32 32 0 0 1 44.192-29.6l130.112 53.632 153.984-169.984a32 32 0 0 1 23.712-10.496z" fill="#4078FD" p-id="2697"></path></svg>'); __publicField2(this, "recoveryVideoSvg", '<svg t="1749003779161" class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8204" width="200" height="200"><path d="M938.666667 553.92V768c0 64.8-52.533333 117.333333-117.333334 117.333333H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V256c0-64.8 52.533333-117.333333 117.333334-117.333333h618.666666c64.8 0 117.333333 52.533333 117.333334 117.333333v297.92z m-64-74.624V256a53.333333 53.333333 0 0 0-53.333334-53.333333H202.666667a53.333333 53.333333 0 0 0-53.333334 53.333333v344.48A290.090667 290.090667 0 0 1 192 597.333333a286.88 286.88 0 0 1 183.296 65.845334C427.029333 528.384 556.906667 437.333333 704 437.333333c65.706667 0 126.997333 16.778667 170.666667 41.962667z m0 82.24c-5.333333-8.32-21.130667-21.653333-43.648-32.917333C796.768 511.488 753.045333 501.333333 704 501.333333c-121.770667 0-229.130667 76.266667-270.432 188.693334-2.730667 7.445333-7.402667 20.32-13.994667 38.581333-7.68 21.301333-34.453333 28.106667-51.370666 13.056-16.437333-14.634667-28.554667-25.066667-36.138667-31.146667A222.890667 222.890667 0 0 0 192 661.333333c-14.464 0-28.725333 1.365333-42.666667 4.053334V768a53.333333 53.333333 0 0 0 53.333334 53.333333h618.666666a53.333333 53.333333 0 0 0 53.333334-53.333333V561.525333zM320 480a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0-64a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" fill="#000000" p-id="8205"></path></svg>'); __publicField2(this, "markDeleteSvg", '<svg class="jhs-icon icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 832c-212.1 0-384-171.9-384-384s171.9-384 384-384 384 171.9 384 384-171.9 384-384 384z"></path><path d="M717.3 338.7L685.3 306.7 512 480 338.7 306.7 306.7 338.7 480 512 306.7 685.3 338.7 717.3 512 544 685.3 717.3 717.3 685.3 544 512z"></path></svg>'); } getName() { throw new Error(`${this.constructor.name} 未显示getName()`); } getBean(name2) { return this.pluginManager.getBean(name2); } async initCss() { return ""; } async handle() { } getPageInfo() { let carNum2, url, actress, actors, publishTime, currentHref2 = window.location.href; if (isJavDb) { carNum2 = $('a[title="複製番號"]').attr("data-clipboard-text"); url = currentHref2.split("?")[0].split("#")[0]; actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" "); actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" "); publishTime = $('strong:contains("日期:")').parent(".panel-block").find(".value").text().trim(); } if (isJavBus) { url = currentHref2.split("?")[0]; carNum2 = url.split("/").filter(Boolean).pop().replace(/_\d{4}-\d{2}-\d{2}$/, ""); actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" "); actors = ""; publishTime = $('span.header:contains("發行日期:")').parent("p").text().trim().replace("發行日期:", "").trim(); } return { carNum: carNum2, url, actress, actors, publishTime }; } getActressId() { const match = currentHref.match(/\/actors\/([^/?]+)/); return match && match.length > 1 ? match[1] : null; } getActressPageInfo() { let currentHref2 = window.location.href; if (!currentHref2.includes("/actors/") && !currentHref2.includes("/star/")) throw new Error("接口调用错误, 非演员详情页"); let nameList = [], $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"); $actor.length && $actor.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let $sectionMeta = $(".section-meta:not(:contains('影片'))"); $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let role = $(".section-meta:contains('男優')").length > 0 ? "actor" : "actress", movieType = "censored"; nameList.some(((n) => n.includes("無碼"))) && (movieType = "uncensored"); currentHref2.includes("uncensored") && (movieType = "uncensored"); let blacklistUrl = null, starId = null; const urlObj = new URL(currentHref2); if (isJavDb) { starId = urlObj.pathname.split("/").filter(((segment) => "" !== segment.trim())).pop(); const params = urlObj.searchParams; params.delete("sort_type"); params.delete("page"); blacklistUrl = urlObj.toString(); } else if (isJavBus) { const splitStr = "/star/", parts = currentHref2.split(splitStr); if (parts.length < 2) throw new Error("提取演员url失败"); const host = parts[0]; starId = parts[1].split("/")[0]; blacklistUrl = host + splitStr + starId; } return { starId, name: nameList[0], allName: nameList, role, movieType, blacklistUrl }; } getSelector(type) { const key = type || (isJavDb ? "javdb" : isJavBus ? "javbus" : null), selectors = { javdb: { boxSelector: ".movie-list", itemSelector: ".movie-list .item", coverImgSelector: ".cover img", requestDomItemSelector: ".movie-list .item", nextPageSelector: ".pagination-next" }, javbus: { boxSelector: ".masonry", itemSelector: ".masonry .item", coverImgSelector: ".masonry .movie-box .photo-frame img", requestDomItemSelector: "#waterfall .item", nextPageSelector: "#next" } }; if (!key || !selectors[key]) throw new Error("类型错误: 无法确定选择器类型 (JavDb 或 JavBus)"); return selectors[key]; } parseMovieId(href) { return href.split("/").pop().split(/[?#]/)[0]; } getBoxCarInfo($box2) { var _a2, _b, _c; const $aLink = $box2.find("a"), url = $aLink.attr("href"); let carNum2 = null, title = null, publishTime = null; const $videoTitleEle = $box2.find(".video-title"); if ($videoTitleEle.length > 0) { const $strongEle = $videoTitleEle.find("strong"); $strongEle.length > 0 && (carNum2 = $strongEle.text().trim()); title = null == (_a2 = $aLink.attr("title")) ? void 0 : _a2.trim(); if (!title) { const fullTitleText = $videoTitleEle.text().trim(); title = carNum2 && fullTitleText.includes(carNum2) ? fullTitleText.replace(carNum2, "").trim() : fullTitleText; } publishTime = $box2.find(".meta").text().trim(); } if (!carNum2) { const $imgEle = $box2.find("img"); $imgEle.length > 0 && (title = (null == (_b = $imgEle.attr("title")) ? void 0 : _b.trim()) || (null == (_c = $imgEle.attr("data-title")) ? void 0 : _c.trim())); const dateElements = $box2.find("date").map(((i, el) => $(el).text().trim())).get(), isDate = (s) => /^\d{4}-\d{1,2}-\d{1,2}$/.test(s); publishTime = dateElements.find(isDate) || null; carNum2 = dateElements.find(((i) => !isDate(i))) || null; } if (!carNum2) { return null; } return { carNum: carNum2, url: url || "", aHref: url || "", title: title || "", publishTime: publishTime || "" }; } getBoxCarInfoList($boxList = null) { $boxList || ($boxList = $(this.getSelector().itemSelector)); if (0 === $boxList.length) { clog.error("获取当前列表页所有item的番号信息失败!"); return []; } const results = []; $boxList.each(((index, element) => { const $box2 = $(element); try { const carInfo = this.getBoxCarInfo($box2); results.push(carInfo); } catch (error) { clog.error("[getBoxCarInfoList] 提取单个 boxCar 信息失败:", error.message, "元素索引:", index); } })); return results; } checkDuplicateCarNumbers(currentList, newList) { if (!isJavDb) return false; if (!currentList || 0 === currentList.length || !newList || 0 === newList.length) return false; const currentCarNums = new Set(currentList.map(((item) => item.carNum)).filter(((n) => n))); if (0 === currentCarNums.size) return false; let consecutiveDuplicates = 0; for (let i = 0; i < newList.length; i++) { const newCarNum = newList[i] ? newList[i].carNum : null; if (newCarNum && currentCarNums.has(newCarNum)) { consecutiveDuplicates++; if (consecutiveDuplicates >= 2) { clog.warn("警告: 检测到连续番号信息重复, 该类别可能已被限制页码。"); return true; } } else consecutiveDuplicates = 0; } return false; } } class CoreFeature { constructor(table) { this.table = table; } ////////////////////////////////////////// /////////////// DataLoad ///////////////// ////////////////////////////////////////// reloadData(data, silent, columnsChanged) { return this.table.dataLoader.load(data, void 0, void 0, void 0, silent, columnsChanged); } ////////////////////////////////////////// ///////////// Localization /////////////// ////////////////////////////////////////// langText() { return this.table.modules.localize.getText(...arguments); } langBind() { return this.table.modules.localize.bind(...arguments); } langLocale() { return this.table.modules.localize.getLocale(...arguments); } ////////////////////////////////////////// ////////// Inter Table Comms ///////////// ////////////////////////////////////////// commsConnections() { return this.table.modules.comms.getConnections(...arguments); } commsSend() { return this.table.modules.comms.send(...arguments); } ////////////////////////////////////////// //////////////// Layout ///////////////// ////////////////////////////////////////// /** @returns {("fitData" | "fitDataFill" | "fitDataTable" | "fitDataStretch" | "fitColumns")} */ layoutMode() { return this.table.modules.layout.getMode(); } layoutRefresh(force) { return this.table.modules.layout.layout(force); } ////////////////////////////////////////// /////////////// Event Bus //////////////// ////////////////////////////////////////// subscribe() { return this.table.eventBus.subscribe(...arguments); } unsubscribe() { return this.table.eventBus.unsubscribe(...arguments); } subscribed(key) { return this.table.eventBus.subscribed(key); } subscriptionChange() { return this.table.eventBus.subscriptionChange(...arguments); } dispatch() { return this.table.eventBus.dispatch(...arguments); } chain() { return this.table.eventBus.chain(...arguments); } confirm() { return this.table.eventBus.confirm(...arguments); } dispatchExternal() { return this.table.externalEvents.dispatch(...arguments); } subscribedExternal(key) { return this.table.externalEvents.subscribed(key); } subscriptionChangeExternal() { return this.table.externalEvents.subscriptionChange(...arguments); } ////////////////////////////////////////// //////////////// Options ///////////////// ////////////////////////////////////////// options(key) { return this.table.options[key]; } setOption(key, value) { if (typeof value !== "undefined") { this.table.options[key] = value; } return this.table.options[key]; } ////////////////////////////////////////// /////////// Deprecation Checks /////////// ////////////////////////////////////////// deprecationCheck(oldOption, newOption, convert) { return this.table.deprecationAdvisor.check(oldOption, newOption, convert); } deprecationCheckMsg(oldOption, msg) { return this.table.deprecationAdvisor.checkMsg(oldOption, msg); } deprecationMsg(msg) { return this.table.deprecationAdvisor.msg(msg); } ////////////////////////////////////////// //////////////// Modules ///////////////// ////////////////////////////////////////// module(key) { return this.table.module(key); } } class Helpers { static elVisible(el) { return !(el.offsetWidth <= 0 && el.offsetHeight <= 0); } static elOffset(el) { var box = el.getBoundingClientRect(); return { top: box.top + window.pageYOffset - document.documentElement.clientTop, left: box.left + window.pageXOffset - document.documentElement.clientLeft }; } static retrieveNestedData(separator, field, data) { var structure = separator ? field.split(separator) : [field], length = structure.length, output; for (let i = 0; i < length; i++) { data = data[structure[i]]; output = data; if (!data) { break; } } return output; } static deepClone(obj, clone, list2 = []) { var objectProto = {}.__proto__, arrayProto = [].__proto__; if (!clone) { clone = Object.assign(Array.isArray(obj) ? [] : {}, obj); } for (var i in obj) { let subject = obj[i], match, copy; if (subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)) { match = list2.findIndex((item) => { return item.subject === subject; }); if (match > -1) { clone[i] = list2[match].copy; } else { copy = Object.assign(Array.isArray(subject) ? [] : {}, subject); list2.unshift({ subject, copy }); clone[i] = this.deepClone(subject, copy, list2); } } } return clone; } } let Popup$1 = class Popup extends CoreFeature { constructor(table, element, parent2) { super(table); this.element = element; this.container = this._lookupContainer(); this.parent = parent2; this.reversedX = false; this.childPopup = null; this.blurable = false; this.blurCallback = null; this.blurEventsBound = false; this.renderedCallback = null; this.visible = false; this.hideable = true; this.element.classList.add("tabulator-popup-container"); this.blurEvent = this.hide.bind(this, false); this.escEvent = this._escapeCheck.bind(this); this.destroyBinding = this.tableDestroyed.bind(this); this.destroyed = false; } tableDestroyed() { this.destroyed = true; this.hide(true); } _lookupContainer() { var container = this.table.options.popupContainer; if (typeof container === "string") { container = document.querySelector(container); if (!container) { console.warn("Menu Error - no container element found matching selector:", this.table.options.popupContainer, "(defaulting to document body)"); } } else if (container === true) { container = this.table.element; } if (container && !this._checkContainerIsParent(container)) { container = false; console.warn("Menu Error - container element does not contain this table:", this.table.options.popupContainer, "(defaulting to document body)"); } if (!container) { container = document.body; } return container; } _checkContainerIsParent(container, element = this.table.element) { if (container === element) { return true; } else { return element.parentNode ? this._checkContainerIsParent(container, element.parentNode) : false; } } renderCallback(callback) { this.renderedCallback = callback; } containerEventCoords(e) { var touch = !(e instanceof MouseEvent); var x = touch ? e.touches[0].pageX : e.pageX; var y = touch ? e.touches[0].pageY : e.pageY; if (this.container !== document.body) { let parentOffset = Helpers.elOffset(this.container); x -= parentOffset.left; y -= parentOffset.top; } return { x, y }; } elementPositionCoords(element, position = "right") { var offset = Helpers.elOffset(element), containerOffset, x, y; if (this.container !== document.body) { containerOffset = Helpers.elOffset(this.container); offset.left -= containerOffset.left; offset.top -= containerOffset.top; } switch (position) { case "right": x = offset.left + element.offsetWidth; y = offset.top - 1; break; case "bottom": x = offset.left; y = offset.top + element.offsetHeight; break; case "left": x = offset.left; y = offset.top - 1; break; case "top": x = offset.left; y = offset.top; break; case "center": x = offset.left + element.offsetWidth / 2; y = offset.top + element.offsetHeight / 2; break; } return { x, y, offset }; } show(origin, position) { var x, y, parentEl, parentOffset, coords; if (this.destroyed || this.table.destroyed) { return this; } if (origin instanceof HTMLElement) { parentEl = origin; coords = this.elementPositionCoords(origin, position); parentOffset = coords.offset; x = coords.x; y = coords.y; } else if (typeof origin === "number") { parentOffset = { top: 0, left: 0 }; x = origin; y = position; } else { coords = this.containerEventCoords(origin); x = coords.x; y = coords.y; this.reversedX = false; } this.element.style.top = y + "px"; this.element.style.left = x + "px"; this.container.appendChild(this.element); if (typeof this.renderedCallback === "function") { this.renderedCallback(); } this._fitToScreen(x, y, parentEl, parentOffset, position); this.visible = true; this.subscribe("table-destroy", this.destroyBinding); this.element.addEventListener("mousedown", (e) => { e.stopPropagation(); }); return this; } _fitToScreen(x, y, parentEl, parentOffset, position) { var scrollTop = this.container === document.body ? document.documentElement.scrollTop : this.container.scrollTop; if (x + this.element.offsetWidth >= this.container.offsetWidth || this.reversedX) { this.element.style.left = ""; if (parentEl) { this.element.style.right = this.container.offsetWidth - parentOffset.left + "px"; } else { this.element.style.right = this.container.offsetWidth - x + "px"; } this.reversedX = true; } let offsetHeight = Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0); if (y + this.element.offsetHeight > offsetHeight) { if (parentEl) { switch (position) { case "bottom": this.element.style.top = parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1 + "px"; break; default: this.element.style.top = parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1 + "px"; } } else { let menuHeight = this.element.offsetHeight; if (menuHeight > offsetHeight) { this.element.style.top = "0px"; this.element.style.height = offsetHeight + "px"; } else { let newTop = y - menuHeight; if (newTop < 0) { newTop = offsetHeight - menuHeight; } this.element.style.top = newTop + "px"; } } } } isVisible() { return this.visible; } hideOnBlur(callback) { this.blurable = true; if (this.visible) { setTimeout(() => { if (this.visible) { this.table.rowManager.element.addEventListener("scroll", this.blurEvent); this.subscribe("cell-editing", this.blurEvent); document.body.addEventListener("click", this.blurEvent); document.body.addEventListener("contextmenu", this.blurEvent); document.body.addEventListener("mousedown", this.blurEvent); window.addEventListener("resize", this.blurEvent); document.body.addEventListener("keydown", this.escEvent); this.blurEventsBound = true; } }, 100); this.blurCallback = callback; } return this; } /** @param {KeyboardEvent} e */ _escapeCheck(e) { if (e.key == 27) { this.hide(); } } blockHide() { this.hideable = false; } restoreHide() { this.hideable = true; } hide(silent = false) { if (this.visible && this.hideable) { if (this.blurable && this.blurEventsBound) { document.body.removeEventListener("keydown", this.escEvent); document.body.removeEventListener("click", this.blurEvent); document.body.removeEventListener("contextmenu", this.blurEvent); document.body.removeEventListener("mousedown", this.blurEvent); window.removeEventListener("resize", this.blurEvent); this.table.rowManager.element.removeEventListener("scroll", this.blurEvent); this.unsubscribe("cell-editing", this.blurEvent); this.blurEventsBound = false; } if (this.childPopup) { this.childPopup.hide(); } if (this.parent) { this.parent.childPopup = null; } if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.visible = false; if (this.blurCallback && !silent) { this.blurCallback(); } this.unsubscribe("table-destroy", this.destroyBinding); } return this; } child(element) { if (this.childPopup) { this.childPopup.hide(); } this.childPopup = new Popup(this.table, element, this); return this.childPopup; } }; class Module extends CoreFeature { constructor(table, name2) { super(table); this._handler = null; } initialize() { } /////////////////////////////////// ////// Options Registration /////// /////////////////////////////////// registerTableOption(key, value) { this.table.optionsList.register(key, value); } registerColumnOption(key, value) { this.table.columnManager.optionsList.register(key, value); } /////////////////////////////////// /// Public Function Registration /// /////////////////////////////////// registerTableFunction(name2, func) { if (typeof this.table[name2] === "undefined") { this.table[name2] = (...args) => { this.table.initGuard(name2); return func(...args); }; } else { console.warn("Unable to bind table function, name already in use", name2); } } registerComponentFunction(component, func, handler) { return this.table.componentFunctionBinder.bind(component, func, handler); } /////////////////////////////////// ////////// Data Pipeline ////////// /////////////////////////////////// registerDataHandler(handler, priority) { this.table.rowManager.registerDataPipelineHandler(handler, priority); this._handler = handler; } registerDisplayHandler(handler, priority) { this.table.rowManager.registerDisplayPipelineHandler(handler, priority); this._handler = handler; } displayRows(adjust) { var index = this.table.rowManager.displayRows.length - 1, lookupIndex; if (this._handler) { lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => { return item.handler === this._handler; }); if (lookupIndex > -1) { index = lookupIndex; } } if (adjust) { index = index + adjust; } if (this._handler) { if (index > -1) { return this.table.rowManager.getDisplayRows(index); } else { return this.activeRows(); } } } activeRows() { return this.table.rowManager.activeRows; } refreshData(renderInPosition, handler) { if (!handler) { handler = this._handler; } if (handler) { this.table.rowManager.refreshActiveData(handler, false, renderInPosition); } } /////////////////////////////////// //////// Footer Management //////// /////////////////////////////////// footerAppend(element) { return this.table.footerManager.append(element); } footerPrepend(element) { return this.table.footerManager.prepend(element); } footerRemove(element) { return this.table.footerManager.remove(element); } /////////////////////////////////// //////// Popups Management //////// /////////////////////////////////// popup(menuEl, menuContainer) { return new Popup$1(this.table, menuEl, menuContainer); } /////////////////////////////////// //////// Alert Management //////// /////////////////////////////////// alert(content, type) { return this.table.alertManager.alert(content, type); } clearAlert() { return this.table.alertManager.clear(); } } var defaultAccessors = { rownum: function(value, data, type, params, column, row) { return row.getPosition(); } }; const _Accessor = class _Accessor extends Module { constructor(table) { super(table); this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; this.registerColumnOption("accessor"); this.registerColumnOption("accessorParams"); this.registerColumnOption("accessorData"); this.registerColumnOption("accessorDataParams"); this.registerColumnOption("accessorDownload"); this.registerColumnOption("accessorDownloadParams"); this.registerColumnOption("accessorClipboard"); this.registerColumnOption("accessorClipboardParams"); this.registerColumnOption("accessorPrint"); this.registerColumnOption("accessorPrintParams"); this.registerColumnOption("accessorHtmlOutput"); this.registerColumnOption("accessorHtmlOutputParams"); } initialize() { this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("row-data-retrieve", this.transformRow.bind(this)); } //initialize column accessor initializeColumn(column) { var match = false, config = {}; this.allowedTypes.forEach((type) => { var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), accessor; if (column.definition[key]) { accessor = this.lookupAccessor(column.definition[key]); if (accessor) { match = true; config[key] = { accessor, params: column.definition[key + "Params"] || {} }; } } }); if (match) { column.modules.accessor = config; } } lookupAccessor(value) { var accessor = false; switch (typeof value) { case "string": if (_Accessor.accessors[value]) { accessor = _Accessor.accessors[value]; } else { console.warn("Accessor Error - No such accessor found, ignoring: ", value); } break; case "function": accessor = value; break; } return accessor; } //apply accessor to row transformRow(row, type) { var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), rowComponent = row.getComponent(); var data = Helpers.deepClone(row.data || {}); this.table.columnManager.traverse(function(column) { var value, accessor, params, colComponent; if (column.modules.accessor) { accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false; if (accessor) { value = column.getFieldValue(data); if (value != "undefined") { colComponent = column.getComponent(); params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params; column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent)); } } } }); return data; } }; __publicField(_Accessor, "moduleName", "accessor"); //load defaults __publicField(_Accessor, "accessors", defaultAccessors); let Accessor = _Accessor; var defaultConfig = { method: "GET" }; function generateParamsList$1(data, prefix) { var output = []; prefix = prefix || ""; if (Array.isArray(data)) { data.forEach((item, i) => { output = output.concat(generateParamsList$1(item, prefix ? prefix + "[" + i + "]" : i)); }); } else if (typeof data === "object") { for (var key in data) { output = output.concat(generateParamsList$1(data[key], prefix ? prefix + "[" + key + "]" : key)); } } else { output.push({ key: prefix, value: data }); } return output; } function serializeParams(params) { var output = generateParamsList$1(params), encoded = []; output.forEach(function(item) { encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value)); }); return encoded.join("&"); } function urlBuilder(url, config, params) { if (url) { if (params && Object.keys(params).length) { if (!config.method || config.method.toLowerCase() == "get") { config.method = "get"; url += (url.includes("?") ? "&" : "?") + serializeParams(params); } } } return url; } function defaultLoaderPromise(url, config, params) { var contentType; return new Promise((resolve, reject) => { url = this.urlGenerator.call(this.table, url, config, params); if (config.method.toUpperCase() != "GET") { contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType]; if (contentType) { for (var key in contentType.headers) { if (!config.headers) { config.headers = {}; } if (typeof config.headers[key] === "undefined") { config.headers[key] = contentType.headers[key]; } } config.body = contentType.body.call(this, url, config, params); } else { console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType); } } if (url) { if (typeof config.headers === "undefined") { config.headers = {}; } if (typeof config.headers.Accept === "undefined") { config.headers.Accept = "application/json"; } if (typeof config.headers["X-Requested-With"] === "undefined") { config.headers["X-Requested-With"] = "XMLHttpRequest"; } if (typeof config.mode === "undefined") { config.mode = "cors"; } if (config.mode == "cors") { if (typeof config.headers["Origin"] === "undefined") { config.headers["Origin"] = window.location.origin; } if (typeof config.credentials === "undefined") { config.credentials = "same-origin"; } } else { if (typeof config.credentials === "undefined") { config.credentials = "include"; } } fetch(url, config).then((response) => { if (response.ok) { response.json().then((data) => { resolve(data); }).catch((error) => { reject(error); console.warn("Ajax Load Error - Invalid JSON returned", error); }); } else { console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText); reject(response); } }).catch((error) => { console.error("Ajax Load Error - Connection Error: ", error); reject(error); }); } else { console.warn("Ajax Load Error - No URL Set"); resolve([]); } }); } function generateParamsList(data, prefix) { var output = []; prefix = prefix || ""; if (Array.isArray(data)) { data.forEach((item, i) => { output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i)); }); } else if (typeof data === "object") { for (var key in data) { output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key)); } } else { output.push({ key: prefix, value: data }); } return output; } var defaultContentTypeFormatters = { "json": { headers: { "Content-Type": "application/json" }, body: function(url, config, params) { return JSON.stringify(params); } }, "form": { headers: {}, body: function(url, config, params) { var output = generateParamsList(params), form = new FormData(); output.forEach(function(item) { form.append(item.key, item.value); }); return form; } } }; const _Ajax = class _Ajax extends Module { constructor(table) { super(table); this.config = {}; this.url = ""; this.urlGenerator = false; this.params = false; this.loaderPromise = false; this.registerTableOption("ajaxURL", false); this.registerTableOption("ajaxURLGenerator", false); this.registerTableOption("ajaxParams", {}); this.registerTableOption("ajaxConfig", "get"); this.registerTableOption("ajaxContentType", "form"); this.registerTableOption("ajaxRequestFunc", false); this.registerTableOption("ajaxRequesting", function() { }); this.registerTableOption("ajaxResponse", false); this.contentTypeFormatters = _Ajax.contentTypeFormatters; } //initialize setup options initialize() { this.loaderPromise = this.table.options.ajaxRequestFunc || _Ajax.defaultLoaderPromise; this.urlGenerator = this.table.options.ajaxURLGenerator || _Ajax.defaultURLGenerator; if (this.table.options.ajaxURL) { this.setUrl(this.table.options.ajaxURL); } this.setDefaultConfig(this.table.options.ajaxConfig); this.registerTableFunction("getAjaxUrl", this.getUrl.bind(this)); this.subscribe("data-loading", this.requestDataCheck.bind(this)); this.subscribe("data-params", this.requestParams.bind(this)); this.subscribe("data-load", this.requestData.bind(this)); } requestParams(data, config, silent, params) { var ajaxParams = this.table.options.ajaxParams; if (ajaxParams) { if (typeof ajaxParams === "function") { ajaxParams = ajaxParams.call(this.table); } params = Object.assign(Object.assign({}, ajaxParams), params); } return params; } requestDataCheck(data, params, config, silent) { return !!(!data && this.url || typeof data === "string"); } requestData(url, params, config, silent, previousData) { var ajaxConfig; if (!previousData && this.requestDataCheck(url)) { if (url) { this.setUrl(url); } ajaxConfig = this.generateConfig(config); return this.sendRequest(this.url, params, ajaxConfig); } else { return previousData; } } setDefaultConfig(config = {}) { this.config = Object.assign({}, _Ajax.defaultConfig); if (typeof config == "string") { this.config.method = config; } else { Object.assign(this.config, config); } } //load config object generateConfig(config = {}) { var ajaxConfig = Object.assign({}, this.config); if (typeof config == "string") { ajaxConfig.method = config; } else { Object.assign(ajaxConfig, config); } return ajaxConfig; } //set request url setUrl(url) { this.url = url; } //get request url getUrl() { return this.url; } //send ajax request sendRequest(url, params, config) { if (this.table.options.ajaxRequesting.call(this.table, url, params) !== false) { return this.loaderPromise(url, config, params).then((data) => { if (this.table.options.ajaxResponse) { data = this.table.options.ajaxResponse.call(this.table, url, params, data); } return data; }); } else { return Promise.reject(); } } }; __publicField(_Ajax, "moduleName", "ajax"); //load defaults __publicField(_Ajax, "defaultConfig", defaultConfig); __publicField(_Ajax, "defaultURLGenerator", urlBuilder); __publicField(_Ajax, "defaultLoaderPromise", defaultLoaderPromise); __publicField(_Ajax, "contentTypeFormatters", defaultContentTypeFormatters); let Ajax = _Ajax; var defaultPasteActions = { replace: function(data) { return this.table.setData(data); }, update: function(data) { return this.table.updateOrAddData(data); }, insert: function(data) { return this.table.addData(data); } }; var defaultPasteParsers = { table: function(clipboard) { var data = [], headerFindSuccess = true, columns = this.table.columnManager.columns, columnMap = [], rows2 = []; clipboard = clipboard.split("\n"); clipboard.forEach(function(row) { data.push(row.split(" ")); }); if (data.length && !(data.length === 1 && data[0].length < 2)) { data[0].forEach(function(value) { var column = columns.find(function(column2) { return value && column2.definition.title && value.trim() && column2.definition.title.trim() === value.trim(); }); if (column) { columnMap.push(column); } else { headerFindSuccess = false; } }); if (!headerFindSuccess) { headerFindSuccess = true; columnMap = []; data[0].forEach(function(value) { var column = columns.find(function(column2) { return value && column2.field && value.trim() && column2.field.trim() === value.trim(); }); if (column) { columnMap.push(column); } else { headerFindSuccess = false; } }); if (!headerFindSuccess) { columnMap = this.table.columnManager.columnsByIndex; } } if (headerFindSuccess) { data.shift(); } data.forEach(function(item) { var row = {}; item.forEach(function(value, i) { if (columnMap[i]) { row[columnMap[i].field] = value; } }); rows2.push(row); }); return rows2; } else { return false; } } }; var bindings$2 = { copyToClipboard: ["ctrl + 67", "meta + 67"] }; var actions$2 = { copyToClipboard: function(e) { if (!this.table.modules.edit.currentCell) { if (this.table.modExists("clipboard", true)) { this.table.modules.clipboard.copy(false, true); } } } }; var extensions$4 = { keybindings: { bindings: bindings$2, actions: actions$2 } }; const _Clipboard = class _Clipboard extends Module { constructor(table) { super(table); this.mode = true; this.pasteParser = function() { }; this.pasteAction = function() { }; this.customSelection = false; this.rowRange = false; this.blocked = true; this.registerTableOption("clipboard", false); this.registerTableOption("clipboardCopyStyled", true); this.registerTableOption("clipboardCopyConfig", false); this.registerTableOption("clipboardCopyFormatter", false); this.registerTableOption("clipboardCopyRowRange", "active"); this.registerTableOption("clipboardPasteParser", "table"); this.registerTableOption("clipboardPasteAction", "insert"); this.registerColumnOption("clipboard"); this.registerColumnOption("titleClipboard"); } initialize() { this.mode = this.table.options.clipboard; this.rowRange = this.table.options.clipboardCopyRowRange; if (this.mode === true || this.mode === "copy") { this.table.element.addEventListener("copy", (e) => { var plain, html2, list2; if (!this.blocked) { e.preventDefault(); if (this.customSelection) { plain = this.customSelection; if (this.table.options.clipboardCopyFormatter) { plain = this.table.options.clipboardCopyFormatter("plain", plain); } } else { list2 = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard"); html2 = this.table.modules.export.generateHTMLTable(list2); plain = html2 ? this.generatePlainContent(list2) : ""; if (this.table.options.clipboardCopyFormatter) { plain = this.table.options.clipboardCopyFormatter("plain", plain); html2 = this.table.options.clipboardCopyFormatter("html", html2); } } if (window.clipboardData && window.clipboardData.setData) { window.clipboardData.setData("Text", plain); } else if (e.clipboardData && e.clipboardData.setData) { e.clipboardData.setData("text/plain", plain); if (html2) { e.clipboardData.setData("text/html", html2); } } else if (e.originalEvent && e.originalEvent.clipboardData.setData) { e.originalEvent.clipboardData.setData("text/plain", plain); if (html2) { e.originalEvent.clipboardData.setData("text/html", html2); } } this.dispatchExternal("clipboardCopied", plain, html2); this.reset(); } }); } if (this.mode === true || this.mode === "paste") { this.table.element.addEventListener("paste", (e) => { this.paste(e); }); } this.setPasteParser(this.table.options.clipboardPasteParser); this.setPasteAction(this.table.options.clipboardPasteAction); this.registerTableFunction("copyToClipboard", this.copy.bind(this)); } reset() { this.blocked = true; this.customSelection = false; } generatePlainContent(list2) { var output = []; list2.forEach((row) => { var rowData = []; row.columns.forEach((col) => { var value = ""; if (col) { if (row.type === "group") { col.value = col.component.getKey(); } if (col.value === null) { value = ""; } else { switch (typeof col.value) { case "object": value = JSON.stringify(col.value); break; case "undefined": value = ""; break; default: value = col.value; } } } rowData.push(value); }); output.push(rowData.join(" ")); }); return output.join("\n"); } copy(range2, internal) { var sel, textRange; this.blocked = false; this.customSelection = false; if (this.mode === true || this.mode === "copy") { this.rowRange = range2 || this.table.options.clipboardCopyRowRange; if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") { range2 = document.createRange(); range2.selectNodeContents(this.table.element); sel = window.getSelection(); if (sel.toString() && internal) { this.customSelection = sel.toString(); } sel.removeAllRanges(); sel.addRange(range2); } else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") { textRange = document.body.createTextRange(); textRange.moveToElementText(this.table.element); textRange.select(); } document.execCommand("copy"); if (sel) { sel.removeAllRanges(); } } } //PASTE EVENT HANDLING setPasteAction(action) { switch (typeof action) { case "string": this.pasteAction = _Clipboard.pasteActions[action]; if (!this.pasteAction) { console.warn("Clipboard Error - No such paste action found:", action); } break; case "function": this.pasteAction = action; break; } } setPasteParser(parser) { switch (typeof parser) { case "string": this.pasteParser = _Clipboard.pasteParsers[parser]; if (!this.pasteParser) { console.warn("Clipboard Error - No such paste parser found:", parser); } break; case "function": this.pasteParser = parser; break; } } paste(e) { var data, rowData, rows2; if (this.checkPasteOrigin(e)) { data = this.getPasteData(e); rowData = this.pasteParser.call(this, data); if (rowData) { e.preventDefault(); if (this.table.modExists("mutator")) { rowData = this.mutateData(rowData); } rows2 = this.pasteAction.call(this, rowData); this.dispatchExternal("clipboardPasted", data, rowData, rows2); } else { this.dispatchExternal("clipboardPasteError", data); } } } mutateData(data) { var output = []; if (Array.isArray(data)) { data.forEach((row) => { output.push(this.table.modules.mutator.transformRow(row, "clipboard")); }); } else { output = data; } return output; } checkPasteOrigin(e) { var valid = true; var blocked = this.confirm("clipboard-paste", [e]); if (blocked || !["DIV", "SPAN"].includes(e.target.tagName)) { valid = false; } return valid; } getPasteData(e) { var data; if (window.clipboardData && window.clipboardData.getData) { data = window.clipboardData.getData("Text"); } else if (e.clipboardData && e.clipboardData.getData) { data = e.clipboardData.getData("text/plain"); } else if (e.originalEvent && e.originalEvent.clipboardData.getData) { data = e.originalEvent.clipboardData.getData("text/plain"); } return data; } }; __publicField(_Clipboard, "moduleName", "clipboard"); __publicField(_Clipboard, "moduleExtensions", extensions$4); //load defaults __publicField(_Clipboard, "pasteActions", defaultPasteActions); __publicField(_Clipboard, "pasteParsers", defaultPasteParsers); let Clipboard = _Clipboard; class CalcComponent { constructor(row) { this._row = row; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._row.table.componentFunctionBinder.handle("row", target._row, name2); } } }); } getData(transform) { return this._row.getData(transform); } getElement() { return this._row.getElement(); } getTable() { return this._row.table; } getCells() { var cells = []; this._row.getCells().forEach(function(cell) { cells.push(cell.getComponent()); }); return cells; } getCell(column) { var cell = this._row.getCell(column); return cell ? cell.getComponent() : false; } _getSelf() { return this._row; } } class CellComponent { constructor(cell) { this._cell = cell; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name2); } } }); } getValue() { return this._cell.getValue(); } getOldValue() { return this._cell.getOldValue(); } getInitialValue() { return this._cell.initialValue; } getElement() { return this._cell.getElement(); } getRow() { return this._cell.row.getComponent(); } getData(transform) { return this._cell.row.getData(transform); } getType() { return "cell"; } getField() { return this._cell.column.getField(); } getColumn() { return this._cell.column.getComponent(); } setValue(value, mutate) { if (typeof mutate == "undefined") { mutate = true; } this._cell.setValue(value, mutate); } restoreOldValue() { this._cell.setValueActual(this._cell.getOldValue()); } restoreInitialValue() { this._cell.setValueActual(this._cell.initialValue); } checkHeight() { this._cell.checkHeight(); } getTable() { return this._cell.table; } _getSelf() { return this._cell; } } class Cell extends CoreFeature { constructor(column, row) { super(column.table); this.table = column.table; this.column = column; this.row = row; this.element = null; this.value = null; this.initialValue; this.oldValue = null; this.modules = {}; this.height = null; this.width = null; this.minWidth = null; this.component = null; this.loaded = false; this.build(); } //////////////// Setup Functions ///////////////// //generate element build() { this.generateElement(); this.setWidth(); this._configureCell(); this.setValueActual(this.column.getFieldValue(this.row.data)); this.initialValue = this.value; } generateElement() { this.element = document.createElement("div"); this.element.className = "tabulator-cell"; this.element.setAttribute("role", "gridcell"); if (this.column.isRowHeader) { this.element.classList.add("tabulator-row-header"); } } _configureCell() { var element = this.element, field = this.column.getField(), vertAligns = { top: "flex-start", bottom: "flex-end", middle: "center" }, hozAligns = { left: "flex-start", right: "flex-end", center: "center" }; element.style.textAlign = this.column.hozAlign; if (this.column.vertAlign) { element.style.display = "inline-flex"; element.style.alignItems = vertAligns[this.column.vertAlign] || ""; if (this.column.hozAlign) { element.style.justifyContent = hozAligns[this.column.hozAlign] || ""; } } if (field) { element.setAttribute("tabulator-field", field); } if (this.column.definition.cssClass) { var classNames = this.column.definition.cssClass.split(" "); classNames.forEach((className) => { element.classList.add(className); }); } this.dispatch("cell-init", this); if (!this.column.visible) { this.hide(); } } //generate cell contents _generateContents() { var val; val = this.chain("cell-format", this, null, () => { return this.element.innerHTML = this.value; }); switch (typeof val) { case "object": if (val instanceof Node) { while (this.element.firstChild) this.element.removeChild(this.element.firstChild); this.element.appendChild(val); } else { this.element.innerHTML = ""; if (val != null) { console.warn("Format Error - Formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", val); } } break; case "undefined": this.element.innerHTML = ""; break; default: this.element.innerHTML = val; } } cellRendered() { this.dispatch("cell-rendered", this); } //////////////////// Getters //////////////////// getElement(containerOnly) { if (!this.loaded) { this.loaded = true; if (!containerOnly) { this.layoutElement(); } } return this.element; } getValue() { return this.value; } getOldValue() { return this.oldValue; } //////////////////// Actions //////////////////// setValue(value, mutate, force) { var changed = this.setValueProcessData(value, mutate, force); if (changed) { this.dispatch("cell-value-updated", this); this.cellRendered(); if (this.column.definition.cellEdited) { this.column.definition.cellEdited.call(this.table, this.getComponent()); } this.dispatchExternal("cellEdited", this.getComponent()); if (this.subscribedExternal("dataChanged")) { this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } } } setValueProcessData(value, mutate, force) { var changed = false; if (this.value !== value || force) { changed = true; if (mutate) { value = this.chain("cell-value-changing", [this, value], null, value); } } this.setValueActual(value); if (changed) { this.dispatch("cell-value-changed", this); } return changed; } setValueActual(value) { this.oldValue = this.value; this.value = value; this.dispatch("cell-value-save-before", this); this.column.setFieldValue(this.row.data, value); this.dispatch("cell-value-save-after", this); if (this.loaded) { this.layoutElement(); } } layoutElement() { this._generateContents(); this.dispatch("cell-layout", this); } setWidth() { this.width = this.column.width; this.element.style.width = this.column.widthStyled; } clearWidth() { this.width = ""; this.element.style.width = ""; } getWidth() { return this.width || this.element.offsetWidth; } setMinWidth() { this.minWidth = this.column.minWidth; this.element.style.minWidth = this.column.minWidthStyled; } setMaxWidth() { this.maxWidth = this.column.maxWidth; this.element.style.maxWidth = this.column.maxWidthStyled; } checkHeight() { this.row.reinitializeHeight(); } clearHeight() { this.element.style.height = ""; this.height = null; this.dispatch("cell-height", this, ""); } setHeight() { this.height = this.row.height; this.element.style.height = this.row.heightStyled; this.dispatch("cell-height", this, this.row.heightStyled); } getHeight() { return this.height || this.element.offsetHeight; } show() { this.element.style.display = this.column.vertAlign ? "inline-flex" : ""; } hide() { this.element.style.display = "none"; } delete() { this.dispatch("cell-delete", this); if (!this.table.rowManager.redrawBlock && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.element = false; this.column.deleteCell(this); this.row.deleteCell(this); this.calcs = {}; } getIndex() { return this.row.getCellIndex(this); } //////////////// Object Generation ///////////////// getComponent() { if (!this.component) { this.component = new CellComponent(this); } return this.component; } } class ColumnComponent { constructor(column) { this._column = column; this.type = "ColumnComponent"; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._column.table.componentFunctionBinder.handle("column", target._column, name2); } } }); } getElement() { return this._column.getElement(); } getDefinition() { return this._column.getDefinition(); } getField() { return this._column.getField(); } getTitleDownload() { return this._column.getTitleDownload(); } getCells() { var cells = []; this._column.cells.forEach(function(cell) { cells.push(cell.getComponent()); }); return cells; } isVisible() { return this._column.visible; } show() { if (this._column.isGroup) { this._column.columns.forEach(function(column) { column.show(); }); } else { this._column.show(); } } hide() { if (this._column.isGroup) { this._column.columns.forEach(function(column) { column.hide(); }); } else { this._column.hide(); } } toggle() { if (this._column.visible) { this.hide(); } else { this.show(); } } delete() { return this._column.delete(); } getSubColumns() { var output = []; if (this._column.columns.length) { this._column.columns.forEach(function(column) { output.push(column.getComponent()); }); } return output; } getParentColumn() { return this._column.getParentComponent(); } _getSelf() { return this._column; } scrollTo(position, ifVisible) { return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible); } getTable() { return this._column.table; } move(to, after) { var toColumn = this._column.table.columnManager.findColumn(to); if (toColumn) { this._column.table.columnManager.moveColumn(this._column, toColumn, after); } else { console.warn("Move Error - No matching column found:", toColumn); } } getNextColumn() { var nextCol = this._column.nextColumn(); return nextCol ? nextCol.getComponent() : false; } getPrevColumn() { var prevCol = this._column.prevColumn(); return prevCol ? prevCol.getComponent() : false; } updateDefinition(updates) { return this._column.updateDefinition(updates); } getWidth() { return this._column.getWidth(); } setWidth(width) { var result; if (width === true) { result = this._column.reinitializeWidth(true); } else { result = this._column.setWidth(width); } this._column.table.columnManager.rerenderColumns(true); return result; } } var defaultColumnOptions = { "title": void 0, "field": void 0, "columns": void 0, "visible": void 0, "hozAlign": void 0, "vertAlign": void 0, "width": void 0, "minWidth": 40, "maxWidth": void 0, "maxInitialWidth": void 0, "cssClass": void 0, "variableHeight": void 0, "headerVertical": void 0, "headerHozAlign": void 0, "headerWordWrap": false, "editableTitle": void 0 }; const _Column = class _Column extends CoreFeature { constructor(def, parent2, rowHeader) { super(parent2.table); this.definition = def; this.parent = parent2; this.type = "column"; this.columns = []; this.cells = []; this.isGroup = false; this.isRowHeader = rowHeader; this.element = this.createElement(); this.contentElement = false; this.titleHolderElement = false; this.titleElement = false; this.groupElement = this.createGroupElement(); this.hozAlign = ""; this.vertAlign = ""; this.field = ""; this.fieldStructure = ""; this.getFieldValue = ""; this.setFieldValue = ""; this.titleDownload = null; this.titleFormatterRendered = false; this.mapDefinitions(); this.setField(this.definition.field); this.modules = {}; this.width = null; this.widthStyled = ""; this.maxWidth = null; this.maxWidthStyled = ""; this.maxInitialWidth = null; this.minWidth = null; this.minWidthStyled = ""; this.widthFixed = false; this.visible = true; this.component = null; if (this.definition.columns) { this.isGroup = true; this.definition.columns.forEach((def2, i) => { var newCol = new _Column(def2, this); this.attachColumn(newCol); }); this.checkColumnVisibility(); } else { parent2.registerColumnField(this); } this._initialize(); } createElement() { var el = document.createElement("div"); el.classList.add("tabulator-col"); el.setAttribute("role", "columnheader"); el.setAttribute("aria-sort", "none"); if (this.isRowHeader) { el.classList.add("tabulator-row-header"); } switch (this.table.options.columnHeaderVertAlign) { case "middle": el.style.justifyContent = "center"; break; case "bottom": el.style.justifyContent = "flex-end"; break; } return el; } createGroupElement() { var el = document.createElement("div"); el.classList.add("tabulator-col-group-cols"); return el; } mapDefinitions() { var defaults = this.table.options.columnDefaults; if (defaults) { for (let key in defaults) { if (typeof this.definition[key] === "undefined") { this.definition[key] = defaults[key]; } } } this.definition = this.table.columnManager.optionsList.generate(_Column.defaultOptionList, this.definition); } checkDefinition() { Object.keys(this.definition).forEach((key) => { if (_Column.defaultOptionList.indexOf(key) === -1) { console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key); } }); } setField(field) { this.field = field; this.fieldStructure = field ? this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field] : []; this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData; this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData; } //register column position with column manager registerColumnPosition(column) { this.parent.registerColumnPosition(column); } //register column position with column manager registerColumnField(column) { this.parent.registerColumnField(column); } //trigger position registration reRegisterPosition() { if (this.isGroup) { this.columns.forEach(function(column) { column.reRegisterPosition(); }); } else { this.registerColumnPosition(this); } } //build header element _initialize() { var def = this.definition; while (this.element.firstChild) this.element.removeChild(this.element.firstChild); if (def.headerVertical) { this.element.classList.add("tabulator-col-vertical"); if (def.headerVertical === "flip") { this.element.classList.add("tabulator-col-vertical-flip"); } } this.contentElement = this._buildColumnHeaderContent(); this.element.appendChild(this.contentElement); if (this.isGroup) { this._buildGroupHeader(); } else { this._buildColumnHeader(); } this.dispatch("column-init", this); } //build header element for header _buildColumnHeader() { var def = this.definition; this.dispatch("column-layout", this); if (typeof def.visible != "undefined") { if (def.visible) { this.show(true); } else { this.hide(true); } } if (def.cssClass) { var classNames = def.cssClass.split(" "); classNames.forEach((className) => { this.element.classList.add(className); }); } if (def.field) { this.element.setAttribute("tabulator-field", def.field); } this.setMinWidth(parseInt(def.minWidth)); if (def.maxInitialWidth) { this.maxInitialWidth = parseInt(def.maxInitialWidth); } if (def.maxWidth) { this.setMaxWidth(parseInt(def.maxWidth)); } this.reinitializeWidth(); this.hozAlign = this.definition.hozAlign; this.vertAlign = this.definition.vertAlign; this.titleElement.style.textAlign = this.definition.headerHozAlign; } _buildColumnHeaderContent() { var contentElement = document.createElement("div"); contentElement.classList.add("tabulator-col-content"); this.titleHolderElement = document.createElement("div"); this.titleHolderElement.classList.add("tabulator-col-title-holder"); contentElement.appendChild(this.titleHolderElement); this.titleElement = this._buildColumnHeaderTitle(); this.titleHolderElement.appendChild(this.titleElement); return contentElement; } //build title element of column _buildColumnHeaderTitle() { var def = this.definition; var titleHolderElement = document.createElement("div"); titleHolderElement.classList.add("tabulator-col-title"); if (def.headerWordWrap) { titleHolderElement.classList.add("tabulator-col-title-wrap"); } if (def.editableTitle) { var titleElement = document.createElement("input"); titleElement.classList.add("tabulator-title-editor"); titleElement.addEventListener("click", (e) => { e.stopPropagation(); titleElement.focus(); }); titleElement.addEventListener("mousedown", (e) => { e.stopPropagation(); }); titleElement.addEventListener("change", () => { def.title = titleElement.value; this.dispatchExternal("columnTitleChanged", this.getComponent()); }); titleHolderElement.appendChild(titleElement); if (def.field) { this.langBind("columns|" + def.field, (text) => { titleElement.value = text || (def.title || " "); }); } else { titleElement.value = def.title || " "; } } else { if (def.field) { this.langBind("columns|" + def.field, (text) => { this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || " ")); }); } else { this._formatColumnHeaderTitle(titleHolderElement, def.title || " "); } } return titleHolderElement; } _formatColumnHeaderTitle(el, title) { var contents = this.chain("column-format", [this, title, el], null, () => { return title; }); switch (typeof contents) { case "object": if (contents instanceof Node) { el.appendChild(contents); } else { el.innerHTML = ""; console.warn("Format Error - Title formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", contents); } break; case "undefined": el.innerHTML = ""; break; default: el.innerHTML = contents; } } //build header element for column group _buildGroupHeader() { this.element.classList.add("tabulator-col-group"); this.element.setAttribute("role", "columngroup"); this.element.setAttribute("aria-title", this.definition.title); if (this.definition.cssClass) { var classNames = this.definition.cssClass.split(" "); classNames.forEach((className) => { this.element.classList.add(className); }); } this.titleElement.style.textAlign = this.definition.headerHozAlign; this.element.appendChild(this.groupElement); } //flat field lookup _getFlatData(data) { return data[this.field]; } //nested field lookup _getNestedData(data) { var dataObj = data, structure = this.fieldStructure, length = structure.length, output; for (let i = 0; i < length; i++) { dataObj = dataObj[structure[i]]; output = dataObj; if (!dataObj) { break; } } return output; } //flat field set _setFlatData(data, value) { if (this.field) { data[this.field] = value; } } //nested field set _setNestedData(data, value) { var dataObj = data, structure = this.fieldStructure, length = structure.length; for (let i = 0; i < length; i++) { if (i == length - 1) { dataObj[structure[i]] = value; } else { if (!dataObj[structure[i]]) { if (typeof value !== "undefined") { dataObj[structure[i]] = {}; } else { break; } } dataObj = dataObj[structure[i]]; } } } //attach column to this group attachColumn(column) { if (this.groupElement) { this.columns.push(column); this.groupElement.appendChild(column.getElement()); column.columnRendered(); } else { console.warn("Column Warning - Column being attached to another column instead of column group"); } } //vertically align header in column verticalAlign(alignment, height) { var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : height || this.parent.getHeadersElement().clientHeight; this.element.style.height = parentHeight + "px"; this.dispatch("column-height", this, this.element.style.height); if (this.isGroup) { this.groupElement.style.minHeight = parentHeight - this.contentElement.offsetHeight + "px"; } this.columns.forEach(function(column) { column.verticalAlign(alignment); }); } //clear vertical alignment clearVerticalAlign() { this.element.style.paddingTop = ""; this.element.style.height = ""; this.element.style.minHeight = ""; this.groupElement.style.minHeight = ""; this.columns.forEach(function(column) { column.clearVerticalAlign(); }); this.dispatch("column-height", this, ""); } //// Retrieve Column Information //// //return column header element getElement() { return this.element; } //return column group element getGroupElement() { return this.groupElement; } //return field name getField() { return this.field; } getTitleDownload() { return this.titleDownload; } //return the first column in a group getFirstColumn() { if (!this.isGroup) { return this; } else { if (this.columns.length) { return this.columns[0].getFirstColumn(); } else { return false; } } } //return the last column in a group getLastColumn() { if (!this.isGroup) { return this; } else { if (this.columns.length) { return this.columns[this.columns.length - 1].getLastColumn(); } else { return false; } } } //return all columns in a group getColumns(traverse) { var columns = []; if (traverse) { this.columns.forEach((column) => { columns.push(column); columns = columns.concat(column.getColumns(true)); }); } else { columns = this.columns; } return columns; } //return all columns in a group getCells() { return this.cells; } //retrieve the top column in a group of columns getTopColumn() { if (this.parent.isGroup) { return this.parent.getTopColumn(); } else { return this; } } //return column definition object getDefinition(updateBranches) { var colDefs = []; if (this.isGroup && updateBranches) { this.columns.forEach(function(column) { colDefs.push(column.getDefinition(true)); }); this.definition.columns = colDefs; } return this.definition; } //////////////////// Actions //////////////////// checkColumnVisibility() { var visible = false; this.columns.forEach(function(column) { if (column.visible) { visible = true; } }); if (visible) { this.show(); this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false); } else { this.hide(); } } //show column show(silent, responsiveToggle) { if (!this.visible) { this.visible = true; this.element.style.display = ""; if (this.parent.isGroup) { this.parent.checkColumnVisibility(); } this.cells.forEach(function(cell) { cell.show(); }); if (!this.isGroup && this.width === null) { this.reinitializeWidth(); } this.table.columnManager.verticalAlignHeaders(); this.dispatch("column-show", this, responsiveToggle); if (!silent) { this.dispatchExternal("columnVisibilityChanged", this.getComponent(), true); } if (this.parent.isGroup) { this.parent.matchChildWidths(); } if (!this.silent) { this.table.columnManager.rerenderColumns(); } } } //hide column hide(silent, responsiveToggle) { if (this.visible) { this.visible = false; this.element.style.display = "none"; this.table.columnManager.verticalAlignHeaders(); if (this.parent.isGroup) { this.parent.checkColumnVisibility(); } this.cells.forEach(function(cell) { cell.hide(); }); this.dispatch("column-hide", this, responsiveToggle); if (!silent) { this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false); } if (this.parent.isGroup) { this.parent.matchChildWidths(); } if (!this.silent) { this.table.columnManager.rerenderColumns(); } } } matchChildWidths() { var childWidth = 0; if (this.contentElement && this.columns.length) { this.columns.forEach(function(column) { if (column.visible) { childWidth += column.getWidth(); } }); this.contentElement.style.maxWidth = childWidth - 1 + "px"; if (this.table.initialized) { this.element.style.width = childWidth + "px"; } if (this.parent.isGroup) { this.parent.matchChildWidths(); } } } removeChild(child) { var index = this.columns.indexOf(child); if (index > -1) { this.columns.splice(index, 1); } if (!this.columns.length) { this.delete(); } } setWidth(width) { this.widthFixed = true; this.setWidthActual(width); } setWidthActual(width) { if (isNaN(width)) { width = Math.floor(this.table.element.clientWidth / 100 * parseInt(width)); } width = Math.max(this.minWidth, width); if (this.maxWidth) { width = Math.min(this.maxWidth, width); } this.width = width; this.widthStyled = width ? width + "px" : ""; this.element.style.width = this.widthStyled; if (!this.isGroup) { this.cells.forEach(function(cell) { cell.setWidth(); }); } if (this.parent.isGroup) { this.parent.matchChildWidths(); } this.dispatch("column-width", this); if (this.subscribedExternal("columnWidth")) { this.dispatchExternal("columnWidth", this.getComponent()); } } checkCellHeights() { var rows2 = []; this.cells.forEach(function(cell) { if (cell.row.heightInitialized) { if (cell.row.getElement().offsetParent !== null) { rows2.push(cell.row); cell.row.clearCellHeight(); } else { cell.row.heightInitialized = false; } } }); rows2.forEach(function(row) { row.calcHeight(); }); rows2.forEach(function(row) { row.setCellHeight(); }); } getWidth() { var width = 0; if (this.isGroup) { this.columns.forEach(function(column) { if (column.visible) { width += column.getWidth(); } }); } else { width = this.width; } return width; } getLeftOffset() { var offset = this.element.offsetLeft; if (this.parent.isGroup) { offset += this.parent.getLeftOffset(); } return offset; } getHeight() { return Math.ceil(this.element.getBoundingClientRect().height); } setMinWidth(minWidth) { if (this.maxWidth && minWidth > this.maxWidth) { minWidth = this.maxWidth; console.warn("the minWidth (" + minWidth + "px) for column '" + this.field + "' cannot be bigger that its maxWidth (" + this.maxWidthStyled + ")"); } this.minWidth = minWidth; this.minWidthStyled = minWidth ? minWidth + "px" : ""; this.element.style.minWidth = this.minWidthStyled; this.cells.forEach(function(cell) { cell.setMinWidth(); }); } setMaxWidth(maxWidth) { if (this.minWidth && maxWidth < this.minWidth) { maxWidth = this.minWidth; console.warn("the maxWidth (" + maxWidth + "px) for column '" + this.field + "' cannot be smaller that its minWidth (" + this.minWidthStyled + ")"); } this.maxWidth = maxWidth; this.maxWidthStyled = maxWidth ? maxWidth + "px" : ""; this.element.style.maxWidth = this.maxWidthStyled; this.cells.forEach(function(cell) { cell.setMaxWidth(); }); } delete() { return new Promise((resolve, reject) => { if (this.isGroup) { this.columns.forEach(function(column) { column.delete(); }); } this.dispatch("column-delete", this); var cellCount = this.cells.length; for (let i = 0; i < cellCount; i++) { this.cells[0].delete(); } if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.element = false; this.contentElement = false; this.titleElement = false; this.groupElement = false; if (this.parent.isGroup) { this.parent.removeChild(this); } this.table.columnManager.deregisterColumn(this); this.table.columnManager.rerenderColumns(true); this.dispatch("column-deleted", this); resolve(); }); } columnRendered() { if (this.titleFormatterRendered) { this.titleFormatterRendered(); } this.dispatch("column-rendered", this); } //////////////// Cell Management ///////////////// //generate cell for this column generateCell(row) { var cell = new Cell(this, row); this.cells.push(cell); return cell; } nextColumn() { var index = this.table.columnManager.findColumnIndex(this); return index > -1 ? this._nextVisibleColumn(index + 1) : false; } _nextVisibleColumn(index) { var column = this.table.columnManager.getColumnByIndex(index); return !column || column.visible ? column : this._nextVisibleColumn(index + 1); } prevColumn() { var index = this.table.columnManager.findColumnIndex(this); return index > -1 ? this._prevVisibleColumn(index - 1) : false; } _prevVisibleColumn(index) { var column = this.table.columnManager.getColumnByIndex(index); return !column || column.visible ? column : this._prevVisibleColumn(index - 1); } reinitializeWidth(force) { this.widthFixed = false; if (typeof this.definition.width !== "undefined" && !force) { this.setWidth(this.definition.width); } this.dispatch("column-width-fit-before", this); this.fitToData(force); this.dispatch("column-width-fit-after", this); } //set column width to maximum cell width for non group columns fitToData(force) { if (this.isGroup) { return; } if (!this.widthFixed) { this.element.style.width = ""; this.cells.forEach((cell) => { cell.clearWidth(); }); } var maxWidth = this.element.offsetWidth; if (!this.width || !this.widthFixed) { this.cells.forEach((cell) => { var width = cell.getWidth(); if (width > maxWidth) { maxWidth = width; } }); if (maxWidth) { var setTo = maxWidth + 1; if (force) { this.setWidth(setTo); } else { if (this.maxInitialWidth && !force) { setTo = Math.min(setTo, this.maxInitialWidth); } this.setWidthActual(setTo); } } } } updateDefinition(updates) { var definition; if (!this.isGroup) { if (!this.parent.isGroup) { definition = Object.assign({}, this.getDefinition()); definition = Object.assign(definition, updates); return this.table.columnManager.addColumn(definition, false, this).then((column) => { if (definition.field == this.field) { this.field = false; } return this.delete().then(() => { return column.getComponent(); }); }); } else { console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns"); return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups"); } } else { console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns"); return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups"); } } deleteCell(cell) { var index = this.cells.indexOf(cell); if (index > -1) { this.cells.splice(index, 1); } } //////////////// Object Generation ///////////////// getComponent() { if (!this.component) { this.component = new ColumnComponent(this); } return this.component; } getPosition() { return this.table.columnManager.getVisibleColumnsByIndex().indexOf(this) + 1; } getParentComponent() { return this.parent instanceof _Column ? this.parent.getComponent() : false; } }; __publicField(_Column, "defaultOptionList", defaultColumnOptions); let Column = _Column; class RowComponent { constructor(row) { this._row = row; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._row.table.componentFunctionBinder.handle("row", target._row, name2); } } }); } getData(transform) { return this._row.getData(transform); } getElement() { return this._row.getElement(); } getCells() { var cells = []; this._row.getCells().forEach(function(cell) { cells.push(cell.getComponent()); }); return cells; } getCell(column) { var cell = this._row.getCell(column); return cell ? cell.getComponent() : false; } getIndex() { return this._row.getData("data")[this._row.table.options.index]; } getPosition() { return this._row.getPosition(); } watchPosition(callback) { return this._row.watchPosition(callback); } delete() { return this._row.delete(); } scrollTo(position, ifVisible) { return this._row.table.rowManager.scrollToRow(this._row, position, ifVisible); } move(to, after) { this._row.moveToRow(to, after); } update(data) { return this._row.updateData(data); } normalizeHeight() { this._row.normalizeHeight(true); } _getSelf() { return this._row; } reformat() { return this._row.reinitialize(); } getTable() { return this._row.table; } getNextRow() { var row = this._row.nextRow(); return row ? row.getComponent() : row; } getPrevRow() { var row = this._row.prevRow(); return row ? row.getComponent() : row; } } class Row extends CoreFeature { constructor(data, parent2, type = "row") { super(parent2.table); this.parent = parent2; this.data = {}; this.type = type; this.element = false; this.modules = {}; this.cells = []; this.height = 0; this.heightStyled = ""; this.manualHeight = false; this.outerHeight = 0; this.initialized = false; this.heightInitialized = false; this.position = 0; this.positionWatchers = []; this.component = null; this.created = false; this.setData(data); } create() { if (!this.created) { this.created = true; this.generateElement(); } } createElement() { var el = document.createElement("div"); el.classList.add("tabulator-row"); el.setAttribute("role", "row"); this.element = el; } getElement() { this.create(); return this.element; } detachElement() { if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } } generateElement() { this.createElement(); this.dispatch("row-init", this); } generateCells() { this.cells = this.table.columnManager.generateCells(this); } //functions to setup on first render initialize(force, inFragment) { this.create(); if (!this.initialized || force) { this.deleteCells(); while (this.element.firstChild) this.element.removeChild(this.element.firstChild); this.dispatch("row-layout-before", this); this.generateCells(); this.initialized = true; this.table.columnManager.renderer.renderRowCells(this, inFragment); if (force) { this.normalizeHeight(); } this.dispatch("row-layout", this); if (this.table.options.rowFormatter) { this.table.options.rowFormatter(this.getComponent()); } this.dispatch("row-layout-after", this); } else { this.table.columnManager.renderer.rerenderRowCells(this, inFragment); } } rendered() { this.cells.forEach((cell) => { cell.cellRendered(); }); } reinitializeHeight() { this.heightInitialized = false; if (this.element && this.element.offsetParent !== null) { this.normalizeHeight(true); } } deinitialize() { this.initialized = false; } deinitializeHeight() { this.heightInitialized = false; } reinitialize(children) { this.initialized = false; this.heightInitialized = false; if (!this.manualHeight) { this.height = 0; this.heightStyled = ""; } if (this.element && this.element.offsetParent !== null) { this.initialize(true); } this.dispatch("row-relayout", this); } //get heights when doing bulk row style calcs in virtual DOM calcHeight(force) { var maxHeight = 0, minHeight = 0; if (this.table.options.rowHeight) { this.height = this.table.options.rowHeight; } else { minHeight = this.calcMinHeight(); maxHeight = this.calcMaxHeight(); if (force) { this.height = Math.max(maxHeight, minHeight); } else { this.height = this.manualHeight ? this.height : Math.max(maxHeight, minHeight); } } this.heightStyled = this.height ? this.height + "px" : ""; this.outerHeight = this.element.offsetHeight; } calcMinHeight() { return this.table.options.resizableRows ? this.element.clientHeight : 0; } calcMaxHeight() { var maxHeight = 0; this.cells.forEach(function(cell) { var height = cell.getHeight(); if (height > maxHeight) { maxHeight = height; } }); return maxHeight; } //set of cells setCellHeight() { this.cells.forEach(function(cell) { cell.setHeight(); }); this.heightInitialized = true; } clearCellHeight() { this.cells.forEach(function(cell) { cell.clearHeight(); }); } //normalize the height of elements in the row normalizeHeight(force) { if (force && !this.table.options.rowHeight) { this.clearCellHeight(); } this.calcHeight(force); this.setCellHeight(); } //set height of rows setHeight(height, force) { if (this.height != height || force) { this.manualHeight = true; this.height = height; this.heightStyled = height ? height + "px" : ""; this.setCellHeight(); this.outerHeight = this.element.offsetHeight; if (this.subscribedExternal("rowHeight")) { this.dispatchExternal("rowHeight", this.getComponent()); } } } //return rows outer height getHeight() { return this.outerHeight; } //return rows outer Width getWidth() { return this.element.offsetWidth; } //////////////// Cell Management ///////////////// deleteCell(cell) { var index = this.cells.indexOf(cell); if (index > -1) { this.cells.splice(index, 1); } } //////////////// Data Management ///////////////// setData(data) { this.data = this.chain("row-data-init-before", [this, data], void 0, data); this.dispatch("row-data-init-after", this); } //update the rows data updateData(updatedData) { var visible = this.element && Helpers.elVisible(this.element), tempData = {}, newRowData; return new Promise((resolve, reject) => { if (typeof updatedData === "string") { updatedData = JSON.parse(updatedData); } this.dispatch("row-data-save-before", this); if (this.subscribed("row-data-changing")) { tempData = Object.assign(tempData, this.data); tempData = Object.assign(tempData, updatedData); } newRowData = this.chain("row-data-changing", [this, tempData, updatedData], null, updatedData); const cellsToUpdate = []; for (let attrname in updatedData) { let columns = this.table.columnManager.getColumnsByFieldRoot(attrname); columns.forEach((column) => { let cell = this.getCell(column.getField()); if (cell) { let value = column.getFieldValue(newRowData); if (cell.getValue() !== value) { cellsToUpdate.push([cell, value]); } } }); } for (let attrname in newRowData) { this.data[attrname] = newRowData[attrname]; } this.dispatch("row-data-save-after", this); cellsToUpdate.forEach(([cell, value]) => { cell.setValueProcessData(value); if (visible) { cell.cellRendered(); } }); if (visible) { this.normalizeHeight(true); if (this.table.options.rowFormatter) { this.table.options.rowFormatter(this.getComponent()); } } else { this.initialized = false; this.height = 0; this.heightStyled = ""; } this.dispatch("row-data-changed", this, visible, updatedData); this.dispatchExternal("rowUpdated", this.getComponent()); if (this.subscribedExternal("dataChanged")) { this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } resolve(); }); } getData(transform) { if (transform) { return this.chain("row-data-retrieve", [this, transform], null, this.data); } return this.data; } getCell(column) { var match = false; column = this.table.columnManager.findColumn(column); if (!this.initialized && this.cells.length === 0) { this.generateCells(); } match = this.cells.find(function(cell) { return cell.column === column; }); return match; } getCellIndex(findCell) { return this.cells.findIndex(function(cell) { return cell === findCell; }); } findCell(subject) { return this.cells.find((cell) => { return cell.element === subject; }); } getCells() { if (!this.initialized && this.cells.length === 0) { this.generateCells(); } return this.cells; } nextRow() { var row = this.table.rowManager.nextDisplayRow(this, true); return row || false; } prevRow() { var row = this.table.rowManager.prevDisplayRow(this, true); return row || false; } moveToRow(to, before) { var toRow = this.table.rowManager.findRow(to); if (toRow) { this.table.rowManager.moveRowActual(this, toRow, !before); this.table.rowManager.refreshActiveData("display", false, true); } else { console.warn("Move Error - No matching row found:", to); } } ///////////////////// Actions ///////////////////// delete() { this.dispatch("row-delete", this); this.deleteActual(); return Promise.resolve(); } deleteActual(blockRedraw) { this.detachModules(); this.table.rowManager.deleteRow(this, blockRedraw); this.deleteCells(); this.initialized = false; this.heightInitialized = false; this.element = false; this.dispatch("row-deleted", this); } detachModules() { this.dispatch("row-deleting", this); } deleteCells() { var cellCount = this.cells.length; for (let i = 0; i < cellCount; i++) { this.cells[0].delete(); } } wipe() { this.detachModules(); this.deleteCells(); if (this.element) { while (this.element.firstChild) this.element.removeChild(this.element.firstChild); if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } } this.element = false; this.modules = {}; } isDisplayed() { return this.table.rowManager.getDisplayRows().includes(this); } getPosition() { return this.isDisplayed() ? this.position : false; } setPosition(position) { if (position != this.position) { this.position = position; this.positionWatchers.forEach((callback) => { callback(this.position); }); } } watchPosition(callback) { this.positionWatchers.push(callback); callback(this.position); } getGroup() { return this.modules.group || false; } //////////////// Object Generation ///////////////// getComponent() { if (!this.component) { this.component = new RowComponent(this); } return this.component; } } var defaultCalculations = { "avg": function(values, data, calcParams) { var output = 0, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : 2; if (values.length) { output = values.reduce(function(sum, value) { return Number(sum) + Number(value); }); output = output / values.length; output = precision !== false ? output.toFixed(precision) : output; } return parseFloat(output).toString(); }, "max": function(values, data, calcParams) { var output = null, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; values.forEach(function(value) { value = Number(value); if (value > output || output === null) { output = value; } }); return output !== null ? precision !== false ? output.toFixed(precision) : output : ""; }, "min": function(values, data, calcParams) { var output = null, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; values.forEach(function(value) { value = Number(value); if (value < output || output === null) { output = value; } }); return output !== null ? precision !== false ? output.toFixed(precision) : output : ""; }, "sum": function(values, data, calcParams) { var output = 0, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; if (values.length) { values.forEach(function(value) { value = Number(value); output += !isNaN(value) ? Number(value) : 0; }); } return precision !== false ? output.toFixed(precision) : output; }, "concat": function(values, data, calcParams) { var output = 0; if (values.length) { output = values.reduce(function(sum, value) { return String(sum) + String(value); }); } return output; }, "count": function(values, data, calcParams) { var output = 0; if (values.length) { values.forEach(function(value) { if (value) { output++; } }); } return output; }, "unique": function(values, data, calcParams) { var unique = values.filter((value, index) => { return (values || value === 0) && values.indexOf(value) === index; }); return unique.length; } }; const _ColumnCalcs = class _ColumnCalcs extends Module { constructor(table) { super(table); this.topCalcs = []; this.botCalcs = []; this.genColumn = false; this.topElement = this.createElement(); this.botElement = this.createElement(); this.topRow = false; this.botRow = false; this.topInitialized = false; this.botInitialized = false; this.blocked = false; this.recalcAfterBlock = false; this.registerTableOption("columnCalcs", true); this.registerColumnOption("topCalc"); this.registerColumnOption("topCalcParams"); this.registerColumnOption("topCalcFormatter"); this.registerColumnOption("topCalcFormatterParams"); this.registerColumnOption("bottomCalc"); this.registerColumnOption("bottomCalcParams"); this.registerColumnOption("bottomCalcFormatter"); this.registerColumnOption("bottomCalcFormatterParams"); } createElement() { var el = document.createElement("div"); el.classList.add("tabulator-calcs-holder"); return el; } initialize() { this.genColumn = new Column({ field: "value" }, this); this.subscribe("cell-value-changed", this.cellValueChanged.bind(this)); this.subscribe("column-init", this.initializeColumnCheck.bind(this)); this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this)); this.subscribe("row-added", this.rowsUpdated.bind(this)); this.subscribe("column-moved", this.recalcActiveRows.bind(this)); this.subscribe("column-add", this.recalcActiveRows.bind(this)); this.subscribe("data-refreshed", this.recalcActiveRowsRefresh.bind(this)); this.subscribe("table-redraw", this.tableRedraw.bind(this)); this.subscribe("rows-visible", this.visibleRows.bind(this)); this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this)); this.subscribe("redraw-blocked", this.blockRedraw.bind(this)); this.subscribe("redraw-restored", this.restoreRedraw.bind(this)); this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this)); this.subscribe("column-resized", this.resizeHolderWidth.bind(this)); this.subscribe("column-show", this.resizeHolderWidth.bind(this)); this.subscribe("column-hide", this.resizeHolderWidth.bind(this)); this.registerTableFunction("getCalcResults", this.getResults.bind(this)); this.registerTableFunction("recalc", this.userRecalc.bind(this)); this.resizeHolderWidth(); } resizeHolderWidth() { this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px"; } tableRedraw(force) { this.recalc(this.table.rowManager.activeRows); if (force) { this.redraw(); } } blockRedraw() { this.blocked = true; this.recalcAfterBlock = false; } restoreRedraw() { this.blocked = false; if (this.recalcAfterBlock) { this.recalcAfterBlock = false; this.recalcActiveRowsRefresh(); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userRecalc() { this.recalc(this.table.rowManager.activeRows); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// blockCheck() { if (this.blocked) { this.recalcAfterBlock = true; } return this.blocked; } visibleRows(viewable, rows2) { if (this.topRow) { rows2.unshift(this.topRow); } if (this.botRow) { rows2.push(this.botRow); } return rows2; } rowsUpdated(row) { if (this.table.options.groupBy) { this.recalcRowGroup(row); } else { this.recalcActiveRows(); } } recalcActiveRowsRefresh() { if (this.table.options.groupBy && this.table.options.dataTreeStartExpanded && this.table.options.dataTree) { this.recalcAll(); } else { this.recalcActiveRows(); } } recalcActiveRows() { this.recalc(this.table.rowManager.activeRows); } cellValueChanged(cell) { if (cell.column.definition.topCalc || cell.column.definition.bottomCalc) { if (this.table.options.groupBy) { if (this.table.options.columnCalcs == "table" || this.table.options.columnCalcs == "both") { this.recalcActiveRows(); } if (this.table.options.columnCalcs != "table") { this.recalcRowGroup(cell.row); } } else { this.recalcActiveRows(); } } } initializeColumnCheck(column) { if (column.definition.topCalc || column.definition.bottomCalc) { this.initializeColumn(column); } } //initialize column calcs initializeColumn(column) { var def = column.definition; var config = { topCalcParams: def.topCalcParams || {}, botCalcParams: def.bottomCalcParams || {} }; if (def.topCalc) { switch (typeof def.topCalc) { case "string": if (_ColumnCalcs.calculations[def.topCalc]) { config.topCalc = _ColumnCalcs.calculations[def.topCalc]; } else { console.warn("Column Calc Error - No such calculation found, ignoring: ", def.topCalc); } break; case "function": config.topCalc = def.topCalc; break; } if (config.topCalc) { column.modules.columnCalcs = config; this.topCalcs.push(column); if (this.table.options.columnCalcs != "group") { this.initializeTopRow(); } } } if (def.bottomCalc) { switch (typeof def.bottomCalc) { case "string": if (_ColumnCalcs.calculations[def.bottomCalc]) { config.botCalc = _ColumnCalcs.calculations[def.bottomCalc]; } else { console.warn("Column Calc Error - No such calculation found, ignoring: ", def.bottomCalc); } break; case "function": config.botCalc = def.bottomCalc; break; } if (config.botCalc) { column.modules.columnCalcs = config; this.botCalcs.push(column); if (this.table.options.columnCalcs != "group") { this.initializeBottomRow(); } } } } //dummy functions to handle being mock column manager registerColumnField() { } removeCalcs() { var changed = false; if (this.topInitialized) { this.topInitialized = false; this.topElement.parentNode.removeChild(this.topElement); changed = true; } if (this.botInitialized) { this.botInitialized = false; this.footerRemove(this.botElement); changed = true; } if (changed) { this.table.rowManager.adjustTableSize(); } } reinitializeCalcs() { if (this.topCalcs.length) { this.initializeTopRow(); } if (this.botCalcs.length) { this.initializeBottomRow(); } } initializeTopRow() { var fragment = document.createDocumentFragment(); if (!this.topInitialized) { fragment.appendChild(document.createElement("br")); fragment.appendChild(this.topElement); this.table.columnManager.getContentsElement().insertBefore(fragment, this.table.columnManager.headersElement.nextSibling); this.topInitialized = true; } } initializeBottomRow() { if (!this.botInitialized) { this.footerPrepend(this.botElement); this.botInitialized = true; } } scrollHorizontal(left) { if (this.botInitialized && this.botRow) { this.botElement.scrollLeft = left; } } recalc(rows2) { var data, row; if (!this.blockCheck()) { if (this.topInitialized || this.botInitialized) { data = this.rowsToData(rows2); if (this.topInitialized) { if (this.topRow) { this.topRow.deleteCells(); } row = this.generateRow("top", data); this.topRow = row; while (this.topElement.firstChild) this.topElement.removeChild(this.topElement.firstChild); this.topElement.appendChild(row.getElement()); row.initialize(true); } if (this.botInitialized) { if (this.botRow) { this.botRow.deleteCells(); } row = this.generateRow("bottom", data); this.botRow = row; while (this.botElement.firstChild) this.botElement.removeChild(this.botElement.firstChild); this.botElement.appendChild(row.getElement()); row.initialize(true); } this.table.rowManager.adjustTableSize(); if (this.table.modExists("frozenColumns")) { this.table.modules.frozenColumns.layout(); } } } } recalcRowGroup(row) { this.recalcGroup(this.table.modules.groupRows.getRowGroup(row)); } recalcAll() { if (this.topCalcs.length || this.botCalcs.length) { if (this.table.options.columnCalcs !== "group") { this.recalcActiveRows(); } if (this.table.options.groupBy && this.table.options.columnCalcs !== "table") { var groups = this.table.modules.groupRows.getChildGroups(); groups.forEach((group) => { this.recalcGroup(group); }); } } } recalcGroup(group) { var data, rowData; if (!this.blockCheck()) { if (group) { if (group.calcs) { if (group.calcs.bottom) { data = this.rowsToData(group.rows); rowData = this.generateRowData("bottom", data); group.calcs.bottom.updateData(rowData); group.calcs.bottom.reinitialize(); } if (group.calcs.top) { data = this.rowsToData(group.rows); rowData = this.generateRowData("top", data); group.calcs.top.updateData(rowData); group.calcs.top.reinitialize(); } } } } } //generate top stats row generateTopRow(rows2) { return this.generateRow("top", this.rowsToData(rows2)); } //generate bottom stats row generateBottomRow(rows2) { return this.generateRow("bottom", this.rowsToData(rows2)); } rowsToData(rows2) { var data = [], hasDataTreeColumnCalcs = this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs, dataTree = this.table.modules.dataTree; rows2.forEach((row) => { var _a2; data.push(row.getData()); if (hasDataTreeColumnCalcs && ((_a2 = row.modules.dataTree) == null ? void 0 : _a2.open)) { this.rowsToData(dataTree.getFilteredTreeChildren(row)).forEach((dataRow) => { data.push(dataRow); }); } }); return data; } //generate stats row generateRow(pos, data) { var rowData = this.generateRowData(pos, data), row; if (this.table.modExists("mutator")) { this.table.modules.mutator.disable(); } row = new Row(rowData, this, "calc"); if (this.table.modExists("mutator")) { this.table.modules.mutator.enable(); } row.getElement().classList.add("tabulator-calcs", "tabulator-calcs-" + pos); row.component = false; row.getComponent = () => { if (!row.component) { row.component = new CalcComponent(row); } return row.component; }; row.generateCells = () => { var cells = []; this.table.columnManager.columnsByIndex.forEach((column) => { this.genColumn.setField(column.getField()); this.genColumn.hozAlign = column.hozAlign; if (column.definition[pos + "CalcFormatter"] && this.table.modExists("format")) { this.genColumn.modules.format = { formatter: this.table.modules.format.lookupFormatter(column.definition[pos + "CalcFormatter"]), params: column.definition[pos + "CalcFormatterParams"] || {} }; } else { this.genColumn.modules.format = { formatter: this.table.modules.format.lookupFormatter("plaintext"), params: {} }; } this.genColumn.definition.cssClass = column.definition.cssClass; var cell = new Cell(this.genColumn, row); cell.getElement(); cell.column = column; cell.setWidth(); column.cells.push(cell); cells.push(cell); if (!column.visible) { cell.hide(); } }); row.cells = cells; }; return row; } //generate stats row generateRowData(pos, data) { var rowData = {}, calcs = pos == "top" ? this.topCalcs : this.botCalcs, type = pos == "top" ? "topCalc" : "botCalc", params, paramKey; calcs.forEach(function(column) { var values = []; if (column.modules.columnCalcs && column.modules.columnCalcs[type]) { data.forEach(function(item) { values.push(column.getFieldValue(item)); }); paramKey = type + "Params"; params = typeof column.modules.columnCalcs[paramKey] === "function" ? column.modules.columnCalcs[paramKey](values, data) : column.modules.columnCalcs[paramKey]; column.setFieldValue(rowData, column.modules.columnCalcs[type](values, data, params)); } }); return rowData; } hasTopCalcs() { return !!this.topCalcs.length; } hasBottomCalcs() { return !!this.botCalcs.length; } //handle table redraw redraw() { if (this.topRow) { this.topRow.normalizeHeight(true); } if (this.botRow) { this.botRow.normalizeHeight(true); } } //return the calculated getResults() { var results = {}, groups; if (this.table.options.groupBy && this.table.modExists("groupRows")) { groups = this.table.modules.groupRows.getGroups(true); groups.forEach((group) => { results[group.getKey()] = this.getGroupResults(group); }); } else { results = { top: this.topRow ? this.topRow.getData() : {}, bottom: this.botRow ? this.botRow.getData() : {} }; } return results; } //get results from a group getGroupResults(group) { var groupObj = group._getSelf(), subGroups = group.getSubGroups(), subGroupResults = {}, results = {}; subGroups.forEach((subgroup) => { subGroupResults[subgroup.getKey()] = this.getGroupResults(subgroup); }); results = { top: groupObj.calcs.top ? groupObj.calcs.top.getData() : {}, bottom: groupObj.calcs.bottom ? groupObj.calcs.bottom.getData() : {}, groups: subGroupResults }; return results; } adjustForScrollbar(width) { if (this.botRow) { if (this.table.rtl) { this.botElement.style.paddingLeft = width + "px"; } else { this.botElement.style.paddingRight = width + "px"; } } } }; __publicField(_ColumnCalcs, "moduleName", "columnCalcs"); //load defaults __publicField(_ColumnCalcs, "calculations", defaultCalculations); let ColumnCalcs = _ColumnCalcs; class DataTree extends Module { constructor(table) { super(table); this.indent = 10; this.field = ""; this.collapseEl = null; this.expandEl = null; this.branchEl = null; this.elementField = false; this.startOpen = function() { }; this.registerTableOption("dataTree", false); this.registerTableOption("dataTreeFilter", true); this.registerTableOption("dataTreeSort", true); this.registerTableOption("dataTreeElementColumn", false); this.registerTableOption("dataTreeBranchElement", true); this.registerTableOption("dataTreeChildIndent", 9); this.registerTableOption("dataTreeChildField", "_children"); this.registerTableOption("dataTreeCollapseElement", false); this.registerTableOption("dataTreeExpandElement", false); this.registerTableOption("dataTreeStartExpanded", false); this.registerTableOption("dataTreeChildColumnCalcs", false); this.registerTableOption("dataTreeSelectPropagate", false); this.registerComponentFunction("row", "treeCollapse", this.collapseRow.bind(this)); this.registerComponentFunction("row", "treeExpand", this.expandRow.bind(this)); this.registerComponentFunction("row", "treeToggle", this.toggleRow.bind(this)); this.registerComponentFunction("row", "getTreeParent", this.getTreeParent.bind(this)); this.registerComponentFunction("row", "getTreeChildren", this.getRowChildren.bind(this)); this.registerComponentFunction("row", "addTreeChild", this.addTreeChildRow.bind(this)); this.registerComponentFunction("row", "isTreeExpanded", this.isRowExpanded.bind(this)); } initialize() { if (this.table.options.dataTree) { var dummyEl = null, options = this.table.options; this.field = options.dataTreeChildField; this.indent = options.dataTreeChildIndent; if (this.options("movableRows")) { console.warn("The movableRows option is not available with dataTree enabled, moving of child rows could result in unpredictable behavior"); } if (options.dataTreeBranchElement) { if (options.dataTreeBranchElement === true) { this.branchEl = document.createElement("div"); this.branchEl.classList.add("tabulator-data-tree-branch"); } else { if (typeof options.dataTreeBranchElement === "string") { dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeBranchElement; this.branchEl = dummyEl.firstChild; } else { this.branchEl = options.dataTreeBranchElement; } } } else { this.branchEl = document.createElement("div"); this.branchEl.classList.add("tabulator-data-tree-branch-empty"); } if (options.dataTreeCollapseElement) { if (typeof options.dataTreeCollapseElement === "string") { dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeCollapseElement; this.collapseEl = dummyEl.firstChild; } else { this.collapseEl = options.dataTreeCollapseElement; } } else { this.collapseEl = document.createElement("div"); this.collapseEl.classList.add("tabulator-data-tree-control"); this.collapseEl.tabIndex = 0; this.collapseEl.innerHTML = "<div class='tabulator-data-tree-control-collapse'></div>"; } if (options.dataTreeExpandElement) { if (typeof options.dataTreeExpandElement === "string") { dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeExpandElement; this.expandEl = dummyEl.firstChild; } else { this.expandEl = options.dataTreeExpandElement; } } else { this.expandEl = document.createElement("div"); this.expandEl.classList.add("tabulator-data-tree-control"); this.expandEl.tabIndex = 0; this.expandEl.innerHTML = "<div class='tabulator-data-tree-control-expand'></div>"; } switch (typeof options.dataTreeStartExpanded) { case "boolean": this.startOpen = function(row, index) { return options.dataTreeStartExpanded; }; break; case "function": this.startOpen = options.dataTreeStartExpanded; break; default: this.startOpen = function(row, index) { return options.dataTreeStartExpanded[index]; }; break; } this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-layout-after", this.layoutRow.bind(this)); this.subscribe("row-deleting", this.rowDeleting.bind(this)); this.subscribe("row-deleted", this.rowDelete.bind(this), 0); this.subscribe("row-data-changed", this.rowDataChanged.bind(this), 10); this.subscribe("cell-value-updated", this.cellValueChanged.bind(this)); this.subscribe("edit-cancelled", this.cellValueChanged.bind(this)); this.subscribe("column-moving-rows", this.columnMoving.bind(this)); this.subscribe("table-built", this.initializeElementField.bind(this)); this.subscribe("table-redrawing", this.tableRedrawing.bind(this)); this.registerDisplayHandler(this.getRows.bind(this), 30); } } tableRedrawing(force) { var rows2; if (force) { rows2 = this.table.rowManager.getRows(); rows2.forEach((row) => { this.reinitializeRowChildren(row); }); } } initializeElementField() { var firstCol = this.table.columnManager.getFirstVisibleColumn(); this.elementField = this.table.options.dataTreeElementColumn || (firstCol ? firstCol.field : false); } getRowChildren(row) { return this.getTreeChildren(row, true); } columnMoving() { var rows2 = []; this.table.rowManager.rows.forEach((row) => { rows2 = rows2.concat(this.getTreeChildren(row, false, true)); }); return rows2; } rowDataChanged(row, visible, updatedData) { if (this.redrawNeeded(updatedData)) { this.initializeRow(row); if (visible) { this.layoutRow(row); this.refreshData(true); } } } cellValueChanged(cell) { var field = cell.column.getField(); if (field === this.elementField) { this.layoutRow(cell.row); } } initializeRow(row) { var childArray = row.getData()[this.field]; var isArray = Array.isArray(childArray); var children = isArray || !isArray && typeof childArray === "object" && childArray !== null; if (!children && row.modules.dataTree && row.modules.dataTree.branchEl && row.modules.dataTree.branchEl.parentNode) { row.modules.dataTree.branchEl.parentNode.removeChild(row.modules.dataTree.branchEl); } if (!children && row.modules.dataTree && row.modules.dataTree.controlEl && row.modules.dataTree.controlEl.parentNode) { row.modules.dataTree.controlEl.parentNode.removeChild(row.modules.dataTree.controlEl); } row.modules.dataTree = { index: row.modules.dataTree ? row.modules.dataTree.index : 0, open: children ? row.modules.dataTree ? row.modules.dataTree.open : this.startOpen(row.getComponent(), 0) : false, controlEl: row.modules.dataTree && children ? row.modules.dataTree.controlEl : false, branchEl: row.modules.dataTree && children ? row.modules.dataTree.branchEl : false, parent: row.modules.dataTree ? row.modules.dataTree.parent : false, children }; } reinitializeRowChildren(row) { var children = this.getTreeChildren(row, false, true); children.forEach(function(child) { child.reinitialize(true); }); } layoutRow(row) { var cell = this.elementField ? row.getCell(this.elementField) : row.getCells()[0], el = cell.getElement(), config = row.modules.dataTree; if (config.branchEl) { if (config.branchEl.parentNode) { config.branchEl.parentNode.removeChild(config.branchEl); } config.branchEl = false; } if (config.controlEl) { if (config.controlEl.parentNode) { config.controlEl.parentNode.removeChild(config.controlEl); } config.controlEl = false; } this.generateControlElement(row, el); row.getElement().classList.add("tabulator-tree-level-" + config.index); if (config.index) { if (this.branchEl) { config.branchEl = this.branchEl.cloneNode(true); el.insertBefore(config.branchEl, el.firstChild); if (this.table.rtl) { config.branchEl.style.marginRight = (config.branchEl.offsetWidth + config.branchEl.style.marginLeft) * (config.index - 1) + config.index * this.indent + "px"; } else { config.branchEl.style.marginLeft = (config.branchEl.offsetWidth + config.branchEl.style.marginRight) * (config.index - 1) + config.index * this.indent + "px"; } } else { if (this.table.rtl) { el.style.paddingRight = parseInt(window.getComputedStyle(el, null).getPropertyValue("padding-right")) + config.index * this.indent + "px"; } else { el.style.paddingLeft = parseInt(window.getComputedStyle(el, null).getPropertyValue("padding-left")) + config.index * this.indent + "px"; } } } } generateControlElement(row, el) { var config = row.modules.dataTree, oldControl = config.controlEl; el = el || row.getCells()[0].getElement(); if (config.children !== false) { if (config.open) { config.controlEl = this.collapseEl.cloneNode(true); config.controlEl.addEventListener("click", (e) => { e.stopPropagation(); this.collapseRow(row); }); } else { config.controlEl = this.expandEl.cloneNode(true); config.controlEl.addEventListener("click", (e) => { e.stopPropagation(); this.expandRow(row); }); } config.controlEl.addEventListener("mousedown", (e) => { e.stopPropagation(); }); if (oldControl && oldControl.parentNode === el) { oldControl.parentNode.replaceChild(config.controlEl, oldControl); } else { el.insertBefore(config.controlEl, el.firstChild); } } } getRows(rows2) { var output = []; rows2.forEach((row, i) => { var config, children; output.push(row); if (row instanceof Row) { row.create(); config = row.modules.dataTree; if (!config.index && config.children !== false) { children = this.getChildren(row, false, true); children.forEach((child) => { child.create(); output.push(child); }); } } }); return output; } getChildren(row, allChildren, sortOnly) { var config = row.modules.dataTree, children = [], output = []; if (config.children !== false && (config.open || allChildren)) { if (!Array.isArray(config.children)) { config.children = this.generateChildren(row); } if (this.table.modExists("filter") && this.table.options.dataTreeFilter) { children = this.table.modules.filter.filter(config.children); } else { children = config.children; } if (this.table.modExists("sort") && this.table.options.dataTreeSort) { this.table.modules.sort.sort(children, sortOnly); } children.forEach((child) => { output.push(child); var subChildren = this.getChildren(child, false, true); subChildren.forEach((sub) => { output.push(sub); }); }); } return output; } generateChildren(row) { var children = []; var childArray = row.getData()[this.field]; if (!Array.isArray(childArray)) { childArray = [childArray]; } childArray.forEach((childData) => { var childRow = new Row(childData || {}, this.table.rowManager); childRow.create(); childRow.modules.dataTree.index = row.modules.dataTree.index + 1; childRow.modules.dataTree.parent = row; if (childRow.modules.dataTree.children) { childRow.modules.dataTree.open = this.startOpen(childRow.getComponent(), childRow.modules.dataTree.index); } children.push(childRow); }); return children; } expandRow(row, silent) { var config = row.modules.dataTree; if (config.children !== false) { config.open = true; row.reinitialize(); this.refreshData(true); this.dispatchExternal("dataTreeRowExpanded", row.getComponent(), row.modules.dataTree.index); } } collapseRow(row) { var config = row.modules.dataTree; if (config.children !== false) { config.open = false; row.reinitialize(); this.refreshData(true); this.dispatchExternal("dataTreeRowCollapsed", row.getComponent(), row.modules.dataTree.index); } } toggleRow(row) { var config = row.modules.dataTree; if (config.children !== false) { if (config.open) { this.collapseRow(row); } else { this.expandRow(row); } } } isRowExpanded(row) { return row.modules.dataTree.open; } getTreeParent(row) { return row.modules.dataTree.parent ? row.modules.dataTree.parent.getComponent() : false; } getTreeParentRoot(row) { return row.modules.dataTree && row.modules.dataTree.parent ? this.getTreeParentRoot(row.modules.dataTree.parent) : row; } getFilteredTreeChildren(row) { var config = row.modules.dataTree, output = [], children; if (config.children) { if (!Array.isArray(config.children)) { config.children = this.generateChildren(row); } if (this.table.modExists("filter") && this.table.options.dataTreeFilter) { children = this.table.modules.filter.filter(config.children); } else { children = config.children; } children.forEach((childRow) => { if (childRow instanceof Row) { output.push(childRow); } }); } return output; } rowDeleting(row) { var config = row.modules.dataTree; if (config && config.children && Array.isArray(config.children)) { config.children.forEach((childRow) => { if (childRow instanceof Row) { childRow.wipe(); } }); } } rowDelete(row) { var parent2 = row.modules.dataTree.parent, childIndex; if (parent2) { childIndex = this.findChildIndex(row, parent2); if (childIndex !== false) { parent2.data[this.field].splice(childIndex, 1); } if (!parent2.data[this.field].length) { delete parent2.data[this.field]; } this.initializeRow(parent2); this.layoutRow(parent2); } this.refreshData(true); } addTreeChildRow(row, data, top, index) { var childIndex = false; if (typeof data === "string") { data = JSON.parse(data); } if (!Array.isArray(row.data[this.field])) { row.data[this.field] = []; row.modules.dataTree.open = this.startOpen(row.getComponent(), row.modules.dataTree.index); } if (typeof index !== "undefined") { childIndex = this.findChildIndex(index, row); if (childIndex !== false) { row.data[this.field].splice(top ? childIndex : childIndex + 1, 0, data); } } if (childIndex === false) { if (top) { row.data[this.field].unshift(data); } else { row.data[this.field].push(data); } } this.initializeRow(row); this.layoutRow(row); this.refreshData(true); } findChildIndex(subject, parent2) { var match = false; if (typeof subject == "object") { if (subject instanceof Row) { match = subject.data; } else if (subject instanceof RowComponent) { match = subject._getSelf().data; } else if (typeof HTMLElement !== "undefined" && subject instanceof HTMLElement) { if (parent2.modules.dataTree) { match = parent2.modules.dataTree.children.find((childRow) => { return childRow instanceof Row ? childRow.element === subject : false; }); if (match) { match = match.data; } } } else if (subject === null) { match = false; } } else if (typeof subject == "undefined") { match = false; } else { match = parent2.data[this.field].find((row) => { return row.data[this.table.options.index] == subject; }); } if (match) { if (Array.isArray(parent2.data[this.field])) { match = parent2.data[this.field].indexOf(match); } if (match == -1) { match = false; } } return match; } getTreeChildren(row, component, recurse) { var config = row.modules.dataTree, output = []; if (config && config.children) { if (!Array.isArray(config.children)) { config.children = this.generateChildren(row); } config.children.forEach((childRow) => { if (childRow instanceof Row) { output.push(component ? childRow.getComponent() : childRow); if (recurse) { this.getTreeChildren(childRow, component, recurse).forEach((child) => { output.push(child); }); } } }); } return output; } getChildField() { return this.field; } redrawNeeded(data) { return (this.field ? typeof data[this.field] !== "undefined" : false) || (this.elementField ? typeof data[this.elementField] !== "undefined" : false); } } __publicField(DataTree, "moduleName", "dataTree"); function csv$1(list2, options = {}, setFileContents) { var delimiter = options.delimiter ? options.delimiter : ",", fileContents = [], headers = []; list2.forEach((row) => { var item = []; switch (row.type) { case "group": console.warn("Download Warning - CSV downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - CSV downloader cannot process column calculations"); break; case "header": row.columns.forEach((col, i) => { if (col && col.depth === 1) { headers[i] = typeof col.value == "undefined" || col.value === null ? "" : '"' + String(col.value).split('"').join('""') + '"'; } }); break; case "row": row.columns.forEach((col) => { if (col) { switch (typeof col.value) { case "object": col.value = col.value !== null ? JSON.stringify(col.value) : ""; break; case "undefined": col.value = ""; break; } item.push('"' + String(col.value).split('"').join('""') + '"'); } }); fileContents.push(item.join(delimiter)); break; } }); if (headers.length) { fileContents.unshift(headers.join(delimiter)); } fileContents = fileContents.join("\n"); if (options.bom) { fileContents = "\uFEFF" + fileContents; } setFileContents(fileContents, "text/csv"); } function json$2(list2, options, setFileContents) { var fileContents = []; list2.forEach((row) => { var item = {}; switch (row.type) { case "header": break; case "group": console.warn("Download Warning - JSON downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - JSON downloader cannot process column calculations"); break; case "row": row.columns.forEach((col) => { if (col) { item[col.component.getTitleDownload() || col.component.getField()] = col.value; } }); fileContents.push(item); break; } }); fileContents = JSON.stringify(fileContents, null, " "); setFileContents(fileContents, "application/json"); } function pdf(list2, options = {}, setFileContents) { var header = [], body = [], autoTableParams = {}, rowGroupStyles = options.rowGroupStyles || { fontStyle: "bold", fontSize: 12, cellPadding: 6, fillColor: 220 }, rowCalcStyles = options.rowCalcStyles || { fontStyle: "bold", fontSize: 10, cellPadding: 4, fillColor: 232 }, jsPDFParams = options.jsPDF || {}, title = options.title ? options.title : "", jspdfLib, doc; if (!jsPDFParams.orientation) { jsPDFParams.orientation = options.orientation || "landscape"; } if (!jsPDFParams.unit) { jsPDFParams.unit = "pt"; } list2.forEach((row) => { switch (row.type) { case "header": header.push(parseRow(row)); break; case "group": body.push(parseRow(row, rowGroupStyles)); break; case "calc": body.push(parseRow(row, rowCalcStyles)); break; case "row": body.push(parseRow(row)); break; } }); function parseRow(row, styles) { var rowData = []; row.columns.forEach((col) => { var cell; if (col) { switch (typeof col.value) { case "object": col.value = col.value !== null ? JSON.stringify(col.value) : ""; break; case "undefined": col.value = ""; break; } cell = { content: col.value, colSpan: col.width, rowSpan: col.height }; if (styles) { cell.styles = styles; } rowData.push(cell); } }); return rowData; } jspdfLib = this.dependencyRegistry.lookup("jspdf", "jsPDF"); doc = new jspdfLib(jsPDFParams); if (options.autoTable) { if (typeof options.autoTable === "function") { autoTableParams = options.autoTable(doc) || {}; } else { autoTableParams = options.autoTable; } } if (title) { autoTableParams.didDrawPage = function(data) { doc.text(title, 40, 30); }; } autoTableParams.head = header; autoTableParams.body = body; doc.autoTable(autoTableParams); if (options.documentProcessing) { options.documentProcessing(doc); } setFileContents(doc.output("arraybuffer"), "application/pdf"); } function xlsx$1(list2, options, setFileContents) { var self = this, sheetName = options.sheetName || "Sheet1", XLSXLib = this.dependencyRegistry.lookup("XLSX"), workbook = XLSXLib.utils.book_new(), tableFeatures = new CoreFeature(this), compression = "compress" in options ? options.compress : true, writeOptions = options.writeOptions || { bookType: "xlsx", bookSST: true, compression }, output; writeOptions.type = "binary"; workbook.SheetNames = []; workbook.Sheets = {}; function generateSheet() { var rows2 = [], merges = [], worksheet = {}, range2 = { s: { c: 0, r: 0 }, e: { c: list2[0] ? list2[0].columns.reduce((a, b) => a + (b && b.width ? b.width : 1), 0) : 0, r: list2.length } }; list2.forEach((row, i) => { var rowData = []; row.columns.forEach(function(col, j) { if (col) { rowData.push(!(col.value instanceof Date) && typeof col.value === "object" ? JSON.stringify(col.value) : col.value); if (col.width > 1 || col.height > -1) { if (col.height > 1 || col.width > 1) { merges.push({ s: { r: i, c: j }, e: { r: i + col.height - 1, c: j + col.width - 1 } }); } } } else { rowData.push(""); } }); rows2.push(rowData); }); XLSXLib.utils.sheet_add_aoa(worksheet, rows2); worksheet["!ref"] = XLSXLib.utils.encode_range(range2); if (merges.length) { worksheet["!merges"] = merges; } return worksheet; } if (options.sheetOnly) { setFileContents(generateSheet()); return; } if (options.sheets) { for (var sheet in options.sheets) { if (options.sheets[sheet] === true) { workbook.SheetNames.push(sheet); workbook.Sheets[sheet] = generateSheet(); } else { workbook.SheetNames.push(sheet); tableFeatures.commsSend(options.sheets[sheet], "download", "intercept", { type: "xlsx", options: { sheetOnly: true }, active: self.active, intercept: function(data) { workbook.Sheets[sheet] = data; } }); } } } else { workbook.SheetNames.push(sheetName); workbook.Sheets[sheetName] = generateSheet(); } if (options.documentProcessing) { workbook = options.documentProcessing(workbook); } function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 255; return buf; } output = XLSXLib.write(workbook, writeOptions); setFileContents(s2ab(output), "application/octet-stream"); } function html$1(list2, options, setFileContents) { if (this.modExists("export", true)) { setFileContents(this.modules.export.generateHTMLTable(list2), "text/html"); } } function jsonLines(list2, options, setFileContents) { const fileContents = []; list2.forEach((row) => { const item = {}; switch (row.type) { case "header": break; case "group": console.warn("Download Warning - JSON downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - JSON downloader cannot process column calculations"); break; case "row": row.columns.forEach((col) => { if (col) { item[col.component.getTitleDownload() || col.component.getField()] = col.value; } }); fileContents.push(JSON.stringify(item)); break; } }); setFileContents(fileContents.join("\n"), "application/x-ndjson"); } var defaultDownloaders = { csv: csv$1, json: json$2, jsonLines, pdf, xlsx: xlsx$1, html: html$1 }; const _Download = class _Download extends Module { constructor(table) { super(table); this.registerTableOption("downloadEncoder", function(data, mimeType) { return new Blob([data], { type: mimeType }); }); this.registerTableOption("downloadConfig", {}); this.registerTableOption("downloadRowRange", "active"); this.registerColumnOption("download"); this.registerColumnOption("titleDownload"); } initialize() { this.deprecatedOptionsCheck(); this.registerTableFunction("download", this.download.bind(this)); this.registerTableFunction("downloadToTab", this.downloadToTab.bind(this)); } deprecatedOptionsCheck() { } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// downloadToTab(type, filename, options, active) { this.download(type, filename, options, active, true); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// //trigger file download download(type, filename, options, range2, interceptCallback) { var downloadFunc = false; function buildLink(data, mime) { if (interceptCallback) { if (interceptCallback === true) { this.triggerDownload(data, mime, type, filename, true); } else { interceptCallback(data); } } else { this.triggerDownload(data, mime, type, filename); } } if (typeof type == "function") { downloadFunc = type; } else { if (_Download.downloaders[type]) { downloadFunc = _Download.downloaders[type]; } else { console.warn("Download Error - No such download type found: ", type); } } if (downloadFunc) { var list2 = this.generateExportList(range2); downloadFunc.call(this.table, list2, options || {}, buildLink.bind(this)); } } generateExportList(range2) { var list2 = this.table.modules.export.generateExportList(this.table.options.downloadConfig, false, range2 || this.table.options.downloadRowRange, "download"); var groupHeader = this.table.options.groupHeaderDownload; if (groupHeader && !Array.isArray(groupHeader)) { groupHeader = [groupHeader]; } list2.forEach((row) => { var group; if (row.type === "group") { group = row.columns[0]; if (groupHeader && groupHeader[row.indent]) { group.value = groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); } } }); return list2; } triggerDownload(data, mime, type, filename, newTab) { var element = document.createElement("a"), blob = this.table.options.downloadEncoder(data, mime); if (blob) { if (newTab) { window.open(window.URL.createObjectURL(blob)); } else { filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type); if (navigator.msSaveOrOpenBlob) { navigator.msSaveOrOpenBlob(blob, filename); } else { element.setAttribute("href", window.URL.createObjectURL(blob)); element.setAttribute("download", filename); element.style.display = "none"; document.body.appendChild(element); element.click(); document.body.removeChild(element); } } this.dispatchExternal("downloadComplete"); } } commsReceived(table, action, data) { switch (action) { case "intercept": this.download(data.type, "", data.options, data.active, data.intercept); break; } } }; __publicField(_Download, "moduleName", "download"); //load defaults __publicField(_Download, "downloaders", defaultDownloaders); let Download = _Download; function maskInput(el, options) { var mask = options.mask, maskLetter = typeof options.maskLetterChar !== "undefined" ? options.maskLetterChar : "A", maskNumber = typeof options.maskNumberChar !== "undefined" ? options.maskNumberChar : "9", maskWildcard = typeof options.maskWildcardChar !== "undefined" ? options.maskWildcardChar : "*"; function fillSymbols(index) { var symbol = mask[index]; if (typeof symbol !== "undefined" && symbol !== maskWildcard && symbol !== maskLetter && symbol !== maskNumber) { el.value = el.value + "" + symbol; fillSymbols(index + 1); } } el.addEventListener("keydown", (e) => { var index = el.value.length, char = e.key; if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) { if (index >= mask.length) { e.preventDefault(); e.stopPropagation(); return false; } else { switch (mask[index]) { case maskLetter: if (char.toUpperCase() == char.toLowerCase()) { e.preventDefault(); e.stopPropagation(); return false; } break; case maskNumber: if (isNaN(char)) { e.preventDefault(); e.stopPropagation(); return false; } break; case maskWildcard: break; default: if (char !== mask[index]) { e.preventDefault(); e.stopPropagation(); return false; } } } } return; }); el.addEventListener("keyup", (e) => { if (e.key.length === 1) { if (options.maskAutoFill) { fillSymbols(el.value.length); } } }); if (!el.placeholder) { el.placeholder = mask; } if (options.maskAutoFill) { fillSymbols(el.value.length); } } function input(cell, onRendered, success, cancel, editorParams) { var cellValue = cell.getValue(), input2 = document.createElement("input"); input2.setAttribute("type", editorParams.search ? "search" : "text"); input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } input2.value = typeof cellValue !== "undefined" ? cellValue : ""; onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; if (editorParams.selectContents) { input2.select(); } } }); function onChange(e) { if ((cellValue === null || typeof cellValue === "undefined") && input2.value !== "" || input2.value !== cellValue) { if (success(input2.value)) { cellValue = input2.value; } } else { cancel(); } } input2.addEventListener("change", onChange); input2.addEventListener("blur", onChange); input2.addEventListener("keydown", function(e) { switch (e.key) { // case "Tab": case "Enter": onChange(); break; case "Escape": cancel(); break; case "End": case "Home": e.stopPropagation(); break; } }); if (editorParams.mask) { maskInput(input2, editorParams); } return input2; } function textarea$1(cell, onRendered, success, cancel, editorParams) { var cellValue = cell.getValue(), vertNav = editorParams.verticalNavigation || "hybrid", value = String(cellValue !== null && typeof cellValue !== "undefined" ? cellValue : ""), input2 = document.createElement("textarea"), scrollHeight = 0; input2.style.display = "block"; input2.style.padding = "2px"; input2.style.height = "100%"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; input2.style.whiteSpace = "pre-wrap"; input2.style.resize = "none"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } input2.value = value; onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; input2.scrollHeight; input2.style.height = input2.scrollHeight + "px"; cell.getRow().normalizeHeight(); if (editorParams.selectContents) { input2.select(); } } }); function onChange(e) { if ((cellValue === null || typeof cellValue === "undefined") && input2.value !== "" || input2.value !== cellValue) { if (success(input2.value)) { cellValue = input2.value; } setTimeout(function() { cell.getRow().normalizeHeight(); }, 300); } else { cancel(); } } input2.addEventListener("change", onChange); input2.addEventListener("blur", onChange); input2.addEventListener("keyup", function() { input2.style.height = ""; var heightNow = input2.scrollHeight; input2.style.height = heightNow + "px"; if (heightNow != scrollHeight) { scrollHeight = heightNow; cell.getRow().normalizeHeight(); } }); input2.addEventListener("keydown", function(e) { switch (e.key) { case "Enter": if (e.shiftKey && editorParams.shiftEnterSubmit) { onChange(); } break; case "Escape": cancel(); break; case "ArrowUp": if (vertNav == "editor" || vertNav == "hybrid" && input2.selectionStart) { e.stopImmediatePropagation(); e.stopPropagation(); } break; case "ArrowDown": if (vertNav == "editor" || vertNav == "hybrid" && input2.selectionStart !== input2.value.length) { e.stopImmediatePropagation(); e.stopPropagation(); } break; case "End": case "Home": e.stopPropagation(); break; } }); if (editorParams.mask) { maskInput(input2, editorParams); } return input2; } function number$1(cell, onRendered, success, cancel, editorParams) { var cellValue = cell.getValue(), vertNav = editorParams.verticalNavigation || "editor", input2 = document.createElement("input"); input2.setAttribute("type", "number"); if (typeof editorParams.max != "undefined") { input2.setAttribute("max", editorParams.max); } if (typeof editorParams.min != "undefined") { input2.setAttribute("min", editorParams.min); } if (typeof editorParams.step != "undefined") { input2.setAttribute("step", editorParams.step); } input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } input2.value = cellValue; var blurFunc = function(e) { onChange(); }; onRendered(function() { if (cell.getType() === "cell") { input2.removeEventListener("blur", blurFunc); input2.focus({ preventScroll: true }); input2.style.height = "100%"; input2.addEventListener("blur", blurFunc); if (editorParams.selectContents) { input2.select(); } } }); function onChange() { var value = input2.value; if (!isNaN(value) && value !== "") { value = Number(value); } if (value !== cellValue) { if (success(value)) { cellValue = value; } } else { cancel(); } } input2.addEventListener("keydown", function(e) { switch (e.key) { case "Enter": onChange(); break; case "Escape": cancel(); break; case "ArrowUp": case "ArrowDown": if (vertNav == "editor") { e.stopImmediatePropagation(); e.stopPropagation(); } break; case "End": case "Home": e.stopPropagation(); break; } }); if (editorParams.mask) { maskInput(input2, editorParams); } return input2; } function range(cell, onRendered, success, cancel, editorParams) { var cellValue = cell.getValue(), input2 = document.createElement("input"); input2.setAttribute("type", "range"); if (typeof editorParams.max != "undefined") { input2.setAttribute("max", editorParams.max); } if (typeof editorParams.min != "undefined") { input2.setAttribute("min", editorParams.min); } if (typeof editorParams.step != "undefined") { input2.setAttribute("step", editorParams.step); } input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } input2.value = cellValue; onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; } }); function onChange() { var value = input2.value; if (!isNaN(value) && value !== "") { value = Number(value); } if (value != cellValue) { if (success(value)) { cellValue = value; } } else { cancel(); } } input2.addEventListener("blur", function(e) { onChange(); }); input2.addEventListener("keydown", function(e) { switch (e.key) { case "Enter": onChange(); break; case "Escape": cancel(); break; } }); return input2; } function date$1(cell, onRendered, success, cancel, editorParams) { var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? window.DateTime || luxon.DateTime : null; var cellValue = cell.getValue(), input2 = document.createElement("input"); function convertDate(value) { var newDatetime; if (DT.isDateTime(value)) { newDatetime = value; } else if (inputFormat === "x") { newDatetime = DT.fromMillis(value); } else if (inputFormat === "iso") { newDatetime = DT.fromISO(String(value)); } else { newDatetime = DT.fromFormat(String(value), inputFormat); } return newDatetime.toFormat("yyyy-MM-dd"); } input2.type = "date"; input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.max) { input2.setAttribute("max", inputFormat ? convertDate(editorParams.max) : editorParams.max); } if (editorParams.min) { input2.setAttribute("min", inputFormat ? convertDate(editorParams.min) : editorParams.min); } if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if (inputFormat) { if (DT) { cellValue = convertDate(cellValue); } else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input2.value = cellValue; onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; if (editorParams.selectContents) { input2.select(); } } }); function onChange() { var value = input2.value, luxDate; if ((cellValue === null || typeof cellValue === "undefined") && value !== "" || value !== cellValue) { if (value && inputFormat) { luxDate = DT.fromFormat(String(value), "yyyy-MM-dd"); switch (inputFormat) { case true: value = luxDate; break; case "x": value = luxDate.toMillis(); break; case "iso": value = luxDate.toISO(); break; default: value = luxDate.toFormat(inputFormat); } } if (success(value)) { cellValue = input2.value; } } else { cancel(); } } input2.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input2) { onChange(); } }); input2.addEventListener("keydown", function(e) { switch (e.key) { // case "Tab": case "Enter": onChange(); break; case "Escape": cancel(); break; case "End": case "Home": e.stopPropagation(); break; case "ArrowUp": case "ArrowDown": if (vertNav == "editor") { e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input2; } function time$1(cell, onRendered, success, cancel, editorParams) { var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? window.DateTime || luxon.DateTime : null, newDatetime; var cellValue = cell.getValue(), input2 = document.createElement("input"); input2.type = "time"; input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if (inputFormat) { if (DT) { if (DT.isDateTime(cellValue)) { newDatetime = cellValue; } else if (inputFormat === "x") { newDatetime = DT.fromMillis(cellValue); } else if (inputFormat === "iso") { newDatetime = DT.fromISO(String(cellValue)); } else { newDatetime = DT.fromFormat(String(cellValue), inputFormat); } cellValue = newDatetime.toFormat("HH:mm"); } else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input2.value = cellValue; onRendered(function() { if (cell.getType() == "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; if (editorParams.selectContents) { input2.select(); } } }); function onChange() { var value = input2.value, luxTime; if ((cellValue === null || typeof cellValue === "undefined") && value !== "" || value !== cellValue) { if (value && inputFormat) { luxTime = DT.fromFormat(String(value), "hh:mm"); switch (inputFormat) { case true: value = luxTime; break; case "x": value = luxTime.toMillis(); break; case "iso": value = luxTime.toISO(); break; default: value = luxTime.toFormat(inputFormat); } } if (success(value)) { cellValue = input2.value; } } else { cancel(); } } input2.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input2) { onChange(); } }); input2.addEventListener("keydown", function(e) { switch (e.key) { // case "Tab": case "Enter": onChange(); break; case "Escape": cancel(); break; case "End": case "Home": e.stopPropagation(); break; case "ArrowUp": case "ArrowDown": if (vertNav == "editor") { e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input2; } function datetime$2(cell, onRendered, success, cancel, editorParams) { var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime") : null, newDatetime; var cellValue = cell.getValue(), input2 = document.createElement("input"); input2.type = "datetime-local"; input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if (inputFormat) { if (DT) { if (DT.isDateTime(cellValue)) { newDatetime = cellValue; } else if (inputFormat === "x") { newDatetime = DT.fromMillis(cellValue); } else if (inputFormat === "iso") { newDatetime = DT.fromISO(String(cellValue)); } else { newDatetime = DT.fromFormat(String(cellValue), inputFormat); } cellValue = newDatetime.toFormat("yyyy-MM-dd") + "T" + newDatetime.toFormat("HH:mm"); } else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input2.value = cellValue; onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); input2.style.height = "100%"; if (editorParams.selectContents) { input2.select(); } } }); function onChange() { var value = input2.value, luxDateTime; if ((cellValue === null || typeof cellValue === "undefined") && value !== "" || value !== cellValue) { if (value && inputFormat) { luxDateTime = DT.fromISO(String(value)); switch (inputFormat) { case true: value = luxDateTime; break; case "x": value = luxDateTime.toMillis(); break; case "iso": value = luxDateTime.toISO(); break; default: value = luxDateTime.toFormat(inputFormat); } } if (success(value)) { cellValue = input2.value; } } else { cancel(); } } input2.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input2) { onChange(); } }); input2.addEventListener("keydown", function(e) { switch (e.key) { // case "Tab": case "Enter": onChange(); break; case "Escape": cancel(); break; case "End": case "Home": e.stopPropagation(); break; case "ArrowUp": case "ArrowDown": if (vertNav == "editor") { e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input2; } let Edit$1 = class Edit { constructor(editor, cell, onRendered, success, cancel, editorParams) { this.edit = editor; this.table = editor.table; this.cell = cell; this.params = this._initializeParams(editorParams); this.data = []; this.displayItems = []; this.currentItems = []; this.focusedItem = null; this.input = this._createInputElement(); this.listEl = this._createListElement(); this.initialValues = null; this.isFilter = cell.getType() === "header"; this.filterTimeout = null; this.filtered = false; this.typing = false; this.values = []; this.popup = null; this.listIteration = 0; this.lastAction = ""; this.filterTerm = ""; this.blurable = true; this.actions = { success, cancel }; this._deprecatedOptionsCheck(); this._initializeValue(); onRendered(this._onRendered.bind(this)); } _deprecatedOptionsCheck() { } _initializeValue() { var initialValue = this.cell.getValue(); if (typeof initialValue === "undefined" && typeof this.params.defaultValue !== "undefined") { initialValue = this.params.defaultValue; } this.initialValues = this.params.multiselect ? initialValue : [initialValue]; if (this.isFilter) { this.input.value = this.initialValues ? this.initialValues.join(",") : ""; this.headerFilterInitialListGen(); } } _onRendered() { var cellEl = this.cell.getElement(); function clickStop(e) { e.stopPropagation(); } if (!this.isFilter) { this.input.style.height = "100%"; this.input.focus({ preventScroll: true }); } cellEl.addEventListener("click", clickStop); setTimeout(() => { cellEl.removeEventListener("click", clickStop); }, 1e3); this.input.addEventListener("mousedown", this._preventPopupBlur.bind(this)); } _createListElement() { var listEl = document.createElement("div"); listEl.classList.add("tabulator-edit-list"); listEl.addEventListener("mousedown", this._preventBlur.bind(this)); listEl.addEventListener("keydown", this._inputKeyDown.bind(this)); return listEl; } _setListWidth() { var element = this.isFilter ? this.input : this.cell.getElement(); this.listEl.style.minWidth = element.offsetWidth + "px"; if (this.params.maxWidth) { if (this.params.maxWidth === true) { this.listEl.style.maxWidth = element.offsetWidth + "px"; } else if (typeof this.params.maxWidth === "number") { this.listEl.style.maxWidth = this.params.maxWidth + "px"; } else { this.listEl.style.maxWidth = this.params.maxWidth; } } } _createInputElement() { var attribs = this.params.elementAttributes; var input2 = document.createElement("input"); input2.setAttribute("type", this.params.clearable ? "search" : "text"); input2.style.padding = "4px"; input2.style.width = "100%"; input2.style.boxSizing = "border-box"; if (!this.params.autocomplete) { input2.style.cursor = "default"; input2.style.caretColor = "transparent"; } if (attribs && typeof attribs == "object") { for (let key in attribs) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + attribs["+" + key]); } else { input2.setAttribute(key, attribs[key]); } } } if (this.params.mask) { maskInput(input2, this.params); } this._bindInputEvents(input2); return input2; } _initializeParams(params) { var valueKeys = ["values", "valuesURL", "valuesLookup"], valueCheck; params = Object.assign({}, params); params.verticalNavigation = params.verticalNavigation || "editor"; params.placeholderLoading = typeof params.placeholderLoading === "undefined" ? "Searching ..." : params.placeholderLoading; params.placeholderEmpty = typeof params.placeholderEmpty === "undefined" ? "No Results Found" : params.placeholderEmpty; params.filterDelay = typeof params.filterDelay === "undefined" ? 300 : params.filterDelay; params.emptyValue = Object.keys(params).includes("emptyValue") ? params.emptyValue : ""; valueCheck = Object.keys(params).filter((key) => valueKeys.includes(key)).length; if (!valueCheck) { console.warn("list editor config error - either the values, valuesURL, or valuesLookup option must be set"); } else if (valueCheck > 1) { console.warn("list editor config error - only one of the values, valuesURL, or valuesLookup options can be set on the same editor"); } if (params.autocomplete) { if (params.multiselect) { params.multiselect = false; console.warn("list editor config error - multiselect option is not available when autocomplete is enabled"); } } else { if (params.freetext) { params.freetext = false; console.warn("list editor config error - freetext option is only available when autocomplete is enabled"); } if (params.filterFunc) { params.filterFunc = false; console.warn("list editor config error - filterFunc option is only available when autocomplete is enabled"); } if (params.filterRemote) { params.filterRemote = false; console.warn("list editor config error - filterRemote option is only available when autocomplete is enabled"); } if (params.mask) { params.mask = false; console.warn("list editor config error - mask option is only available when autocomplete is enabled"); } if (params.allowEmpty) { params.allowEmpty = false; console.warn("list editor config error - allowEmpty option is only available when autocomplete is enabled"); } if (params.listOnEmpty) { params.listOnEmpty = false; console.warn("list editor config error - listOnEmpty option is only available when autocomplete is enabled"); } } if (params.filterRemote && !(typeof params.valuesLookup === "function" || params.valuesURL)) { params.filterRemote = false; console.warn("list editor config error - filterRemote option should only be used when values list is populated from a remote source"); } return params; } ////////////////////////////////////// ////////// Event Handling //////////// ////////////////////////////////////// _bindInputEvents(input2) { input2.addEventListener("focus", this._inputFocus.bind(this)); input2.addEventListener("click", this._inputClick.bind(this)); input2.addEventListener("blur", this._inputBlur.bind(this)); input2.addEventListener("keydown", this._inputKeyDown.bind(this)); input2.addEventListener("search", this._inputSearch.bind(this)); if (this.params.autocomplete) { input2.addEventListener("keyup", this._inputKeyUp.bind(this)); } } _inputFocus(e) { this.rebuildOptionsList(); } _filter() { if (this.params.filterRemote) { clearTimeout(this.filterTimeout); this.filterTimeout = setTimeout(() => { this.rebuildOptionsList(); }, this.params.filterDelay); } else { this._filterList(); } } _inputClick(e) { e.stopPropagation(); } _inputBlur(e) { if (this.blurable) { if (this.popup) { this.popup.hide(); } else { this._resolveValue(true); } } } _inputSearch() { this._clearChoices(); } _inputKeyDown(e) { switch (e.key) { case "ArrowUp": this._keyUp(e); break; case "ArrowDown": this._keyDown(e); break; case "ArrowLeft": case "ArrowRight": this._keySide(e); break; case "Enter": this._keyEnter(); break; case "Escape": this._keyEsc(); break; case "Home": case "End": this._keyHomeEnd(e); break; case "Tab": this._keyTab(e); break; default: this._keySelectLetter(e); } } _inputKeyUp(e) { switch (e.key) { case "ArrowUp": case "ArrowLeft": case "ArrowRight": case "ArrowDown": case "Enter": case "Escape": break; default: this._keyAutoCompLetter(e); } } _preventPopupBlur() { if (this.popup) { this.popup.blockHide(); } setTimeout(() => { if (this.popup) { this.popup.restoreHide(); } }, 10); } _preventBlur() { this.blurable = false; setTimeout(() => { this.blurable = true; }, 10); } ////////////////////////////////////// //////// Keyboard Navigation ///////// ////////////////////////////////////// _keyTab(e) { if (this.params.autocomplete && this.lastAction === "typing") { this._resolveValue(true); } else { if (this.focusedItem) { this._chooseItem(this.focusedItem, true); } } } _keyUp(e) { var index = this.displayItems.indexOf(this.focusedItem); if (this.params.verticalNavigation == "editor" || this.params.verticalNavigation == "hybrid" && index) { e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); if (index > 0) { this._focusItem(this.displayItems[index - 1]); } } } _keyDown(e) { var index = this.displayItems.indexOf(this.focusedItem); if (this.params.verticalNavigation == "editor" || this.params.verticalNavigation == "hybrid" && index < this.displayItems.length - 1) { e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); if (index < this.displayItems.length - 1) { if (index == -1) { this._focusItem(this.displayItems[0]); } else { this._focusItem(this.displayItems[index + 1]); } } } } _keySide(e) { if (!this.params.autocomplete) { e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); } } _keyEnter(e) { if (this.params.autocomplete && this.lastAction === "typing") { this._resolveValue(true); } else { if (this.focusedItem) { if (this.isFilter && !this.params.multiselect && this.focusedItem.selected) { this._resolveValue(); } else { this._chooseItem(this.focusedItem); } } } } _keyEsc(e) { this._cancel(); } _keyHomeEnd(e) { if (this.params.autocomplete) { e.stopImmediatePropagation(); } } _keySelectLetter(e) { if (!this.params.autocomplete) { e.preventDefault(); if (e.key.length === 1) { this._scrollToValue(e.key.toUpperCase().charCodeAt(0)); } } } _keyAutoCompLetter(e) { this._filter(); this.lastAction = "typing"; this.typing = true; } _scrollToValue(char) { clearTimeout(this.filterTimeout); var character = String.fromCharCode(char).toLowerCase(); this.filterTerm += character.toLowerCase(); var match = this.displayItems.find((item) => { return typeof item.label !== "undefined" && item.label.toLowerCase().startsWith(this.filterTerm); }); if (match) { this._focusItem(match); } this.filterTimeout = setTimeout(() => { this.filterTerm = ""; }, 800); } _focusItem(item) { this.lastAction = "focus"; if (this.focusedItem && this.focusedItem.element) { this.focusedItem.element.classList.remove("focused"); } this.focusedItem = item; if (item && item.element) { item.element.classList.add("focused"); item.element.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" }); } } ////////////////////////////////////// /////// Data List Generation ///////// ////////////////////////////////////// headerFilterInitialListGen() { this._generateOptions(true); } rebuildOptionsList() { this._generateOptions().then(this._sortOptions.bind(this)).then(this._buildList.bind(this)).then(this._showList.bind(this)).catch((e) => { if (!Number.isInteger(e)) { console.error("List generation error", e); } }); } _filterList() { this._buildList(this._filterOptions()); this._showList(); } _generateOptions(silent) { var values = []; var iteration = ++this.listIteration; this.filtered = false; if (this.params.values) { values = this.params.values; } else if (this.params.valuesURL) { values = this._ajaxRequest(this.params.valuesURL, this.input.value); } else { if (typeof this.params.valuesLookup === "function") { values = this.params.valuesLookup(this.cell, this.input.value); } else if (this.params.valuesLookup) { values = this._uniqueColumnValues(this.params.valuesLookupField); } } if (values instanceof Promise) { if (!silent) { this._addPlaceholder(this.params.placeholderLoading); } return values.then().then((responseValues) => { if (this.listIteration === iteration) { return this._parseList(responseValues); } else { return Promise.reject(iteration); } }); } else { return Promise.resolve(this._parseList(values)); } } _addPlaceholder(contents) { var placeholder = document.createElement("div"); if (typeof contents === "function") { contents = contents(this.cell.getComponent(), this.listEl); } if (contents) { this._clearList(); if (contents instanceof HTMLElement) { placeholder = contents; } else { placeholder.classList.add("tabulator-edit-list-placeholder"); placeholder.innerHTML = contents; } this.listEl.appendChild(placeholder); this._showList(); } } _ajaxRequest(url, term) { var params = this.params.filterRemote ? { term } : {}; url = urlBuilder(url, {}, params); return fetch(url).then((response) => { if (response.ok) { return response.json().catch((error) => { console.warn("List Ajax Load Error - Invalid JSON returned", error); return Promise.reject(error); }); } else { console.error("List Ajax Load Error - Connection Error: " + response.status, response.statusText); return Promise.reject(response); } }).catch((error) => { console.error("List Ajax Load Error - Connection Error: ", error); return Promise.reject(error); }); } _uniqueColumnValues(field) { var output = {}, data = this.table.getData(this.params.valuesLookup), column; if (field) { column = this.table.columnManager.getColumnByField(field); } else { column = this.cell.getColumn()._getSelf(); } if (column) { data.forEach((row) => { var val = column.getFieldValue(row); if (!this._emptyValueCheck(val)) { if (this.params.multiselect && Array.isArray(val)) { val.forEach((item) => { if (!this._emptyValueCheck(item)) { output[item] = true; } }); } else { output[val] = true; } } }); } else { console.warn("unable to find matching column to create select lookup list:", field); output = []; } return Object.keys(output); } _emptyValueCheck(value) { return value === null || typeof value === "undefined" || value === ""; } _parseList(inputValues) { var data = []; if (!Array.isArray(inputValues)) { inputValues = Object.entries(inputValues).map(([key, value]) => { return { label: value, value: key }; }); } inputValues.forEach((value) => { if (typeof value !== "object") { value = { label: value, value }; } this._parseListItem(value, data, 0); }); if (!this.currentItems.length && this.params.freetext) { this.input.value = this.initialValues; this.typing = true; this.lastAction = "typing"; } if (this.params.multiselect) { this.initialValues = null; } this.data = data; return data; } _parseListItem(option, data, level) { var item = {}; if (option.options) { item = this._parseListGroup(option, level + 1); } else { item = { label: option.label, value: option.value, itemParams: option.itemParams, elementAttributes: option.elementAttributes, element: false, selected: false, visible: true, level, original: option }; if (this.params.multiselect) { var existingIndex = this.currentItems.findIndex((existing) => existing.value === option.value); if (existingIndex > -1) { if (this.focusedItem === this.currentItems[existingIndex]) { this.focusedItem = item; } this.currentItems[existingIndex] = item; item.selected = true; } else if (this.initialValues && this.initialValues.indexOf(option.value) > -1) { this._chooseItem(item, true); } } else if (this.initialValues && this.initialValues.indexOf(option.value) > -1) { this._chooseItem(item, true); } } data.push(item); } _parseListGroup(option, level) { var item = { label: option.label, group: true, itemParams: option.itemParams, elementAttributes: option.elementAttributes, element: false, visible: true, level, options: [], original: option }; option.options.forEach((child) => { this._parseListItem(child, item.options, level); }); return item; } _sortOptions(options) { var sorter; if (this.params.sort) { sorter = typeof this.params.sort === "function" ? this.params.sort : this._defaultSortFunction.bind(this); this._sortGroup(sorter, options); } return options; } _sortGroup(sorter, options) { options.sort((a, b) => { return sorter(a.label, b.label, a.value, b.value, a.original, b.original); }); options.forEach((option) => { if (option.group) { this._sortGroup(sorter, option.options); } }); } _defaultSortFunction(as, bs) { var a, b, a1, b1, i = 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/; var emptyAlign = 0; if (this.params.sort === "desc") { [as, bs] = [bs, as]; } if (!as && as !== 0) { emptyAlign = !bs && bs !== 0 ? 0 : -1; } else if (!bs && bs !== 0) { emptyAlign = 1; } else { if (isFinite(as) && isFinite(bs)) return as - bs; a = String(as).toLowerCase(); b = String(bs).toLowerCase(); if (a === b) return 0; if (!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1; a = a.match(rx); b = b.match(rx); L = a.length > b.length ? b.length : a.length; while (i < L) { a1 = a[i]; b1 = b[i++]; if (a1 !== b1) { if (isFinite(a1) && isFinite(b1)) { if (a1.charAt(0) === "0") a1 = "." + a1; if (b1.charAt(0) === "0") b1 = "." + b1; return a1 - b1; } else return a1 > b1 ? 1 : -1; } } return a.length > b.length; } return emptyAlign; } _filterOptions() { var filterFunc = this.params.filterFunc || this._defaultFilterFunc, term = this.input.value; if (term) { this.filtered = true; this.data.forEach((item) => { this._filterItem(filterFunc, term, item); }); } else { this.filtered = false; } return this.data; } _filterItem(func, term, item) { var matches = false; if (!item.group) { item.visible = func(term, item.label, item.value, item.original); } else { item.options.forEach((option) => { if (this._filterItem(func, term, option)) { matches = true; } }); item.visible = matches; } return item.visible; } _defaultFilterFunc(term, label, value, item) { term = String(term).toLowerCase(); if (label !== null && typeof label !== "undefined") { if (String(label).toLowerCase().indexOf(term) > -1 || String(value).toLowerCase().indexOf(term) > -1) { return true; } } return false; } ////////////////////////////////////// /////////// Display List ///////////// ////////////////////////////////////// _clearList() { while (this.listEl.firstChild) this.listEl.removeChild(this.listEl.firstChild); this.displayItems = []; } _buildList(data) { this._clearList(); data.forEach((option) => { this._buildItem(option); }); if (!this.displayItems.length) { this._addPlaceholder(this.params.placeholderEmpty); } } _buildItem(item) { var el = item.element, contents; if (!this.filtered || item.visible) { if (!el) { el = document.createElement("div"); el.tabIndex = 0; contents = this.params.itemFormatter ? this.params.itemFormatter(item.label, item.value, item.original, el) : item.label; if (contents instanceof HTMLElement) { el.appendChild(contents); } else { el.innerHTML = contents; } if (item.group) { el.classList.add("tabulator-edit-list-group"); } else { el.classList.add("tabulator-edit-list-item"); } el.classList.add("tabulator-edit-list-group-level-" + item.level); if (item.elementAttributes && typeof item.elementAttributes == "object") { for (let key in item.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); el.setAttribute(key, this.input.getAttribute(key) + item.elementAttributes["+" + key]); } else { el.setAttribute(key, item.elementAttributes[key]); } } } if (item.group) { el.addEventListener("click", this._groupClick.bind(this, item)); } else { el.addEventListener("click", this._itemClick.bind(this, item)); } el.addEventListener("mousedown", this._preventBlur.bind(this)); item.element = el; } this._styleItem(item); this.listEl.appendChild(el); if (item.group) { item.options.forEach((option) => { this._buildItem(option); }); } else { this.displayItems.push(item); } } } _showList() { var startVis = this.popup && this.popup.isVisible(); if (this.input.parentNode) { if (this.params.autocomplete && this.input.value === "" && !this.params.listOnEmpty) { if (this.popup) { this.popup.hide(true); } return; } this._setListWidth(); if (!this.popup) { this.popup = this.edit.popup(this.listEl); } this.popup.show(this.cell.getElement(), "bottom"); if (!startVis) { setTimeout(() => { this.popup.hideOnBlur(this._resolveValue.bind(this, true)); }, 10); } } } _styleItem(item) { if (item && item.element) { if (item.selected) { item.element.classList.add("active"); } else { item.element.classList.remove("active"); } } } ////////////////////////////////////// ///////// User Interaction /////////// ////////////////////////////////////// _itemClick(item, e) { e.stopPropagation(); this._chooseItem(item); } _groupClick(item, e) { e.stopPropagation(); } ////////////////////////////////////// ////// Current Item Management /////// ////////////////////////////////////// _cancel() { this.popup.hide(true); this.actions.cancel(); } _clearChoices() { this.typing = true; this.currentItems.forEach((item) => { item.selected = false; this._styleItem(item); }); this.currentItems = []; this.focusedItem = null; } _chooseItem(item, silent) { var index; this.typing = false; if (this.params.multiselect) { index = this.currentItems.indexOf(item); if (index > -1) { this.currentItems.splice(index, 1); item.selected = false; } else { this.currentItems.push(item); item.selected = true; } this.input.value = this.currentItems.map((item2) => item2.label).join(","); this._styleItem(item); } else { if (this.isFilter && !silent && item.selected) { this._clearChoices(); this.input.value = ""; this._resolveValue(); return; } this.currentItems = [item]; item.selected = true; this.input.value = item.label; this._styleItem(item); if (!silent) { this._resolveValue(); } } this._focusItem(item); } _resolveValue(blur) { var output, initialValue; if (this.popup) { this.popup.hide(true); } if (this.params.multiselect) { output = this.currentItems.map((item) => item.value); } else { if (blur && this.params.autocomplete && this.typing) { if (this.params.freetext || this.params.allowEmpty && this.input.value === "") { output = this.input.value; } else { this.actions.cancel(); return; } } else { if (this.currentItems[0]) { output = this.currentItems[0].value; } else if (this.isFilter && this.focusedItem && this.focusedItem.selected) { output = this.focusedItem.value; } else { initialValue = Array.isArray(this.initialValues) ? this.initialValues[0] : this.initialValues; if (initialValue === null || typeof initialValue === "undefined" || initialValue === "") { output = initialValue; } else { output = this.params.emptyValue; } } } } if (output === "") { output = this.params.emptyValue; } this.actions.success(output); if (this.isFilter) { this.initialValues = output && !Array.isArray(output) ? [output] : output; this.currentItems = []; } } }; function list(cell, onRendered, success, cancel, editorParams) { var list2 = new Edit$1(this, cell, onRendered, success, cancel, editorParams); return list2.input; } function star$1(cell, onRendered, success, cancel, editorParams) { var self = this, element = cell.getElement(), value = cell.getValue(), maxStars = element.getElementsByTagName("svg").length || 5, size = element.getElementsByTagName("svg")[0] ? element.getElementsByTagName("svg")[0].getAttribute("width") : 14, stars = [], starsHolder = document.createElement("div"), star2 = document.createElementNS("http://www.w3.org/2000/svg", "svg"); function starChange(val) { stars.forEach(function(star3, i2) { if (i2 < val) { if (self.table.browser == "ie") { star3.setAttribute("class", "tabulator-star-active"); } else { star3.classList.replace("tabulator-star-inactive", "tabulator-star-active"); } star3.innerHTML = '<polygon fill="#488CE9" stroke="#014AAE" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>'; } else { if (self.table.browser == "ie") { star3.setAttribute("class", "tabulator-star-inactive"); } else { star3.classList.replace("tabulator-star-active", "tabulator-star-inactive"); } star3.innerHTML = '<polygon fill="#010155" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>'; } }); } function buildStar(i2) { var starHolder = document.createElement("span"); var nextStar = star2.cloneNode(true); stars.push(nextStar); starHolder.addEventListener("mouseenter", function(e) { e.stopPropagation(); e.stopImmediatePropagation(); starChange(i2); }); starHolder.addEventListener("mousemove", function(e) { e.stopPropagation(); e.stopImmediatePropagation(); }); starHolder.addEventListener("click", function(e) { e.stopPropagation(); e.stopImmediatePropagation(); success(i2); element.blur(); }); starHolder.appendChild(nextStar); starsHolder.appendChild(starHolder); } function changeValue(val) { value = val; starChange(val); } element.style.whiteSpace = "nowrap"; element.style.overflow = "hidden"; element.style.textOverflow = "ellipsis"; starsHolder.style.verticalAlign = "middle"; starsHolder.style.display = "inline-block"; starsHolder.style.padding = "4px"; star2.setAttribute("width", size); star2.setAttribute("height", size); star2.setAttribute("viewBox", "0 0 512 512"); star2.setAttribute("xml:space", "preserve"); star2.style.padding = "0 1px"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); starsHolder.setAttribute(key, starsHolder.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { starsHolder.setAttribute(key, editorParams.elementAttributes[key]); } } } for (var i = 1; i <= maxStars; i++) { buildStar(i); } value = Math.min(parseInt(value), maxStars); starChange(value); starsHolder.addEventListener("mousemove", function(e) { starChange(0); }); starsHolder.addEventListener("click", function(e) { success(0); }); element.addEventListener("blur", function(e) { cancel(); }); element.addEventListener("keydown", function(e) { switch (e.key) { case "ArrowRight": changeValue(value + 1); break; case "ArrowLeft": changeValue(value - 1); break; case "Enter": success(value); break; case "Escape": cancel(); break; } }); return starsHolder; } function progress$1(cell, onRendered, success, cancel, editorParams) { var element = cell.getElement(), max = typeof editorParams.max === "undefined" ? element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("max") || 100 : editorParams.max, min = typeof editorParams.min === "undefined" ? element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("min") || 0 : editorParams.min, percent = (max - min) / 100, value = cell.getValue() || 0, handle2 = document.createElement("div"), bar = document.createElement("div"), mouseDrag, mouseDragWidth; function updateValue() { var style = window.getComputedStyle(element, null); var calcVal = percent * Math.round(bar.offsetWidth / ((element.clientWidth - parseInt(style.getPropertyValue("padding-left")) - parseInt(style.getPropertyValue("padding-right"))) / 100)) + min; success(calcVal); element.setAttribute("aria-valuenow", calcVal); element.setAttribute("aria-label", value); } handle2.style.position = "absolute"; handle2.style.right = "0"; handle2.style.top = "0"; handle2.style.bottom = "0"; handle2.style.width = "5px"; handle2.classList.add("tabulator-progress-handle"); bar.style.display = "inline-block"; bar.style.position = "relative"; bar.style.height = "100%"; bar.style.backgroundColor = "#488CE9"; bar.style.maxWidth = "100%"; bar.style.minWidth = "0%"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); bar.setAttribute(key, bar.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { bar.setAttribute(key, editorParams.elementAttributes[key]); } } } element.style.padding = "4px 4px"; value = Math.min(parseFloat(value), max); value = Math.max(parseFloat(value), min); value = Math.round((value - min) / percent); bar.style.width = value + "%"; element.setAttribute("aria-valuemin", min); element.setAttribute("aria-valuemax", max); bar.appendChild(handle2); handle2.addEventListener("mousedown", function(e) { mouseDrag = e.screenX; mouseDragWidth = bar.offsetWidth; }); handle2.addEventListener("mouseover", function() { handle2.style.cursor = "ew-resize"; }); element.addEventListener("mousemove", function(e) { if (mouseDrag) { bar.style.width = mouseDragWidth + e.screenX - mouseDrag + "px"; } }); element.addEventListener("mouseup", function(e) { if (mouseDrag) { e.stopPropagation(); e.stopImmediatePropagation(); mouseDrag = false; mouseDragWidth = false; updateValue(); } }); element.addEventListener("keydown", function(e) { switch (e.key) { case "ArrowRight": e.preventDefault(); bar.style.width = bar.clientWidth + element.clientWidth / 100 + "px"; break; case "ArrowLeft": e.preventDefault(); bar.style.width = bar.clientWidth - element.clientWidth / 100 + "px"; break; case "Tab": case "Enter": updateValue(); break; case "Escape": cancel(); break; } }); element.addEventListener("blur", function() { cancel(); }); return bar; } function tickCross$1(cell, onRendered, success, cancel, editorParams) { var value = cell.getValue(), input2 = document.createElement("input"), tristate = editorParams.tristate, indetermValue = typeof editorParams.indeterminateValue === "undefined" ? null : editorParams.indeterminateValue, indetermState = false, trueValueSet = Object.keys(editorParams).includes("trueValue"), falseValueSet = Object.keys(editorParams).includes("falseValue"); input2.setAttribute("type", "checkbox"); input2.style.marginTop = "5px"; input2.style.boxSizing = "border-box"; if (editorParams.elementAttributes && typeof editorParams.elementAttributes == "object") { for (let key in editorParams.elementAttributes) { if (key.charAt(0) == "+") { key = key.slice(1); input2.setAttribute(key, input2.getAttribute(key) + editorParams.elementAttributes["+" + key]); } else { input2.setAttribute(key, editorParams.elementAttributes[key]); } } } input2.value = value; if (tristate && (typeof value === "undefined" || value === indetermValue || value === "")) { indetermState = true; input2.indeterminate = true; } if (this.table.browser != "firefox" && this.table.browser != "safari") { onRendered(function() { if (cell.getType() === "cell") { input2.focus({ preventScroll: true }); } }); } input2.checked = trueValueSet ? value === editorParams.trueValue : value === true || value === "true" || value === "True" || value === 1; function setValue(blur) { var checkedValue = input2.checked; if (trueValueSet && checkedValue) { checkedValue = editorParams.trueValue; } else if (falseValueSet && !checkedValue) { checkedValue = editorParams.falseValue; } if (tristate) { if (!blur) { if (input2.checked && !indetermState) { input2.checked = false; input2.indeterminate = true; indetermState = true; return indetermValue; } else { indetermState = false; return checkedValue; } } else { if (indetermState) { return indetermValue; } else { return checkedValue; } } } else { return checkedValue; } } input2.addEventListener("change", function(e) { success(setValue()); }); input2.addEventListener("blur", function(e) { success(setValue(true)); }); input2.addEventListener("keydown", function(e) { if (e.key == "Enter") { success(setValue()); } if (e.key == "Escape") { cancel(); } }); return input2; } function adaptable$1(cell, onRendered, success, cancel, params) { var column = cell._getSelf().column, lookup2, editorFunc, editorParams; function defaultLookup(cell2) { var value = cell2.getValue(), editor = "input"; switch (typeof value) { case "number": editor = "number"; break; case "boolean": editor = "tickCross"; break; case "string": if (value.includes("\n")) { editor = "textarea"; } break; } return editor; } lookup2 = params.editorLookup ? params.editorLookup(cell) : defaultLookup(cell); if (params.paramsLookup) { editorParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup2, cell) : params.paramsLookup[lookup2]; } editorFunc = this.table.modules.edit.lookupEditor(lookup2, column); return editorFunc.call(this, cell, onRendered, success, cancel, editorParams || {}); } var defaultEditors = { input, textarea: textarea$1, number: number$1, range, date: date$1, time: time$1, datetime: datetime$2, list, star: star$1, progress: progress$1, tickCross: tickCross$1, adaptable: adaptable$1 }; const _Edit = class _Edit extends Module { constructor(table) { super(table); this.currentCell = false; this.mouseClick = false; this.recursionBlock = false; this.invalidEdit = false; this.editedCells = []; this.convertEmptyValues = false; this.editors = _Edit.editors; this.registerTableOption("editTriggerEvent", "focus"); this.registerTableOption("editorEmptyValue"); this.registerTableOption("editorEmptyValueFunc", this.emptyValueCheck.bind(this)); this.registerColumnOption("editable"); this.registerColumnOption("editor"); this.registerColumnOption("editorParams"); this.registerColumnOption("editorEmptyValue"); this.registerColumnOption("editorEmptyValueFunc"); this.registerColumnOption("cellEditing"); this.registerColumnOption("cellEdited"); this.registerColumnOption("cellEditCancelled"); this.registerTableFunction("getEditedCells", this.getEditedCells.bind(this)); this.registerTableFunction("clearCellEdited", this.clearCellEdited.bind(this)); this.registerTableFunction("navigatePrev", this.navigatePrev.bind(this)); this.registerTableFunction("navigateNext", this.navigateNext.bind(this)); this.registerTableFunction("navigateLeft", this.navigateLeft.bind(this)); this.registerTableFunction("navigateRight", this.navigateRight.bind(this)); this.registerTableFunction("navigateUp", this.navigateUp.bind(this)); this.registerTableFunction("navigateDown", this.navigateDown.bind(this)); this.registerComponentFunction("cell", "isEdited", this.cellIsEdited.bind(this)); this.registerComponentFunction("cell", "clearEdited", this.clearEdited.bind(this)); this.registerComponentFunction("cell", "edit", this.editCell.bind(this)); this.registerComponentFunction("cell", "cancelEdit", this.cellCancelEdit.bind(this)); this.registerComponentFunction("cell", "navigatePrev", this.navigatePrev.bind(this)); this.registerComponentFunction("cell", "navigateNext", this.navigateNext.bind(this)); this.registerComponentFunction("cell", "navigateLeft", this.navigateLeft.bind(this)); this.registerComponentFunction("cell", "navigateRight", this.navigateRight.bind(this)); this.registerComponentFunction("cell", "navigateUp", this.navigateUp.bind(this)); this.registerComponentFunction("cell", "navigateDown", this.navigateDown.bind(this)); } initialize() { this.subscribe("cell-init", this.bindEditor.bind(this)); this.subscribe("cell-delete", this.clearEdited.bind(this)); this.subscribe("cell-value-changed", this.updateCellClass.bind(this)); this.subscribe("column-layout", this.initializeColumnCheck.bind(this)); this.subscribe("column-delete", this.columnDeleteCheck.bind(this)); this.subscribe("row-deleting", this.rowDeleteCheck.bind(this)); this.subscribe("row-layout", this.rowEditableCheck.bind(this)); this.subscribe("data-refreshing", this.cancelEdit.bind(this)); this.subscribe("clipboard-paste", this.pasteBlocker.bind(this)); if (!this.confirm("edit-nav-disabled")) { this.subscribe("keybinding-nav-prev", this.navigatePrev.bind(this, void 0)); this.subscribe("keybinding-nav-next", this.keybindingNavigateNext.bind(this)); this.subscribe("keybinding-nav-up", this.navigateUp.bind(this, void 0)); this.subscribe("keybinding-nav-down", this.navigateDown.bind(this, void 0)); } this.subscribe("edit-check-editing", this.checkEditing.bind(this)); this.subscribe("edit-cancel-cell", this.cancelEditEvent.bind(this)); if (Object.keys(this.table.options).includes("editorEmptyValue")) { this.convertEmptyValues = true; } } /////////////////////////////////// ///////// Paste Negation ////////// /////////////////////////////////// pasteBlocker(e) { if (this.currentCell) { return true; } } /////////////////////////////////// ////// Keybinding Functions /////// /////////////////////////////////// keybindingNavigateNext(e) { var cell = this.currentCell, newRow = this.options("tabEndNewRow"); if (cell) { if (!this.navigateNext(cell, e)) { if (newRow) { cell.getElement().firstChild.blur(); if (!this.invalidEdit) { if (newRow === true) { newRow = this.table.addRow({}); } else { if (typeof newRow == "function") { newRow = this.table.addRow(newRow(cell.row.getComponent())); } else { newRow = this.table.addRow(Object.assign({}, newRow)); } } newRow.then(() => { setTimeout(() => { cell.getComponent().navigateNext(); }); }); } } } } } /////////////////////////////////// ///////// Cell Functions ////////// /////////////////////////////////// cellIsEdited(cell) { return !!cell.modules.edit && cell.modules.edit.edited; } cellCancelEdit(cell) { if (cell === this.currentCell) { this.table.modules.edit.cancelEdit(); } else { console.warn("Cancel Editor Error - This cell is not currently being edited "); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// updateCellClass(cell) { if (this.allowEdit(cell)) { cell.getElement().classList.add("tabulator-editable"); } else { cell.getElement().classList.remove("tabulator-editable"); } } clearCellEdited(cells) { if (!cells) { cells = this.table.modules.edit.getEditedCells(); } if (!Array.isArray(cells)) { cells = [cells]; } cells.forEach((cell) => { this.table.modules.edit.clearEdited(cell._getSelf()); }); } navigatePrev(cell = this.currentCell, e) { var nextCell, prevRow; if (cell) { if (e) { e.preventDefault(); } nextCell = this.navigateLeft(); if (nextCell) { return true; } else { prevRow = this.table.rowManager.prevDisplayRow(cell.row, true); if (prevRow) { nextCell = this.findPrevEditableCell(prevRow, prevRow.cells.length); if (nextCell) { nextCell.getComponent().edit(); return true; } } } } return false; } navigateNext(cell = this.currentCell, e) { var nextCell, nextRow; if (cell) { if (e) { e.preventDefault(); } nextCell = this.navigateRight(); if (nextCell) { return true; } else { nextRow = this.table.rowManager.nextDisplayRow(cell.row, true); if (nextRow) { nextCell = this.findNextEditableCell(nextRow, -1); if (nextCell) { nextCell.getComponent().edit(); return true; } } } } return false; } navigateLeft(cell = this.currentCell, e) { var index, nextCell; if (cell) { if (e) { e.preventDefault(); } index = cell.getIndex(); nextCell = this.findPrevEditableCell(cell.row, index); if (nextCell) { nextCell.getComponent().edit(); return true; } } return false; } navigateRight(cell = this.currentCell, e) { var index, nextCell; if (cell) { if (e) { e.preventDefault(); } index = cell.getIndex(); nextCell = this.findNextEditableCell(cell.row, index); if (nextCell) { nextCell.getComponent().edit(); return true; } } return false; } navigateUp(cell = this.currentCell, e) { var index, nextRow; if (cell) { if (e) { e.preventDefault(); } index = cell.getIndex(); nextRow = this.table.rowManager.prevDisplayRow(cell.row, true); if (nextRow) { nextRow.cells[index].getComponent().edit(); return true; } } return false; } navigateDown(cell = this.currentCell, e) { var index, nextRow; if (cell) { if (e) { e.preventDefault(); } index = cell.getIndex(); nextRow = this.table.rowManager.nextDisplayRow(cell.row, true); if (nextRow) { nextRow.cells[index].getComponent().edit(); return true; } } return false; } findNextEditableCell(row, index) { var nextCell = false; if (index < row.cells.length - 1) { for (var i = index + 1; i < row.cells.length; i++) { let cell = row.cells[i]; if (cell.column.modules.edit && Helpers.elVisible(cell.getElement())) { let allowEdit = this.allowEdit(cell); if (allowEdit) { nextCell = cell; break; } } } } return nextCell; } findPrevEditableCell(row, index) { var prevCell = false; if (index > 0) { for (var i = index - 1; i >= 0; i--) { let cell = row.cells[i]; if (cell.column.modules.edit && Helpers.elVisible(cell.getElement())) { let allowEdit = this.allowEdit(cell); if (allowEdit) { prevCell = cell; break; } } } } return prevCell; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnCheck(column) { if (typeof column.definition.editor !== "undefined") { this.initializeColumn(column); } } columnDeleteCheck(column) { if (this.currentCell && this.currentCell.column === column) { this.cancelEdit(); } } rowDeleteCheck(row) { if (this.currentCell && this.currentCell.row === row) { this.cancelEdit(); } } rowEditableCheck(row) { row.getCells().forEach((cell) => { if (cell.column.modules.edit && typeof cell.column.modules.edit.check === "function") { this.updateCellClass(cell); } }); } //initialize column editor initializeColumn(column) { var convertEmpty = Object.keys(column.definition).includes("editorEmptyValue"); var config = { editor: false, blocked: false, check: column.definition.editable, params: column.definition.editorParams || {}, convertEmptyValues: convertEmpty, editorEmptyValue: column.definition.editorEmptyValue, editorEmptyValueFunc: column.definition.editorEmptyValueFunc }; config.editor = this.lookupEditor(column.definition.editor, column); if (config.editor) { column.modules.edit = config; } } lookupEditor(editor, column) { var editorFunc; switch (typeof editor) { case "string": if (this.editors[editor]) { editorFunc = this.editors[editor]; } else { console.warn("Editor Error - No such editor found: ", editor); } break; case "function": editorFunc = editor; break; case "boolean": if (editor === true) { if (typeof column.definition.formatter !== "function") { if (this.editors[column.definition.formatter]) { editorFunc = this.editors[column.definition.formatter]; } else { editorFunc = this.editors["input"]; } } else { console.warn("Editor Error - Cannot auto lookup editor for a custom formatter: ", column.definition.formatter); } } break; } return editorFunc; } getCurrentCell() { return this.currentCell ? this.currentCell.getComponent() : false; } checkEditing() { return !!this.currentCell; } cancelEditEvent() { if (this.currentCell) { this.cancelEdit(); return true; } return false; } clearEditor(cancel) { var cell = this.currentCell, cellEl; this.invalidEdit = false; if (cell) { this.currentCell = false; cellEl = cell.getElement(); this.dispatch("edit-editor-clear", cell, cancel); cellEl.classList.remove("tabulator-editing"); while (cellEl.firstChild) cellEl.removeChild(cellEl.firstChild); cell.row.getElement().classList.remove("tabulator-editing"); cell.table.element.classList.remove("tabulator-editing"); } } cancelEdit() { if (this.currentCell) { var cell = this.currentCell; var component = this.currentCell.getComponent(); this.clearEditor(true); cell.setValueActual(cell.getValue()); cell.cellRendered(); if (cell.column.definition.editor == "textarea" || cell.column.definition.variableHeight) { cell.row.normalizeHeight(true); } if (cell.column.definition.cellEditCancelled) { cell.column.definition.cellEditCancelled.call(this.table, component); } this.dispatch("edit-cancelled", cell); this.dispatchExternal("cellEditCancelled", component); } } //return a formatted value for a cell bindEditor(cell) { if (cell.column.modules.edit) { var self = this, element = cell.getElement(true); this.updateCellClass(cell); element.setAttribute("tabindex", 0); element.addEventListener("mousedown", function(e) { if (e.button === 2) { e.preventDefault(); } else { self.mouseClick = true; } }); if (this.options("editTriggerEvent") === "dblclick") { element.addEventListener("dblclick", function(e) { if (!element.classList.contains("tabulator-editing")) { element.focus({ preventScroll: true }); self.edit(cell, e, false); } }); } if (this.options("editTriggerEvent") === "focus" || this.options("editTriggerEvent") === "click") { element.addEventListener("click", function(e) { if (!element.classList.contains("tabulator-editing")) { element.focus({ preventScroll: true }); self.edit(cell, e, false); } }); } if (this.options("editTriggerEvent") === "focus") { element.addEventListener("focus", function(e) { if (!self.recursionBlock) { self.edit(cell, e, false); } }); } } } focusCellNoEvent(cell, block) { this.recursionBlock = true; if (!(block && this.table.browser === "ie")) { cell.getElement().focus({ preventScroll: true }); } this.recursionBlock = false; } editCell(cell, forceEdit) { this.focusCellNoEvent(cell); this.edit(cell, false, forceEdit); } focusScrollAdjust(cell) { if (this.table.rowManager.getRenderMode() == "virtual") { var topEdge = this.table.rowManager.element.scrollTop, bottomEdge = this.table.rowManager.element.clientHeight + this.table.rowManager.element.scrollTop, rowEl = cell.row.getElement(); if (rowEl.offsetTop < topEdge) { this.table.rowManager.element.scrollTop -= topEdge - rowEl.offsetTop; } else { if (rowEl.offsetTop + rowEl.offsetHeight > bottomEdge) { this.table.rowManager.element.scrollTop += rowEl.offsetTop + rowEl.offsetHeight - bottomEdge; } } var leftEdge = this.table.rowManager.element.scrollLeft, rightEdge = this.table.rowManager.element.clientWidth + this.table.rowManager.element.scrollLeft, cellEl = cell.getElement(); if (this.table.modExists("frozenColumns")) { leftEdge += parseInt(this.table.modules.frozenColumns.leftMargin || 0); rightEdge -= parseInt(this.table.modules.frozenColumns.rightMargin || 0); } if (this.table.options.renderHorizontal === "virtual") { leftEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft); rightEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft); } if (cellEl.offsetLeft < leftEdge) { this.table.rowManager.element.scrollLeft -= leftEdge - cellEl.offsetLeft; } else { if (cellEl.offsetLeft + cellEl.offsetWidth > rightEdge) { this.table.rowManager.element.scrollLeft += cellEl.offsetLeft + cellEl.offsetWidth - rightEdge; } } } } allowEdit(cell) { var check = cell.column.modules.edit ? true : false; if (cell.column.modules.edit) { switch (typeof cell.column.modules.edit.check) { case "function": if (cell.row.initialized) { check = cell.column.modules.edit.check(cell.getComponent()); } break; case "string": check = !!cell.row.data[cell.column.modules.edit.check]; break; case "boolean": check = cell.column.modules.edit.check; break; } } return check; } edit(cell, e, forceEdit) { var self = this, allowEdit = true, rendered = function() { }, element = cell.getElement(), editFinished = false, cellEditor, component, params; if (this.currentCell) { if (!this.invalidEdit && this.currentCell !== cell) { this.cancelEdit(); } return; } function success(value) { if (self.currentCell === cell && !editFinished) { var valid = self.chain("edit-success", [cell, value], true, true); if (valid === true || self.table.options.validationMode === "highlight") { editFinished = true; self.clearEditor(); if (!cell.modules.edit) { cell.modules.edit = {}; } cell.modules.edit.edited = true; if (self.editedCells.indexOf(cell) == -1) { self.editedCells.push(cell); } value = self.transformEmptyValues(value, cell); cell.setValue(value, true); return valid === true; } else { editFinished = true; self.invalidEdit = true; self.focusCellNoEvent(cell, true); rendered(); setTimeout(() => { editFinished = false; }, 10); return false; } } } function cancel() { if (self.currentCell === cell && !editFinished) { self.cancelEdit(); } } function onRendered(callback) { rendered = callback; } if (!cell.column.modules.edit.blocked) { allowEdit = this.allowEdit(cell); if (allowEdit || forceEdit) { if (e) { e.stopPropagation(); } self.cancelEdit(); self.currentCell = cell; this.focusScrollAdjust(cell); component = cell.getComponent(); if (this.mouseClick) { this.mouseClick = false; if (cell.column.definition.cellClick) { cell.column.definition.cellClick.call(this.table, e, component); } } if (cell.column.definition.cellEditing) { cell.column.definition.cellEditing.call(this.table, component); } this.dispatch("cell-editing", cell); this.dispatchExternal("cellEditing", component); params = typeof cell.column.modules.edit.params === "function" ? cell.column.modules.edit.params(component) : cell.column.modules.edit.params; cellEditor = cell.column.modules.edit.editor.call(self, component, onRendered, success, cancel, params); if (this.currentCell && cellEditor !== false) { if (cellEditor instanceof Node) { element.classList.add("tabulator-editing"); cell.row.getElement().classList.add("tabulator-editing"); cell.table.element.classList.add("tabulator-editing"); while (element.firstChild) element.removeChild(element.firstChild); element.appendChild(cellEditor); rendered(); var children = element.children; for (var i = 0; i < children.length; i++) { children[i].addEventListener("click", function(e2) { e2.stopPropagation(); }); } } else { console.warn("Edit Error - Editor should return an instance of Node, the editor returned:", cellEditor); this.blur(element); return false; } } else { this.blur(element); return false; } return true; } else { this.mouseClick = false; this.blur(element); return false; } } else { this.mouseClick = false; this.blur(element); return false; } } emptyValueCheck(value) { return value === "" || value === null || typeof value === "undefined"; } transformEmptyValues(value, cell) { var mod = cell.column.modules.edit, convert = mod.convertEmptyValues || this.convertEmptyValues, checkFunc; if (convert) { checkFunc = mod.editorEmptyValueFunc || this.options("editorEmptyValueFunc"); if (checkFunc && checkFunc(value)) { value = mod.convertEmptyValues ? mod.editorEmptyValue : this.options("editorEmptyValue"); } } return value; } blur(element) { if (!this.confirm("edit-blur", [element])) { element.blur(); } } getEditedCells() { var output = []; this.editedCells.forEach((cell) => { output.push(cell.getComponent()); }); return output; } clearEdited(cell) { var editIndex; if (cell.modules.edit && cell.modules.edit.edited) { cell.modules.edit.edited = false; this.dispatch("edit-edited-clear", cell); } editIndex = this.editedCells.indexOf(cell); if (editIndex > -1) { this.editedCells.splice(editIndex, 1); } } }; __publicField(_Edit, "moduleName", "edit"); //load defaults __publicField(_Edit, "editors", defaultEditors); let Edit2 = _Edit; class ExportRow { constructor(type, columns, component, indent) { this.type = type; this.columns = columns; this.component = component || false; this.indent = indent || 0; } } class ExportColumn { constructor(value, component, width, height, depth) { this.value = value; this.component = component || false; this.width = width; this.height = height; this.depth = depth; } } var columnLookups$1 = {}; var rowLookups$1 = { visible: function() { return this.rowManager.getVisibleRows(false, true); }, all: function() { return this.rowManager.rows; }, selected: function() { return this.modules.selectRow.selectedRows; }, active: function() { if (this.options.pagination) { return this.rowManager.getDisplayRows(this.rowManager.displayRows.length - 2); } else { return this.rowManager.getDisplayRows(); } } }; const _Export = class _Export extends Module { constructor(table) { super(table); this.config = {}; this.cloneTableStyle = true; this.colVisProp = ""; this.colVisPropAttach = ""; this.registerTableOption("htmlOutputConfig", false); this.registerColumnOption("htmlOutput"); this.registerColumnOption("titleHtmlOutput"); } initialize() { this.registerTableFunction("getHtml", this.getHtml.bind(this)); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// generateExportList(config, style, range2, colVisProp) { var headers, body, columns, colLookup; this.cloneTableStyle = style; this.config = config || {}; this.colVisProp = colVisProp; this.colVisPropAttach = this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1); colLookup = _Export.columnLookups[range2]; if (colLookup) { columns = colLookup.call(this.table); columns = columns.filter((col) => this.columnVisCheck(col)); } headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders(columns)) : []; if (columns) { columns = columns.map((col) => col.getComponent()); } body = this.bodyToExportRows(this.rowLookup(range2), columns); return headers.concat(body); } generateTable(config, style, range2, colVisProp) { var list2 = this.generateExportList(config, style, range2, colVisProp); return this.generateTableElement(list2); } rowLookup(range2) { var rows2 = [], rowLookup; if (typeof range2 == "function") { range2.call(this.table).forEach((row) => { row = this.table.rowManager.findRow(row); if (row) { rows2.push(row); } }); } else { rowLookup = _Export.rowLookups[range2] || _Export.rowLookups["active"]; rows2 = rowLookup.call(this.table); } return Object.assign([], rows2); } generateColumnGroupHeaders(columns) { var output = []; if (!columns) { columns = this.config.columnGroups !== false ? this.table.columnManager.columns : this.table.columnManager.columnsByIndex; } columns.forEach((column) => { var colData = this.processColumnGroup(column); if (colData) { output.push(colData); } }); return output; } processColumnGroup(column) { var subGroups = column.columns, maxDepth = 0, title = column.definition["title" + this.colVisPropAttach] || column.definition.title; var groupData = { title, column, depth: 1 }; if (subGroups.length) { groupData.subGroups = []; groupData.width = 0; subGroups.forEach((subGroup) => { var subGroupData = this.processColumnGroup(subGroup); if (subGroupData) { groupData.width += subGroupData.width; groupData.subGroups.push(subGroupData); if (subGroupData.depth > maxDepth) { maxDepth = subGroupData.depth; } } }); groupData.depth += maxDepth; if (!groupData.width) { return false; } } else { if (this.columnVisCheck(column)) { groupData.width = 1; } else { return false; } } return groupData; } columnVisCheck(column) { var visProp = column.definition[this.colVisProp]; if (this.config.rowHeaders === false && column.isRowHeader) { return false; } if (typeof visProp === "function") { visProp = visProp.call(this.table, column.getComponent()); } if (visProp === false || visProp === true) { return visProp; } return column.visible && column.field; } headersToExportRows(columns) { var headers = [], headerDepth = 0, exportRows = []; function parseColumnGroup(column, level) { var depth = headerDepth - level; if (typeof headers[level] === "undefined") { headers[level] = []; } column.height = column.subGroups ? 1 : depth - column.depth + 1; headers[level].push(column); if (column.height > 1) { for (let i = 1; i < column.height; i++) { if (typeof headers[level + i] === "undefined") { headers[level + i] = []; } headers[level + i].push(false); } } if (column.width > 1) { for (let i = 1; i < column.width; i++) { headers[level].push(false); } } if (column.subGroups) { column.subGroups.forEach(function(subGroup) { parseColumnGroup(subGroup, level + 1); }); } } columns.forEach(function(column) { if (column.depth > headerDepth) { headerDepth = column.depth; } }); columns.forEach(function(column) { parseColumnGroup(column, 0); }); headers.forEach((header) => { var columns2 = []; header.forEach((col) => { if (col) { let title = typeof col.title === "undefined" ? "" : col.title; columns2.push(new ExportColumn(title, col.column.getComponent(), col.width, col.height, col.depth)); } else { columns2.push(null); } }); exportRows.push(new ExportRow("header", columns2)); }); return exportRows; } bodyToExportRows(rows2, columns = []) { var exportRows = []; if (columns.length === 0) { this.table.columnManager.columnsByIndex.forEach((column) => { if (this.columnVisCheck(column)) { columns.push(column.getComponent()); } }); } if (this.config.columnCalcs !== false && this.table.modExists("columnCalcs")) { if (this.table.modules.columnCalcs.topInitialized) { rows2.unshift(this.table.modules.columnCalcs.topRow); } if (this.table.modules.columnCalcs.botInitialized) { rows2.push(this.table.modules.columnCalcs.botRow); } } rows2 = rows2.filter((row) => { switch (row.type) { case "group": return this.config.rowGroups !== false; case "calc": return this.config.columnCalcs !== false; case "row": return !(this.table.options.dataTree && this.config.dataTree === false && row.modules.dataTree.parent); } return true; }); rows2.forEach((row, i) => { var rowData = row.getData(this.colVisProp); var exportCols = []; var indent = 0; switch (row.type) { case "group": indent = row.level; exportCols.push(new ExportColumn(row.key, row.getComponent(), columns.length, 1)); break; case "calc": case "row": columns.forEach((col) => { exportCols.push(new ExportColumn(col._column.getFieldValue(rowData), col, 1, 1)); }); if (this.table.options.dataTree && this.config.dataTree !== false) { indent = row.modules.dataTree.index; } break; } exportRows.push(new ExportRow(row.type, exportCols, row.getComponent(), indent)); }); return exportRows; } generateTableElement(list2) { var table = document.createElement("table"), headerEl = document.createElement("thead"), bodyEl = document.createElement("tbody"), styles = this.lookupTableStyles(), rowFormatter = this.table.options["rowFormatter" + this.colVisPropAttach], setup = {}; setup.rowFormatter = rowFormatter !== null ? rowFormatter : this.table.options.rowFormatter; if (this.table.options.dataTree && this.config.dataTree !== false && this.table.modExists("columnCalcs")) { setup.treeElementField = this.table.modules.dataTree.elementField; } setup.groupHeader = this.table.options["groupHeader" + this.colVisPropAttach]; if (setup.groupHeader && !Array.isArray(setup.groupHeader)) { setup.groupHeader = [setup.groupHeader]; } table.classList.add("tabulator-print-table"); this.mapElementStyles(this.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]); if (list2.length > 1e3) { console.warn("It may take a long time to render an HTML table with more than 1000 rows"); } list2.forEach((row, i) => { let rowEl; switch (row.type) { case "header": headerEl.appendChild(this.generateHeaderElement(row, setup, styles)); break; case "group": bodyEl.appendChild(this.generateGroupElement(row, setup, styles)); break; case "calc": bodyEl.appendChild(this.generateCalcElement(row, setup, styles)); break; case "row": rowEl = this.generateRowElement(row, setup, styles); this.mapElementStyles(i % 2 && styles.evenRow ? styles.evenRow : styles.oddRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); bodyEl.appendChild(rowEl); break; } }); if (headerEl.innerHTML) { table.appendChild(headerEl); } table.appendChild(bodyEl); this.mapElementStyles(this.table.element, table, ["border-top", "border-left", "border-right", "border-bottom"]); return table; } lookupTableStyles() { var styles = {}; if (this.cloneTableStyle && window.getComputedStyle) { styles.oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)"); styles.evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)"); styles.calcRow = this.table.element.querySelector(".tabulator-row.tabulator-calcs"); styles.firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)"); styles.firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0]; if (styles.firstRow) { styles.styleCells = styles.firstRow.getElementsByClassName("tabulator-cell"); styles.styleRowHeader = styles.firstRow.getElementsByClassName("tabulator-row-header")[0]; styles.firstCell = styles.styleCells[0]; styles.lastCell = styles.styleCells[styles.styleCells.length - 1]; } } return styles; } generateHeaderElement(row, setup, styles) { var rowEl = document.createElement("tr"); row.columns.forEach((column) => { if (column) { var cellEl = document.createElement("th"); var classNames = column.component._column.definition.cssClass ? column.component._column.definition.cssClass.split(" ") : []; cellEl.colSpan = column.width; cellEl.rowSpan = column.height; cellEl.innerHTML = column.value; if (this.cloneTableStyle) { cellEl.style.boxSizing = "border-box"; } classNames.forEach(function(className) { cellEl.classList.add(className); }); this.mapElementStyles(column.component.getElement(), cellEl, ["text-align", "border-left", "border-right", "background-color", "color", "font-weight", "font-family", "font-size"]); this.mapElementStyles(column.component._column.contentElement, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]); if (column.component._column.visible) { this.mapElementStyles(column.component.getElement(), cellEl, ["width"]); } else { if (column.component._column.definition.width) { cellEl.style.width = column.component._column.definition.width + "px"; } } if (column.component._column.parent && column.component._column.parent.isGroup) { this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]); } else { this.mapElementStyles(column.component.getElement(), cellEl, ["border-top"]); } if (column.component._column.isGroup) { this.mapElementStyles(column.component.getElement(), cellEl, ["border-bottom"]); } else { this.mapElementStyles(this.table.columnManager.getElement(), cellEl, ["border-bottom"]); } rowEl.appendChild(cellEl); } }); return rowEl; } generateGroupElement(row, setup, styles) { var rowEl = document.createElement("tr"), cellEl = document.createElement("td"), group = row.columns[0]; rowEl.classList.add("tabulator-print-table-row"); if (setup.groupHeader && setup.groupHeader[row.indent]) { group.value = setup.groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); } else { if (setup.groupHeader !== false) { group.value = row.component._group.generator(group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); } } cellEl.colSpan = group.width; cellEl.innerHTML = group.value; rowEl.classList.add("tabulator-print-table-group"); rowEl.classList.add("tabulator-group-level-" + row.indent); if (group.component.isVisible()) { rowEl.classList.add("tabulator-group-visible"); } this.mapElementStyles(styles.firstGroup, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); this.mapElementStyles(styles.firstGroup, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]); rowEl.appendChild(cellEl); return rowEl; } generateCalcElement(row, setup, styles) { var rowEl = this.generateRowElement(row, setup, styles); rowEl.classList.add("tabulator-print-table-calcs"); this.mapElementStyles(styles.calcRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); return rowEl; } generateRowElement(row, setup, styles) { var rowEl = document.createElement("tr"); rowEl.classList.add("tabulator-print-table-row"); row.columns.forEach((col, i) => { if (col) { var cellEl = document.createElement("td"), column = col.component._column, table = this.table, index = table.columnManager.findColumnIndex(column), value = col.value, cellStyle, styleProps; var cellWrapper = { modules: {}, getValue: function() { return value; }, getField: function() { return column.definition.field; }, getElement: function() { return cellEl; }, getType: function() { return "cell"; }, getColumn: function() { return column.getComponent(); }, getData: function() { return row.component.getData(); }, getRow: function() { return row.component; }, getTable: function() { return table; }, getComponent: function() { return cellWrapper; }, column }; var classNames = column.definition.cssClass ? column.definition.cssClass.split(" ") : []; classNames.forEach(function(className) { cellEl.classList.add(className); }); if (this.table.modExists("format") && this.config.formatCells !== false) { value = this.table.modules.format.formatExportValue(cellWrapper, this.colVisProp); } else { switch (typeof value) { case "object": value = value !== null ? JSON.stringify(value) : ""; break; case "undefined": value = ""; break; } } if (value instanceof Node) { cellEl.appendChild(value); } else { cellEl.innerHTML = value; } styleProps = ["padding-top", "padding-left", "padding-right", "padding-bottom", "border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "text-align"]; if (column.isRowHeader) { cellStyle = styles.styleRowHeader; styleProps.push("background-color"); } else { cellStyle = styles.styleCells && styles.styleCells[index] ? styles.styleCells[index] : styles.firstCell; } if (cellStyle) { this.mapElementStyles(cellStyle, cellEl, styleProps); if (column.definition.align) { cellEl.style.textAlign = column.definition.align; } } if (this.table.options.dataTree && this.config.dataTree !== false) { if (setup.treeElementField && setup.treeElementField == column.field || !setup.treeElementField && i == 0) { if (row.component._row.modules.dataTree.controlEl) { cellEl.insertBefore(row.component._row.modules.dataTree.controlEl.cloneNode(true), cellEl.firstChild); } if (row.component._row.modules.dataTree.branchEl) { cellEl.insertBefore(row.component._row.modules.dataTree.branchEl.cloneNode(true), cellEl.firstChild); } } } rowEl.appendChild(cellEl); if (cellWrapper.modules.format && cellWrapper.modules.format.renderedCallback) { cellWrapper.modules.format.renderedCallback(); } } }); if (setup.rowFormatter && row.type === "row" && this.config.formatCells !== false) { let formatComponent = Object.assign(row.component); formatComponent.getElement = function() { return rowEl; }; setup.rowFormatter(row.component); } return rowEl; } generateHTMLTable(list2) { var holder = document.createElement("div"); holder.appendChild(this.generateTableElement(list2)); return holder.innerHTML; } getHtml(visible, style, config, colVisProp) { var list2 = this.generateExportList(config || this.table.options.htmlOutputConfig, style, visible, colVisProp || "htmlOutput"); return this.generateHTMLTable(list2); } mapElementStyles(from, to, props) { if (this.cloneTableStyle && from && to) { var lookup2 = { "background-color": "backgroundColor", "color": "fontColor", "width": "width", "font-weight": "fontWeight", "font-family": "fontFamily", "font-size": "fontSize", "text-align": "textAlign", "border-top": "borderTop", "border-left": "borderLeft", "border-right": "borderRight", "border-bottom": "borderBottom", "padding-top": "paddingTop", "padding-left": "paddingLeft", "padding-right": "paddingRight", "padding-bottom": "paddingBottom" }; if (window.getComputedStyle) { var fromStyle = window.getComputedStyle(from); props.forEach(function(prop) { if (!to.style[lookup2[prop]]) { to.style[lookup2[prop]] = fromStyle.getPropertyValue(prop); } }); } } } }; __publicField(_Export, "moduleName", "export"); __publicField(_Export, "columnLookups", columnLookups$1); __publicField(_Export, "rowLookups", rowLookups$1); let Export = _Export; var defaultFilters = { //equal to "=": function(filterVal, rowVal, rowData, filterParams) { return rowVal == filterVal ? true : false; }, //less than "<": function(filterVal, rowVal, rowData, filterParams) { return rowVal < filterVal ? true : false; }, //less than or equal to "<=": function(filterVal, rowVal, rowData, filterParams) { return rowVal <= filterVal ? true : false; }, //greater than ">": function(filterVal, rowVal, rowData, filterParams) { return rowVal > filterVal ? true : false; }, //greater than or equal to ">=": function(filterVal, rowVal, rowData, filterParams) { return rowVal >= filterVal ? true : false; }, //not equal to "!=": function(filterVal, rowVal, rowData, filterParams) { return rowVal != filterVal ? true : false; }, "regex": function(filterVal, rowVal, rowData, filterParams) { if (typeof filterVal == "string") { filterVal = new RegExp(filterVal); } return filterVal.test(rowVal); }, //contains the string "like": function(filterVal, rowVal, rowData, filterParams) { if (filterVal === null || typeof filterVal === "undefined") { return rowVal === filterVal ? true : false; } else { if (typeof rowVal !== "undefined" && rowVal !== null) { return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1; } else { return false; } } }, //contains the keywords "keywords": function(filterVal, rowVal, rowData, filterParams) { var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator), value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(), matches = []; keywords.forEach((keyword) => { if (value.includes(keyword)) { matches.push(true); } }); return filterParams.matchAll ? matches.length === keywords.length : !!matches.length; }, //starts with the string "starts": function(filterVal, rowVal, rowData, filterParams) { if (filterVal === null || typeof filterVal === "undefined") { return rowVal === filterVal ? true : false; } else { if (typeof rowVal !== "undefined" && rowVal !== null) { return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase()); } else { return false; } } }, //ends with the string "ends": function(filterVal, rowVal, rowData, filterParams) { if (filterVal === null || typeof filterVal === "undefined") { return rowVal === filterVal ? true : false; } else { if (typeof rowVal !== "undefined" && rowVal !== null) { return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase()); } else { return false; } } }, //in array "in": function(filterVal, rowVal, rowData, filterParams) { if (Array.isArray(filterVal)) { return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true; } else { console.warn("Filter Error - filter value is not an array:", filterVal); return false; } }, // Smart filter // Supports ., !, <, >, <=, >=, = and falls back to like filter "smart": function(filterVal, rowVal, rowData, filterParams) { const search = filterVal.trim(); if (search === ".") return !(rowVal === null || rowVal == ""); if (search === "!") return rowVal === null || rowVal == ""; if (search.indexOf("<=") === 0) return this["<="]( parseFloat(search.substring(2)), rowVal, rowData, filterParams ); if (search.indexOf(">=") === 0) return this[">="]( parseFloat(search.substring(2)), rowVal, rowData, filterParams ); if (search.indexOf("<") === 0) return this["<"]( parseFloat(search.substring(1)), rowVal, rowData, filterParams ); if (search.indexOf(">") === 0) return this[">"]( parseFloat(search.substring(1)), rowVal, rowData, filterParams ); if (search.indexOf("=") === 0) return this["="](search.substring(1).trim(), rowVal, rowData, filterParams); if (search.includes(" ")) { const terms = search.split(/\s+/).filter((term) => term.length > 0); if (terms.length > 1) { const fuzzySearch = terms.join(" AND "); return this["smarter"](fuzzySearch, rowVal, rowData, filterParams); } } return this["like"](search, rowVal, rowData, filterParams); }, // Smarter filter // Just like the smart filter but you can combine multiple filters (AND/OR) // Examples: // - "john AND smith" - both terms must match // - "john OR jane" - either term must match // - ">100 AND <500" - value must be between 100 and 500 // - "! OR foo" - either empty or foo "smarter": function(filterVal, rowVal, rowData, filterParams) { const search = filterVal.trim(); if (!search) return true; const parts = search.split(/\s+(AND|OR)\s+/i); if (parts.length === 1) { return this["smart"](search, rowVal, rowData, filterParams); } let result = null; let currentOperator = null; for (let i = 0; i < parts.length; i++) { const part = parts[i].trim(); if (part === "AND" || part === "OR") { currentOperator = part; continue; } const partResult = this["smart"](part, rowVal, rowData, filterParams); if (result === null) { result = partResult; } else if (currentOperator === "AND") { result = result && partResult; } else if (currentOperator === "OR") { result = result || partResult; } } return result !== null ? result : true; } }; const _Filter = class _Filter extends Module { constructor(table) { super(table); this.filterList = []; this.headerFilters = {}; this.headerFilterColumns = []; this.prevHeaderFilterChangeCheck = ""; this.prevHeaderFilterChangeCheck = "{}"; this.changed = false; this.tableInitialized = false; this.registerTableOption("filterMode", "local"); this.registerTableOption("initialFilter", false); this.registerTableOption("initialHeaderFilter", false); this.registerTableOption("headerFilterLiveFilterDelay", 300); this.registerTableOption("placeholderHeaderFilter", false); this.registerColumnOption("headerFilter"); this.registerColumnOption("headerFilterPlaceholder"); this.registerColumnOption("headerFilterParams"); this.registerColumnOption("headerFilterEmptyCheck"); this.registerColumnOption("headerFilterFunc"); this.registerColumnOption("headerFilterFuncParams"); this.registerColumnOption("headerFilterLiveFilter"); this.registerTableFunction("searchRows", this.searchRows.bind(this)); this.registerTableFunction("searchData", this.searchData.bind(this)); this.registerTableFunction("setFilter", this.userSetFilter.bind(this)); this.registerTableFunction("refreshFilter", this.userRefreshFilter.bind(this)); this.registerTableFunction("addFilter", this.userAddFilter.bind(this)); this.registerTableFunction("getFilters", this.getFilters.bind(this)); this.registerTableFunction("setHeaderFilterFocus", this.userSetHeaderFilterFocus.bind(this)); this.registerTableFunction("getHeaderFilterValue", this.userGetHeaderFilterValue.bind(this)); this.registerTableFunction("setHeaderFilterValue", this.userSetHeaderFilterValue.bind(this)); this.registerTableFunction("getHeaderFilters", this.getHeaderFilters.bind(this)); this.registerTableFunction("removeFilter", this.userRemoveFilter.bind(this)); this.registerTableFunction("clearFilter", this.userClearFilter.bind(this)); this.registerTableFunction("clearHeaderFilter", this.userClearHeaderFilter.bind(this)); this.registerComponentFunction("column", "headerFilterFocus", this.setHeaderFilterFocus.bind(this)); this.registerComponentFunction("column", "reloadHeaderFilter", this.reloadHeaderFilter.bind(this)); this.registerComponentFunction("column", "getHeaderFilterValue", this.getHeaderFilterValue.bind(this)); this.registerComponentFunction("column", "setHeaderFilterValue", this.setHeaderFilterValue.bind(this)); } initialize() { this.subscribe("column-init", this.initializeColumnHeaderFilter.bind(this)); this.subscribe("column-width-fit-before", this.hideHeaderFilterElements.bind(this)); this.subscribe("column-width-fit-after", this.showHeaderFilterElements.bind(this)); this.subscribe("table-built", this.tableBuilt.bind(this)); this.subscribe("placeholder", this.generatePlaceholder.bind(this)); if (this.table.options.filterMode === "remote") { this.subscribe("data-params", this.remoteFilterParams.bind(this)); } this.registerDataHandler(this.filter.bind(this), 10); } tableBuilt() { if (this.table.options.initialFilter) { this.setFilter(this.table.options.initialFilter); } if (this.table.options.initialHeaderFilter) { this.table.options.initialHeaderFilter.forEach((item) => { var column = this.table.columnManager.findColumn(item.field); if (column) { this.setHeaderFilterValue(column, item.value); } else { console.warn("Column Filter Error - No matching column found:", item.field); return false; } }); } this.tableInitialized = true; } remoteFilterParams(data, config, silent, params) { params.filter = this.getFilters(true, true); return params; } generatePlaceholder(text) { if (this.table.options.placeholderHeaderFilter && Object.keys(this.headerFilters).length) { return this.table.options.placeholderHeaderFilter; } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// //set standard filters userSetFilter(field, type, value, params) { this.setFilter(field, type, value, params); this.refreshFilter(); } //set standard filters userRefreshFilter() { this.refreshFilter(); } //add filter to array userAddFilter(field, type, value, params) { this.addFilter(field, type, value, params); this.refreshFilter(); } userSetHeaderFilterFocus(field) { var column = this.table.columnManager.findColumn(field); if (column) { this.setHeaderFilterFocus(column); } else { console.warn("Column Filter Focus Error - No matching column found:", field); return false; } } userGetHeaderFilterValue(field) { var column = this.table.columnManager.findColumn(field); if (column) { return this.getHeaderFilterValue(column); } else { console.warn("Column Filter Error - No matching column found:", field); } } userSetHeaderFilterValue(field, value) { var column = this.table.columnManager.findColumn(field); if (column) { this.setHeaderFilterValue(column, value); } else { console.warn("Column Filter Error - No matching column found:", field); return false; } } //remove filter from array userRemoveFilter(field, type, value) { this.removeFilter(field, type, value); this.refreshFilter(); } //clear filters userClearFilter(all) { this.clearFilter(all); this.refreshFilter(); } //clear header filters userClearHeaderFilter() { this.clearHeaderFilter(); this.refreshFilter(); } //search for specific row components searchRows(field, type, value) { return this.search("rows", field, type, value); } //search for specific data searchData(field, type, value) { return this.search("data", field, type, value); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnHeaderFilter(column) { var def = column.definition; if (def.headerFilter) { this.initializeColumn(column); } } //initialize column header filter initializeColumn(column, value) { var self = this, field = column.getField(); function success(value2) { var filterType = column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text" || column.modules.filter.tagType == "textarea" ? "partial" : "match", type = "", filterChangeCheck = "", filterFunc; if (typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value2) { column.modules.filter.prevSuccess = value2; if (!column.modules.filter.emptyFunc(value2)) { column.modules.filter.value = value2; switch (typeof column.definition.headerFilterFunc) { case "string": if (_Filter.filters[column.definition.headerFilterFunc]) { type = column.definition.headerFilterFunc; filterFunc = function(data) { var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value2, fieldVal, data) : params; return _Filter.filters[column.definition.headerFilterFunc](value2, fieldVal, data, params); }; } else { console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc); } break; case "function": filterFunc = function(data) { var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value2, fieldVal, data) : params; return column.definition.headerFilterFunc(value2, fieldVal, data, params); }; type = filterFunc; break; } if (!filterFunc) { switch (filterType) { case "partial": filterFunc = function(data) { var colVal = column.getFieldValue(data); if (typeof colVal !== "undefined" && colVal !== null) { return String(colVal).toLowerCase().indexOf(String(value2).toLowerCase()) > -1; } else { return false; } }; type = "like"; break; default: filterFunc = function(data) { return column.getFieldValue(data) == value2; }; type = "="; } } self.headerFilters[field] = { value: value2, func: filterFunc, type }; } else { delete self.headerFilters[field]; } column.modules.filter.value = value2; filterChangeCheck = JSON.stringify(self.headerFilters); if (self.prevHeaderFilterChangeCheck !== filterChangeCheck) { self.prevHeaderFilterChangeCheck = filterChangeCheck; self.trackChanges(); self.refreshFilter(); } } return true; } column.modules.filter = { success, attrType: false, tagType: false, emptyFunc: false }; this.generateHeaderFilterElement(column); } generateHeaderFilterElement(column, initialValue, reinitialize) { var self = this, success = column.modules.filter.success, field = column.getField(), filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params, onRenderedCallback; column.modules.filter.value = initialValue; function cancel() { } function onRendered(callback) { onRenderedCallback = callback; } if (column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode) { column.contentElement.removeChild(column.modules.filter.headerElement.parentNode); } if (field) { column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value) { return !value && value !== 0; }; filterElement = document.createElement("div"); filterElement.classList.add("tabulator-header-filter"); switch (typeof column.definition.headerFilter) { case "string": if (self.table.modules.edit.editors[column.definition.headerFilter]) { editor = self.table.modules.edit.editors[column.definition.headerFilter]; if ((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck) { column.modules.filter.emptyFunc = function(value) { return value !== true && value !== false; }; } } else { console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor); } break; case "function": editor = column.definition.headerFilter; break; case "boolean": if (column.modules.edit && column.modules.edit.editor) { editor = column.modules.edit.editor; } else { if (column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]) { editor = self.table.modules.edit.editors[column.definition.formatter]; if ((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck) { column.modules.filter.emptyFunc = function(value) { return value !== true && value !== false; }; } } else { editor = self.table.modules.edit.editors["input"]; } } break; } if (editor) { cellWrapper = { getValue: function() { return typeof initialValue !== "undefined" ? initialValue : ""; }, getField: function() { return column.definition.field; }, getElement: function() { return filterElement; }, getColumn: function() { return column.getComponent(); }, getTable: () => { return this.table; }, getType: () => { return "header"; }, getRow: function() { return { normalizeHeight: function() { } }; } }; params = column.definition.headerFilterParams || {}; params = typeof params === "function" ? params.call(self.table, cellWrapper) : params; editorElement = editor.call(this.table.modules.edit, cellWrapper, onRendered, success, cancel, params); if (!editorElement) { console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false"); return; } if (!(editorElement instanceof Node)) { console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement); return; } self.langBind("headerFilters|columns|" + column.definition.field, function(value) { editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : column.definition.headerFilterPlaceholder || self.langText("headerFilters|default")); }); editorElement.addEventListener("click", function(e) { e.stopPropagation(); editorElement.focus(); }); editorElement.addEventListener("focus", (e) => { var left = this.table.columnManager.contentsElement.scrollLeft; var headerPos = this.table.rowManager.element.scrollLeft; if (left !== headerPos) { this.table.rowManager.scrollHorizontal(left); this.table.columnManager.scrollHorizontal(left); } }); typingTimer = false; searchTrigger = function(e) { if (typingTimer) { clearTimeout(typingTimer); } typingTimer = setTimeout(function() { success(editorElement.value); }, self.table.options.headerFilterLiveFilterDelay); }; column.modules.filter.headerElement = editorElement; column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : ""; column.modules.filter.tagType = editorElement.tagName.toLowerCase(); if (column.definition.headerFilterLiveFilter !== false) { if (!(column.definition.headerFilter === "autocomplete" || column.definition.headerFilter === "tickCross" || (column.definition.editor === "autocomplete" || column.definition.editor === "tickCross") && column.definition.headerFilter === true)) { editorElement.addEventListener("keyup", searchTrigger); editorElement.addEventListener("search", searchTrigger); if (column.modules.filter.attrType == "number") { editorElement.addEventListener("change", function(e) { success(editorElement.value); }); } if (column.modules.filter.attrType == "text" && this.table.browser !== "ie") { editorElement.setAttribute("type", "search"); } } if (column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea") { editorElement.addEventListener("mousedown", function(e) { e.stopPropagation(); }); } } filterElement.appendChild(editorElement); column.contentElement.appendChild(filterElement); if (!reinitialize) { self.headerFilterColumns.push(column); } if (onRenderedCallback) { onRenderedCallback(); } } } else { console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title); } } //hide all header filter elements (used to ensure correct column widths in "fitData" layout mode) hideHeaderFilterElements() { this.headerFilterColumns.forEach(function(column) { if (column.modules.filter && column.modules.filter.headerElement) { column.modules.filter.headerElement.style.display = "none"; } }); } //show all header filter elements (used to ensure correct column widths in "fitData" layout mode) showHeaderFilterElements() { this.headerFilterColumns.forEach(function(column) { if (column.modules.filter && column.modules.filter.headerElement) { column.modules.filter.headerElement.style.display = ""; } }); } //programmatically set focus of header filter setHeaderFilterFocus(column) { if (column.modules.filter && column.modules.filter.headerElement) { column.modules.filter.headerElement.focus(); } else { console.warn("Column Filter Focus Error - No header filter set on column:", column.getField()); } } //programmatically get value of header filter getHeaderFilterValue(column) { if (column.modules.filter && column.modules.filter.headerElement) { return column.modules.filter.value; } else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } //programmatically set value of header filter setHeaderFilterValue(column, value) { if (column) { if (column.modules.filter && column.modules.filter.headerElement) { this.generateHeaderFilterElement(column, value, true); column.modules.filter.success(value); } else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } reloadHeaderFilter(column) { if (column) { if (column.modules.filter && column.modules.filter.headerElement) { this.generateHeaderFilterElement(column, column.modules.filter.value, true); } else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } refreshFilter() { if (this.tableInitialized) { if (this.table.options.filterMode === "remote") { this.reloadData(null, false, false); } else { this.refreshData(true); } } } //check if the filters has changed since last use trackChanges() { this.changed = true; this.dispatch("filter-changed"); } //check if the filters has changed since last use hasChanged() { var changed = this.changed; this.changed = false; return changed; } //set standard filters setFilter(field, type, value, params) { this.filterList = []; if (!Array.isArray(field)) { field = [{ field, type, value, params }]; } this.addFilter(field); } //add filter to array addFilter(field, type, value, params) { var changed = false; if (!Array.isArray(field)) { field = [{ field, type, value, params }]; } field.forEach((filter) => { filter = this.findFilter(filter); if (filter) { this.filterList.push(filter); changed = true; } }); if (changed) { this.trackChanges(); } } findFilter(filter) { var column; if (Array.isArray(filter)) { return this.findSubFilters(filter); } var filterFunc = false; if (typeof filter.field == "function") { filterFunc = function(data) { return filter.field(data, filter.type || {}); }; } else { if (_Filter.filters[filter.type]) { column = this.table.columnManager.getColumnByField(filter.field); if (column) { filterFunc = function(data) { return _Filter.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {}); }; } else { filterFunc = function(data) { return _Filter.filters[filter.type](filter.value, data[filter.field], data, filter.params || {}); }; } } else { console.warn("Filter Error - No such filter type found, ignoring: ", filter.type); } } filter.func = filterFunc; return filter.func ? filter : false; } findSubFilters(filters) { var output = []; filters.forEach((filter) => { filter = this.findFilter(filter); if (filter) { output.push(filter); } }); return output.length ? output : false; } //get all filters getFilters(all, ajax) { var output = []; if (all) { output = this.getHeaderFilters(); } if (ajax) { output.forEach(function(item) { if (typeof item.type == "function") { item.type = "function"; } }); } output = output.concat(this.filtersToArray(this.filterList, ajax)); return output; } //filter to Object filtersToArray(filterList, ajax) { var output = []; filterList.forEach((filter) => { var item; if (Array.isArray(filter)) { output.push(this.filtersToArray(filter, ajax)); } else { item = { field: filter.field, type: filter.type, value: filter.value }; if (ajax) { if (typeof item.type == "function") { item.type = "function"; } } output.push(item); } }); return output; } //get all filters getHeaderFilters() { var output = []; for (var key in this.headerFilters) { output.push({ field: key, type: this.headerFilters[key].type, value: this.headerFilters[key].value }); } return output; } //remove filter from array removeFilter(field, type, value) { if (!Array.isArray(field)) { field = [{ field, type, value }]; } field.forEach((filter) => { var index = -1; if (typeof filter.field == "object") { index = this.filterList.findIndex((element) => { return filter === element; }); } else { index = this.filterList.findIndex((element) => { return filter.field === element.field && filter.type === element.type && filter.value === element.value; }); } if (index > -1) { this.filterList.splice(index, 1); } else { console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type); } }); this.trackChanges(); } //clear filters clearFilter(all) { this.filterList = []; if (all) { this.clearHeaderFilter(); } this.trackChanges(); } //clear header filters clearHeaderFilter() { this.headerFilters = {}; this.prevHeaderFilterChangeCheck = "{}"; this.headerFilterColumns.forEach((column) => { if (typeof column.modules.filter.value !== "undefined") { delete column.modules.filter.value; } column.modules.filter.prevSuccess = void 0; this.reloadHeaderFilter(column); }); this.trackChanges(); } //search data and return matching rows search(searchType, field, type, value) { var activeRows = [], filterList = []; if (!Array.isArray(field)) { field = [{ field, type, value }]; } field.forEach((filter) => { filter = this.findFilter(filter); if (filter) { filterList.push(filter); } }); this.table.rowManager.rows.forEach((row) => { var match = true; filterList.forEach((filter) => { if (!this.filterRecurse(filter, row.getData())) { match = false; } }); if (match) { activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent()); } }); return activeRows; } //filter row array filter(rowList, filters) { var activeRows = [], activeRowComponents = []; if (this.subscribedExternal("dataFiltering")) { this.dispatchExternal("dataFiltering", this.getFilters(true)); } if (this.table.options.filterMode !== "remote" && (this.filterList.length || Object.keys(this.headerFilters).length)) { rowList.forEach((row) => { if (this.filterRow(row)) { activeRows.push(row); } }); } else { activeRows = rowList.slice(0); } if (this.subscribedExternal("dataFiltered")) { activeRows.forEach((row) => { activeRowComponents.push(row.getComponent()); }); this.dispatchExternal("dataFiltered", this.getFilters(true), activeRowComponents); } return activeRows; } //filter individual row filterRow(row, filters) { var match = true, data = row.getData(); this.filterList.forEach((filter) => { if (!this.filterRecurse(filter, data)) { match = false; } }); for (var field in this.headerFilters) { if (!this.headerFilters[field].func(data)) { match = false; } } return match; } filterRecurse(filter, data) { var match = false; if (Array.isArray(filter)) { filter.forEach((subFilter) => { if (this.filterRecurse(subFilter, data)) { match = true; } }); } else { match = filter.func(data); } return match; } }; __publicField(_Filter, "moduleName", "filter"); //load defaults __publicField(_Filter, "filters", defaultFilters); let Filter = _Filter; function plaintext(cell, formatterParams, onRendered) { return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } function html(cell, formatterParams, onRendered) { return cell.getValue(); } function textarea(cell, formatterParams, onRendered) { cell.getElement().style.whiteSpace = "pre-wrap"; return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } function money(cell, formatterParams, onRendered) { var floatVal = parseFloat(cell.getValue()), sign = "", number2, integer, decimal, rgx, value; var decimalSym = formatterParams.decimal || "."; var thousandSym = formatterParams.thousand || ","; var negativeSign = formatterParams.negativeSign || "-"; var symbol = formatterParams.symbol || ""; var after = !!formatterParams.symbolAfter; var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2; if (Number.isNaN(floatVal)) { return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } if (floatVal < 0) { floatVal = Math.abs(floatVal); sign = negativeSign; } number2 = precision !== false ? floatVal.toFixed(precision) : floatVal; number2 = String(number2).split("."); integer = number2[0]; decimal = number2.length > 1 ? decimalSym + number2[1] : ""; if (formatterParams.thousand !== false) { rgx = /(\d+)(\d{3})/; while (rgx.test(integer)) { integer = integer.replace(rgx, "$1" + thousandSym + "$2"); } } value = integer + decimal; if (sign === true) { value = "(" + value + ")"; return after ? value + symbol : symbol + value; } else { return after ? sign + value + symbol : sign + symbol + value; } } function link(cell, formatterParams, onRendered) { var value = cell.getValue(), urlPrefix = formatterParams.urlPrefix || "", download = formatterParams.download, label = value, el = document.createElement("a"), data; function labelTraverse(path, data2) { var item = path.shift(), value2 = data2[item]; if (path.length && typeof value2 === "object") { return labelTraverse(path, value2); } return value2; } if (formatterParams.labelField) { data = cell.getData(); label = labelTraverse(formatterParams.labelField.split(this.table.options.nestedFieldSeparator), data); } if (formatterParams.label) { switch (typeof formatterParams.label) { case "string": label = formatterParams.label; break; case "function": label = formatterParams.label(cell); break; } } if (label) { if (formatterParams.urlField) { data = cell.getData(); value = Helpers.retrieveNestedData(this.table.options.nestedFieldSeparator, formatterParams.urlField, data); } if (formatterParams.url) { switch (typeof formatterParams.url) { case "string": value = formatterParams.url; break; case "function": value = formatterParams.url(cell); break; } } el.setAttribute("href", urlPrefix + value); if (formatterParams.target) { el.setAttribute("target", formatterParams.target); } if (formatterParams.download) { if (typeof download == "function") { download = download(cell); } else { download = download === true ? "" : download; } el.setAttribute("download", download); } el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label)); return el; } else { return " "; } } function image(cell, formatterParams, onRendered) { var el = document.createElement("img"), src = cell.getValue(); if (formatterParams.urlPrefix) { src = formatterParams.urlPrefix + cell.getValue(); } if (formatterParams.urlSuffix) { src = src + formatterParams.urlSuffix; } el.setAttribute("src", src); switch (typeof formatterParams.height) { case "number": el.style.height = formatterParams.height + "px"; break; case "string": el.style.height = formatterParams.height; break; } switch (typeof formatterParams.width) { case "number": el.style.width = formatterParams.width + "px"; break; case "string": el.style.width = formatterParams.width; break; } el.addEventListener("load", function() { cell.getRow().normalizeHeight(); }); return el; } function tickCross(cell, formatterParams, onRendered) { var value = cell.getValue(), element = cell.getElement(), empty = formatterParams.allowEmpty, truthy = formatterParams.allowTruthy, trueValueSet = Object.keys(formatterParams).includes("trueValue"), tick = typeof formatterParams.tickElement !== "undefined" ? formatterParams.tickElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path class="tabulator-tick" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>', cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path class="tabulator-cross" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>'; if (trueValueSet && value === formatterParams.trueValue || !trueValueSet && (truthy && value || (value === true || value === "true" || value === "True" || value === 1 || value === "1"))) { element.setAttribute("aria-checked", true); return tick || ""; } else { if (empty && (value === "null" || value === "" || value === null || typeof value === "undefined")) { element.setAttribute("aria-checked", "mixed"); return ""; } else { element.setAttribute("aria-checked", false); return cross || ""; } } } function datetime$1(cell, formatterParams, onRendered) { var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; var outputFormat = formatterParams.outputFormat || "dd/MM/yyyy HH:mm:ss"; var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; var value = cell.getValue(); if (typeof DT != "undefined") { var newDatetime; if (DT.isDateTime(value)) { newDatetime = value; } else if (inputFormat === "x") { newDatetime = DT.fromMillis(value); } else if (inputFormat === "iso") { newDatetime = DT.fromISO(String(value)); } else { newDatetime = DT.fromFormat(String(value), inputFormat); } if (newDatetime.isValid) { if (formatterParams.timezone) { newDatetime = newDatetime.setZone(formatterParams.timezone); } return newDatetime.toFormat(outputFormat); } else { if (invalid === true || !value) { return value; } else if (typeof invalid === "function") { return invalid(value); } else { return invalid; } } } else { console.error("Format Error - 'datetime' formatter is dependant on luxon.js"); } } function datetimediff(cell, formatterParams, onRendered) { var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false; var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : "days"; var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false; var date2 = typeof formatterParams.date !== "undefined" ? formatterParams.date : DT.now(); var value = cell.getValue(); if (typeof DT != "undefined") { var newDatetime; if (DT.isDateTime(value)) { newDatetime = value; } else if (inputFormat === "x") { newDatetime = DT.fromMillis(value); } else if (inputFormat === "iso") { newDatetime = DT.fromISO(String(value)); } else { newDatetime = DT.fromFormat(String(value), inputFormat); } if (newDatetime.isValid) { if (humanize) { return newDatetime.diff(date2, unit).toHuman() + (suffix ? " " + suffix : ""); } else { return parseInt(newDatetime.diff(date2, unit)[unit]) + (suffix ? " " + suffix : ""); } } else { if (invalid === true) { return value; } else if (typeof invalid === "function") { return invalid(value); } else { return invalid; } } } else { console.error("Format Error - 'datetimediff' formatter is dependant on luxon.js"); } } function lookup(cell, formatterParams, onRendered) { var value = cell.getValue(); if (typeof formatterParams[value] === "undefined") { console.warn("Missing display value for " + value); return value; } return formatterParams[value]; } function star(cell, formatterParams, onRendered) { var value = cell.getValue(), element = cell.getElement(), maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5, stars = document.createElement("span"), star2 = document.createElementNS("http://www.w3.org/2000/svg", "svg"), starActive = '<polygon fill="#FFEA00" stroke="#C1AB60" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>', starInactive = '<polygon fill="#D2D2D2" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>'; stars.style.verticalAlign = "middle"; star2.setAttribute("width", "14"); star2.setAttribute("height", "14"); star2.setAttribute("viewBox", "0 0 512 512"); star2.setAttribute("xml:space", "preserve"); star2.style.padding = "0 1px"; value = value && !Number.isNaN(value) ? parseInt(value) : 0; value = Math.max(0, Math.min(value, maxStars)); for (var i = 1; i <= maxStars; i++) { var nextStar = star2.cloneNode(true); nextStar.innerHTML = i <= value ? starActive : starInactive; stars.appendChild(nextStar); } element.style.whiteSpace = "nowrap"; element.style.overflow = "hidden"; element.style.textOverflow = "ellipsis"; element.setAttribute("aria-label", value); return stars; } function traffic(cell, formatterParams, onRendered) { var value = this.sanitizeHTML(cell.getValue()) || 0, el = document.createElement("span"), max = formatterParams && formatterParams.max ? formatterParams.max : 100, min = formatterParams && formatterParams.min ? formatterParams.min : 0, colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"], color2 = "#666666", percent, percentValue; if (Number.isNaN(value) || typeof cell.getValue() === "undefined") { return; } el.classList.add("tabulator-traffic-light"); percentValue = parseFloat(value) <= max ? parseFloat(value) : max; percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min; percent = (max - min) / 100; percentValue = Math.round((percentValue - min) / percent); switch (typeof colors) { case "string": color2 = colors; break; case "function": color2 = colors(value); break; case "object": if (Array.isArray(colors)) { var unit = 100 / colors.length; var index = Math.floor(percentValue / unit); index = Math.min(index, colors.length - 1); index = Math.max(index, 0); color2 = colors[index]; break; } } el.style.backgroundColor = color2; return el; } function progress(cell, formatterParams = {}, onRendered) { var value = this.sanitizeHTML(cell.getValue()) || 0, element = cell.getElement(), max = formatterParams.max ? formatterParams.max : 100, min = formatterParams.min ? formatterParams.min : 0, legendAlign = formatterParams.legendAlign ? formatterParams.legendAlign : "center", percent, percentValue, color2, legend, legendColor; percentValue = parseFloat(value) <= max ? parseFloat(value) : max; percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min; percent = (max - min) / 100; percentValue = Math.round((percentValue - min) / percent); switch (typeof formatterParams.color) { case "string": color2 = formatterParams.color; break; case "function": color2 = formatterParams.color(value); break; case "object": if (Array.isArray(formatterParams.color)) { let unit = 100 / formatterParams.color.length; let index = Math.floor(percentValue / unit); index = Math.min(index, formatterParams.color.length - 1); index = Math.max(index, 0); color2 = formatterParams.color[index]; break; } default: color2 = "#2DC214"; } switch (typeof formatterParams.legend) { case "string": legend = formatterParams.legend; break; case "function": legend = formatterParams.legend(value); break; case "boolean": legend = value; break; default: legend = false; } switch (typeof formatterParams.legendColor) { case "string": legendColor = formatterParams.legendColor; break; case "function": legendColor = formatterParams.legendColor(value); break; case "object": if (Array.isArray(formatterParams.legendColor)) { let unit = 100 / formatterParams.legendColor.length; let index = Math.floor(percentValue / unit); index = Math.min(index, formatterParams.legendColor.length - 1); index = Math.max(index, 0); legendColor = formatterParams.legendColor[index]; } break; default: legendColor = "#000"; } element.style.minWidth = "30px"; element.style.position = "relative"; element.setAttribute("aria-label", percentValue); var barEl = document.createElement("div"); barEl.style.display = "inline-block"; barEl.style.width = percentValue + "%"; barEl.style.backgroundColor = color2; barEl.style.height = "100%"; barEl.setAttribute("data-max", max); barEl.setAttribute("data-min", min); var barContainer = document.createElement("div"); barContainer.style.position = "relative"; barContainer.style.width = "100%"; barContainer.style.height = "100%"; if (legend) { var legendEl = document.createElement("div"); legendEl.style.position = "absolute"; legendEl.style.top = 0; legendEl.style.left = 0; legendEl.style.textAlign = legendAlign; legendEl.style.width = "100%"; legendEl.style.color = legendColor; legendEl.innerHTML = legend; } onRendered(function() { if (!(cell instanceof CellComponent)) { var holderEl = document.createElement("div"); holderEl.style.position = "absolute"; holderEl.style.top = "4px"; holderEl.style.bottom = "4px"; holderEl.style.left = "4px"; holderEl.style.right = "4px"; element.appendChild(holderEl); element = holderEl; } element.appendChild(barContainer); barContainer.appendChild(barEl); if (legend) { barContainer.appendChild(legendEl); } }); return ""; } function color(cell, formatterParams, onRendered) { cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue()); return ""; } function buttonTick(cell, formatterParams, onRendered) { return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>'; } function buttonCross(cell, formatterParams, onRendered) { return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>'; } function toggle(cell, formatterParams, onRendered) { var value = cell.getValue(), size = formatterParams.size || 15, sizePx = size + "px", containEl, switchEl, onValue = formatterParams.hasOwnProperty("onValue") ? formatterParams.onValue : true, offValue = formatterParams.hasOwnProperty("offValue") ? formatterParams.offValue : false, state = formatterParams.onTruthy ? value : value === onValue; containEl = document.createElement("div"); containEl.classList.add("tabulator-toggle"); if (state) { containEl.classList.add("tabulator-toggle-on"); containEl.style.flexDirection = "row-reverse"; if (formatterParams.onColor) { containEl.style.background = formatterParams.onColor; } } else { if (formatterParams.offColor) { containEl.style.background = formatterParams.offColor; } } containEl.style.width = 2.5 * size + "px"; containEl.style.borderRadius = sizePx; if (formatterParams.clickable) { containEl.addEventListener("click", (e) => { cell.setValue(state ? offValue : onValue); }); } switchEl = document.createElement("div"); switchEl.classList.add("tabulator-toggle-switch"); switchEl.style.height = sizePx; switchEl.style.width = sizePx; switchEl.style.borderRadius = sizePx; containEl.appendChild(switchEl); return containEl; } function rownum(cell, formatterParams, onRendered) { var content = document.createElement("span"); var row = cell.getRow(); var table = cell.getTable(); row.watchPosition((position) => { if (formatterParams.relativeToPage) { position += table.modules.page.getPageSize() * (table.modules.page.getPage() - 1); } content.innerText = position; }); return content; } function handle(cell, formatterParams, onRendered) { cell.getElement().classList.add("tabulator-row-handle"); return "<div class='tabulator-row-handle-box'><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div></div>"; } function adaptable(cell, params, onRendered) { var lookup2, formatterFunc, formatterParams; function defaultLookup(cell2) { var value = cell2.getValue(), formatter = "plaintext"; switch (typeof value) { case "boolean": formatter = "tickCross"; break; case "string": if (value.includes("\n")) { formatter = "textarea"; } break; } return formatter; } lookup2 = params.formatterLookup ? params.formatterLookup(cell) : defaultLookup(cell); if (params.paramsLookup) { formatterParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup2, cell) : params.paramsLookup[lookup2]; } formatterFunc = this.table.modules.format.lookupFormatter(lookup2); return formatterFunc.call(this, cell, formatterParams || {}, onRendered); } function array$2(cell, formatterParams, onRendered) { var delimiter = formatterParams.delimiter || ",", value = cell.getValue(), table = this.table, valueMap; if (formatterParams.valueMap) { if (typeof formatterParams.valueMap === "string") { valueMap = function(value2) { return value2.map((item) => { return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, formatterParams.valueMap, item); }); }; } else { valueMap = formatterParams.valueMap; } } if (Array.isArray(value)) { if (valueMap) { value = valueMap(value); } return value.join(delimiter); } else { return value; } } function json$1(cell, formatterParams, onRendered) { var indent = formatterParams.indent || " ", multiline = typeof formatterParams.multiline === "undefined" ? true : formatterParams.multiline, replacer = formatterParams.replacer || null, value = cell.getValue(); if (multiline) { cell.getElement().style.whiteSpace = "pre-wrap"; } return JSON.stringify(value, replacer, indent); } var defaultFormatters = { plaintext, html, textarea, money, link, image, tickCross, datetime: datetime$1, datetimediff, lookup, star, traffic, progress, color, buttonTick, buttonCross, toggle, rownum, handle, adaptable, array: array$2, json: json$1 }; const _Format = class _Format extends Module { constructor(table) { super(table); this.registerColumnOption("formatter"); this.registerColumnOption("formatterParams"); this.registerColumnOption("formatterPrint"); this.registerColumnOption("formatterPrintParams"); this.registerColumnOption("formatterClipboard"); this.registerColumnOption("formatterClipboardParams"); this.registerColumnOption("formatterHtmlOutput"); this.registerColumnOption("formatterHtmlOutputParams"); this.registerColumnOption("titleFormatter"); this.registerColumnOption("titleFormatterParams"); } initialize() { this.subscribe("cell-format", this.formatValue.bind(this)); this.subscribe("cell-rendered", this.cellRendered.bind(this)); this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("column-format", this.formatHeader.bind(this)); } //initialize column formatter initializeColumn(column) { column.modules.format = this.lookupTypeFormatter(column, ""); if (typeof column.definition.formatterPrint !== "undefined") { column.modules.format.print = this.lookupTypeFormatter(column, "Print"); } if (typeof column.definition.formatterClipboard !== "undefined") { column.modules.format.clipboard = this.lookupTypeFormatter(column, "Clipboard"); } if (typeof column.definition.formatterHtmlOutput !== "undefined") { column.modules.format.htmlOutput = this.lookupTypeFormatter(column, "HtmlOutput"); } } lookupTypeFormatter(column, type) { var config = { params: column.definition["formatter" + type + "Params"] || {} }, formatter = column.definition["formatter" + type]; config.formatter = this.lookupFormatter(formatter); return config; } lookupFormatter(formatter) { var formatterFunc; switch (typeof formatter) { case "string": if (_Format.formatters[formatter]) { formatterFunc = _Format.formatters[formatter]; } else { console.warn("Formatter Error - No such formatter found: ", formatter); formatterFunc = _Format.formatters.plaintext; } break; case "function": formatterFunc = formatter; break; default: formatterFunc = _Format.formatters.plaintext; break; } return formatterFunc; } cellRendered(cell) { if (cell.modules.format && cell.modules.format.renderedCallback && !cell.modules.format.rendered) { cell.modules.format.renderedCallback(); cell.modules.format.rendered = true; } } //return a formatted value for a column header formatHeader(column, title, el) { var formatter, params, onRendered, mockCell; if (column.definition.titleFormatter) { formatter = this.lookupFormatter(column.definition.titleFormatter); onRendered = (callback) => { column.titleFormatterRendered = callback; }; mockCell = { getValue: function() { return title; }, getElement: function() { return el; }, getType: function() { return "header"; }, getColumn: function() { return column.getComponent(); }, getTable: () => { return this.table; } }; params = column.definition.titleFormatterParams || {}; params = typeof params === "function" ? params() : params; return formatter.call(this, mockCell, params, onRendered); } else { return title; } } //return a formatted value for a cell formatValue(cell) { var component = cell.getComponent(), params = typeof cell.column.modules.format.params === "function" ? cell.column.modules.format.params(component) : cell.column.modules.format.params; function onRendered(callback) { if (!cell.modules.format) { cell.modules.format = {}; } cell.modules.format.renderedCallback = callback; cell.modules.format.rendered = false; } return cell.column.modules.format.formatter.call(this, component, params, onRendered); } formatExportValue(cell, type) { var formatter = cell.column.modules.format[type], params; if (formatter) { let onRendered = function(callback) { if (!cell.modules.format) { cell.modules.format = {}; } cell.modules.format.renderedCallback = callback; cell.modules.format.rendered = false; }; params = typeof formatter.params === "function" ? formatter.params(cell.getComponent()) : formatter.params; return formatter.formatter.call(this, cell.getComponent(), params, onRendered); } else { return this.formatValue(cell); } } sanitizeHTML(value) { if (value) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", "`": "`", "=": "=" }; return String(value).replace(/[&<>"'`=/]/g, function(s) { return entityMap[s]; }); } else { return value; } } emptyToSpace(value) { return value === null || typeof value === "undefined" || value === "" ? " " : value; } }; __publicField(_Format, "moduleName", "format"); //load defaults __publicField(_Format, "formatters", defaultFormatters); let Format = _Format; class FrozenColumns extends Module { constructor(table) { super(table); this.leftColumns = []; this.rightColumns = []; this.initializationMode = "left"; this.active = false; this.blocked = true; this.registerColumnOption("frozen"); } //reset initial state reset() { this.initializationMode = "left"; this.leftColumns = []; this.rightColumns = []; this.active = false; } initialize() { this.subscribe("cell-layout", this.layoutCell.bind(this)); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("column-width", this.layout.bind(this)); this.subscribe("row-layout-after", this.layoutRow.bind(this)); this.subscribe("table-layout", this.layout.bind(this)); this.subscribe("columns-loading", this.reset.bind(this)); this.subscribe("column-add", this.reinitializeColumns.bind(this)); this.subscribe("column-deleted", this.reinitializeColumns.bind(this)); this.subscribe("column-hide", this.reinitializeColumns.bind(this)); this.subscribe("column-show", this.reinitializeColumns.bind(this)); this.subscribe("columns-loaded", this.reinitializeColumns.bind(this)); this.subscribe("table-redraw", this.layout.bind(this)); this.subscribe("layout-refreshing", this.blockLayout.bind(this)); this.subscribe("layout-refreshed", this.unblockLayout.bind(this)); this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this)); } blockLayout() { this.blocked = true; } unblockLayout() { this.blocked = false; } layoutCell(cell) { this.layoutElement(cell.element, cell.column); } reinitializeColumns() { this.reset(); this.table.columnManager.columnsByIndex.forEach((column) => { this.initializeColumn(column); }); this.layout(); } //initialize specific column initializeColumn(column) { var config = { margin: 0, edge: false }; if (!column.isGroup) { if (this.frozenCheck(column)) { config.position = this.initializationMode; if (this.initializationMode == "left") { this.leftColumns.push(column); } else { this.rightColumns.unshift(column); } this.active = true; column.modules.frozen = config; } else { this.initializationMode = "right"; } } } frozenCheck(column) { if (column.parent.isGroup && column.definition.frozen) { console.warn("Frozen Column Error - Parent column group must be frozen, not individual columns or sub column groups"); } if (column.parent.isGroup) { return this.frozenCheck(column.parent); } else { return column.definition.frozen; } } //layout calculation rows layoutCalcRows() { if (this.table.modExists("columnCalcs")) { if (this.table.modules.columnCalcs.topInitialized && this.table.modules.columnCalcs.topRow) { this.layoutRow(this.table.modules.columnCalcs.topRow); } if (this.table.modules.columnCalcs.botInitialized && this.table.modules.columnCalcs.botRow) { this.layoutRow(this.table.modules.columnCalcs.botRow); } if (this.table.modExists("groupRows")) { this.layoutGroupCalcs(this.table.modules.groupRows.getGroups()); } } } layoutGroupCalcs(groups) { groups.forEach((group) => { if (group.calcs.top) { this.layoutRow(group.calcs.top); } if (group.calcs.bottom) { this.layoutRow(group.calcs.bottom); } if (group.groupList && group.groupList.length) { this.layoutGroupCalcs(group.groupList); } }); } //calculate column positions and layout headers layoutColumnPosition(allCells) { var leftParents = []; var leftMargin = 0; var rightMargin = 0; this.leftColumns.forEach((column, i) => { column.modules.frozen.marginValue = leftMargin; column.modules.frozen.margin = column.modules.frozen.marginValue + "px"; if (column.visible) { leftMargin += column.getWidth(); } if (i == this.leftColumns.length - 1) { column.modules.frozen.edge = true; } else { column.modules.frozen.edge = false; } if (column.parent.isGroup) { var parentEl = this.getColGroupParentElement(column); if (!leftParents.includes(parentEl)) { this.layoutElement(parentEl, column); leftParents.push(parentEl); } parentEl.classList.toggle("tabulator-frozen-left", column.modules.frozen.edge && column.modules.frozen.position === "left"); parentEl.classList.toggle("tabulator-frozen-right", column.modules.frozen.edge && column.modules.frozen.position === "right"); } else { this.layoutElement(column.getElement(), column); } if (allCells) { column.cells.forEach((cell) => { this.layoutElement(cell.getElement(true), column); }); } }); this.rightColumns.forEach((column, i) => { column.modules.frozen.marginValue = rightMargin; column.modules.frozen.margin = column.modules.frozen.marginValue + "px"; if (column.visible) { rightMargin += column.getWidth(); } if (i == this.rightColumns.length - 1) { column.modules.frozen.edge = true; } else { column.modules.frozen.edge = false; } if (column.parent.isGroup) { this.layoutElement(this.getColGroupParentElement(column), column); } else { this.layoutElement(column.getElement(), column); } if (allCells) { column.cells.forEach((cell) => { this.layoutElement(cell.getElement(true), column); }); } }); } getColGroupParentElement(column) { return column.parent.isGroup ? this.getColGroupParentElement(column.parent) : column.getElement(); } //layout columns appropriately layout() { if (this.active && !this.blocked) { this.layoutColumnPosition(); this.reinitializeRows(); this.layoutCalcRows(); } } reinitializeRows() { var visibleRows = this.table.rowManager.getVisibleRows(true); var otherRows = this.table.rowManager.getRows().filter((row) => !visibleRows.includes(row)); otherRows.forEach((row) => { row.deinitialize(); }); visibleRows.forEach((row) => { if (row.type === "row") { this.layoutRow(row); } }); } layoutRow(row) { if (this.table.options.layout === "fitDataFill" && this.rightColumns.length) { this.table.rowManager.getTableElement().style.minWidth = "calc(100% - " + this.rightMargin + ")"; } this.leftColumns.forEach((column) => { var cell = row.getCell(column); if (cell) { this.layoutElement(cell.getElement(true), column); } }); this.rightColumns.forEach((column) => { var cell = row.getCell(column); if (cell) { this.layoutElement(cell.getElement(true), column); } }); } layoutElement(element, column) { var position; if (column.modules.frozen && element) { element.style.position = "sticky"; if (this.table.rtl) { position = column.modules.frozen.position === "left" ? "right" : "left"; } else { position = column.modules.frozen.position; } element.style[position] = column.modules.frozen.margin; element.classList.add("tabulator-frozen"); element.classList.toggle("tabulator-frozen-left", column.modules.frozen.edge && column.modules.frozen.position === "left"); element.classList.toggle("tabulator-frozen-right", column.modules.frozen.edge && column.modules.frozen.position === "right"); } } adjustForScrollbar(width) { if (this.rightColumns.length) { this.table.columnManager.getContentsElement().style.width = "calc(100% - " + width + "px)"; } } getFrozenColumns() { return this.leftColumns.concat(this.rightColumns); } _calcSpace(columns, index) { var width = 0; for (let i = 0; i < index; i++) { if (columns[i].visible) { width += columns[i].getWidth(); } } return width; } } __publicField(FrozenColumns, "moduleName", "frozenColumns"); class FrozenRows extends Module { constructor(table) { super(table); this.topElement = document.createElement("div"); this.rows = []; this.registerComponentFunction("row", "freeze", this.freezeRow.bind(this)); this.registerComponentFunction("row", "unfreeze", this.unfreezeRow.bind(this)); this.registerComponentFunction("row", "isFrozen", this.isRowFrozen.bind(this)); this.registerTableOption("frozenRowsField", "id"); this.registerTableOption("frozenRows", false); } initialize() { var fragment = document.createDocumentFragment(); this.rows = []; this.topElement.classList.add("tabulator-frozen-rows-holder"); fragment.appendChild(this.topElement); this.table.columnManager.getContentsElement().insertBefore(fragment, this.table.columnManager.headersElement.nextSibling); this.subscribe("row-deleting", this.detachRow.bind(this)); this.subscribe("rows-visible", this.visibleRows.bind(this)); this.registerDisplayHandler(this.getRows.bind(this), 10); if (this.table.options.frozenRows) { this.subscribe("data-processed", this.initializeRows.bind(this)); this.subscribe("row-added", this.initializeRow.bind(this)); this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this)); this.subscribe("column-resized", this.resizeHolderWidth.bind(this)); this.subscribe("column-show", this.resizeHolderWidth.bind(this)); this.subscribe("column-hide", this.resizeHolderWidth.bind(this)); } this.resizeHolderWidth(); } resizeHolderWidth() { this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px"; } initializeRows() { this.table.rowManager.getRows().forEach((row) => { this.initializeRow(row); }); } initializeRow(row) { var frozenRows = this.table.options.frozenRows, rowType = typeof frozenRows; if (rowType === "number") { if (row.getPosition() && row.getPosition() + this.rows.length <= frozenRows) { this.freezeRow(row); } } else if (rowType === "function") { if (frozenRows.call(this.table, row.getComponent())) { this.freezeRow(row); } } else if (Array.isArray(frozenRows)) { if (frozenRows.includes(row.data[this.options("frozenRowsField")])) { this.freezeRow(row); } } } isRowFrozen(row) { var index = this.rows.indexOf(row); return index > -1; } isFrozen() { return !!this.rows.length; } visibleRows(viewable, rows2) { this.rows.forEach((row) => { rows2.push(row); }); return rows2; } //filter frozen rows out of display data getRows(rows2) { var output = rows2.slice(0); this.rows.forEach(function(row) { var index = output.indexOf(row); if (index > -1) { output.splice(index, 1); } }); return output; } freezeRow(row) { if (!row.modules.frozen) { row.modules.frozen = true; this.topElement.appendChild(row.getElement()); row.initialize(); row.normalizeHeight(); this.rows.push(row); this.refreshData(false, "display"); this.table.rowManager.adjustTableSize(); this.styleRows(); } else { console.warn("Freeze Error - Row is already frozen"); } } unfreezeRow(row) { if (row.modules.frozen) { row.modules.frozen = false; this.detachRow(row); this.table.rowManager.adjustTableSize(); this.refreshData(false, "display"); if (this.rows.length) { this.styleRows(); } } else { console.warn("Freeze Error - Row is already unfrozen"); } } detachRow(row) { var index = this.rows.indexOf(row); if (index > -1) { var rowEl = row.getElement(); if (rowEl.parentNode) { rowEl.parentNode.removeChild(rowEl); } this.rows.splice(index, 1); } } styleRows(row) { this.rows.forEach((row2, i) => { this.table.rowManager.styleRow(row2, i); }); } } __publicField(FrozenRows, "moduleName", "frozenRows"); class GroupComponent { constructor(group) { this._group = group; this.type = "GroupComponent"; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._group.groupManager.table.componentFunctionBinder.handle("group", target._group, name2); } } }); } getKey() { return this._group.key; } getField() { return this._group.field; } getElement() { return this._group.element; } getRows() { return this._group.getRows(true); } getSubGroups() { return this._group.getSubGroups(true); } getParentGroup() { return this._group.parent ? this._group.parent.getComponent() : false; } isVisible() { return this._group.visible; } show() { this._group.show(); } hide() { this._group.hide(); } toggle() { this._group.toggleVisibility(); } scrollTo(position, ifVisible) { return this._group.groupManager.table.rowManager.scrollToRow(this._group, position, ifVisible); } _getSelf() { return this._group; } getTable() { return this._group.groupManager.table; } } class Group { constructor(groupManager, parent2, level, key, field, generator, oldGroup) { this.groupManager = groupManager; this.parent = parent2; this.key = key; this.level = level; this.field = field; this.hasSubGroups = level < groupManager.groupIDLookups.length - 1; this.addRow = this.hasSubGroups ? this._addRowToGroup : this._addRow; this.type = "group"; this.old = oldGroup; this.rows = []; this.groups = []; this.groupList = []; this.generator = generator; this.element = false; this.elementContents = false; this.height = 0; this.outerHeight = 0; this.initialized = false; this.calcs = {}; this.initialized = false; this.modules = {}; this.arrowElement = false; this.visible = oldGroup ? oldGroup.visible : typeof groupManager.startOpen[level] !== "undefined" ? groupManager.startOpen[level] : groupManager.startOpen[0]; this.component = null; this.createElements(); this.addBindings(); this.createValueGroups(); } wipe(elementsOnly) { if (!elementsOnly) { if (this.groupList.length) { this.groupList.forEach(function(group) { group.wipe(); }); } else { this.rows.forEach((row) => { if (row.modules) { delete row.modules.group; } }); } } this.element = false; this.arrowElement = false; this.elementContents = false; } createElements() { var arrow = document.createElement("div"); arrow.classList.add("tabulator-arrow"); this.element = document.createElement("div"); this.element.classList.add("tabulator-row"); this.element.classList.add("tabulator-group"); this.element.classList.add("tabulator-group-level-" + this.level); this.element.setAttribute("role", "rowgroup"); this.arrowElement = document.createElement("div"); this.arrowElement.classList.add("tabulator-group-toggle"); this.arrowElement.appendChild(arrow); if (this.groupManager.table.options.movableRows !== false && this.groupManager.table.modExists("moveRow")) { this.groupManager.table.modules.moveRow.initializeGroupHeader(this); } } createValueGroups() { var level = this.level + 1; if (this.groupManager.allowedValues && this.groupManager.allowedValues[level]) { this.groupManager.allowedValues[level].forEach((value) => { this._createGroup(value, level); }); } } addBindings() { var toggleElement; if (this.groupManager.table.options.groupToggleElement) { toggleElement = this.groupManager.table.options.groupToggleElement == "arrow" ? this.arrowElement : this.element; toggleElement.addEventListener("click", (e) => { if (this.groupManager.table.options.groupToggleElement === "arrow") { e.stopPropagation(); e.stopImmediatePropagation(); } setTimeout(() => { this.toggleVisibility(); }); }); } } _createGroup(groupID, level) { var groupKey = level + "_" + groupID; var group = new Group(this.groupManager, this, level, groupID, this.groupManager.groupIDLookups[level].field, this.groupManager.headerGenerator[level] || this.groupManager.headerGenerator[0], this.old ? this.old.groups[groupKey] : false); this.groups[groupKey] = group; this.groupList.push(group); } _addRowToGroup(row) { var level = this.level + 1; if (this.hasSubGroups) { var groupID = this.groupManager.groupIDLookups[level].func(row.getData()), groupKey = level + "_" + groupID; if (this.groupManager.allowedValues && this.groupManager.allowedValues[level]) { if (this.groups[groupKey]) { this.groups[groupKey].addRow(row); } } else { if (!this.groups[groupKey]) { this._createGroup(groupID, level); } this.groups[groupKey].addRow(row); } } } _addRow(row) { this.rows.push(row); row.modules.group = this; } insertRow(row, to, after) { var data = this.conformRowData({}); row.updateData(data); var toIndex = this.rows.indexOf(to); if (toIndex > -1) { if (after) { this.rows.splice(toIndex + 1, 0, row); } else { this.rows.splice(toIndex, 0, row); } } else { if (after) { this.rows.push(row); } else { this.rows.unshift(row); } } row.modules.group = this; if (this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table") { this.groupManager.table.modules.columnCalcs.recalcGroup(this); } this.groupManager.updateGroupRows(true); } scrollHeader(left) { if (this.arrowElement) { this.arrowElement.style.marginLeft = left; this.groupList.forEach(function(child) { child.scrollHeader(left); }); } } getRowIndex(row) { } //update row data to match grouping constraints conformRowData(data) { if (this.field) { data[this.field] = this.key; } else { console.warn("Data Conforming Error - Cannot conform row data to match new group as groupBy is a function"); } if (this.parent) { data = this.parent.conformRowData(data); } return data; } removeRow(row) { var index = this.rows.indexOf(row); var el = row.getElement(); if (index > -1) { this.rows.splice(index, 1); } if (!this.groupManager.table.options.groupValues && !this.rows.length) { if (this.parent) { this.parent.removeGroup(this); } else { this.groupManager.removeGroup(this); } this.groupManager.updateGroupRows(true); } else { if (el.parentNode) { el.parentNode.removeChild(el); } if (!this.groupManager.blockRedraw) { this.generateGroupHeaderContents(); if (this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table") { this.groupManager.table.modules.columnCalcs.recalcGroup(this); } } } } removeGroup(group) { var groupKey = group.level + "_" + group.key, index; if (this.groups[groupKey]) { delete this.groups[groupKey]; index = this.groupList.indexOf(group); if (index > -1) { this.groupList.splice(index, 1); } if (!this.groupList.length) { if (this.parent) { this.parent.removeGroup(this); } else { this.groupManager.removeGroup(this); } } } } getHeadersAndRows() { var output = []; output.push(this); this._visSet(); if (this.calcs.top) { this.calcs.top.detachElement(); this.calcs.top.deleteCells(); } if (this.calcs.bottom) { this.calcs.bottom.detachElement(); this.calcs.bottom.deleteCells(); } if (this.visible) { if (this.groupList.length) { this.groupList.forEach(function(group) { output = output.concat(group.getHeadersAndRows()); }); } else { if (this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasTopCalcs()) { this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows); output.push(this.calcs.top); } output = output.concat(this.rows); if (this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasBottomCalcs()) { this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows); output.push(this.calcs.bottom); } } } else { if (!this.groupList.length && this.groupManager.table.options.columnCalcs != "table") { if (this.groupManager.table.modExists("columnCalcs")) { if (this.groupManager.table.modules.columnCalcs.hasTopCalcs()) { if (this.groupManager.table.options.groupClosedShowCalcs) { this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows); output.push(this.calcs.top); } } if (this.groupManager.table.modules.columnCalcs.hasBottomCalcs()) { if (this.groupManager.table.options.groupClosedShowCalcs) { this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows); output.push(this.calcs.bottom); } } } } } return output; } getData(visible, transform) { var output = []; this._visSet(); if (!visible || visible && this.visible) { this.rows.forEach((row) => { output.push(row.getData(transform || "data")); }); } return output; } getRowCount() { var count = 0; if (this.groupList.length) { this.groupList.forEach((group) => { count += group.getRowCount(); }); } else { count = this.rows.length; } return count; } toggleVisibility() { if (this.visible) { this.hide(); } else { this.show(); } } hide() { this.visible = false; if (this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination) { this.element.classList.remove("tabulator-group-visible"); if (this.groupList.length) { this.groupList.forEach((group) => { var rows2 = group.getHeadersAndRows(); rows2.forEach((row) => { row.detachElement(); }); }); } else { this.rows.forEach((row) => { var rowEl = row.getElement(); rowEl.parentNode.removeChild(rowEl); }); } this.groupManager.updateGroupRows(true); } else { this.groupManager.updateGroupRows(true); } this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), false); } show() { this.visible = true; if (this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination) { this.element.classList.add("tabulator-group-visible"); var prev = this.generateElement(); if (this.groupList.length) { this.groupList.forEach((group) => { var rows2 = group.getHeadersAndRows(); rows2.forEach((row) => { var rowEl = row.getElement(); prev.parentNode.insertBefore(rowEl, prev.nextSibling); row.initialize(); prev = rowEl; }); }); } else { this.rows.forEach((row) => { var rowEl = row.getElement(); prev.parentNode.insertBefore(rowEl, prev.nextSibling); row.initialize(); prev = rowEl; }); } this.groupManager.updateGroupRows(true); } else { this.groupManager.updateGroupRows(true); } this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), true); } _visSet() { var data = []; if (typeof this.visible == "function") { this.rows.forEach(function(row) { data.push(row.getData()); }); this.visible = this.visible(this.key, this.getRowCount(), data, this.getComponent()); } } getRowGroup(row) { var match = false; if (this.groupList.length) { this.groupList.forEach(function(group) { var result = group.getRowGroup(row); if (result) { match = result; } }); } else { if (this.rows.find(function(item) { return item === row; })) { match = this; } } return match; } getSubGroups(component) { var output = []; this.groupList.forEach(function(child) { output.push(component ? child.getComponent() : child); }); return output; } getRows(component, includeChildren) { var output = []; if (includeChildren && this.groupList.length) { this.groupList.forEach((group) => { output = output.concat(group.getRows(component, includeChildren)); }); } else { this.rows.forEach(function(row) { output.push(component ? row.getComponent() : row); }); } return output; } generateGroupHeaderContents() { var data = []; var rows2 = this.getRows(false, true); rows2.forEach(function(row) { data.push(row.getData()); }); this.elementContents = this.generator(this.key, this.getRowCount(), data, this.getComponent()); while (this.element.firstChild) this.element.removeChild(this.element.firstChild); if (typeof this.elementContents === "string") { this.element.innerHTML = this.elementContents; } else { this.element.appendChild(this.elementContents); } this.element.insertBefore(this.arrowElement, this.element.firstChild); } getPath(path = []) { path.unshift(this.key); if (this.parent) { this.parent.getPath(path); } return path; } ////////////// Standard Row Functions ////////////// getElement() { return this.elementContents ? this.element : this.generateElement(); } generateElement() { this.addBindings = false; this._visSet(); if (this.visible) { this.element.classList.add("tabulator-group-visible"); } else { this.element.classList.remove("tabulator-group-visible"); } for (var i = 0; i < this.element.childNodes.length; ++i) { this.element.childNodes[i].parentNode.removeChild(this.element.childNodes[i]); } this.generateGroupHeaderContents(); return this.element; } detachElement() { if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } } //normalize the height of elements in the row normalizeHeight() { this.setHeight(this.element.clientHeight); } initialize(force) { if (!this.initialized || force) { this.normalizeHeight(); this.initialized = true; } } reinitialize() { this.initialized = false; this.height = 0; if (Helpers.elVisible(this.element)) { this.initialize(true); } } setHeight(height) { if (this.height != height) { this.height = height; this.outerHeight = this.element.offsetHeight; } } //return rows outer height getHeight() { return this.outerHeight; } getGroup() { return this; } reinitializeHeight() { } calcHeight() { this.outerHeight = this.element.offsetHeight; } setCellHeight() { } clearCellHeight() { } deinitializeHeight() { } rendered() { } //////////////// Object Generation ///////////////// getComponent() { if (!this.component) { this.component = new GroupComponent(this); } return this.component; } } class GroupRows extends Module { constructor(table) { super(table); this.groupIDLookups = false; this.startOpen = [function() { return false; }]; this.headerGenerator = [function() { return ""; }]; this.groupList = []; this.allowedValues = false; this.groups = {}; this.displayHandler = this.getRows.bind(this); this.blockRedraw = false; this.registerTableOption("groupBy", false); this.registerTableOption("groupStartOpen", true); this.registerTableOption("groupValues", false); this.registerTableOption("groupUpdateOnCellEdit", false); this.registerTableOption("groupHeader", false); this.registerTableOption("groupHeaderPrint", null); this.registerTableOption("groupHeaderClipboard", null); this.registerTableOption("groupHeaderHtmlOutput", null); this.registerTableOption("groupHeaderDownload", null); this.registerTableOption("groupToggleElement", "arrow"); this.registerTableOption("groupClosedShowCalcs", false); this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this)); this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this)); this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this)); this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this)); this.registerTableFunction("getGroups", this.userGetGroups.bind(this)); this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this)); this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this)); } //initialize group configuration initialize() { this.subscribe("table-destroy", this._blockRedrawing.bind(this)); this.subscribe("rows-wipe", this._blockRedrawing.bind(this)); this.subscribe("rows-wiped", this._restore_redrawing.bind(this)); if (this.table.options.groupBy) { if (this.table.options.groupUpdateOnCellEdit) { this.subscribe("cell-value-updated", this.cellUpdated.bind(this)); this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0); } this.subscribe("table-built", this.configureGroupSetup.bind(this)); this.subscribe("row-deleting", this.rowDeleting.bind(this)); this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this)); this.subscribe("rows-wipe", this.wipe.bind(this)); this.subscribe("rows-added", this.rowsUpdated.bind(this)); this.subscribe("row-moving", this.rowMoving.bind(this)); this.subscribe("row-adding-index", this.rowAddingIndex.bind(this)); this.subscribe("rows-sample", this.rowSample.bind(this)); this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this)); this.subscribe("table-layout", this.virtualRenderFill.bind(this)); this.registerDisplayHandler(this.displayHandler, 20); this.initialized = true; } } _blockRedrawing() { this.blockRedraw = true; } _restore_redrawing() { this.blockRedraw = false; } configureGroupSetup() { if (this.table.options.groupBy) { var groupBy = this.table.options.groupBy, startOpen = this.table.options.groupStartOpen, groupHeader = this.table.options.groupHeader; this.allowedValues = this.table.options.groupValues; if (Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length) { console.warn("Error creating group headers, groupHeader array is shorter than groupBy array"); } this.headerGenerator = [function() { return ""; }]; this.startOpen = [function() { return false; }]; this.langBind("groups|item", (langValue, lang) => { this.headerGenerator[0] = (value, count, data) => { return (typeof value === "undefined" ? "" : value) + "<span>(" + count + " " + (count === 1 ? langValue : lang.groups.items) + ")</span>"; }; }); this.groupIDLookups = []; if (groupBy) { if (this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both") { this.table.modules.columnCalcs.removeCalcs(); } } else { if (this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group") { var cols = this.table.columnManager.getRealColumns(); cols.forEach((col) => { if (col.definition.topCalc) { this.table.modules.columnCalcs.initializeTopRow(); } if (col.definition.bottomCalc) { this.table.modules.columnCalcs.initializeBottomRow(); } }); } } if (!Array.isArray(groupBy)) { groupBy = [groupBy]; } groupBy.forEach((group, i) => { var lookupFunc, column; if (typeof group == "function") { lookupFunc = group; } else { column = this.table.columnManager.getColumnByField(group); if (column) { lookupFunc = function(data) { return column.getFieldValue(data); }; } else { lookupFunc = function(data) { return data[group]; }; } } this.groupIDLookups.push({ field: typeof group === "function" ? false : group, func: lookupFunc, values: this.allowedValues ? this.allowedValues[i] : false }); }); if (startOpen) { if (!Array.isArray(startOpen)) { startOpen = [startOpen]; } startOpen.forEach((level) => { }); this.startOpen = startOpen; } if (groupHeader) { this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader]; } } else { this.groupList = []; this.groups = {}; } } rowSample(rows2, prevValue) { if (this.table.options.groupBy) { var group = this.getGroups(false)[0]; prevValue.push(group.getRows(false)[0]); } return prevValue; } virtualRenderFill() { const layout = this.layoutMode(); if (layout === "fitDataFill" || layout === "fitDataStretch" || layout === "fitColumns") { this.table.rowManager.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px"; } } rowAddingIndex(row, index, top) { if (this.table.options.groupBy) { this.assignRowToGroup(row); var groupRows = row.modules.group.rows; if (groupRows.length > 1) { if (!index || index && groupRows.indexOf(index) == -1) { if (top) { if (groupRows[0] !== row) { index = groupRows[0]; this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } } else { if (groupRows[groupRows.length - 1] !== row) { index = groupRows[groupRows.length - 1]; this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } } } else { this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } } return index; } } trackChanges() { this.dispatch("group-changed"); } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// setGroupBy(groups) { this.table.options.groupBy = groups; if (!this.initialized) { this.initialize(); } this.configureGroupSetup(); if (!groups && this.table.modExists("columnCalcs") && this.table.options.columnCalcs === true) { this.table.modules.columnCalcs.reinitializeCalcs(); } this.refreshData(); this.trackChanges(); } setGroupValues(groupValues) { this.table.options.groupValues = groupValues; this.configureGroupSetup(); this.refreshData(); this.trackChanges(); } setGroupStartOpen(values) { this.table.options.groupStartOpen = values; this.configureGroupSetup(); if (this.table.options.groupBy) { this.refreshData(); this.trackChanges(); } else { console.warn("Grouping Update - cant refresh view, no groups have been set"); } } setGroupHeader(values) { this.table.options.groupHeader = values; this.configureGroupSetup(); if (this.table.options.groupBy) { this.refreshData(); this.trackChanges(); } else { console.warn("Grouping Update - cant refresh view, no groups have been set"); } } userGetGroups(values) { return this.getGroups(true); } // get grouped table data in the same format as getData() userGetGroupedData() { return this.table.options.groupBy ? this.getGroupedData() : this.getData(); } /////////////////////////////////////// ///////// Component Functions ///////// /////////////////////////////////////// rowGetGroup(row) { return row.modules.group ? row.modules.group.getComponent() : false; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// rowMoving(from, to, after) { if (this.table.options.groupBy) { if (!after && to instanceof Group) { to = this.table.rowManager.prevDisplayRow(from) || to; } var toGroup = to instanceof Group ? to : to.modules.group; var fromGroup = from instanceof Group ? from : from.modules.group; if (toGroup === fromGroup) { this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after); } else { if (fromGroup) { fromGroup.removeRow(from); } toGroup.insertRow(from, to, after); } } } rowDeleting(row) { if (this.table.options.groupBy && row.modules.group) { row.modules.group.removeRow(row); } } rowsUpdated(row) { if (this.table.options.groupBy) { this.updateGroupRows(true); } } cellUpdated(cell) { if (this.table.options.groupBy) { this.reassignRowToGroup(cell.row); } } //return appropriate rows with group headers getRows(rows2) { if (this.table.options.groupBy && this.groupIDLookups.length) { this.dispatchExternal("dataGrouping"); this.generateGroups(rows2); if (this.subscribedExternal("dataGrouped")) { this.dispatchExternal("dataGrouped", this.getGroups(true)); } return this.updateGroupRows(); } else { return rows2.slice(0); } } getGroups(component) { var groupComponents = []; this.groupList.forEach(function(group) { groupComponents.push(component ? group.getComponent() : group); }); return groupComponents; } getChildGroups(group) { var groupComponents = []; if (!group) { group = this; } group.groupList.forEach((child) => { if (child.groupList.length) { groupComponents = groupComponents.concat(this.getChildGroups(child)); } else { groupComponents.push(child); } }); return groupComponents; } wipe() { if (this.table.options.groupBy) { this.groupList.forEach(function(group) { group.wipe(); }); this.groupList = []; this.groups = {}; } } pullGroupListData(groupList) { var groupListData = []; groupList.forEach((group) => { var groupHeader = {}; groupHeader.level = 0; groupHeader.rowCount = 0; groupHeader.headerContent = ""; var childData = []; if (group.hasSubGroups) { childData = this.pullGroupListData(group.groupList); groupHeader.level = group.level; groupHeader.rowCount = childData.length - group.groupList.length; groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group); groupListData.push(groupHeader); groupListData = groupListData.concat(childData); } else { groupHeader.level = group.level; groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group); groupHeader.rowCount = group.getRows().length; groupListData.push(groupHeader); group.getRows().forEach((row) => { groupListData.push(row.getData("data")); }); } }); return groupListData; } getGroupedData() { return this.pullGroupListData(this.groupList); } getRowGroup(row) { var match = false; if (this.options("dataTree")) { row = this.table.modules.dataTree.getTreeParentRoot(row); } this.groupList.forEach((group) => { var result = group.getRowGroup(row); if (result) { match = result; } }); return match; } countGroups() { return this.groupList.length; } generateGroups(rows2) { var oldGroups = this.groups; this.groups = {}; this.groupList = []; if (this.allowedValues && this.allowedValues[0]) { this.allowedValues[0].forEach((value) => { this.createGroup(value, 0, oldGroups); }); rows2.forEach((row) => { this.assignRowToExistingGroup(row, oldGroups); }); } else { rows2.forEach((row) => { this.assignRowToGroup(row, oldGroups); }); } Object.values(oldGroups).forEach((group) => { group.wipe(true); }); } createGroup(groupID, level, oldGroups) { var groupKey = level + "_" + groupID, group; oldGroups = oldGroups || []; group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]); this.groups[groupKey] = group; this.groupList.push(group); } assignRowToExistingGroup(row, oldGroups) { var groupID = this.groupIDLookups[0].func(row.getData()), groupKey = "0_" + groupID; if (this.groups[groupKey]) { this.groups[groupKey].addRow(row); } } assignRowToGroup(row, oldGroups) { var groupID = this.groupIDLookups[0].func(row.getData()), newGroupNeeded = !this.groups["0_" + groupID]; if (newGroupNeeded) { this.createGroup(groupID, 0, oldGroups); } this.groups["0_" + groupID].addRow(row); return !newGroupNeeded; } reassignRowToGroup(row) { if (row.type === "row") { var oldRowGroup = row.modules.group, oldGroupPath = oldRowGroup.getPath(), newGroupPath = this.getExpectedPath(row), samePath; samePath = oldGroupPath.length == newGroupPath.length && oldGroupPath.every((element, index) => { return element === newGroupPath[index]; }); if (!samePath) { oldRowGroup.removeRow(row); this.assignRowToGroup(row, this.groups); this.refreshData(true); } } } getExpectedPath(row) { var groupPath = [], rowData = row.getData(); this.groupIDLookups.forEach((groupId) => { groupPath.push(groupId.func(rowData)); }); return groupPath; } updateGroupRows(force) { var output = []; if (!this.blockRedraw) { this.groupList.forEach((group) => { output = output.concat(group.getHeadersAndRows()); }); if (force) { this.refreshData(true); } } return output; } scrollHeaders(left) { if (this.table.options.groupBy) { if (this.table.options.renderHorizontal === "virtual") { left -= this.table.columnManager.renderer.vDomPadLeft; } left = left + "px"; this.groupList.forEach((group) => { group.scrollHeader(left); }); } } removeGroup(group) { var groupKey = group.level + "_" + group.key, index; if (this.groups[groupKey]) { delete this.groups[groupKey]; index = this.groupList.indexOf(group); if (index > -1) { this.groupList.splice(index, 1); } } } checkBasicModeGroupHeaderWidth() { var element = this.table.rowManager.tableElement, onlyGroupHeaders = true; this.table.rowManager.getDisplayRows().forEach((row, index) => { this.table.rowManager.styleRow(row, index); element.appendChild(row.getElement()); row.initialize(true); if (row.type !== "group") { onlyGroupHeaders = false; } }); if (onlyGroupHeaders) { element.style.minWidth = this.table.columnManager.getWidth() + "px"; } else { element.style.minWidth = ""; } } } __publicField(GroupRows, "moduleName", "groupRows"); var defaultUndoers = { cellEdit: function(action) { action.component.setValueProcessData(action.data.oldValue); action.component.cellRendered(); }, rowAdd: function(action) { action.component.deleteActual(); this.table.rowManager.checkPlaceholder(); }, rowDelete: function(action) { var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); if (this.table.options.groupBy && this.table.modExists("groupRows")) { this.table.modules.groupRows.updateGroupRows(true); } this._rebindRow(action.component, newRow); this.table.rowManager.checkPlaceholder(); }, rowMove: function(action) { var after = action.data.posFrom - action.data.posTo > 0; this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posFrom), after); this.table.rowManager.regenerateRowPositions(); this.table.rowManager.reRenderInPosition(); } }; var defaultRedoers = { cellEdit: function(action) { action.component.setValueProcessData(action.data.newValue); action.component.cellRendered(); }, rowAdd: function(action) { var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); if (this.table.options.groupBy && this.table.modExists("groupRows")) { this.table.modules.groupRows.updateGroupRows(true); } this._rebindRow(action.component, newRow); this.table.rowManager.checkPlaceholder(); }, rowDelete: function(action) { action.component.deleteActual(); this.table.rowManager.checkPlaceholder(); }, rowMove: function(action) { this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posTo), action.data.after); this.table.rowManager.regenerateRowPositions(); this.table.rowManager.reRenderInPosition(); } }; var bindings$1 = { undo: ["ctrl + 90", "meta + 90"], redo: ["ctrl + 89", "meta + 89"] }; var actions$1 = { undo: function(e) { var cell = false; if (this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")) { cell = this.table.modules.edit.currentCell; if (!cell) { e.preventDefault(); this.table.modules.history.undo(); } } }, redo: function(e) { var cell = false; if (this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")) { cell = this.table.modules.edit.currentCell; if (!cell) { e.preventDefault(); this.table.modules.history.redo(); } } } }; var extensions$3 = { keybindings: { bindings: bindings$1, actions: actions$1 } }; const _History = class _History extends Module { constructor(table) { super(table); this.history = []; this.index = -1; this.registerTableOption("history", false); } initialize() { if (this.table.options.history) { this.subscribe("cell-value-updated", this.cellUpdated.bind(this)); this.subscribe("cell-delete", this.clearComponentHistory.bind(this)); this.subscribe("row-delete", this.rowDeleted.bind(this)); this.subscribe("rows-wipe", this.clear.bind(this)); this.subscribe("row-added", this.rowAdded.bind(this)); this.subscribe("row-move", this.rowMoved.bind(this)); } this.registerTableFunction("undo", this.undo.bind(this)); this.registerTableFunction("redo", this.redo.bind(this)); this.registerTableFunction("getHistoryUndoSize", this.getHistoryUndoSize.bind(this)); this.registerTableFunction("getHistoryRedoSize", this.getHistoryRedoSize.bind(this)); this.registerTableFunction("clearHistory", this.clear.bind(this)); } rowMoved(from, to, after) { this.action("rowMove", from, { posFrom: from.getPosition(), posTo: to.getPosition(), to, after }); } rowAdded(row, data, pos, index) { this.action("rowAdd", row, { data, pos, index }); } rowDeleted(row) { var index, rows2; if (this.table.options.groupBy) { rows2 = row.getComponent().getGroup()._getSelf().rows; index = rows2.indexOf(row); if (index) { index = rows2[index - 1]; } } else { index = row.table.rowManager.getRowIndex(row); if (index) { index = row.table.rowManager.rows[index - 1]; } } this.action("rowDelete", row, { data: row.getData(), pos: !index, index }); } cellUpdated(cell) { this.action("cellEdit", cell, { oldValue: cell.oldValue, newValue: cell.value }); } clear() { this.history = []; this.index = -1; } action(type, component, data) { this.history = this.history.slice(0, this.index + 1); this.history.push({ type, component, data }); this.index++; } getHistoryUndoSize() { return this.index + 1; } getHistoryRedoSize() { return this.history.length - (this.index + 1); } clearComponentHistory(component) { var index = this.history.findIndex(function(item) { return item.component === component; }); if (index > -1) { this.history.splice(index, 1); if (index <= this.index) { this.index--; } this.clearComponentHistory(component); } } undo() { if (this.index > -1) { let action = this.history[this.index]; _History.undoers[action.type].call(this, action); this.index--; this.dispatchExternal("historyUndo", action.type, action.component.getComponent(), action.data); return true; } else { console.warn(this.options("history") ? "History Undo Error - No more history to undo" : "History module not enabled"); return false; } } redo() { if (this.history.length - 1 > this.index) { this.index++; let action = this.history[this.index]; _History.redoers[action.type].call(this, action); this.dispatchExternal("historyRedo", action.type, action.component.getComponent(), action.data); return true; } else { console.warn(this.options("history") ? "History Redo Error - No more history to redo" : "History module not enabled"); return false; } } //rebind rows to new element after deletion _rebindRow(oldRow, newRow) { this.history.forEach(function(action) { if (action.component instanceof Row) { if (action.component === oldRow) { action.component = newRow; } } else if (action.component instanceof Cell) { if (action.component.row === oldRow) { var field = action.component.column.getField(); if (field) { action.component = newRow.getCell(field); } } } }); } }; __publicField(_History, "moduleName", "history"); __publicField(_History, "moduleExtensions", extensions$3); //load defaults __publicField(_History, "undoers", defaultUndoers); __publicField(_History, "redoers", defaultRedoers); let History = _History; class HtmlTableImport extends Module { constructor(table) { super(table); this.fieldIndex = []; this.hasIndex = false; } initialize() { this.tableElementCheck(); } tableElementCheck() { if (this.table.originalElement && this.table.originalElement.tagName === "TABLE") { if (this.table.originalElement.childNodes.length) { this.parseTable(); } else { console.warn("Unable to parse data from empty table tag, Tabulator should be initialized on a div tag unless importing data from a table element."); } } } parseTable() { var element = this.table.originalElement, options = this.table.options, headers = element.getElementsByTagName("th"), rows2 = element.getElementsByTagName("tbody")[0], data = []; this.hasIndex = false; this.dispatchExternal("htmlImporting"); rows2 = rows2 ? rows2.getElementsByTagName("tr") : []; this._extractOptions(element, options); if (headers.length) { this._extractHeaders(headers, rows2); } else { this._generateBlankHeaders(headers, rows2); } for (var index = 0; index < rows2.length; index++) { var row = rows2[index], cells = row.getElementsByTagName("td"), item = {}; if (!this.hasIndex) { item[options.index] = index; } for (var i = 0; i < cells.length; i++) { var cell = cells[i]; if (typeof this.fieldIndex[i] !== "undefined") { item[this.fieldIndex[i]] = cell.innerHTML; } } data.push(item); } options.data = data; this.dispatchExternal("htmlImported"); } //extract tabulator attribute options _extractOptions(element, options, defaultOptions2) { var attributes = element.attributes; var optionsArr = defaultOptions2 ? Object.keys(defaultOptions2) : Object.keys(options); var optionsList = {}; optionsArr.forEach((item) => { optionsList[item.toLowerCase()] = item; }); for (var index in attributes) { var attrib = attributes[index]; var name2; if (attrib && typeof attrib == "object" && attrib.name && attrib.name.indexOf("tabulator-") === 0) { name2 = attrib.name.replace("tabulator-", ""); if (typeof optionsList[name2] !== "undefined") { options[optionsList[name2]] = this._attribValue(attrib.value); } } } } //get value of attribute _attribValue(value) { if (value === "true") { return true; } if (value === "false") { return false; } return value; } //find column if it has already been defined _findCol(title) { var match = this.table.options.columns.find((column) => { return column.title === title; }); return match || false; } //extract column from headers _extractHeaders(headers, rows2) { for (var index = 0; index < headers.length; index++) { var header = headers[index], exists2 = false, col = this._findCol(header.textContent), width; if (col) { exists2 = true; } else { col = { title: header.textContent.trim() }; } if (!col.field) { col.field = header.textContent.trim().toLowerCase().replaceAll(" ", "_"); } width = header.getAttribute("width"); if (width && !col.width) { col.width = width; } this._extractOptions(header, col, this.table.columnManager.optionsList.registeredDefaults); this.fieldIndex[index] = col.field; if (col.field == this.table.options.index) { this.hasIndex = true; } if (!exists2) { this.table.options.columns.push(col); } } } //generate blank headers _generateBlankHeaders(headers, rows2) { for (var index = 0; index < headers.length; index++) { var header = headers[index], col = { title: "", field: "col" + index }; this.fieldIndex[index] = col.field; var width = header.getAttribute("width"); if (width) { col.width = width; } this.table.options.columns.push(col); } } } __publicField(HtmlTableImport, "moduleName", "htmlTableImport"); function csv(input2) { var data = [], row = 0, col = 0, inQuote = false; for (let index = 0; index < input2.length; index++) { let char = input2[index], nextChar = input2[index + 1]; if (!data[row]) { data[row] = []; } if (!data[row][col]) { data[row][col] = ""; } if (char == '"' && inQuote && nextChar == '"') { data[row][col] += char; index++; continue; } if (char == '"') { inQuote = !inQuote; continue; } if (char == "," && !inQuote) { col++; continue; } if (char == "\r" && nextChar == "\n" && !inQuote) { col = 0; row++; index++; continue; } if ((char == "\r" || char == "\n") && !inQuote) { col = 0; row++; continue; } data[row][col] += char; } return data; } function json(input2) { try { return JSON.parse(input2); } catch (e) { console.warn("JSON Import Error - File contents is invalid JSON", e); return Promise.reject(); } } function array$1(input2) { return input2; } function xlsx(input2) { var XLSXLib = this.dependencyRegistry.lookup("XLSX"), workbook2 = XLSXLib.read(input2), sheet = workbook2.Sheets[workbook2.SheetNames[0]]; return XLSXLib.utils.sheet_to_json(sheet, { header: 1 }); } var defaultImporters = { csv, json, array: array$1, xlsx }; const _Import = class _Import extends Module { constructor(table) { super(table); this.registerTableOption("importFormat"); this.registerTableOption("importReader", "text"); this.registerTableOption("importHeaderTransform"); this.registerTableOption("importValueTransform"); this.registerTableOption("importDataValidator"); this.registerTableOption("importFileValidator"); } initialize() { this.registerTableFunction("import", this.importFromFile.bind(this)); if (this.table.options.importFormat) { this.subscribe("data-loading", this.loadDataCheck.bind(this), 10); this.subscribe("data-load", this.loadData.bind(this), 10); } } loadDataCheck(data) { return this.table.options.importFormat && (typeof data === "string" || Array.isArray(data) && data.length && Array.isArray(data)); } loadData(data, params, config, silent, previousData) { return this.importData(this.lookupImporter(), data).then(this.structureData.bind(this)).catch((err) => { console.error("Import Error:", err || "Unable to import data"); return Promise.reject(err); }); } lookupImporter(importFormat) { var importer; if (!importFormat) { importFormat = this.table.options.importFormat; } if (typeof importFormat === "string") { importer = _Import.importers[importFormat]; } else { importer = importFormat; } if (!importer) { console.error("Import Error - Importer not found:", importFormat); } return importer; } importFromFile(importFormat, extension, importReader) { var importer = this.lookupImporter(importFormat); if (importer) { return this.pickFile(extension, importReader).then(this.importData.bind(this, importer)).then(this.structureData.bind(this)).then(this.mutateData.bind(this)).then(this.validateData.bind(this)).then(this.setData.bind(this)).catch((err) => { this.dispatch("import-error", err); this.dispatchExternal("importError", err); console.error("Import Error:", err || "Unable to import file"); this.table.dataLoader.alertError(); setTimeout(() => { this.table.dataLoader.clearAlert(); }, 3e3); return Promise.reject(err); }); } } pickFile(extensions2, importReader) { return new Promise((resolve, reject) => { var input2 = document.createElement("input"); input2.type = "file"; input2.accept = extensions2; input2.addEventListener("change", (e) => { var file = input2.files[0], reader = new FileReader(), valid = this.validateFile(file); if (valid === true) { this.dispatch("import-importing", input2.files); this.dispatchExternal("importImporting", input2.files); switch (importReader || this.table.options.importReader) { case "buffer": reader.readAsArrayBuffer(file); break; case "binary": reader.readAsBinaryString(file); break; case "url": reader.readAsDataURL(file); break; case "text": default: reader.readAsText(file); } reader.onload = (e2) => { resolve(reader.result); }; reader.onerror = (e2) => { console.warn("File Load Error - Unable to read file"); reject(e2); }; } else { reject(valid); } }); this.dispatch("import-choose"); this.dispatchExternal("importChoose"); input2.click(); }); } importData(importer, fileContents) { var data; this.table.dataLoader.alertLoader(); return new Promise((resolve, reject) => { setTimeout(() => { data = importer.call(this.table, fileContents); if (data instanceof Promise) { resolve(data); } else { data ? resolve(data) : reject(); } }, 10); }); } structureData(parsedData) { var data = []; if (Array.isArray(parsedData) && parsedData.length && Array.isArray(parsedData[0])) { if (this.table.options.autoColumns) { data = this.structureArrayToObject(parsedData); } else { data = this.structureArrayToColumns(parsedData); } return data; } else { return parsedData; } } mutateData(data) { var output = []; if (Array.isArray(data)) { data.forEach((row) => { output.push(this.table.modules.mutator.transformRow(row, "import")); }); } else { output = data; } return output; } transformHeader(headers) { var output = []; if (this.table.options.importHeaderTransform) { headers.forEach((item) => { output.push(this.table.options.importHeaderTransform.call(this.table, item, headers)); }); } else { return headers; } return output; } transformData(row) { var output = []; if (this.table.options.importValueTransform) { row.forEach((item) => { output.push(this.table.options.importValueTransform.call(this.table, item, row)); }); } else { return row; } return output; } structureArrayToObject(parsedData) { var columns = this.transformHeader(parsedData.shift()); var data = parsedData.map((values) => { var row = {}; values = this.transformData(values); columns.forEach((key, i) => { row[key] = values[i]; }); return row; }); return data; } structureArrayToColumns(parsedData) { var data = [], firstRow = this.transformHeader(parsedData[0]), columns = this.table.getColumns(); if (columns[0] && firstRow[0]) { if (columns[0].getDefinition().title === firstRow[0]) { parsedData.shift(); } } parsedData.forEach((rowData) => { var row = {}; rowData = this.transformData(rowData); rowData.forEach((value, index) => { var column = columns[index]; if (column) { row[column.getField()] = value; } }); data.push(row); }); return data; } validateFile(file) { if (this.table.options.importFileValidator) { return this.table.options.importFileValidator.call(this.table, file); } return true; } validateData(data) { var result; if (this.table.options.importDataValidator) { result = this.table.options.importDataValidator.call(this.table, data); if (result === true) { return data; } else { return Promise.reject(result); } } return data; } setData(data) { this.dispatch("import-imported", data); this.dispatchExternal("importImported", data); this.table.dataLoader.clearAlert(); return this.table.setData(data); } }; __publicField(_Import, "moduleName", "import"); //load defaults __publicField(_Import, "importers", defaultImporters); let Import = _Import; class Interaction extends Module { constructor(table) { super(table); this.eventMap = { //row events rowClick: "row-click", rowDblClick: "row-dblclick", rowContext: "row-contextmenu", rowMouseEnter: "row-mouseenter", rowMouseLeave: "row-mouseleave", rowMouseOver: "row-mouseover", rowMouseOut: "row-mouseout", rowMouseMove: "row-mousemove", rowMouseDown: "row-mousedown", rowMouseUp: "row-mouseup", rowTap: "row", rowDblTap: "row", rowTapHold: "row", //cell events cellClick: "cell-click", cellDblClick: "cell-dblclick", cellContext: "cell-contextmenu", cellMouseEnter: "cell-mouseenter", cellMouseLeave: "cell-mouseleave", cellMouseOver: "cell-mouseover", cellMouseOut: "cell-mouseout", cellMouseMove: "cell-mousemove", cellMouseDown: "cell-mousedown", cellMouseUp: "cell-mouseup", cellTap: "cell", cellDblTap: "cell", cellTapHold: "cell", //column header events headerClick: "column-click", headerDblClick: "column-dblclick", headerContext: "column-contextmenu", headerMouseEnter: "column-mouseenter", headerMouseLeave: "column-mouseleave", headerMouseOver: "column-mouseover", headerMouseOut: "column-mouseout", headerMouseMove: "column-mousemove", headerMouseDown: "column-mousedown", headerMouseUp: "column-mouseup", headerTap: "column", headerDblTap: "column", headerTapHold: "column", //group header groupClick: "group-click", groupDblClick: "group-dblclick", groupContext: "group-contextmenu", groupMouseEnter: "group-mouseenter", groupMouseLeave: "group-mouseleave", groupMouseOver: "group-mouseover", groupMouseOut: "group-mouseout", groupMouseMove: "group-mousemove", groupMouseDown: "group-mousedown", groupMouseUp: "group-mouseup", groupTap: "group", groupDblTap: "group", groupTapHold: "group" }; this.subscribers = {}; this.touchSubscribers = {}; this.columnSubscribers = {}; this.touchWatchers = { row: { tap: null, tapDbl: null, tapHold: null }, cell: { tap: null, tapDbl: null, tapHold: null }, column: { tap: null, tapDbl: null, tapHold: null }, group: { tap: null, tapDbl: null, tapHold: null } }; this.registerColumnOption("headerClick"); this.registerColumnOption("headerDblClick"); this.registerColumnOption("headerContext"); this.registerColumnOption("headerMouseEnter"); this.registerColumnOption("headerMouseLeave"); this.registerColumnOption("headerMouseOver"); this.registerColumnOption("headerMouseOut"); this.registerColumnOption("headerMouseMove"); this.registerColumnOption("headerMouseDown"); this.registerColumnOption("headerMouseUp"); this.registerColumnOption("headerTap"); this.registerColumnOption("headerDblTap"); this.registerColumnOption("headerTapHold"); this.registerColumnOption("cellClick"); this.registerColumnOption("cellDblClick"); this.registerColumnOption("cellContext"); this.registerColumnOption("cellMouseEnter"); this.registerColumnOption("cellMouseLeave"); this.registerColumnOption("cellMouseOver"); this.registerColumnOption("cellMouseOut"); this.registerColumnOption("cellMouseMove"); this.registerColumnOption("cellMouseDown"); this.registerColumnOption("cellMouseUp"); this.registerColumnOption("cellTap"); this.registerColumnOption("cellDblTap"); this.registerColumnOption("cellTapHold"); } initialize() { this.initializeExternalEvents(); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("cell-dblclick", this.cellContentsSelectionFixer.bind(this)); this.subscribe("scroll-horizontal", this.clearTouchWatchers.bind(this)); this.subscribe("scroll-vertical", this.clearTouchWatchers.bind(this)); } clearTouchWatchers() { var types = Object.values(this.touchWatchers); types.forEach((type) => { clearTimeout(type.tapDbl); clearTimeout(type.tapHold); for (let key in type) { type[key] = null; } }); } cellContentsSelectionFixer(e, cell) { var range2; if (this.table.modExists("edit")) { if (this.table.modules.edit.currentCell === cell) { return; } } e.preventDefault(); try { if (document.selection) { range2 = document.body.createTextRange(); range2.moveToElementText(cell.getElement()); range2.select(); } else if (window.getSelection) { range2 = document.createRange(); range2.selectNode(cell.getElement()); window.getSelection().removeAllRanges(); window.getSelection().addRange(range2); } } catch (e2) { } } initializeExternalEvents() { for (let key in this.eventMap) { this.subscriptionChangeExternal(key, this.subscriptionChanged.bind(this, key)); } } subscriptionChanged(key, added) { if (added) { if (!this.subscribers[key]) { if (this.eventMap[key].includes("-")) { this.subscribers[key] = this.handle.bind(this, key); this.subscribe(this.eventMap[key], this.subscribers[key]); } else { this.subscribeTouchEvents(key); } } } else { if (this.eventMap[key].includes("-")) { if (this.subscribers[key] && !this.columnSubscribers[key] && !this.subscribedExternal(key)) { this.unsubscribe(this.eventMap[key], this.subscribers[key]); delete this.subscribers[key]; } } else { this.unsubscribeTouchEvents(key); } } } subscribeTouchEvents(key) { var type = this.eventMap[key]; if (!this.touchSubscribers[type + "-touchstart"]) { this.touchSubscribers[type + "-touchstart"] = this.handleTouch.bind(this, type, "start"); this.touchSubscribers[type + "-touchend"] = this.handleTouch.bind(this, type, "end"); this.subscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]); this.subscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]); } this.subscribers[key] = true; } unsubscribeTouchEvents(key) { var noTouch = true, type = this.eventMap[key]; if (this.subscribers[key] && !this.subscribedExternal(key)) { delete this.subscribers[key]; for (let i in this.eventMap) { if (this.eventMap[i] === type) { if (this.subscribers[i]) { noTouch = false; } } } if (noTouch) { this.unsubscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]); this.unsubscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]); delete this.touchSubscribers[type + "-touchstart"]; delete this.touchSubscribers[type + "-touchend"]; } } } initializeColumn(column) { var def = column.definition; for (let key in this.eventMap) { if (def[key]) { this.subscriptionChanged(key, true); if (!this.columnSubscribers[key]) { this.columnSubscribers[key] = []; } this.columnSubscribers[key].push(column); } } } handle(action, e, component) { this.dispatchEvent(action, e, component); } handleTouch(type, action, e, component) { var watchers = this.touchWatchers[type]; if (type === "column") { type = "header"; } switch (action) { case "start": watchers.tap = true; clearTimeout(watchers.tapHold); watchers.tapHold = setTimeout(() => { clearTimeout(watchers.tapHold); watchers.tapHold = null; watchers.tap = null; clearTimeout(watchers.tapDbl); watchers.tapDbl = null; this.dispatchEvent(type + "TapHold", e, component); }, 1e3); break; case "end": if (watchers.tap) { watchers.tap = null; this.dispatchEvent(type + "Tap", e, component); } if (watchers.tapDbl) { clearTimeout(watchers.tapDbl); watchers.tapDbl = null; this.dispatchEvent(type + "DblTap", e, component); } else { watchers.tapDbl = setTimeout(() => { clearTimeout(watchers.tapDbl); watchers.tapDbl = null; }, 300); } clearTimeout(watchers.tapHold); watchers.tapHold = null; break; } } dispatchEvent(action, e, component) { var componentObj = component.getComponent(), callback; if (this.columnSubscribers[action]) { if (component instanceof Cell) { callback = component.column.definition[action]; } else if (component instanceof Column) { callback = component.definition[action]; } if (callback) { callback(e, componentObj); } } this.dispatchExternal(action, e, componentObj); } } __publicField(Interaction, "moduleName", "interaction"); var defaultBindings = { navPrev: "shift + 9", navNext: 9, navUp: 38, navDown: 40, navLeft: 37, navRight: 39, scrollPageUp: 33, scrollPageDown: 34, scrollToStart: 36, scrollToEnd: 35 }; var defaultActions = { keyBlock: function(e) { e.stopPropagation(); e.preventDefault(); }, scrollPageUp: function(e) { var rowManager = this.table.rowManager, newPos = rowManager.scrollTop - rowManager.element.clientHeight; e.preventDefault(); if (rowManager.displayRowsCount) { if (newPos >= 0) { rowManager.element.scrollTop = newPos; } else { rowManager.scrollToRow(rowManager.getDisplayRows()[0]); } } this.table.element.focus(); }, scrollPageDown: function(e) { var rowManager = this.table.rowManager, newPos = rowManager.scrollTop + rowManager.element.clientHeight, scrollMax = rowManager.element.scrollHeight; e.preventDefault(); if (rowManager.displayRowsCount) { if (newPos <= scrollMax) { rowManager.element.scrollTop = newPos; } else { rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); } } this.table.element.focus(); }, scrollToStart: function(e) { var rowManager = this.table.rowManager; e.preventDefault(); if (rowManager.displayRowsCount) { rowManager.scrollToRow(rowManager.getDisplayRows()[0]); } this.table.element.focus(); }, scrollToEnd: function(e) { var rowManager = this.table.rowManager; e.preventDefault(); if (rowManager.displayRowsCount) { rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); } this.table.element.focus(); }, navPrev: function(e) { this.dispatch("keybinding-nav-prev", e); }, navNext: function(e) { this.dispatch("keybinding-nav-next", e); }, navLeft: function(e) { this.dispatch("keybinding-nav-left", e); }, navRight: function(e) { this.dispatch("keybinding-nav-right", e); }, navUp: function(e) { this.dispatch("keybinding-nav-up", e); }, navDown: function(e) { this.dispatch("keybinding-nav-down", e); } }; const _Keybindings = class _Keybindings extends Module { constructor(table) { super(table); this.watchKeys = null; this.pressedKeys = null; this.keyupBinding = false; this.keydownBinding = false; this.registerTableOption("keybindings", {}); this.registerTableOption("tabEndNewRow", false); } initialize() { var bindings2 = this.table.options.keybindings, mergedBindings = {}; this.watchKeys = {}; this.pressedKeys = []; if (bindings2 !== false) { Object.assign(mergedBindings, _Keybindings.bindings); Object.assign(mergedBindings, bindings2); this.mapBindings(mergedBindings); this.bindEvents(); } this.subscribe("table-destroy", this.clearBindings.bind(this)); } mapBindings(bindings2) { for (let key in bindings2) { if (_Keybindings.actions[key]) { if (bindings2[key]) { if (typeof bindings2[key] !== "object") { bindings2[key] = [bindings2[key]]; } bindings2[key].forEach((binding) => { var bindingList = Array.isArray(binding) ? binding : [binding]; bindingList.forEach((item) => { this.mapBinding(key, item); }); }); } } else { console.warn("Key Binding Error - no such action:", key); } } } getKeyCode(e) { if (e.key.length === 1) { return e.key.toUpperCase().charCodeAt(0); } var specialKeys = { "Enter": 13, "Escape": 27, "Tab": 9, "Backspace": 8, "Delete": 46, "ArrowUp": 38, "ArrowDown": 40, "ArrowLeft": 37, "ArrowRight": 39, "Home": 36, "End": 35, "PageUp": 33, "PageDown": 34, "Insert": 45 }; return specialKeys[e.key] || e.keyCode || 0; } mapBinding(action, symbolsList) { var binding = { action: _Keybindings.actions[action], keys: [], ctrl: false, shift: false, meta: false }; var symbols = symbolsList.toString().toLowerCase().split(" ").join("").split("+"); symbols.forEach((symbol) => { switch (symbol) { case "ctrl": binding.ctrl = true; break; case "shift": binding.shift = true; break; case "meta": binding.meta = true; break; default: symbol = isNaN(symbol) ? symbol.toUpperCase().charCodeAt(0) : parseInt(symbol); binding.keys.push(symbol); if (!this.watchKeys[symbol]) { this.watchKeys[symbol] = []; } this.watchKeys[symbol].push(binding); } }); } bindEvents() { var self = this; this.keyupBinding = function(e) { var code = self.getKeyCode(e); var bindings2 = self.watchKeys[code]; if (bindings2) { self.pressedKeys.push(code); bindings2.forEach(function(binding) { self.checkBinding(e, binding); }); } }; this.keydownBinding = function(e) { var code = self.getKeyCode(e); var bindings2 = self.watchKeys[code]; if (bindings2) { var index = self.pressedKeys.indexOf(code); if (index > -1) { self.pressedKeys.splice(index, 1); } } }; this.table.element.addEventListener("keydown", this.keyupBinding); this.table.element.addEventListener("keyup", this.keydownBinding); } clearBindings() { if (this.keyupBinding) { this.table.element.removeEventListener("keydown", this.keyupBinding); } if (this.keydownBinding) { this.table.element.removeEventListener("keyup", this.keydownBinding); } } checkBinding(e, binding) { var match = true; if (e.ctrlKey == binding.ctrl && e.shiftKey == binding.shift && e.metaKey == binding.meta) { binding.keys.forEach((key) => { var index = this.pressedKeys.indexOf(key); if (index == -1) { match = false; } }); if (match) { binding.action.call(this, e); } return true; } return false; } }; __publicField(_Keybindings, "moduleName", "keybindings"); //load defaults __publicField(_Keybindings, "bindings", defaultBindings); __publicField(_Keybindings, "actions", defaultActions); let Keybindings = _Keybindings; class Menu extends Module { constructor(table) { super(table); this.menuContainer = null; this.nestedMenuBlock = false; this.currentComponent = null; this.rootPopup = null; this.columnSubscribers = {}; this.registerTableOption("rowContextMenu", false); this.registerTableOption("rowClickMenu", false); this.registerTableOption("rowDblClickMenu", false); this.registerTableOption("groupContextMenu", false); this.registerTableOption("groupClickMenu", false); this.registerTableOption("groupDblClickMenu", false); this.registerColumnOption("headerContextMenu"); this.registerColumnOption("headerClickMenu"); this.registerColumnOption("headerDblClickMenu"); this.registerColumnOption("headerMenu"); this.registerColumnOption("headerMenuIcon"); this.registerColumnOption("contextMenu"); this.registerColumnOption("clickMenu"); this.registerColumnOption("dblClickMenu"); } initialize() { this.deprecatedOptionsCheck(); this.initializeRowWatchers(); this.initializeGroupWatchers(); this.subscribe("column-init", this.initializeColumn.bind(this)); } deprecatedOptionsCheck() { } initializeRowWatchers() { if (this.table.options.rowContextMenu) { this.subscribe("row-contextmenu", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu)); this.table.on("rowTapHold", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu)); } if (this.table.options.rowClickMenu) { this.subscribe("row-click", this.loadMenuEvent.bind(this, this.table.options.rowClickMenu)); } if (this.table.options.rowDblClickMenu) { this.subscribe("row-dblclick", this.loadMenuEvent.bind(this, this.table.options.rowDblClickMenu)); } } initializeGroupWatchers() { if (this.table.options.groupContextMenu) { this.subscribe("group-contextmenu", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu)); this.table.on("groupTapHold", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu)); } if (this.table.options.groupClickMenu) { this.subscribe("group-click", this.loadMenuEvent.bind(this, this.table.options.groupClickMenu)); } if (this.table.options.groupDblClickMenu) { this.subscribe("group-dblclick", this.loadMenuEvent.bind(this, this.table.options.groupDblClickMenu)); } } initializeColumn(column) { var def = column.definition; if (def.headerContextMenu && !this.columnSubscribers.headerContextMenu) { this.columnSubscribers.headerContextMenu = this.loadMenuTableColumnEvent.bind(this, "headerContextMenu"); this.subscribe("column-contextmenu", this.columnSubscribers.headerContextMenu); this.table.on("headerTapHold", this.loadMenuTableColumnEvent.bind(this, "headerContextMenu")); } if (def.headerClickMenu && !this.columnSubscribers.headerClickMenu) { this.columnSubscribers.headerClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerClickMenu"); this.subscribe("column-click", this.columnSubscribers.headerClickMenu); } if (def.headerDblClickMenu && !this.columnSubscribers.headerDblClickMenu) { this.columnSubscribers.headerDblClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerDblClickMenu"); this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickMenu); } if (def.headerMenu) { this.initializeColumnHeaderMenu(column); } if (def.contextMenu && !this.columnSubscribers.contextMenu) { this.columnSubscribers.contextMenu = this.loadMenuTableCellEvent.bind(this, "contextMenu"); this.subscribe("cell-contextmenu", this.columnSubscribers.contextMenu); this.table.on("cellTapHold", this.loadMenuTableCellEvent.bind(this, "contextMenu")); } if (def.clickMenu && !this.columnSubscribers.clickMenu) { this.columnSubscribers.clickMenu = this.loadMenuTableCellEvent.bind(this, "clickMenu"); this.subscribe("cell-click", this.columnSubscribers.clickMenu); } if (def.dblClickMenu && !this.columnSubscribers.dblClickMenu) { this.columnSubscribers.dblClickMenu = this.loadMenuTableCellEvent.bind(this, "dblClickMenu"); this.subscribe("cell-dblclick", this.columnSubscribers.dblClickMenu); } } initializeColumnHeaderMenu(column) { var icon = column.definition.headerMenuIcon, headerMenuEl; headerMenuEl = document.createElement("span"); headerMenuEl.classList.add("tabulator-header-popup-button"); if (icon) { if (typeof icon === "function") { icon = icon(column.getComponent()); } if (icon instanceof HTMLElement) { headerMenuEl.appendChild(icon); } else { headerMenuEl.innerHTML = icon; } } else { headerMenuEl.innerHTML = "⋮"; } headerMenuEl.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); this.loadMenuEvent(column.definition.headerMenu, e, column); }); column.titleElement.insertBefore(headerMenuEl, column.titleElement.firstChild); } loadMenuTableCellEvent(option, e, cell) { if (cell._cell) { cell = cell._cell; } if (cell.column.definition[option]) { this.loadMenuEvent(cell.column.definition[option], e, cell); } } loadMenuTableColumnEvent(option, e, column) { if (column._column) { column = column._column; } if (column.definition[option]) { this.loadMenuEvent(column.definition[option], e, column); } } loadMenuEvent(menu, e, component) { if (component._group) { component = component._group; } else if (component._row) { component = component._row; } menu = typeof menu == "function" ? menu.call(this.table, e, component.getComponent()) : menu; this.loadMenu(e, component, menu); } loadMenu(e, component, menu, parentEl, parentPopup) { var touch = !(e instanceof MouseEvent), menuEl = document.createElement("div"), popup; menuEl.classList.add("tabulator-menu"); if (!touch) { e.preventDefault(); } if (!menu || !menu.length) { return; } if (!parentEl) { if (this.nestedMenuBlock) { if (this.rootPopup) { return; } } else { this.nestedMenuBlock = setTimeout(() => { this.nestedMenuBlock = false; }, 100); } if (this.rootPopup) { this.rootPopup.hide(); } this.rootPopup = popup = this.popup(menuEl); } else { popup = parentPopup.child(menuEl); } menu.forEach((item) => { var itemEl = document.createElement("div"), label = item.label, disabled = item.disabled; if (item.separator) { itemEl.classList.add("tabulator-menu-separator"); } else { itemEl.classList.add("tabulator-menu-item"); if (typeof label == "function") { label = label.call(this.table, component.getComponent()); } if (label instanceof Node) { itemEl.appendChild(label); } else { itemEl.innerHTML = label; } if (typeof disabled == "function") { disabled = disabled.call(this.table, component.getComponent()); } if (disabled) { itemEl.classList.add("tabulator-menu-item-disabled"); itemEl.addEventListener("click", (e2) => { e2.stopPropagation(); }); } else { if (item.menu && item.menu.length) { itemEl.addEventListener("click", (e2) => { e2.stopPropagation(); this.loadMenu(e2, component, item.menu, itemEl, popup); }); } else { if (item.action) { itemEl.addEventListener("click", (e2) => { item.action(e2, component.getComponent()); }); } } } if (item.menu && item.menu.length) { itemEl.classList.add("tabulator-menu-item-submenu"); } } menuEl.appendChild(itemEl); }); menuEl.addEventListener("click", (e2) => { if (this.rootPopup) { this.rootPopup.hide(); } }); popup.show(parentEl || e); if (popup === this.rootPopup) { this.rootPopup.hideOnBlur(() => { this.rootPopup = null; if (this.currentComponent) { this.dispatch("menu-closed", menu, popup); this.dispatchExternal("menuClosed", this.currentComponent.getComponent()); this.currentComponent = null; } }); this.currentComponent = component; this.dispatch("menu-opened", menu, popup); this.dispatchExternal("menuOpened", component.getComponent()); } } } __publicField(Menu, "moduleName", "menu"); class MoveColumns extends Module { constructor(table) { super(table); this.placeholderElement = this.createPlaceholderElement(); this.hoverElement = false; this.checkTimeout = false; this.checkPeriod = 250; this.moving = false; this.toCol = false; this.toColAfter = false; this.startX = 0; this.autoScrollMargin = 40; this.autoScrollStep = 5; this.autoScrollTimeout = false; this.touchMove = false; this.moveHover = this.moveHover.bind(this); this.endMove = this.endMove.bind(this); this.registerTableOption("movableColumns", false); } createPlaceholderElement() { var el = document.createElement("div"); el.classList.add("tabulator-col"); el.classList.add("tabulator-col-placeholder"); return el; } initialize() { if (this.table.options.movableColumns) { this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("alert-show", this.abortMove.bind(this)); } } abortMove() { clearTimeout(this.checkTimeout); } initializeColumn(column) { var self = this, config = {}, colEl; if (!column.modules.frozen && !column.isGroup && !column.isRowHeader) { colEl = column.getElement(); config.mousemove = (function(e) { if (column.parent === self.moving.parent) { if ((self.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(colEl).left + self.table.columnManager.contentsElement.scrollLeft > column.getWidth() / 2) { if (self.toCol !== column || !self.toColAfter) { colEl.parentNode.insertBefore(self.placeholderElement, colEl.nextSibling); self.moveColumn(column, true); } } else { if (self.toCol !== column || self.toColAfter) { colEl.parentNode.insertBefore(self.placeholderElement, colEl); self.moveColumn(column, false); } } } }).bind(self); colEl.addEventListener("mousedown", function(e) { self.touchMove = false; if (e.which === 1) { self.checkTimeout = setTimeout(function() { self.startMove(e, column); }, self.checkPeriod); } }); colEl.addEventListener("mouseup", function(e) { if (e.which === 1) { if (self.checkTimeout) { clearTimeout(self.checkTimeout); } } }); self.bindTouchEvents(column); } column.modules.moveColumn = config; } bindTouchEvents(column) { var colEl = column.getElement(), startXMove = false, nextCol, prevCol, nextColWidth, prevColWidth, nextColWidthLast, prevColWidthLast; colEl.addEventListener("touchstart", (e) => { this.checkTimeout = setTimeout(() => { this.touchMove = true; nextCol = column.nextColumn(); nextColWidth = nextCol ? nextCol.getWidth() / 2 : 0; prevCol = column.prevColumn(); prevColWidth = prevCol ? prevCol.getWidth() / 2 : 0; nextColWidthLast = 0; prevColWidthLast = 0; startXMove = false; this.startMove(e, column); }, this.checkPeriod); }, { passive: true }); colEl.addEventListener("touchmove", (e) => { var diff, moveToCol; if (this.moving) { this.moveHover(e); if (!startXMove) { startXMove = e.touches[0].pageX; } diff = e.touches[0].pageX - startXMove; if (diff > 0) { if (nextCol && diff - nextColWidthLast > nextColWidth) { moveToCol = nextCol; if (moveToCol !== column) { startXMove = e.touches[0].pageX; moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement().nextSibling); this.moveColumn(moveToCol, true); } } } else { if (prevCol && -diff - prevColWidthLast > prevColWidth) { moveToCol = prevCol; if (moveToCol !== column) { startXMove = e.touches[0].pageX; moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement()); this.moveColumn(moveToCol, false); } } } if (moveToCol) { nextCol = moveToCol.nextColumn(); nextColWidthLast = nextColWidth; nextColWidth = nextCol ? nextCol.getWidth() / 2 : 0; prevCol = moveToCol.prevColumn(); prevColWidthLast = prevColWidth; prevColWidth = prevCol ? prevCol.getWidth() / 2 : 0; } } }, { passive: true }); colEl.addEventListener("touchend", (e) => { if (this.checkTimeout) { clearTimeout(this.checkTimeout); } if (this.moving) { this.endMove(e); } }); } startMove(e, column) { var element = column.getElement(), headerElement = this.table.columnManager.getContentsElement(), headersElement = this.table.columnManager.getHeadersElement(); if (this.table.modules.selectRange && this.table.modules.selectRange.columnSelection) { if (this.table.modules.selectRange.mousedown && this.table.modules.selectRange.selecting === "column") { return; } } this.moving = column; this.startX = (this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(element).left; this.table.element.classList.add("tabulator-block-select"); this.placeholderElement.style.width = column.getWidth() + "px"; this.placeholderElement.style.height = column.getHeight() + "px"; element.parentNode.insertBefore(this.placeholderElement, element); element.parentNode.removeChild(element); this.hoverElement = element.cloneNode(true); this.hoverElement.classList.add("tabulator-moving"); headerElement.appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.bottom = headerElement.clientHeight - headersElement.offsetHeight + "px"; if (!this.touchMove) { this._bindMouseMove(); document.body.addEventListener("mousemove", this.moveHover); document.body.addEventListener("mouseup", this.endMove); } this.moveHover(e); this.dispatch("column-moving", e, this.moving); } _bindMouseMove() { this.table.columnManager.columnsByIndex.forEach(function(column) { if (column.modules.moveColumn.mousemove) { column.getElement().addEventListener("mousemove", column.modules.moveColumn.mousemove); } }); } _unbindMouseMove() { this.table.columnManager.columnsByIndex.forEach(function(column) { if (column.modules.moveColumn.mousemove) { column.getElement().removeEventListener("mousemove", column.modules.moveColumn.mousemove); } }); } moveColumn(column, after) { var movingCells = this.moving.getCells(); this.toCol = column; this.toColAfter = after; if (after) { column.getCells().forEach(function(cell, i) { var cellEl = cell.getElement(true); if (cellEl.parentNode && movingCells[i]) { cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl.nextSibling); } }); } else { column.getCells().forEach(function(cell, i) { var cellEl = cell.getElement(true); if (cellEl.parentNode && movingCells[i]) { cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl); } }); } } endMove(e) { if (e.which === 1 || this.touchMove) { this._unbindMouseMove(); this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling); this.placeholderElement.parentNode.removeChild(this.placeholderElement); this.hoverElement.parentNode.removeChild(this.hoverElement); this.table.element.classList.remove("tabulator-block-select"); if (this.toCol) { this.table.columnManager.moveColumnActual(this.moving, this.toCol, this.toColAfter); } this.moving = false; this.toCol = false; this.toColAfter = false; if (!this.touchMove) { document.body.removeEventListener("mousemove", this.moveHover); document.body.removeEventListener("mouseup", this.endMove); } } } moveHover(e) { var columnHolder = this.table.columnManager.getContentsElement(), scrollLeft = columnHolder.scrollLeft, xPos = (this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(columnHolder).left + scrollLeft, scrollPos; this.hoverElement.style.left = xPos - this.startX + "px"; if (xPos - scrollLeft < this.autoScrollMargin) { if (!this.autoScrollTimeout) { this.autoScrollTimeout = setTimeout(() => { scrollPos = Math.max(0, scrollLeft - 5); this.table.rowManager.getElement().scrollLeft = scrollPos; this.autoScrollTimeout = false; }, 1); } } if (scrollLeft + columnHolder.clientWidth - xPos < this.autoScrollMargin) { if (!this.autoScrollTimeout) { this.autoScrollTimeout = setTimeout(() => { scrollPos = Math.min(columnHolder.clientWidth, scrollLeft + 5); this.table.rowManager.getElement().scrollLeft = scrollPos; this.autoScrollTimeout = false; }, 1); } } } } __publicField(MoveColumns, "moduleName", "moveColumn"); var defaultSenders = { delete: function(fromRow, toRow, toTable) { fromRow.delete(); } }; var defaultReceivers = { insert: function(fromRow, toRow, fromTable) { this.table.addRow(fromRow.getData(), void 0, toRow); return true; }, add: function(fromRow, toRow, fromTable) { this.table.addRow(fromRow.getData()); return true; }, update: function(fromRow, toRow, fromTable) { if (toRow) { toRow.update(fromRow.getData()); return true; } return false; }, replace: function(fromRow, toRow, fromTable) { if (toRow) { this.table.addRow(fromRow.getData(), void 0, toRow); toRow.delete(); return true; } return false; } }; const _MoveRows = class _MoveRows extends Module { constructor(table) { super(table); this.placeholderElement = this.createPlaceholderElement(); this.hoverElement = false; this.checkTimeout = false; this.checkPeriod = 150; this.moving = false; this.toRow = false; this.toRowAfter = false; this.hasHandle = false; this.startY = 0; this.startX = 0; this.moveHover = this.moveHover.bind(this); this.endMove = this.endMove.bind(this); this.tableRowDropEvent = false; this.touchMove = false; this.connection = false; this.connectionSelectorsTables = false; this.connectionSelectorsElements = false; this.connectionElements = []; this.connections = []; this.connectedTable = false; this.connectedRow = false; this.registerTableOption("movableRows", false); this.registerTableOption("movableRowsConnectedTables", false); this.registerTableOption("movableRowsConnectedElements", false); this.registerTableOption("movableRowsSender", false); this.registerTableOption("movableRowsReceiver", "insert"); this.registerColumnOption("rowHandle"); } createPlaceholderElement() { var el = document.createElement("div"); el.classList.add("tabulator-row"); el.classList.add("tabulator-row-placeholder"); return el; } initialize() { if (this.table.options.movableRows) { this.connectionSelectorsTables = this.table.options.movableRowsConnectedTables; this.connectionSelectorsElements = this.table.options.movableRowsConnectedElements; this.connection = this.connectionSelectorsTables || this.connectionSelectorsElements; this.subscribe("cell-init", this.initializeCell.bind(this)); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("row-init", this.initializeRow.bind(this)); } } initializeGroupHeader(group) { var self = this, config = {}; config.mouseup = (function(e) { self.tableRowDrop(e, group); }).bind(self); config.mousemove = (function(e) { var rowEl; if (e.pageY - Helpers.elOffset(group.element).top + self.table.rowManager.element.scrollTop > group.getHeight() / 2) { if (self.toRow !== group || !self.toRowAfter) { rowEl = group.getElement(); rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling); self.moveRow(group, true); } } else { if (self.toRow !== group || self.toRowAfter) { rowEl = group.getElement(); if (rowEl.previousSibling) { rowEl.parentNode.insertBefore(self.placeholderElement, rowEl); self.moveRow(group, false); } } } }).bind(self); group.modules.moveRow = config; } initializeRow(row) { var self = this, config = {}, rowEl; config.mouseup = (function(e) { self.tableRowDrop(e, row); }).bind(self); config.mousemove = (function(e) { var rowEl2 = row.getElement(); if (e.pageY - Helpers.elOffset(rowEl2).top + self.table.rowManager.element.scrollTop > row.getHeight() / 2) { if (self.toRow !== row || !self.toRowAfter) { rowEl2.parentNode.insertBefore(self.placeholderElement, rowEl2.nextSibling); self.moveRow(row, true); } } else { if (self.toRow !== row || self.toRowAfter) { rowEl2.parentNode.insertBefore(self.placeholderElement, rowEl2); self.moveRow(row, false); } } }).bind(self); if (!this.hasHandle) { rowEl = row.getElement(); rowEl.addEventListener("mousedown", function(e) { if (e.which === 1) { self.checkTimeout = setTimeout(function() { self.startMove(e, row); }, self.checkPeriod); } }); rowEl.addEventListener("mouseup", function(e) { if (e.which === 1) { if (self.checkTimeout) { clearTimeout(self.checkTimeout); } } }); this.bindTouchEvents(row, row.getElement()); } row.modules.moveRow = config; } initializeColumn(column) { if (column.definition.rowHandle && this.table.options.movableRows !== false) { this.hasHandle = true; } } initializeCell(cell) { if (cell.column.definition.rowHandle && this.table.options.movableRows !== false) { var self = this, cellEl = cell.getElement(true); cellEl.addEventListener("mousedown", function(e) { if (e.which === 1) { self.checkTimeout = setTimeout(function() { self.startMove(e, cell.row); }, self.checkPeriod); } }); cellEl.addEventListener("mouseup", function(e) { if (e.which === 1) { if (self.checkTimeout) { clearTimeout(self.checkTimeout); } } }); this.bindTouchEvents(cell.row, cellEl); } } bindTouchEvents(row, element) { var startYMove = false, nextRow, prevRow, nextRowHeight, prevRowHeight, nextRowHeightLast, prevRowHeightLast; element.addEventListener("touchstart", (e) => { this.checkTimeout = setTimeout(() => { this.touchMove = true; nextRow = row.nextRow(); nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0; prevRow = row.prevRow(); prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0; nextRowHeightLast = 0; prevRowHeightLast = 0; startYMove = false; this.startMove(e, row); }, this.checkPeriod); }, { passive: true }); this.moving, this.toRow, this.toRowAfter; element.addEventListener("touchmove", (e) => { var diff, moveToRow; if (this.moving) { e.preventDefault(); this.moveHover(e); if (!startYMove) { startYMove = e.touches[0].pageY; } diff = e.touches[0].pageY - startYMove; if (diff > 0) { if (nextRow && diff - nextRowHeightLast > nextRowHeight) { moveToRow = nextRow; if (moveToRow !== row) { startYMove = e.touches[0].pageY; moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement().nextSibling); this.moveRow(moveToRow, true); } } } else { if (prevRow && -diff - prevRowHeightLast > prevRowHeight) { moveToRow = prevRow; if (moveToRow !== row) { startYMove = e.touches[0].pageY; moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement()); this.moveRow(moveToRow, false); } } } if (moveToRow) { nextRow = moveToRow.nextRow(); nextRowHeightLast = nextRowHeight; nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0; prevRow = moveToRow.prevRow(); prevRowHeightLast = prevRowHeight; prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0; } } }); element.addEventListener("touchend", (e) => { if (this.checkTimeout) { clearTimeout(this.checkTimeout); } if (this.moving) { this.endMove(e); this.touchMove = false; } }); } _bindMouseMove() { this.table.rowManager.getDisplayRows().forEach((row) => { if ((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove) { row.getElement().addEventListener("mousemove", row.modules.moveRow.mousemove); } }); } _unbindMouseMove() { this.table.rowManager.getDisplayRows().forEach((row) => { if ((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove) { row.getElement().removeEventListener("mousemove", row.modules.moveRow.mousemove); } }); } startMove(e, row) { var element = row.getElement(); this.setStartPosition(e, row); this.moving = row; this.table.element.classList.add("tabulator-block-select"); this.placeholderElement.style.width = row.getWidth() + "px"; this.placeholderElement.style.height = row.getHeight() + "px"; if (!this.connection) { element.parentNode.insertBefore(this.placeholderElement, element); element.parentNode.removeChild(element); } else { this.table.element.classList.add("tabulator-movingrow-sending"); this.connectToTables(row); } this.hoverElement = element.cloneNode(true); this.hoverElement.classList.add("tabulator-moving"); if (this.connection) { document.body.appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.top = "0"; this.hoverElement.style.width = this.table.element.clientWidth + "px"; this.hoverElement.style.whiteSpace = "nowrap"; this.hoverElement.style.overflow = "hidden"; this.hoverElement.style.pointerEvents = "none"; } else { this.table.rowManager.getTableElement().appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.top = "0"; this._bindMouseMove(); } document.body.addEventListener("mousemove", this.moveHover); document.body.addEventListener("mouseup", this.endMove); this.dispatchExternal("rowMoving", row.getComponent()); this.moveHover(e); } setStartPosition(e, row) { var pageX = this.touchMove ? e.touches[0].pageX : e.pageX, pageY = this.touchMove ? e.touches[0].pageY : e.pageY, element, position; element = row.getElement(); if (this.connection) { position = element.getBoundingClientRect(); this.startX = position.left - pageX + window.pageXOffset; this.startY = position.top - pageY + window.pageYOffset; } else { this.startY = pageY - element.getBoundingClientRect().top; } } endMove(e) { if (!e || e.which === 1 || this.touchMove) { this._unbindMouseMove(); if (!this.connection) { this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling); this.placeholderElement.parentNode.removeChild(this.placeholderElement); } this.hoverElement.parentNode.removeChild(this.hoverElement); this.table.element.classList.remove("tabulator-block-select"); if (this.toRow) { this.table.rowManager.moveRow(this.moving, this.toRow, this.toRowAfter); } else { this.dispatchExternal("rowMoveCancelled", this.moving.getComponent()); } this.moving = false; this.toRow = false; this.toRowAfter = false; document.body.removeEventListener("mousemove", this.moveHover); document.body.removeEventListener("mouseup", this.endMove); if (this.connection) { this.table.element.classList.remove("tabulator-movingrow-sending"); this.disconnectFromTables(); } } } moveRow(row, after) { this.toRow = row; this.toRowAfter = after; } moveHover(e) { if (this.connection) { this.moveHoverConnections.call(this, e); } else { this.moveHoverTable.call(this, e); } } moveHoverTable(e) { var rowHolder = this.table.rowManager.getElement(), scrollTop = rowHolder.scrollTop, yPos = (this.touchMove ? e.touches[0].pageY : e.pageY) - rowHolder.getBoundingClientRect().top + scrollTop; this.hoverElement.style.top = Math.min(yPos - this.startY, this.table.rowManager.element.scrollHeight - this.hoverElement.offsetHeight) + "px"; } moveHoverConnections(e) { this.hoverElement.style.left = this.startX + (this.touchMove ? e.touches[0].pageX : e.pageX) + "px"; this.hoverElement.style.top = this.startY + (this.touchMove ? e.touches[0].pageY : e.pageY) + "px"; } elementRowDrop(e, element, row) { this.dispatchExternal("movableRowsElementDrop", e, element, row ? row.getComponent() : false); } //establish connection with other tables connectToTables(row) { var connectionTables; if (this.connectionSelectorsTables) { connectionTables = this.commsConnections(this.connectionSelectorsTables); this.dispatchExternal("movableRowsSendingStart", connectionTables); this.commsSend(this.connectionSelectorsTables, "moveRow", "connect", { row }); } if (this.connectionSelectorsElements) { this.connectionElements = []; if (!Array.isArray(this.connectionSelectorsElements)) { this.connectionSelectorsElements = [this.connectionSelectorsElements]; } this.connectionSelectorsElements.forEach((query) => { if (typeof query === "string") { this.connectionElements = this.connectionElements.concat(Array.prototype.slice.call(document.querySelectorAll(query))); } else { this.connectionElements.push(query); } }); this.connectionElements.forEach((element) => { var dropEvent = (e) => { this.elementRowDrop(e, element, this.moving); }; element.addEventListener("mouseup", dropEvent); element.tabulatorElementDropEvent = dropEvent; element.classList.add("tabulator-movingrow-receiving"); }); } } //disconnect from other tables disconnectFromTables() { var connectionTables; if (this.connectionSelectorsTables) { connectionTables = this.commsConnections(this.connectionSelectorsTables); this.dispatchExternal("movableRowsSendingStop", connectionTables); this.commsSend(this.connectionSelectorsTables, "moveRow", "disconnect"); } this.connectionElements.forEach((element) => { element.classList.remove("tabulator-movingrow-receiving"); element.removeEventListener("mouseup", element.tabulatorElementDropEvent); delete element.tabulatorElementDropEvent; }); } //accept incomming connection connect(table, row) { if (!this.connectedTable) { this.connectedTable = table; this.connectedRow = row; this.table.element.classList.add("tabulator-movingrow-receiving"); this.table.rowManager.getDisplayRows().forEach((row2) => { if (row2.type === "row" && row2.modules.moveRow && row2.modules.moveRow.mouseup) { row2.getElement().addEventListener("mouseup", row2.modules.moveRow.mouseup); } }); this.tableRowDropEvent = this.tableRowDrop.bind(this); this.table.element.addEventListener("mouseup", this.tableRowDropEvent); this.dispatchExternal("movableRowsReceivingStart", row, table); return true; } else { console.warn("Move Row Error - Table cannot accept connection, already connected to table:", this.connectedTable); return false; } } //close incoming connection disconnect(table) { if (table === this.connectedTable) { this.connectedTable = false; this.connectedRow = false; this.table.element.classList.remove("tabulator-movingrow-receiving"); this.table.rowManager.getDisplayRows().forEach((row) => { if (row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup) { row.getElement().removeEventListener("mouseup", row.modules.moveRow.mouseup); } }); this.table.element.removeEventListener("mouseup", this.tableRowDropEvent); this.dispatchExternal("movableRowsReceivingStop", table); } else { console.warn("Move Row Error - trying to disconnect from non connected table"); } } dropComplete(table, row, success) { var sender = false; if (success) { switch (typeof this.table.options.movableRowsSender) { case "string": sender = _MoveRows.senders[this.table.options.movableRowsSender]; break; case "function": sender = this.table.options.movableRowsSender; break; } if (sender) { sender.call(this, this.moving ? this.moving.getComponent() : void 0, row ? row.getComponent() : void 0, table); } else { if (this.table.options.movableRowsSender) { console.warn("Mover Row Error - no matching sender found:", this.table.options.movableRowsSender); } } this.dispatchExternal("movableRowsSent", this.moving.getComponent(), row ? row.getComponent() : void 0, table); } else { this.dispatchExternal("movableRowsSentFailed", this.moving.getComponent(), row ? row.getComponent() : void 0, table); } this.endMove(); } tableRowDrop(e, row) { var receiver = false, success = false; e.stopImmediatePropagation(); switch (typeof this.table.options.movableRowsReceiver) { case "string": receiver = _MoveRows.receivers[this.table.options.movableRowsReceiver]; break; case "function": receiver = this.table.options.movableRowsReceiver; break; } if (receiver) { success = receiver.call(this, this.connectedRow.getComponent(), row ? row.getComponent() : void 0, this.connectedTable); } else { console.warn("Mover Row Error - no matching receiver found:", this.table.options.movableRowsReceiver); } if (success) { this.dispatchExternal("movableRowsReceived", this.connectedRow.getComponent(), row ? row.getComponent() : void 0, this.connectedTable); } else { this.dispatchExternal("movableRowsReceivedFailed", this.connectedRow.getComponent(), row ? row.getComponent() : void 0, this.connectedTable); } this.commsSend(this.connectedTable, "moveRow", "dropcomplete", { row, success }); } commsReceived(table, action, data) { switch (action) { case "connect": return this.connect(table, data.row); case "disconnect": return this.disconnect(table); case "dropcomplete": return this.dropComplete(table, data.row, data.success); } } }; __publicField(_MoveRows, "moduleName", "moveRow"); //load defaults __publicField(_MoveRows, "senders", defaultSenders); __publicField(_MoveRows, "receivers", defaultReceivers); let MoveRows = _MoveRows; var defaultMutators = {}; const _Mutator = class _Mutator extends Module { constructor(table) { super(table); this.allowedTypes = ["", "data", "edit", "clipboard", "import"]; this.enabled = true; this.registerColumnOption("mutator"); this.registerColumnOption("mutatorParams"); this.registerColumnOption("mutatorData"); this.registerColumnOption("mutatorDataParams"); this.registerColumnOption("mutatorEdit"); this.registerColumnOption("mutatorEditParams"); this.registerColumnOption("mutatorClipboard"); this.registerColumnOption("mutatorClipboardParams"); this.registerColumnOption("mutatorImport"); this.registerColumnOption("mutatorImportParams"); this.registerColumnOption("mutateLink"); } initialize() { this.subscribe("cell-value-changing", this.transformCell.bind(this)); this.subscribe("cell-value-changed", this.mutateLink.bind(this)); this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("row-data-init-before", this.rowDataChanged.bind(this)); this.subscribe("row-data-changing", this.rowDataChanged.bind(this)); } rowDataChanged(row, tempData, updatedData) { return this.transformRow(tempData, "data", updatedData); } //initialize column mutator initializeColumn(column) { var match = false, config = {}; this.allowedTypes.forEach((type) => { var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)), mutator; if (column.definition[key]) { mutator = this.lookupMutator(column.definition[key]); if (mutator) { match = true; config[key] = { mutator, params: column.definition[key + "Params"] || {} }; } } }); if (match) { column.modules.mutate = config; } } lookupMutator(value) { var mutator = false; switch (typeof value) { case "string": if (_Mutator.mutators[value]) { mutator = _Mutator.mutators[value]; } else { console.warn("Mutator Error - No such mutator found, ignoring: ", value); } break; case "function": mutator = value; break; } return mutator; } //apply mutator to row transformRow(data, type, updatedData) { var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)), value; if (this.enabled) { this.table.columnManager.traverse((column) => { var mutator, params, component; if (column.modules.mutate) { mutator = column.modules.mutate[key] || column.modules.mutate.mutator || false; if (mutator) { value = column.getFieldValue(typeof updatedData !== "undefined" ? updatedData : data); if (type == "data" && !updatedData || typeof value !== "undefined") { component = column.getComponent(); params = typeof mutator.params === "function" ? mutator.params(value, data, type, component) : mutator.params; column.setFieldValue(data, mutator.mutator(value, data, type, params, component)); } } } }); } return data; } //apply mutator to new cell value transformCell(cell, value) { if (cell.column.modules.mutate) { var mutator = cell.column.modules.mutate.mutatorEdit || cell.column.modules.mutate.mutator || false, tempData = {}; if (mutator) { tempData = Object.assign(tempData, cell.row.getData()); cell.column.setFieldValue(tempData, value); return mutator.mutator(value, tempData, "edit", mutator.params, cell.getComponent()); } } return value; } mutateLink(cell) { var links = cell.column.definition.mutateLink; if (links) { if (!Array.isArray(links)) { links = [links]; } links.forEach((link2) => { var linkCell = cell.row.getCell(link2); if (linkCell) { linkCell.setValue(linkCell.getValue(), true, true); } }); } } enable() { this.enabled = true; } disable() { this.enabled = false; } }; __publicField(_Mutator, "moduleName", "mutator"); //load defaults __publicField(_Mutator, "mutators", defaultMutators); let Mutator = _Mutator; function rows(pageSize, currentRow, currentPage, totalRows, totalPages) { var el = document.createElement("span"), showingEl = document.createElement("span"), valueEl = document.createElement("span"), ofEl = document.createElement("span"), totalEl = document.createElement("span"), rowsEl = document.createElement("span"); this.table.modules.localize.langBind("pagination|counter|showing", (value) => { showingEl.innerHTML = value; }); this.table.modules.localize.langBind("pagination|counter|of", (value) => { ofEl.innerHTML = value; }); this.table.modules.localize.langBind("pagination|counter|rows", (value) => { rowsEl.innerHTML = value; }); if (totalRows) { valueEl.innerHTML = " " + currentRow + "-" + Math.min(currentRow + pageSize - 1, totalRows) + " "; totalEl.innerHTML = " " + totalRows + " "; el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(ofEl); el.appendChild(totalEl); el.appendChild(rowsEl); } else { valueEl.innerHTML = " 0 "; el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(rowsEl); } return el; } function pages(pageSize, currentRow, currentPage, totalRows, totalPages) { var el = document.createElement("span"), showingEl = document.createElement("span"), valueEl = document.createElement("span"), ofEl = document.createElement("span"), totalEl = document.createElement("span"), rowsEl = document.createElement("span"); this.table.modules.localize.langBind("pagination|counter|showing", (value) => { showingEl.innerHTML = value; }); valueEl.innerHTML = " " + currentPage + " "; this.table.modules.localize.langBind("pagination|counter|of", (value) => { ofEl.innerHTML = value; }); totalEl.innerHTML = " " + totalPages + " "; this.table.modules.localize.langBind("pagination|counter|pages", (value) => { rowsEl.innerHTML = value; }); el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(ofEl); el.appendChild(totalEl); el.appendChild(rowsEl); return el; } var defaultPageCounters = { rows, pages }; const _Page = class _Page extends Module { constructor(table) { super(table); this.mode = "local"; this.progressiveLoad = false; this.element = null; this.pageCounterElement = null; this.pageCounter = null; this.size = 0; this.page = 1; this.count = 5; this.max = 1; this.remoteRowCountEstimate = null; this.initialLoad = true; this.dataChanging = false; this.pageSizes = []; this.registerTableOption("pagination", false); this.registerTableOption("paginationMode", "local"); this.registerTableOption("paginationSize", false); this.registerTableOption("paginationInitialPage", 1); this.registerTableOption("paginationCounter", false); this.registerTableOption("paginationCounterElement", false); this.registerTableOption("paginationButtonCount", 5); this.registerTableOption("paginationSizeSelector", false); this.registerTableOption("paginationElement", false); this.registerTableOption("paginationAddRow", "page"); this.registerTableOption("paginationOutOfRange", false); this.registerTableOption("progressiveLoad", false); this.registerTableOption("progressiveLoadDelay", 0); this.registerTableOption("progressiveLoadScrollMargin", 0); this.registerTableFunction("setMaxPage", this.setMaxPage.bind(this)); this.registerTableFunction("setPage", this.setPage.bind(this)); this.registerTableFunction("setPageToRow", this.userSetPageToRow.bind(this)); this.registerTableFunction("setPageSize", this.userSetPageSize.bind(this)); this.registerTableFunction("getPageSize", this.getPageSize.bind(this)); this.registerTableFunction("previousPage", this.previousPage.bind(this)); this.registerTableFunction("nextPage", this.nextPage.bind(this)); this.registerTableFunction("getPage", this.getPage.bind(this)); this.registerTableFunction("getPageMax", this.getPageMax.bind(this)); this.registerComponentFunction("row", "pageTo", this.setPageToRow.bind(this)); } initialize() { if (this.table.options.pagination) { this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("row-added", this.rowsUpdated.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("footer-redraw", this.footerRedraw.bind(this)); if (this.table.options.paginationAddRow == "page") { this.subscribe("row-adding-position", this.rowAddingPosition.bind(this)); } if (this.table.options.paginationMode === "remote") { this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); } if (this.table.options.progressiveLoad) { console.error("Progressive Load Error - Pagination and progressive load cannot be used at the same time"); } this.registerDisplayHandler(this.restOnRenderBefore.bind(this), 40); this.registerDisplayHandler(this.getRows.bind(this), 50); this.createElements(); this.initializePageCounter(); this.initializePaginator(); } else if (this.table.options.progressiveLoad) { this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.initializeProgressive(this.table.options.progressiveLoad); if (this.table.options.progressiveLoad === "scroll") { this.subscribe("scroll-vertical", this.scrollVertical.bind(this)); } } } rowAddingPosition(row, top) { var rowManager = this.table.rowManager, displayRows = rowManager.getDisplayRows(), index; if (top) { if (displayRows.length) { index = displayRows[0]; } else { if (rowManager.activeRows.length) { index = rowManager.activeRows[rowManager.activeRows.length - 1]; top = false; } } } else { if (displayRows.length) { index = displayRows[displayRows.length - 1]; top = displayRows.length < this.size ? false : true; } } return { index, top }; } calculatePageSizes() { var testElRow, testElCell; if (this.table.options.paginationSize) { this.size = this.table.options.paginationSize; } else { testElRow = document.createElement("div"); testElRow.classList.add("tabulator-row"); testElRow.style.visibility = "hidden"; testElCell = document.createElement("div"); testElCell.classList.add("tabulator-cell"); testElCell.innerHTML = "Page Row Test"; testElRow.appendChild(testElCell); this.table.rowManager.getTableElement().appendChild(testElRow); this.size = Math.floor(this.table.rowManager.getElement().clientHeight / testElRow.offsetHeight); this.table.rowManager.getTableElement().removeChild(testElRow); } this.dispatchExternal("pageSizeChanged", this.size); this.generatePageSizeSelectList(); } initialLoadComplete() { this.initialLoad = false; } remotePageParams(data, config, silent, params) { if (!this.initialLoad) { if (this.progressiveLoad && !silent || !this.progressiveLoad && !this.dataChanging) { this.reset(true); } } params.page = this.page; if (this.size) { params.size = this.size; } return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userSetPageToRow(row) { if (this.table.options.pagination) { row = this.table.rowManager.findRow(row); if (row) { return this.setPageToRow(row); } } return Promise.reject(); } userSetPageSize(size) { if (this.table.options.pagination) { this.setPageSize(size); return this.setPage(1); } else { return false; } } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// scrollVertical(top, dir) { var element, diff, margin; if (!dir && !this.table.dataLoader.loading) { element = this.table.rowManager.getElement(); diff = element.scrollHeight - element.clientHeight - top; margin = this.table.options.progressiveLoadScrollMargin || element.clientHeight * 2; if (diff < margin) { this.nextPage().catch(() => { }); } } } restOnRenderBefore(rows2, renderInPosition) { if (!renderInPosition) { if (this.mode === "local") { this.reset(); } } return rows2; } rowsUpdated() { this.refreshData(true, "all"); } createElements() { var button; this.element = document.createElement("span"); this.element.classList.add("tabulator-paginator"); this.pagesElement = document.createElement("span"); this.pagesElement.classList.add("tabulator-pages"); button = document.createElement("button"); button.classList.add("tabulator-page"); button.setAttribute("type", "button"); button.setAttribute("role", "button"); button.setAttribute("aria-label", ""); button.setAttribute("title", ""); this.firstBut = button.cloneNode(true); this.firstBut.setAttribute("data-page", "first"); this.prevBut = button.cloneNode(true); this.prevBut.setAttribute("data-page", "prev"); this.nextBut = button.cloneNode(true); this.nextBut.setAttribute("data-page", "next"); this.lastBut = button.cloneNode(true); this.lastBut.setAttribute("data-page", "last"); if (this.table.options.paginationSizeSelector) { this.pageSizeSelect = document.createElement("select"); this.pageSizeSelect.classList.add("tabulator-page-size"); } } generatePageSizeSelectList() { var pageSizes = []; if (this.pageSizeSelect) { if (Array.isArray(this.table.options.paginationSizeSelector)) { pageSizes = this.table.options.paginationSizeSelector; this.pageSizes = pageSizes; if (this.pageSizes.indexOf(this.size) == -1) { pageSizes.unshift(this.size); } } else { if (this.pageSizes.indexOf(this.size) == -1) { pageSizes = []; for (let i = 1; i < 5; i++) { pageSizes.push(this.size * i); } this.pageSizes = pageSizes; } else { pageSizes = this.pageSizes; } } while (this.pageSizeSelect.firstChild) this.pageSizeSelect.removeChild(this.pageSizeSelect.firstChild); pageSizes.forEach((item) => { var itemEl = document.createElement("option"); itemEl.value = item; if (item === true) { this.langBind("pagination|all", function(value) { itemEl.innerHTML = value; }); } else { itemEl.innerHTML = item; } this.pageSizeSelect.appendChild(itemEl); }); this.pageSizeSelect.value = this.size; } } initializePageCounter() { var counter = this.table.options.paginationCounter, pageCounter = null; if (counter) { if (typeof counter === "function") { pageCounter = counter; } else { pageCounter = _Page.pageCounters[counter]; } if (pageCounter) { this.pageCounter = pageCounter; this.pageCounterElement = document.createElement("span"); this.pageCounterElement.classList.add("tabulator-page-counter"); } else { console.warn("Pagination Error - No such page counter found: ", counter); } } } //setup pagination initializePaginator(hidden) { var pageSelectLabel, paginationCounterHolder; if (!hidden) { this.langBind("pagination|first", (value) => { this.firstBut.innerHTML = value; }); this.langBind("pagination|first_title", (value) => { this.firstBut.setAttribute("aria-label", value); this.firstBut.setAttribute("title", value); }); this.langBind("pagination|prev", (value) => { this.prevBut.innerHTML = value; }); this.langBind("pagination|prev_title", (value) => { this.prevBut.setAttribute("aria-label", value); this.prevBut.setAttribute("title", value); }); this.langBind("pagination|next", (value) => { this.nextBut.innerHTML = value; }); this.langBind("pagination|next_title", (value) => { this.nextBut.setAttribute("aria-label", value); this.nextBut.setAttribute("title", value); }); this.langBind("pagination|last", (value) => { this.lastBut.innerHTML = value; }); this.langBind("pagination|last_title", (value) => { this.lastBut.setAttribute("aria-label", value); this.lastBut.setAttribute("title", value); }); this.firstBut.addEventListener("click", () => { this.setPage(1); }); this.prevBut.addEventListener("click", () => { this.previousPage(); }); this.nextBut.addEventListener("click", () => { this.nextPage(); }); this.lastBut.addEventListener("click", () => { this.setPage(this.max); }); if (this.table.options.paginationElement) { this.element = this.table.options.paginationElement; } if (this.pageSizeSelect) { pageSelectLabel = document.createElement("label"); this.langBind("pagination|page_size", (value) => { this.pageSizeSelect.setAttribute("aria-label", value); this.pageSizeSelect.setAttribute("title", value); pageSelectLabel.innerHTML = value; }); this.element.appendChild(pageSelectLabel); this.element.appendChild(this.pageSizeSelect); this.pageSizeSelect.addEventListener("change", (e) => { this.setPageSize(this.pageSizeSelect.value == "true" ? true : this.pageSizeSelect.value); this.setPage(1); }); } this.element.appendChild(this.firstBut); this.element.appendChild(this.prevBut); this.element.appendChild(this.pagesElement); this.element.appendChild(this.nextBut); this.element.appendChild(this.lastBut); if (!this.table.options.paginationElement) { if (this.table.options.paginationCounter) { if (this.table.options.paginationCounterElement) { if (this.table.options.paginationCounterElement instanceof HTMLElement) { this.table.options.paginationCounterElement.appendChild(this.pageCounterElement); } else if (typeof this.table.options.paginationCounterElement === "string") { paginationCounterHolder = document.querySelector(this.table.options.paginationCounterElement); if (paginationCounterHolder) { paginationCounterHolder.appendChild(this.pageCounterElement); } else { console.warn("Pagination Error - Unable to find element matching paginationCounterElement selector:", this.table.options.paginationCounterElement); } } } else { this.footerAppend(this.pageCounterElement); } } this.footerAppend(this.element); } this.page = this.table.options.paginationInitialPage; this.count = this.table.options.paginationButtonCount; } this.mode = this.table.options.paginationMode; } initializeProgressive(mode) { this.initializePaginator(true); this.mode = "progressive_" + mode; this.progressiveLoad = true; } trackChanges() { this.dispatch("page-changed"); } //calculate maximum page from number of rows setMaxRows(rowCount) { if (!rowCount) { this.max = 1; } else { this.max = this.size === true ? 1 : Math.ceil(rowCount / this.size); } if (this.page > this.max) { this.page = this.max; } } //reset to first page without triggering action reset(force) { if (!this.initialLoad) { if (this.mode == "local" || force) { this.page = 1; this.trackChanges(); } } } //set the maximum page setMaxPage(max) { max = parseInt(max); this.max = max || 1; if (this.page > this.max) { this.page = this.max; this.trigger(); } } //set current page number setPage(page) { switch (page) { case "first": return this.setPage(1); case "prev": return this.previousPage(); case "next": return this.nextPage(); case "last": return this.setPage(this.max); } page = parseInt(page); if (page > 0 && page <= this.max || this.mode !== "local") { this.page = page; this.trackChanges(); return this.trigger(); } else { console.warn("Pagination Error - Requested page is out of range of 1 - " + this.max + ":", page); return Promise.reject(); } } setPageToRow(row) { var rows2 = this.displayRows(-1); var index = rows2.indexOf(row); if (index > -1) { var page = this.size === true ? 1 : Math.ceil((index + 1) / this.size); return this.setPage(page); } else { console.warn("Pagination Error - Requested row is not visible"); return Promise.reject(); } } setPageSize(size) { if (size !== true) { size = parseInt(size); } if (size > 0) { this.size = size; this.dispatchExternal("pageSizeChanged", size); } if (this.pageSizeSelect) { this.generatePageSizeSelectList(); } this.trackChanges(); } _setPageCounter(totalRows, size, currentRow) { var content; if (this.pageCounter) { if (this.mode === "remote") { size = this.size; currentRow = (this.page - 1) * this.size + 1; totalRows = this.remoteRowCountEstimate; } content = this.pageCounter.call(this, size, currentRow, this.page, totalRows, this.max); switch (typeof content) { case "object": if (content instanceof Node) { while (this.pageCounterElement.firstChild) this.pageCounterElement.removeChild(this.pageCounterElement.firstChild); this.pageCounterElement.appendChild(content); } else { this.pageCounterElement.innerHTML = ""; if (content != null) { console.warn("Page Counter Error - Page Counter has returned a type of object, the only valid page counter object return is an instance of Node, the page counter returned:", content); } } break; case "undefined": this.pageCounterElement.innerHTML = ""; break; default: this.pageCounterElement.innerHTML = content; } } } //setup the pagination buttons _setPageButtons() { let leftSize = Math.floor((this.count - 1) / 2); let rightSize = Math.ceil((this.count - 1) / 2); let min = this.max - this.page + leftSize + 1 < this.count ? this.max - this.count + 1 : Math.max(this.page - leftSize, 1); let max = this.page <= rightSize ? Math.min(this.count, this.max) : Math.min(this.page + rightSize, this.max); while (this.pagesElement.firstChild) this.pagesElement.removeChild(this.pagesElement.firstChild); if (this.page == 1) { this.firstBut.disabled = true; this.prevBut.disabled = true; } else { this.firstBut.disabled = false; this.prevBut.disabled = false; } if (this.page == this.max) { this.lastBut.disabled = true; this.nextBut.disabled = true; } else { this.lastBut.disabled = false; this.nextBut.disabled = false; } for (let i = min; i <= max; i++) { if (i > 0 && i <= this.max) { this.pagesElement.appendChild(this._generatePageButton(i)); } } this.footerRedraw(); } _generatePageButton(page) { var button = document.createElement("button"); button.classList.add("tabulator-page"); if (page == this.page) { button.classList.add("active"); } button.setAttribute("type", "button"); button.setAttribute("role", "button"); this.langBind("pagination|page_title", (value) => { button.setAttribute("aria-label", value + " " + page); button.setAttribute("title", value + " " + page); }); button.setAttribute("data-page", page); button.textContent = page; button.addEventListener("click", (e) => { this.setPage(page); }); return button; } //previous page previousPage() { if (this.page > 1) { this.page--; this.trackChanges(); return this.trigger(); } else { console.warn("Pagination Error - Previous page would be less than page 1:", 0); return Promise.reject(); } } //next page nextPage() { if (this.page < this.max) { this.page++; this.trackChanges(); return this.trigger(); } else { if (!this.progressiveLoad) { console.warn("Pagination Error - Next page would be greater than maximum page of " + this.max + ":", this.max + 1); } return Promise.reject(); } } //return current page number getPage() { return this.page; } //return max page number getPageMax() { return this.max; } getPageSize(size) { return this.size; } getMode() { return this.mode; } //return appropriate rows for current page getRows(data) { var actualRowPageSize = 0, output, start, end, actualStartRow; var actualRows = data.filter((row) => { return row.type === "row"; }); if (this.mode == "local") { output = []; this.setMaxRows(data.length); if (this.size === true) { start = 0; end = data.length; } else { start = this.size * (this.page - 1); end = start + parseInt(this.size); } this._setPageButtons(); for (let i = start; i < end; i++) { let row = data[i]; if (row) { output.push(row); if (row.type === "row") { if (!actualStartRow) { actualStartRow = row; } actualRowPageSize++; } } } this._setPageCounter(actualRows.length, actualRowPageSize, actualStartRow ? actualRows.indexOf(actualStartRow) + 1 : 0); return output; } else { this._setPageButtons(); this._setPageCounter(actualRows.length); return data.slice(0); } } trigger() { var left; switch (this.mode) { case "local": left = this.table.rowManager.scrollLeft; this.refreshData(); this.table.rowManager.scrollHorizontal(left); this.dispatchExternal("pageLoaded", this.getPage()); return Promise.resolve(); case "remote": this.dataChanging = true; return this.reloadData(null).finally(() => { this.dataChanging = false; }); case "progressive_load": case "progressive_scroll": return this.reloadData(null, true); default: console.warn("Pagination Error - no such pagination mode:", this.mode); return Promise.reject(); } } _parseRemoteData(data) { var margin, paginationOutOfRange; if (typeof data.last_page === "undefined") { console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").last_page || "last_page") + "' property"); } if (data.data) { this.max = parseInt(data.last_page) || 1; this.remoteRowCountEstimate = typeof data.last_row !== "undefined" ? data.last_row : data.last_page * this.size - (this.page == data.last_page ? this.size - data.data.length : 0); if (this.progressiveLoad) { switch (this.mode) { case "progressive_load": if (this.page == 1) { this.table.rowManager.setData(data.data, false, this.page == 1); } else { this.table.rowManager.addRows(data.data); } if (this.page < this.max) { setTimeout(() => { this.nextPage(); }, this.table.options.progressiveLoadDelay); } break; case "progressive_scroll": data = this.page === 1 ? data.data : this.table.rowManager.getData().concat(data.data); this.table.rowManager.setData(data, this.page !== 1, this.page == 1); margin = this.table.options.progressiveLoadScrollMargin || this.table.rowManager.element.clientHeight * 2; if (this.table.rowManager.element.scrollHeight <= this.table.rowManager.element.clientHeight + margin) { if (this.page < this.max) { setTimeout(() => { this.nextPage(); }); } } break; } return false; } else { if (this.page > this.max) { console.warn("Remote Pagination Error - Server returned last page value lower than the current page"); paginationOutOfRange = this.options("paginationOutOfRange"); if (paginationOutOfRange) { return this.setPage(typeof paginationOutOfRange === "function" ? paginationOutOfRange.call(this, this.page, this.max) : paginationOutOfRange); } } this.dispatchExternal("pageLoaded", this.getPage()); } } else { console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").data || "data") + "' property"); } return data.data; } //handle the footer element being redrawn footerRedraw() { var footer = this.table.footerManager.containerElement; if (Math.ceil(footer.clientWidth) - footer.scrollWidth < 0) { this.pagesElement.style.display = "none"; } else { this.pagesElement.style.display = ""; if (Math.ceil(footer.clientWidth) - footer.scrollWidth < 0) { this.pagesElement.style.display = "none"; } } } }; __publicField(_Page, "moduleName", "page"); //load defaults __publicField(_Page, "pageCounters", defaultPageCounters); let Page = _Page; var defaultReaders = { local: function(id, type) { var data = localStorage.getItem(id + "-" + type); return data ? JSON.parse(data) : false; }, cookie: function(id, type) { var cookie = document.cookie, key = id + "-" + type, cookiePos = cookie.indexOf(key + "="), end, data; if (cookiePos > -1) { cookie = cookie.slice(cookiePos); end = cookie.indexOf(";"); if (end > -1) { cookie = cookie.slice(0, end); } data = cookie.replace(key + "=", ""); } return data ? JSON.parse(data) : false; } }; var defaultWriters = { local: function(id, type, data) { localStorage.setItem(id + "-" + type, JSON.stringify(data)); }, cookie: function(id, type, data) { var expireDate = /* @__PURE__ */ new Date(); expireDate.setDate(expireDate.getDate() + 1e4); document.cookie = id + "-" + type + "=" + JSON.stringify(data) + "; expires=" + expireDate.toUTCString(); } }; const _Persistence = class _Persistence extends Module { constructor(table) { super(table); this.mode = ""; this.id = ""; this.defWatcherBlock = false; this.config = {}; this.readFunc = false; this.writeFunc = false; this.registerTableOption("persistence", false); this.registerTableOption("persistenceID", ""); this.registerTableOption("persistenceMode", true); this.registerTableOption("persistenceReaderFunc", false); this.registerTableOption("persistenceWriterFunc", false); } // Test for whether localStorage is available for use. localStorageTest() { var testKey = "_tabulator_test"; try { window.localStorage.setItem(testKey, testKey); window.localStorage.removeItem(testKey); return true; } catch (e) { return false; } } //setup parameters initialize() { if (this.table.options.persistence) { var mode = this.table.options.persistenceMode, id = this.table.options.persistenceID, retrievedData; this.mode = mode !== true ? mode : this.localStorageTest() ? "local" : "cookie"; if (this.table.options.persistenceReaderFunc) { if (typeof this.table.options.persistenceReaderFunc === "function") { this.readFunc = this.table.options.persistenceReaderFunc; } else { if (_Persistence.readers[this.table.options.persistenceReaderFunc]) { this.readFunc = _Persistence.readers[this.table.options.persistenceReaderFunc]; } else { console.warn("Persistence Read Error - invalid reader set", this.table.options.persistenceReaderFunc); } } } else { if (_Persistence.readers[this.mode]) { this.readFunc = _Persistence.readers[this.mode]; } else { console.warn("Persistence Read Error - invalid reader set", this.mode); } } if (this.table.options.persistenceWriterFunc) { if (typeof this.table.options.persistenceWriterFunc === "function") { this.writeFunc = this.table.options.persistenceWriterFunc; } else { if (_Persistence.writers[this.table.options.persistenceWriterFunc]) { this.writeFunc = _Persistence.writers[this.table.options.persistenceWriterFunc]; } else { console.warn("Persistence Write Error - invalid reader set", this.table.options.persistenceWriterFunc); } } } else { if (_Persistence.writers[this.mode]) { this.writeFunc = _Persistence.writers[this.mode]; } else { console.warn("Persistence Write Error - invalid writer set", this.mode); } } this.id = "tabulator-" + (id || (this.table.element.getAttribute("id") || "")); this.config = { sort: this.table.options.persistence === true || this.table.options.persistence.sort, filter: this.table.options.persistence === true || this.table.options.persistence.filter, headerFilter: this.table.options.persistence === true || this.table.options.persistence.headerFilter, group: this.table.options.persistence === true || this.table.options.persistence.group, page: this.table.options.persistence === true || this.table.options.persistence.page, columns: this.table.options.persistence === true ? ["title", "width", "visible"] : this.table.options.persistence.columns }; if (this.config.page) { retrievedData = this.retrieveData("page"); if (retrievedData) { if (typeof retrievedData.paginationSize !== "undefined" && (this.config.page === true || this.config.page.size)) { this.table.options.paginationSize = retrievedData.paginationSize; } if (typeof retrievedData.paginationInitialPage !== "undefined" && (this.config.page === true || this.config.page.page)) { this.table.options.paginationInitialPage = retrievedData.paginationInitialPage; } } } if (this.config.group) { retrievedData = this.retrieveData("group"); if (retrievedData) { if (typeof retrievedData.groupBy !== "undefined" && (this.config.group === true || this.config.group.groupBy)) { this.table.options.groupBy = retrievedData.groupBy; } if (typeof retrievedData.groupStartOpen !== "undefined" && (this.config.group === true || this.config.group.groupStartOpen)) { this.table.options.groupStartOpen = retrievedData.groupStartOpen; } if (typeof retrievedData.groupHeader !== "undefined" && (this.config.group === true || this.config.group.groupHeader)) { this.table.options.groupHeader = retrievedData.groupHeader; } } } if (this.config.columns) { this.table.options.columns = this.load("columns", this.table.options.columns); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("column-show", this.save.bind(this, "columns")); this.subscribe("column-hide", this.save.bind(this, "columns")); this.subscribe("column-moved", this.save.bind(this, "columns")); } this.subscribe("table-built", this.tableBuilt.bind(this), 0); this.subscribe("table-redraw", this.tableRedraw.bind(this)); this.subscribe("filter-changed", this.eventSave.bind(this, "filter")); this.subscribe("filter-changed", this.eventSave.bind(this, "headerFilter")); this.subscribe("sort-changed", this.eventSave.bind(this, "sort")); this.subscribe("group-changed", this.eventSave.bind(this, "group")); this.subscribe("page-changed", this.eventSave.bind(this, "page")); this.subscribe("column-resized", this.eventSave.bind(this, "columns")); this.subscribe("column-width", this.eventSave.bind(this, "columns")); this.subscribe("layout-refreshed", this.eventSave.bind(this, "columns")); } this.registerTableFunction("getColumnLayout", this.getColumnLayout.bind(this)); this.registerTableFunction("setColumnLayout", this.setColumnLayout.bind(this)); } eventSave(type) { if (this.config[type]) { this.save(type); } } tableBuilt() { var sorters, filters, headerFilters; if (this.config.sort) { sorters = this.load("sort"); if (!sorters === false) { this.table.options.initialSort = sorters; } } if (this.config.filter) { filters = this.load("filter"); if (!filters === false) { this.table.options.initialFilter = filters; } } if (this.config.headerFilter) { headerFilters = this.load("headerFilter"); if (!headerFilters === false) { this.table.options.initialHeaderFilter = headerFilters; } } } tableRedraw(force) { if (force && this.config.columns) { this.save("columns"); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// getColumnLayout() { return this.parseColumns(this.table.columnManager.getColumns()); } setColumnLayout(layout) { this.table.columnManager.setColumns(this.mergeDefinition(this.table.options.columns, layout, true)); return true; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumn(column) { var def, keys; if (this.config.columns) { this.defWatcherBlock = true; def = column.getDefinition(); keys = this.config.columns === true ? Object.keys(def) : this.config.columns; keys.forEach((key) => { var props = Object.getOwnPropertyDescriptor(def, key); var value = def[key]; if (props) { Object.defineProperty(def, key, { set: (newValue) => { value = newValue; if (!this.defWatcherBlock) { this.save("columns"); } if (props.set) { props.set(newValue); } }, get: () => { if (props.get) { props.get(); } return value; } }); } }); this.defWatcherBlock = false; } } //load saved definitions load(type, current) { var data = this.retrieveData(type); if (current) { data = data ? this.mergeDefinition(current, data) : current; } return data; } //retrieve data from memory retrieveData(type) { return this.readFunc ? this.readFunc(this.id, type) : false; } //merge old and new column definitions mergeDefinition(oldCols, newCols, mergeAllNew) { var output = []; newCols = newCols || []; newCols.forEach((column, to) => { var from = this._findColumn(oldCols, column), keys; if (from) { if (mergeAllNew) { keys = Object.keys(column); } else if (this.config.columns === true || this.config.columns == void 0) { keys = Object.keys(from); keys.push("width"); } else { keys = this.config.columns; } keys.forEach((key) => { if (key !== "columns" && typeof column[key] !== "undefined") { from[key] = column[key]; } }); if (from.columns) { from.columns = this.mergeDefinition(from.columns, column.columns); } output.push(from); } }); oldCols.forEach((column, i) => { var from = this._findColumn(newCols, column); if (!from) { if (output.length > i) { output.splice(i, 0, column); } else { output.push(column); } } }); return output; } //find matching columns _findColumn(columns, subject) { var type = subject.columns ? "group" : subject.field ? "field" : "object"; return columns.find(function(col) { switch (type) { case "group": return col.title === subject.title && col.columns.length === subject.columns.length; case "field": return col.field === subject.field; case "object": return col === subject; } }); } //save data save(type) { var data = {}; switch (type) { case "columns": data = this.parseColumns(this.table.columnManager.getColumns()); break; case "filter": data = this.table.modules.filter.getFilters(); break; case "headerFilter": data = this.table.modules.filter.getHeaderFilters(); break; case "sort": data = this.validateSorters(this.table.modules.sort.getSort()); break; case "group": data = this.getGroupConfig(); break; case "page": data = this.getPageConfig(); break; } if (this.writeFunc) { this.writeFunc(this.id, type, data); } } //ensure sorters contain no function data validateSorters(data) { data.forEach(function(item) { item.column = item.field; delete item.field; }); return data; } getGroupConfig() { var data = {}; if (this.config.group) { if (this.config.group === true || this.config.group.groupBy) { data.groupBy = this.table.options.groupBy; } if (this.config.group === true || this.config.group.groupStartOpen) { data.groupStartOpen = this.table.options.groupStartOpen; } if (this.config.group === true || this.config.group.groupHeader) { data.groupHeader = this.table.options.groupHeader; } } return data; } getPageConfig() { var data = {}; if (this.config.page) { if (this.config.page === true || this.config.page.size) { data.paginationSize = this.table.modules.page.getPageSize(); } if (this.config.page === true || this.config.page.page) { data.paginationInitialPage = this.table.modules.page.getPage(); } } return data; } //parse columns for data to store parseColumns(columns) { var definitions = [], excludedKeys = ["headerContextMenu", "headerMenu", "contextMenu", "clickMenu"]; columns.forEach((column) => { var defStore = {}, colDef = column.getDefinition(), keys; if (column.isGroup) { defStore.title = colDef.title; defStore.columns = this.parseColumns(column.getColumns()); } else { defStore.field = column.getField(); if (this.config.columns === true || this.config.columns == void 0) { keys = Object.keys(colDef); keys.push("width"); keys.push("visible"); } else { keys = this.config.columns; } keys.forEach((key) => { switch (key) { case "width": defStore.width = column.getWidth(); break; case "visible": defStore.visible = column.visible; break; default: if (typeof colDef[key] !== "function" && excludedKeys.indexOf(key) === -1) { defStore[key] = colDef[key]; } } }); } definitions.push(defStore); }); return definitions; } }; __publicField(_Persistence, "moduleName", "persistence"); __publicField(_Persistence, "moduleInitOrder", -10); //load defaults __publicField(_Persistence, "readers", defaultReaders); __publicField(_Persistence, "writers", defaultWriters); let Persistence = _Persistence; class Popup2 extends Module { constructor(table) { super(table); this.columnSubscribers = {}; this.registerTableOption("rowContextPopup", false); this.registerTableOption("rowClickPopup", false); this.registerTableOption("rowDblClickPopup", false); this.registerTableOption("groupContextPopup", false); this.registerTableOption("groupClickPopup", false); this.registerTableOption("groupDblClickPopup", false); this.registerColumnOption("headerContextPopup"); this.registerColumnOption("headerClickPopup"); this.registerColumnOption("headerDblClickPopup"); this.registerColumnOption("headerPopup"); this.registerColumnOption("headerPopupIcon"); this.registerColumnOption("contextPopup"); this.registerColumnOption("clickPopup"); this.registerColumnOption("dblClickPopup"); this.registerComponentFunction("cell", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("column", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("row", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("group", "popup", this._componentPopupCall.bind(this)); } initialize() { this.initializeRowWatchers(); this.initializeGroupWatchers(); this.subscribe("column-init", this.initializeColumn.bind(this)); } _componentPopupCall(component, contents, position) { this.loadPopupEvent(contents, null, component, position); } initializeRowWatchers() { if (this.table.options.rowContextPopup) { this.subscribe("row-contextmenu", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup)); this.table.on("rowTapHold", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup)); } if (this.table.options.rowClickPopup) { this.subscribe("row-click", this.loadPopupEvent.bind(this, this.table.options.rowClickPopup)); } if (this.table.options.rowDblClickPopup) { this.subscribe("row-dblclick", this.loadPopupEvent.bind(this, this.table.options.rowDblClickPopup)); } } initializeGroupWatchers() { if (this.table.options.groupContextPopup) { this.subscribe("group-contextmenu", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup)); this.table.on("groupTapHold", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup)); } if (this.table.options.groupClickPopup) { this.subscribe("group-click", this.loadPopupEvent.bind(this, this.table.options.groupClickPopup)); } if (this.table.options.groupDblClickPopup) { this.subscribe("group-dblclick", this.loadPopupEvent.bind(this, this.table.options.groupDblClickPopup)); } } initializeColumn(column) { var def = column.definition; if (def.headerContextPopup && !this.columnSubscribers.headerContextPopup) { this.columnSubscribers.headerContextPopup = this.loadPopupTableColumnEvent.bind(this, "headerContextPopup"); this.subscribe("column-contextmenu", this.columnSubscribers.headerContextPopup); this.table.on("headerTapHold", this.loadPopupTableColumnEvent.bind(this, "headerContextPopup")); } if (def.headerClickPopup && !this.columnSubscribers.headerClickPopup) { this.columnSubscribers.headerClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerClickPopup"); this.subscribe("column-click", this.columnSubscribers.headerClickPopup); } if (def.headerDblClickPopup && !this.columnSubscribers.headerDblClickPopup) { this.columnSubscribers.headerDblClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerDblClickPopup"); this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickPopup); } if (def.headerPopup) { this.initializeColumnHeaderPopup(column); } if (def.contextPopup && !this.columnSubscribers.contextPopup) { this.columnSubscribers.contextPopup = this.loadPopupTableCellEvent.bind(this, "contextPopup"); this.subscribe("cell-contextmenu", this.columnSubscribers.contextPopup); this.table.on("cellTapHold", this.loadPopupTableCellEvent.bind(this, "contextPopup")); } if (def.clickPopup && !this.columnSubscribers.clickPopup) { this.columnSubscribers.clickPopup = this.loadPopupTableCellEvent.bind(this, "clickPopup"); this.subscribe("cell-click", this.columnSubscribers.clickPopup); } if (def.dblClickPopup && !this.columnSubscribers.dblClickPopup) { this.columnSubscribers.dblClickPopup = this.loadPopupTableCellEvent.bind(this, "dblClickPopup"); this.subscribe("cell-click", this.columnSubscribers.dblClickPopup); } } initializeColumnHeaderPopup(column) { var icon = column.definition.headerPopupIcon, headerPopupEl; headerPopupEl = document.createElement("span"); headerPopupEl.classList.add("tabulator-header-popup-button"); if (icon) { if (typeof icon === "function") { icon = icon(column.getComponent()); } if (icon instanceof HTMLElement) { headerPopupEl.appendChild(icon); } else { headerPopupEl.innerHTML = icon; } } else { headerPopupEl.innerHTML = "⋮"; } headerPopupEl.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); this.loadPopupEvent(column.definition.headerPopup, e, column); }); column.titleElement.insertBefore(headerPopupEl, column.titleElement.firstChild); } loadPopupTableCellEvent(option, e, cell) { if (cell._cell) { cell = cell._cell; } if (cell.column.definition[option]) { this.loadPopupEvent(cell.column.definition[option], e, cell); } } loadPopupTableColumnEvent(option, e, column) { if (column._column) { column = column._column; } if (column.definition[option]) { this.loadPopupEvent(column.definition[option], e, column); } } loadPopupEvent(contents, e, component, position) { var renderedCallback; function onRendered(callback) { renderedCallback = callback; } if (component._group) { component = component._group; } else if (component._row) { component = component._row; } contents = typeof contents == "function" ? contents.call(this.table, e, component.getComponent(), onRendered) : contents; this.loadPopup(e, component, contents, renderedCallback, position); } loadPopup(e, component, contents, renderedCallback, position) { var touch = !(e instanceof MouseEvent), contentsEl, popup; if (contents instanceof HTMLElement) { contentsEl = contents; } else { contentsEl = document.createElement("div"); contentsEl.innerHTML = contents; } contentsEl.classList.add("tabulator-popup"); contentsEl.addEventListener("click", (e2) => { e2.stopPropagation(); }); if (!touch) { e.preventDefault(); } popup = this.popup(contentsEl); if (typeof renderedCallback === "function") { popup.renderCallback(renderedCallback); } if (e) { popup.show(e); } else { popup.show(component.getElement(), position || "center"); } popup.hideOnBlur(() => { this.dispatchExternal("popupClosed", component.getComponent()); }); this.dispatchExternal("popupOpened", component.getComponent()); } } __publicField(Popup2, "moduleName", "popup"); class Print extends Module { constructor(table) { super(table); this.element = false; this.manualBlock = false; this.beforeprintEventHandler = null; this.afterprintEventHandler = null; this.registerTableOption("printAsHtml", false); this.registerTableOption("printFormatter", false); this.registerTableOption("printHeader", false); this.registerTableOption("printFooter", false); this.registerTableOption("printStyled", true); this.registerTableOption("printRowRange", "visible"); this.registerTableOption("printConfig", {}); this.registerColumnOption("print"); this.registerColumnOption("titlePrint"); } initialize() { if (this.table.options.printAsHtml) { this.beforeprintEventHandler = this.replaceTable.bind(this); this.afterprintEventHandler = this.cleanup.bind(this); window.addEventListener("beforeprint", this.beforeprintEventHandler); window.addEventListener("afterprint", this.afterprintEventHandler); this.subscribe("table-destroy", this.destroy.bind(this)); } this.registerTableFunction("print", this.printFullscreen.bind(this)); } destroy() { if (this.table.options.printAsHtml) { window.removeEventListener("beforeprint", this.beforeprintEventHandler); window.removeEventListener("afterprint", this.afterprintEventHandler); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// replaceTable() { if (!this.manualBlock) { this.element = document.createElement("div"); this.element.classList.add("tabulator-print-table"); this.element.appendChild(this.table.modules.export.generateTable(this.table.options.printConfig, this.table.options.printStyled, this.table.options.printRowRange, "print")); this.table.element.style.display = "none"; this.table.element.parentNode.insertBefore(this.element, this.table.element); } } cleanup() { document.body.classList.remove("tabulator-print-fullscreen-hide"); if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); this.table.element.style.display = ""; } } printFullscreen(visible, style, config) { var scrollX = window.scrollX, scrollY = window.scrollY, headerEl = document.createElement("div"), footerEl = document.createElement("div"), tableEl = this.table.modules.export.generateTable(typeof config != "undefined" ? config : this.table.options.printConfig, typeof style != "undefined" ? style : this.table.options.printStyled, visible || this.table.options.printRowRange, "print"), headerContent, footerContent; this.manualBlock = true; this.element = document.createElement("div"); this.element.classList.add("tabulator-print-fullscreen"); if (this.table.options.printHeader) { headerEl.classList.add("tabulator-print-header"); headerContent = typeof this.table.options.printHeader == "function" ? this.table.options.printHeader.call(this.table) : this.table.options.printHeader; if (typeof headerContent == "string") { headerEl.innerHTML = headerContent; } else { headerEl.appendChild(headerContent); } this.element.appendChild(headerEl); } this.element.appendChild(tableEl); if (this.table.options.printFooter) { footerEl.classList.add("tabulator-print-footer"); footerContent = typeof this.table.options.printFooter == "function" ? this.table.options.printFooter.call(this.table) : this.table.options.printFooter; if (typeof footerContent == "string") { footerEl.innerHTML = footerContent; } else { footerEl.appendChild(footerContent); } this.element.appendChild(footerEl); } document.body.classList.add("tabulator-print-fullscreen-hide"); document.body.appendChild(this.element); if (this.table.options.printFormatter) { this.table.options.printFormatter(this.element, tableEl); } window.print(); this.cleanup(); window.scrollTo(scrollX, scrollY); this.manualBlock = false; } } __publicField(Print, "moduleName", "print"); class ReactiveData extends Module { constructor(table) { super(table); this.data = false; this.blocked = false; this.origFuncs = {}; this.currentVersion = 0; this.registerTableOption("reactiveData", false); } initialize() { if (this.table.options.reactiveData) { this.subscribe("cell-value-save-before", this.block.bind(this, "cellsave")); this.subscribe("cell-value-save-after", this.unblock.bind(this, "cellsave")); this.subscribe("row-data-save-before", this.block.bind(this, "rowsave")); this.subscribe("row-data-save-after", this.unblock.bind(this, "rowsave")); this.subscribe("row-data-init-after", this.watchRow.bind(this)); this.subscribe("data-processing", this.watchData.bind(this)); this.subscribe("table-destroy", this.unwatchData.bind(this)); } } watchData(data) { var self = this, version; this.currentVersion++; version = this.currentVersion; this.unwatchData(); this.data = data; this.origFuncs.push = data.push; Object.defineProperty(this.data, "push", { enumerable: false, configurable: true, value: function() { var args = Array.from(arguments), result; if (!self.blocked && version === self.currentVersion) { self.block("data-push"); args.forEach((arg) => { self.table.rowManager.addRowActual(arg, false); }); result = Array.prototype.push.apply(data, arguments); self.unblock("data-push"); } return result; } }); this.origFuncs.unshift = data.unshift; Object.defineProperty(this.data, "unshift", { enumerable: false, configurable: true, value: function() { var args = Array.from(arguments), result; if (!self.blocked && version === self.currentVersion) { self.block("data-unshift"); args.forEach((arg) => { self.table.rowManager.addRowActual(arg, true); }); result = Array.prototype.unshift.apply(data, arguments); self.unblock("data-unshift"); } return result; } }); this.origFuncs.shift = data.shift; Object.defineProperty(this.data, "shift", { enumerable: false, configurable: true, value: function() { var row, result; if (!self.blocked && version === self.currentVersion) { self.block("data-shift"); if (self.data.length) { row = self.table.rowManager.getRowFromDataObject(self.data[0]); if (row) { row.deleteActual(); } } result = Array.prototype.shift.call(data); self.unblock("data-shift"); } return result; } }); this.origFuncs.pop = data.pop; Object.defineProperty(this.data, "pop", { enumerable: false, configurable: true, value: function() { var row, result; if (!self.blocked && version === self.currentVersion) { self.block("data-pop"); if (self.data.length) { row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]); if (row) { row.deleteActual(); } } result = Array.prototype.pop.call(data); self.unblock("data-pop"); } return result; } }); this.origFuncs.splice = data.splice; Object.defineProperty(this.data, "splice", { enumerable: false, configurable: true, value: function() { var args = Array.from(arguments), start = args[0] < 0 ? data.length + args[0] : args[0], end = args[1], newRows = args[2] ? args.slice(2) : false, startRow, result; if (!self.blocked && version === self.currentVersion) { self.block("data-splice"); if (newRows) { startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false; if (startRow) { newRows.forEach((rowData) => { self.table.rowManager.addRowActual(rowData, true, startRow, true); }); } else { newRows = newRows.slice().reverse(); newRows.forEach((rowData) => { self.table.rowManager.addRowActual(rowData, true, false, true); }); } } if (end !== 0) { var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end); oldRows.forEach((rowData, i) => { var row = self.table.rowManager.getRowFromDataObject(rowData); if (row) { row.deleteActual(i !== oldRows.length - 1); } }); } if (newRows || end !== 0) { self.table.rowManager.reRenderInPosition(); } result = Array.prototype.splice.apply(data, arguments); self.unblock("data-splice"); } return result; } }); } unwatchData() { if (this.data !== false) { for (var key in this.origFuncs) { Object.defineProperty(this.data, key, { enumerable: true, configurable: true, writable: true, value: this.origFuncs[key] }); } } } watchRow(row) { var data = row.getData(); for (var key in data) { this.watchKey(row, data, key); } if (this.table.options.dataTree) { this.watchTreeChildren(row); } } watchTreeChildren(row) { var self = this, childField = row.getData()[this.table.options.dataTreeChildField]; if (childField) { Object.defineProperty(childField, "push", { enumerable: false, configurable: true, value: function() { var result; if (!self.blocked) { self.block("tree-push"); result = Array.prototype.push.apply(childField, arguments); self.rebuildTree(row); self.unblock("tree-push"); } return result; } }); Object.defineProperty(childField, "unshift", { enumerable: false, configurable: true, value: function() { var result; if (!self.blocked) { self.block("tree-unshift"); result = Array.prototype.unshift.apply(childField, arguments); self.rebuildTree(row); self.unblock("tree-unshift"); } return result; } }); Object.defineProperty(childField, "shift", { enumerable: false, configurable: true, value: function() { var result; if (!self.blocked) { self.block("tree-shift"); result = Array.prototype.shift.call(childField); self.rebuildTree(row); self.unblock("tree-shift"); } return result; } }); Object.defineProperty(childField, "pop", { enumerable: false, configurable: true, value: function() { var result; if (!self.blocked) { self.block("tree-pop"); result = Array.prototype.pop.call(childField); self.rebuildTree(row); self.unblock("tree-pop"); } return result; } }); Object.defineProperty(childField, "splice", { enumerable: false, configurable: true, value: function() { var result; if (!self.blocked) { self.block("tree-splice"); result = Array.prototype.splice.apply(childField, arguments); self.rebuildTree(row); self.unblock("tree-splice"); } return result; } }); } } rebuildTree(row) { this.table.modules.dataTree.initializeRow(row); this.table.modules.dataTree.layoutRow(row); this.table.rowManager.refreshActiveData("tree", false, true); } watchKey(row, data, key) { var self = this, props = Object.getOwnPropertyDescriptor(data, key), value = data[key], version = this.currentVersion; Object.defineProperty(data, key, { set: (newValue) => { value = newValue; if (!self.blocked && version === self.currentVersion) { self.block("key"); var update = {}; update[key] = newValue; row.updateData(update); self.unblock("key"); } if (props.set) { props.set(newValue); } }, get: () => { if (props.get) { props.get(); } return value; } }); } unwatchRow(row) { var data = row.getData(); for (var key in data) { Object.defineProperty(data, key, { value: data[key] }); } } block(key) { if (!this.blocked) { this.blocked = key; } } unblock(key) { if (this.blocked === key) { this.blocked = false; } } } __publicField(ReactiveData, "moduleName", "reactiveData"); class ResizeColumns extends Module { constructor(table) { super(table); this.startColumn = false; this.startX = false; this.startWidth = false; this.latestX = false; this.handle = null; this.initialNextColumn = null; this.nextColumn = null; this.initialized = false; this.registerColumnOption("resizable", true); this.registerTableOption("resizableColumnFit", false); this.registerTableOption("resizableColumnGuide", false); } initialize() { this.subscribe("column-rendered", this.layoutColumnHeader.bind(this)); } initializeEventWatchers() { if (!this.initialized) { this.subscribe("cell-rendered", this.layoutCellHandles.bind(this)); this.subscribe("cell-delete", this.deInitializeComponent.bind(this)); this.subscribe("cell-height", this.resizeHandle.bind(this)); this.subscribe("column-moved", this.columnLayoutUpdated.bind(this)); this.subscribe("column-hide", this.deInitializeColumn.bind(this)); this.subscribe("column-show", this.columnLayoutUpdated.bind(this)); this.subscribe("column-width", this.columnWidthUpdated.bind(this)); this.subscribe("column-delete", this.deInitializeComponent.bind(this)); this.subscribe("column-height", this.resizeHandle.bind(this)); this.initialized = true; } } layoutCellHandles(cell) { if (cell.row.type === "row") { this.deInitializeComponent(cell); this.initializeColumn("cell", cell, cell.column, cell.element); } } layoutColumnHeader(column) { if (column.definition.resizable) { this.initializeEventWatchers(); this.deInitializeComponent(column); this.initializeColumn("header", column, column, column.element); } } columnLayoutUpdated(column) { var prev = column.prevColumn(); this.reinitializeColumn(column); if (prev) { this.reinitializeColumn(prev); } } columnWidthUpdated(column) { if (column.modules.frozen) { if (this.table.modules.frozenColumns.leftColumns.includes(column)) { this.table.modules.frozenColumns.leftColumns.forEach((col) => { this.reinitializeColumn(col); }); } else if (this.table.modules.frozenColumns.rightColumns.includes(column)) { this.table.modules.frozenColumns.rightColumns.forEach((col) => { this.reinitializeColumn(col); }); } } } frozenColumnOffset(column) { var offset = false; if (column.modules.frozen) { offset = column.modules.frozen.marginValue; if (column.modules.frozen.position === "left") { offset += column.getWidth() - 3; } else { if (offset) { offset -= 3; } } } return offset !== false ? offset + "px" : false; } reinitializeColumn(column) { var frozenOffset = this.frozenColumnOffset(column); column.cells.forEach((cell) => { if (cell.modules.resize && cell.modules.resize.handleEl) { if (frozenOffset) { cell.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset; cell.modules.resize.handleEl.style["z-index"] = 11; } cell.element.after(cell.modules.resize.handleEl); } }); if (column.modules.resize && column.modules.resize.handleEl) { if (frozenOffset) { column.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset; } column.element.after(column.modules.resize.handleEl); } } initializeColumn(type, component, column, element) { var self = this, variableHeight = false, mode = column.definition.resizable, config = {}, nearestColumn = column.getLastColumn(); if (type === "header") { variableHeight = column.definition.formatter == "textarea" || column.definition.variableHeight; config = { variableHeight }; } if ((mode === true || mode == type) && this._checkResizability(nearestColumn)) { var handle2 = document.createElement("span"); handle2.className = "tabulator-col-resize-handle"; handle2.addEventListener("click", function(e) { e.stopPropagation(); }); var handleDown = function(e) { self.startColumn = column; self.initialNextColumn = self.nextColumn = nearestColumn.nextColumn(); self._mouseDown(e, nearestColumn, handle2); }; handle2.addEventListener("mousedown", handleDown); handle2.addEventListener("touchstart", handleDown, { passive: true }); handle2.addEventListener("dblclick", (e) => { var oldWidth = nearestColumn.getWidth(); e.stopPropagation(); nearestColumn.reinitializeWidth(true); if (oldWidth !== nearestColumn.getWidth()) { self.dispatch("column-resized", nearestColumn); self.dispatchExternal("columnResized", nearestColumn.getComponent()); } }); if (column.modules.frozen) { handle2.style.position = "sticky"; handle2.style[column.modules.frozen.position] = this.frozenColumnOffset(column); } config.handleEl = handle2; if (element.parentNode && column.visible) { element.after(handle2); } } component.modules.resize = config; } deInitializeColumn(column) { this.deInitializeComponent(column); column.cells.forEach((cell) => { this.deInitializeComponent(cell); }); } deInitializeComponent(component) { var handleEl; if (component.modules.resize) { handleEl = component.modules.resize.handleEl; if (handleEl && handleEl.parentElement) { handleEl.parentElement.removeChild(handleEl); } } } resizeHandle(component, height) { if (component.modules.resize && component.modules.resize.handleEl) { component.modules.resize.handleEl.style.height = height; } } getResizingClientX(e) { var _a2, _b; if (typeof e.clientX !== "undefined") return e.clientX; const touch = this.table.options.resizableColumnGuide ? (_a2 = e.changedTouches) == null ? void 0 : _a2[0] : (_b = e.touches) == null ? void 0 : _b[0]; return touch == null ? void 0 : touch.clientX; } resize(e, column) { var x = this.getResizingClientX(e); if (typeof x !== "number" || !isFinite(x)) { console.warn("ResizeColumns: could not resolve pointer X from event", e); return; } var startDiff = x - this.startX, moveDiff = x - this.latestX, blockedBefore, blockedAfter; this.latestX = x; if (this.table.rtl) { startDiff = -startDiff; moveDiff = -moveDiff; } blockedBefore = column.width == column.minWidth || column.width == column.maxWidth; column.setWidth(this.startWidth + startDiff); blockedAfter = column.width == column.minWidth || column.width == column.maxWidth; if (moveDiff < 0) { this.nextColumn = this.initialNextColumn; } if (this.table.options.resizableColumnFit && this.nextColumn && !(blockedBefore && blockedAfter)) { let colWidth = this.nextColumn.getWidth(); if (moveDiff > 0) { if (colWidth <= this.nextColumn.minWidth) { this.nextColumn = this.nextColumn.nextColumn(); } } if (this.nextColumn) { this.nextColumn.setWidth(this.nextColumn.getWidth() - moveDiff); } } this.table.columnManager.rerenderColumns(true); if (!this.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight) { column.checkCellHeights(); } } calcGuidePosition(e, column, handle2) { var mouseX = typeof e.clientX === "undefined" ? e.touches[0].clientX : e.clientX, handleX = handle2.getBoundingClientRect().x - this.table.element.getBoundingClientRect().x, tableX = this.table.element.getBoundingClientRect().x, columnX = column.element.getBoundingClientRect().left - tableX, mouseDiff = mouseX - this.startX, pos = Math.max(handleX + mouseDiff, columnX + column.minWidth); if (column.maxWidth) { pos = Math.min(pos, columnX + column.maxWidth); } return pos; } _checkResizability(column) { return column.definition.resizable; } _mouseDown(e, column, handle2) { var self = this, guideEl; this.dispatchExternal("columnResizing", column.getComponent()); if (self.table.options.resizableColumnGuide) { guideEl = document.createElement("span"); guideEl.classList.add("tabulator-col-resize-guide"); self.table.element.appendChild(guideEl); setTimeout(() => { guideEl.style.left = self.calcGuidePosition(e, column, handle2) + "px"; }); } self.table.element.classList.add("tabulator-block-select"); function mouseMove(e2) { if (self.table.options.resizableColumnGuide) { guideEl.style.left = self.calcGuidePosition(e2, column, handle2) + "px"; } else { self.resize(e2, column); } } function mouseUp(e2) { if (self.table.options.resizableColumnGuide) { self.resize(e2, column); guideEl.remove(); } if (self.startColumn.modules.edit) { self.startColumn.modules.edit.blocked = false; } if (self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight) { column.checkCellHeights(); } document.body.removeEventListener("mouseup", mouseUp); document.body.removeEventListener("mousemove", mouseMove); handle2.removeEventListener("touchmove", mouseMove); handle2.removeEventListener("touchend", mouseUp); self.table.element.classList.remove("tabulator-block-select"); if (self.startWidth !== column.getWidth()) { self.table.columnManager.verticalAlignHeaders(); self.dispatch("column-resized", column); self.dispatchExternal("columnResized", column.getComponent()); } } e.stopPropagation(); if (self.startColumn.modules.edit) { self.startColumn.modules.edit.blocked = true; } self.startX = typeof e.clientX === "undefined" ? e.touches[0].clientX : e.clientX; self.latestX = self.startX; self.startWidth = column.getWidth(); document.body.addEventListener("mousemove", mouseMove); document.body.addEventListener("mouseup", mouseUp); handle2.addEventListener("touchmove", mouseMove, { passive: true }); handle2.addEventListener("touchend", mouseUp); } } __publicField(ResizeColumns, "moduleName", "resizeColumns"); class ResizeRows extends Module { constructor(table) { super(table); this.startColumn = false; this.startY = false; this.startHeight = false; this.handle = null; this.prevHandle = null; this.registerTableOption("resizableRows", false); this.registerTableOption("resizableRowGuide", false); } initialize() { if (this.table.options.resizableRows) { this.subscribe("row-layout-after", this.initializeRow.bind(this)); } } initializeRow(row) { var self = this, rowEl = row.getElement(); var handle2 = document.createElement("div"); handle2.className = "tabulator-row-resize-handle"; var prevHandle = document.createElement("div"); prevHandle.className = "tabulator-row-resize-handle prev"; handle2.addEventListener("click", function(e) { e.stopPropagation(); }); var handleDown = function(e) { self.startRow = row; self._mouseDown(e, row, handle2); }; handle2.addEventListener("mousedown", handleDown); handle2.addEventListener("touchstart", handleDown, { passive: true }); prevHandle.addEventListener("click", function(e) { e.stopPropagation(); }); var prevHandleDown = function(e) { var prevRow = self.table.rowManager.prevDisplayRow(row); if (prevRow) { self.startRow = prevRow; self._mouseDown(e, prevRow, prevHandle); } }; prevHandle.addEventListener("mousedown", prevHandleDown); prevHandle.addEventListener("touchstart", prevHandleDown, { passive: true }); rowEl.appendChild(handle2); rowEl.appendChild(prevHandle); } resize(e, row) { row.setHeight(this.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - this.startY)); } calcGuidePosition(e, row, handle2) { var mouseY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY, handleY = handle2.getBoundingClientRect().y - this.table.element.getBoundingClientRect().y, tableY = this.table.element.getBoundingClientRect().y, rowY = row.element.getBoundingClientRect().top - tableY, mouseDiff = mouseY - this.startY; return Math.max(handleY + mouseDiff, rowY); } _mouseDown(e, row, handle2) { var self = this, guideEl; self.dispatchExternal("rowResizing", row.getComponent()); if (self.table.options.resizableRowGuide) { guideEl = document.createElement("span"); guideEl.classList.add("tabulator-row-resize-guide"); self.table.element.appendChild(guideEl); setTimeout(() => { guideEl.style.top = self.calcGuidePosition(e, row, handle2) + "px"; }); } self.table.element.classList.add("tabulator-block-select"); function mouseMove(e2) { if (self.table.options.resizableRowGuide) { guideEl.style.top = self.calcGuidePosition(e2, row, handle2) + "px"; } else { self.resize(e2, row); } } function mouseUp(e2) { if (self.table.options.resizableRowGuide) { self.resize(e2, row); guideEl.remove(); } document.body.removeEventListener("mouseup", mouseMove); document.body.removeEventListener("mousemove", mouseMove); handle2.removeEventListener("touchmove", mouseMove); handle2.removeEventListener("touchend", mouseUp); self.table.element.classList.remove("tabulator-block-select"); self.dispatchExternal("rowResized", row.getComponent()); } e.stopPropagation(); self.startY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY; self.startHeight = row.getHeight(); document.body.addEventListener("mousemove", mouseMove); document.body.addEventListener("mouseup", mouseUp); handle2.addEventListener("touchmove", mouseMove, { passive: true }); handle2.addEventListener("touchend", mouseUp); } } __publicField(ResizeRows, "moduleName", "resizeRows"); class ResizeTable extends Module { constructor(table) { super(table); this.binding = false; this.visibilityObserver = false; this.resizeObserver = false; this.containerObserver = false; this.tableHeight = 0; this.tableWidth = 0; this.containerHeight = 0; this.containerWidth = 0; this.autoResize = false; this.visible = false; this.initialized = false; this.initialRedraw = false; this.registerTableOption("autoResize", true); } initialize() { if (this.table.options.autoResize) { var table = this.table, tableStyle; this.tableHeight = table.element.clientHeight; this.tableWidth = table.element.clientWidth; if (table.element.parentNode) { this.containerHeight = table.element.parentNode.clientHeight; this.containerWidth = table.element.parentNode.clientWidth; } if (typeof IntersectionObserver !== "undefined" && typeof ResizeObserver !== "undefined" && table.rowManager.getRenderMode() === "virtual") { this.initializeVisibilityObserver(); this.autoResize = true; this.resizeObserver = new ResizeObserver((entry) => { if (!table.browserMobile || table.browserMobile && (!table.modules.edit || table.modules.edit && !table.modules.edit.currentCell)) { var nodeHeight = Math.floor(entry[0].contentRect.height); var nodeWidth = Math.floor(entry[0].contentRect.width); if (this.tableHeight != nodeHeight || this.tableWidth != nodeWidth) { this.tableHeight = nodeHeight; this.tableWidth = nodeWidth; if (table.element.parentNode) { this.containerHeight = table.element.parentNode.clientHeight; this.containerWidth = table.element.parentNode.clientWidth; } this.redrawTable(); } } }); this.resizeObserver.observe(table.element); tableStyle = window.getComputedStyle(table.element); if (this.table.element.parentNode && !this.table.rowManager.fixedHeight && (tableStyle.getPropertyValue("max-height") || tableStyle.getPropertyValue("min-height"))) { this.containerObserver = new ResizeObserver((entry) => { if (!table.browserMobile || table.browserMobile && (!table.modules.edit || table.modules.edit && !table.modules.edit.currentCell)) { var nodeHeight = Math.floor(entry[0].contentRect.height); var nodeWidth = Math.floor(entry[0].contentRect.width); if (this.containerHeight != nodeHeight || this.containerWidth != nodeWidth) { this.containerHeight = nodeHeight; this.containerWidth = nodeWidth; this.tableHeight = table.element.clientHeight; this.tableWidth = table.element.clientWidth; } this.redrawTable(); } }); this.containerObserver.observe(this.table.element.parentNode); } this.subscribe("table-resize", this.tableResized.bind(this)); } else { this.binding = function() { if (!table.browserMobile || table.browserMobile && (!table.modules.edit || table.modules.edit && !table.modules.edit.currentCell)) { table.columnManager.rerenderColumns(true); table.redraw(); } }; window.addEventListener("resize", this.binding); } this.subscribe("table-destroy", this.clearBindings.bind(this)); } } initializeVisibilityObserver() { this.visibilityObserver = new IntersectionObserver((entries) => { this.visible = entries[entries.length - 1].isIntersecting; if (!this.initialized) { this.initialized = true; this.initialRedraw = !this.visible; } else { if (this.visible) { this.redrawTable(this.initialRedraw); this.initialRedraw = false; } } }); this.visibilityObserver.observe(this.table.element); } redrawTable(force) { if (this.initialized && this.visible) { this.table.columnManager.rerenderColumns(true); this.table.redraw(force); } } tableResized() { this.table.rowManager.redraw(); } clearBindings() { if (this.binding) { window.removeEventListener("resize", this.binding); } if (this.resizeObserver) { this.resizeObserver.unobserve(this.table.element); } if (this.visibilityObserver) { this.visibilityObserver.unobserve(this.table.element); } if (this.containerObserver) { this.containerObserver.unobserve(this.table.element.parentNode); } } } __publicField(ResizeTable, "moduleName", "resizeTable"); function responsiveCollapse(cell, formatterParams, onRendered) { var el = document.createElement("div"), config = cell.getRow()._row.modules.responsiveLayout; el.classList.add("tabulator-responsive-collapse-toggle"); el.innerHTML = `<svg class='tabulator-responsive-collapse-toggle-open' viewbox="0 0 24 24"> <line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" /> <line y1="7" x1="12" y2="17" x2="12" fill="none" stroke-width="3" stroke-linecap="round" /> </svg> <svg class='tabulator-responsive-collapse-toggle-close' viewbox="0 0 24 24"> <line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" /> </svg>`; cell.getElement().classList.add("tabulator-row-handle"); function toggleList(isOpen) { var collapseEl = config.element; config.open = isOpen; if (collapseEl) { if (config.open) { el.classList.add("open"); collapseEl.style.display = ""; } else { el.classList.remove("open"); collapseEl.style.display = "none"; } } } el.addEventListener("click", function(e) { e.stopImmediatePropagation(); toggleList(!config.open); cell.getTable().rowManager.adjustTableSize(); }); toggleList(config.open); return el; } var extensions$2 = { format: { formatters: { responsiveCollapse } } }; class ResponsiveLayout extends Module { constructor(table) { super(table); this.columns = []; this.hiddenColumns = []; this.mode = ""; this.index = 0; this.collapseFormatter = []; this.collapseStartOpen = true; this.collapseHandleColumn = false; this.registerTableOption("responsiveLayout", false); this.registerTableOption("responsiveLayoutCollapseStartOpen", true); this.registerTableOption("responsiveLayoutCollapseUseFormatters", true); this.registerTableOption("responsiveLayoutCollapseFormatter", false); this.registerColumnOption("responsive"); } //generate responsive columns list initialize() { if (this.table.options.responsiveLayout) { this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("column-show", this.updateColumnVisibility.bind(this)); this.subscribe("column-hide", this.updateColumnVisibility.bind(this)); this.subscribe("columns-loaded", this.initializeResponsivity.bind(this)); this.subscribe("column-moved", this.initializeResponsivity.bind(this)); this.subscribe("column-add", this.initializeResponsivity.bind(this)); this.subscribe("column-delete", this.initializeResponsivity.bind(this)); this.subscribe("table-redrawing", this.tableRedraw.bind(this)); if (this.table.options.responsiveLayout === "collapse") { this.subscribe("row-data-changed", this.generateCollapsedRowContent.bind(this)); this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-layout", this.layoutRow.bind(this)); } } } tableRedraw(force) { if (["fitColumns", "fitDataStretch"].indexOf(this.layoutMode()) === -1) { if (!force) { this.update(); } } } initializeResponsivity() { var columns = []; this.mode = this.table.options.responsiveLayout; this.collapseFormatter = this.table.options.responsiveLayoutCollapseFormatter || this.formatCollapsedData; this.collapseStartOpen = this.table.options.responsiveLayoutCollapseStartOpen; this.hiddenColumns = []; if (this.collapseFormatter) { this.collapseFormatter = this.collapseFormatter.bind(this.table); } this.table.columnManager.columnsByIndex.forEach((column, i) => { if (column.modules.responsive) { if (column.modules.responsive.order && column.modules.responsive.visible) { column.modules.responsive.index = i; columns.push(column); if (!column.visible && this.mode === "collapse") { this.hiddenColumns.push(column); } } } }); columns = columns.reverse(); columns = columns.sort((a, b) => { var diff = b.modules.responsive.order - a.modules.responsive.order; return diff || b.modules.responsive.index - a.modules.responsive.index; }); this.columns = columns; if (this.mode === "collapse") { this.generateCollapsedContent(); } for (let col of this.table.columnManager.columnsByIndex) { if (col.definition.formatter == "responsiveCollapse") { this.collapseHandleColumn = col; break; } } if (this.collapseHandleColumn) { if (this.hiddenColumns.length) { this.collapseHandleColumn.show(); } else { this.collapseHandleColumn.hide(); } } } //define layout information initializeColumn(column) { var def = column.getDefinition(); column.modules.responsive = { order: typeof def.responsive === "undefined" ? 1 : def.responsive, visible: def.visible === false ? false : true }; } initializeRow(row) { var el; if (row.type !== "calc") { el = document.createElement("div"); el.classList.add("tabulator-responsive-collapse"); row.modules.responsiveLayout = { element: el, open: this.collapseStartOpen }; if (!this.collapseStartOpen) { el.style.display = "none"; } } } layoutRow(row) { var rowEl = row.getElement(); if (row.modules.responsiveLayout) { rowEl.appendChild(row.modules.responsiveLayout.element); this.generateCollapsedRowContent(row); } } //update column visibility updateColumnVisibility(column, responsiveToggle) { if (!responsiveToggle && column.modules.responsive) { column.modules.responsive.visible = column.visible; this.initializeResponsivity(); } } hideColumn(column) { var colCount = this.hiddenColumns.length; column.hide(false, true); if (this.mode === "collapse") { this.hiddenColumns.unshift(column); this.generateCollapsedContent(); if (this.collapseHandleColumn && !colCount) { this.collapseHandleColumn.show(); } } } showColumn(column) { var index; column.show(false, true); column.setWidth(column.getWidth()); if (this.mode === "collapse") { index = this.hiddenColumns.indexOf(column); if (index > -1) { this.hiddenColumns.splice(index, 1); } this.generateCollapsedContent(); if (this.collapseHandleColumn && !this.hiddenColumns.length) { this.collapseHandleColumn.hide(); } } } //redraw columns to fit space update() { var working = true; while (working) { let width = this.table.modules.layout.getMode() == "fitColumns" ? this.table.columnManager.getFlexBaseWidth() : this.table.columnManager.getWidth(); let diff = (this.table.options.headerVisible ? this.table.columnManager.element.clientWidth : this.table.element.clientWidth) - width; if (diff < 0) { let column = this.columns[this.index]; if (column) { this.hideColumn(column); this.index++; } else { working = false; } } else { let column = this.columns[this.index - 1]; if (column) { if (diff > 0) { if (diff >= column.getWidth()) { this.showColumn(column); this.index--; } else { working = false; } } else { working = false; } } else { working = false; } } if (!this.table.rowManager.activeRowsCount) { this.table.rowManager.renderEmptyScroll(); } } } generateCollapsedContent() { var rows2 = this.table.rowManager.getDisplayRows(); rows2.forEach((row) => { this.generateCollapsedRowContent(row); }); } generateCollapsedRowContent(row) { var el, contents; if (row.modules.responsiveLayout) { el = row.modules.responsiveLayout.element; while (el.firstChild) el.removeChild(el.firstChild); contents = this.collapseFormatter(this.generateCollapsedRowData(row)); if (contents) { el.appendChild(contents); } row.calcHeight(true); } } generateCollapsedRowData(row) { var data = row.getData(), output = [], mockCellComponent; this.hiddenColumns.forEach((column) => { var value = column.getFieldValue(data); if (column.definition.title && column.field) { if (column.modules.format && this.table.options.responsiveLayoutCollapseUseFormatters) { let onRendered = function(callback) { callback(); }; mockCellComponent = { value: false, data: {}, getValue: function() { return value; }, getData: function() { return data; }, getType: function() { return "cell"; }, getElement: function() { return document.createElement("div"); }, getRow: function() { return row.getComponent(); }, getColumn: function() { return column.getComponent(); }, getTable: () => { return this.table; } }; output.push({ field: column.field, title: column.definition.title, value: column.modules.format.formatter.call(this.table.modules.format, mockCellComponent, column.modules.format.params, onRendered) }); } else { output.push({ field: column.field, title: column.definition.title, value }); } } }); return output; } formatCollapsedData(data) { var list2 = document.createElement("table"); data.forEach((item) => { var row = document.createElement("tr"); var titleData = document.createElement("td"); var valueData = document.createElement("td"); var node_content; var titleHighlight = document.createElement("strong"); titleData.appendChild(titleHighlight); this.modules.localize.bind("columns|" + item.field, function(text) { titleHighlight.innerHTML = text || item.title; }); if (item.value instanceof Node) { node_content = document.createElement("div"); node_content.appendChild(item.value); valueData.appendChild(node_content); } else { valueData.innerHTML = item.value; } row.appendChild(titleData); row.appendChild(valueData); list2.appendChild(row); }); return Object.keys(data).length ? list2 : ""; } } __publicField(ResponsiveLayout, "moduleName", "responsiveLayout"); __publicField(ResponsiveLayout, "moduleExtensions", extensions$2); function rowSelection(cell, formatterParams, onRendered) { var checkbox = document.createElement("input"); var blocked = false; checkbox.type = "checkbox"; checkbox.setAttribute("aria-label", "Select Row"); if (this.table.modExists("selectRow", true)) { checkbox.addEventListener("click", (e) => { e.stopPropagation(); }); if (typeof cell.getRow == "function") { var row = cell.getRow(); if (row instanceof RowComponent) { checkbox.addEventListener("change", (e) => { if (this.table.options.selectableRowsRangeMode === "click") { if (!blocked) { row.toggleSelect(); } else { blocked = false; } } else { row.toggleSelect(); } }); if (this.table.options.selectableRowsRangeMode === "click") { checkbox.addEventListener("click", (e) => { blocked = true; this.table.modules.selectRow.handleComplexRowClick(row._row, e); }); } checkbox.checked = row.isSelected && row.isSelected(); this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox); } else { checkbox = ""; } } else { checkbox.addEventListener("change", (e) => { if (this.table.modules.selectRow.selectedRows.length) { this.table.deselectRow(); } else { this.table.selectRow(formatterParams.rowRange); } }); this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox); } } return checkbox; } var extensions$1 = { format: { formatters: { rowSelection } } }; class SelectRow extends Module { constructor(table) { super(table); this.selecting = false; this.lastClickedRow = false; this.selectPrev = []; this.selectedRows = []; this.headerCheckboxElement = null; this.registerTableOption("selectableRows", "highlight"); this.registerTableOption("selectableRowsRangeMode", "drag"); this.registerTableOption("selectableRowsRollingSelection", true); this.registerTableOption("selectableRowsPersistence", true); this.registerTableOption("selectableRowsCheck", function(data, row) { return true; }); this.registerTableFunction("selectRow", this.selectRows.bind(this)); this.registerTableFunction("deselectRow", this.deselectRows.bind(this)); this.registerTableFunction("toggleSelectRow", this.toggleRow.bind(this)); this.registerTableFunction("getSelectedRows", this.getSelectedRows.bind(this)); this.registerTableFunction("getSelectedData", this.getSelectedData.bind(this)); this.registerComponentFunction("row", "select", this.selectRows.bind(this)); this.registerComponentFunction("row", "deselect", this.deselectRows.bind(this)); this.registerComponentFunction("row", "toggleSelect", this.toggleRow.bind(this)); this.registerComponentFunction("row", "isSelected", this.isRowSelected.bind(this)); } initialize() { this.deprecatedOptionsCheck(); if (this.table.options.selectableRows === "highlight" && this.table.options.selectableRange) { this.table.options.selectableRows = false; } if (this.table.options.selectableRows !== false) { this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-deleting", this.rowDeleted.bind(this)); this.subscribe("rows-wipe", this.clearSelectionData.bind(this)); this.subscribe("rows-retrieve", this.rowRetrieve.bind(this)); if (this.table.options.selectableRows && !this.table.options.selectableRowsPersistence) { this.subscribe("data-refreshing", this.deselectRows.bind(this)); } } } deprecatedOptionsCheck() { } rowRetrieve(type, prevValue) { return type === "selected" ? this.selectedRows : prevValue; } rowDeleted(row) { this._deselectRow(row, true); } clearSelectionData(silent) { var prevSelected = this.selectedRows.length; this.selecting = false; this.lastClickedRow = false; this.selectPrev = []; this.selectedRows = []; if (prevSelected && silent !== true) { this._rowSelectionChanged(); } } initializeRow(row) { var self = this, selectable = self.checkRowSelectability(row), element = row.getElement(); var endSelect = function() { setTimeout(function() { self.selecting = false; }, 50); document.body.removeEventListener("mouseup", endSelect); }; row.modules.select = { selected: false }; element.classList.toggle("tabulator-selectable", selectable); element.classList.toggle("tabulator-unselectable", !selectable); if (self.checkRowSelectability(row)) { if (self.table.options.selectableRows && self.table.options.selectableRows != "highlight") { if (self.table.options.selectableRowsRangeMode === "click") { element.addEventListener("click", this.handleComplexRowClick.bind(this, row)); } else { element.addEventListener("click", function(e) { if (!self.table.modExists("edit") || !self.table.modules.edit.getCurrentCell()) { self.table._clearSelection(); } if (!self.selecting) { self.toggleRow(row); } }); element.addEventListener("mousedown", function(e) { if (e.shiftKey) { self.table._clearSelection(); self.selecting = true; self.selectPrev = []; document.body.addEventListener("mouseup", endSelect); document.body.addEventListener("keyup", endSelect); self.toggleRow(row); return false; } }); element.addEventListener("mouseenter", function(e) { if (self.selecting) { self.table._clearSelection(); self.toggleRow(row); if (self.selectPrev[1] == row) { self.toggleRow(self.selectPrev[0]); } } }); element.addEventListener("mouseout", function(e) { if (self.selecting) { self.table._clearSelection(); self.selectPrev.unshift(row); } }); } } } } handleComplexRowClick(row, e) { if (e.shiftKey) { this.table._clearSelection(); this.lastClickedRow = this.lastClickedRow || row; var lastClickedRowIdx = this.table.rowManager.getDisplayRowIndex(this.lastClickedRow); var rowIdx = this.table.rowManager.getDisplayRowIndex(row); var fromRowIdx = lastClickedRowIdx <= rowIdx ? lastClickedRowIdx : rowIdx; var toRowIdx = lastClickedRowIdx >= rowIdx ? lastClickedRowIdx : rowIdx; var rows2 = this.table.rowManager.getDisplayRows().slice(0); var toggledRows = rows2.splice(fromRowIdx, toRowIdx - fromRowIdx + 1); if (e.ctrlKey || e.metaKey) { toggledRows.forEach((toggledRow) => { if (toggledRow !== this.lastClickedRow) { if (this.table.options.selectableRows !== true && !this.isRowSelected(row)) { if (this.selectedRows.length < this.table.options.selectableRows) { this.toggleRow(toggledRow); } } else { this.toggleRow(toggledRow); } } }); this.lastClickedRow = row; } else { this.deselectRows(void 0, true); if (this.table.options.selectableRows !== true) { if (toggledRows.length > this.table.options.selectableRows) { toggledRows = toggledRows.slice(0, this.table.options.selectableRows); } } this.selectRows(toggledRows); } this.table._clearSelection(); } else if (e.ctrlKey || e.metaKey) { this.toggleRow(row); this.lastClickedRow = row; } else { this.deselectRows(void 0, true); if (this.selectedRows.length === 1 && this.isRowSelected(row)) ; else { this.selectRows(row); } this.lastClickedRow = row; } } checkRowSelectability(row) { if (row && row.type === "row") { return this.table.options.selectableRowsCheck.call(this.table, row.getComponent()); } return false; } //toggle row selection toggleRow(row) { if (this.checkRowSelectability(row)) { if (row.modules.select && row.modules.select.selected) { this._deselectRow(row); } else { this._selectRow(row); } } } //select a number of rows selectRows(rows2) { var changes = [], rowMatch, change; switch (typeof rows2) { case "undefined": rowMatch = this.table.rowManager.rows; break; case "number": rowMatch = this.table.rowManager.findRow(rows2); break; case "string": rowMatch = this.table.rowManager.findRow(rows2); if (!rowMatch) { rowMatch = this.table.rowManager.getRows(rows2); } break; default: rowMatch = rows2; break; } if (Array.isArray(rowMatch)) { if (rowMatch.length) { rowMatch.forEach((row) => { change = this._selectRow(row, true, true); if (change) { changes.push(change); } }); this._rowSelectionChanged(false, changes); } } else { if (rowMatch) { this._selectRow(rowMatch, false, true); } } } //select an individual row _selectRow(rowInfo, silent, force) { if (!isNaN(this.table.options.selectableRows) && this.table.options.selectableRows !== true && !force) { if (this.selectedRows.length >= this.table.options.selectableRows) { if (this.table.options.selectableRowsRollingSelection) { this._deselectRow(this.selectedRows[0]); } else { return false; } } } var row = this.table.rowManager.findRow(rowInfo); if (row) { if (this.selectedRows.indexOf(row) == -1) { row.getElement().classList.add("tabulator-selected"); if (!row.modules.select) { row.modules.select = {}; } row.modules.select.selected = true; if (row.modules.select.checkboxEl) { row.modules.select.checkboxEl.checked = true; } this.selectedRows.push(row); if (this.table.options.dataTreeSelectPropagate) { this.childRowSelection(row, true); } this.dispatchExternal("rowSelected", row.getComponent()); this._rowSelectionChanged(silent, row); return row; } } else { if (!silent) { console.warn("Selection Error - No such row found, ignoring selection:" + rowInfo); } } } isRowSelected(row) { return this.selectedRows.indexOf(row) !== -1; } //deselect a number of rows deselectRows(rows2, silent) { var changes = [], rowMatch, change; switch (typeof rows2) { case "undefined": rowMatch = Object.assign([], this.selectedRows); break; case "number": rowMatch = this.table.rowManager.findRow(rows2); break; case "string": rowMatch = this.table.rowManager.findRow(rows2); if (!rowMatch) { rowMatch = this.table.rowManager.getRows(rows2); } break; default: rowMatch = rows2; break; } if (Array.isArray(rowMatch)) { if (rowMatch.length) { rowMatch.forEach((row) => { change = this._deselectRow(row, true, true); if (change) { changes.push(change); } }); this._rowSelectionChanged(silent, [], changes); } } else { if (rowMatch) { this._deselectRow(rowMatch, silent, true); } } } //deselect an individual row _deselectRow(rowInfo, silent) { var self = this, row = self.table.rowManager.findRow(rowInfo), index, element; if (row) { index = self.selectedRows.findIndex(function(selectedRow) { return selectedRow == row; }); if (index > -1) { element = row.getElement(); if (element) { element.classList.remove("tabulator-selected"); } if (!row.modules.select) { row.modules.select = {}; } row.modules.select.selected = false; if (row.modules.select.checkboxEl) { row.modules.select.checkboxEl.checked = false; } self.selectedRows.splice(index, 1); if (this.table.options.dataTreeSelectPropagate) { this.childRowSelection(row, false); } this.dispatchExternal("rowDeselected", row.getComponent()); self._rowSelectionChanged(silent, void 0, row); return row; } } else { if (!silent) { console.warn("Deselection Error - No such row found, ignoring selection:" + rowInfo); } } } getSelectedData() { var data = []; this.selectedRows.forEach(function(row) { data.push(row.getData()); }); return data; } getSelectedRows() { var rows2 = []; this.selectedRows.forEach(function(row) { rows2.push(row.getComponent()); }); return rows2; } _rowSelectionChanged(silent, selected = [], deselected = []) { if (this.headerCheckboxElement) { if (this.selectedRows.length === 0) { this.headerCheckboxElement.checked = false; this.headerCheckboxElement.indeterminate = false; } else if (this.table.rowManager.rows.length === this.selectedRows.length) { this.headerCheckboxElement.checked = true; this.headerCheckboxElement.indeterminate = false; } else { this.headerCheckboxElement.indeterminate = true; this.headerCheckboxElement.checked = false; } } if (!silent) { if (!Array.isArray(selected)) { selected = [selected]; } selected = selected.map((row) => row.getComponent()); if (!Array.isArray(deselected)) { deselected = [deselected]; } deselected = deselected.map((row) => row.getComponent()); this.dispatchExternal("rowSelectionChanged", this.getSelectedData(), this.getSelectedRows(), selected, deselected); } } registerRowSelectCheckbox(row, element) { if (!row._row.modules.select) { row._row.modules.select = {}; } row._row.modules.select.checkboxEl = element; } registerHeaderSelectCheckbox(element) { this.headerCheckboxElement = element; } childRowSelection(row, select) { var children = this.table.modules.dataTree.getChildren(row, true, true); if (select) { for (let child of children) { this._selectRow(child, true); } } else { for (let child of children) { this._deselectRow(child, true); } } } } __publicField(SelectRow, "moduleName", "selectRow"); __publicField(SelectRow, "moduleExtensions", extensions$1); class RangeComponent { constructor(range2) { this._range = range2; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._range.table.componentFunctionBinder.handle("range", target._range, name2); } } }); } getElement() { return this._range.element; } getData() { return this._range.getData(); } getCells() { return this._range.getCells(true, true); } getStructuredCells() { return this._range.getStructuredCells(); } getRows() { return this._range.getRows().map((row) => row.getComponent()); } getColumns() { return this._range.getColumns().map((column) => column.getComponent()); } getBounds() { return this._range.getBounds(); } getTopEdge() { return this._range.top; } getBottomEdge() { return this._range.bottom; } getLeftEdge() { return this._range.left; } getRightEdge() { return this._range.right; } setBounds(start, end) { if (this._range.destroyedGuard("setBounds")) { this._range.setBounds(start ? start._cell : start, end ? end._cell : end); } } setStartBound(start) { if (this._range.destroyedGuard("setStartBound")) { this._range.setEndBound(start ? start._cell : start); this._range.rangeManager.layoutElement(); } } setEndBound(end) { if (this._range.destroyedGuard("setEndBound")) { this._range.setEndBound(end ? end._cell : end); this._range.rangeManager.layoutElement(); } } clearValues() { if (this._range.destroyedGuard("clearValues")) { this._range.clearValues(); } } remove() { if (this._range.destroyedGuard("remove")) { this._range.destroy(true); } } } class Range extends CoreFeature { constructor(table, rangeManager, start, end) { super(table); this.rangeManager = rangeManager; this.element = null; this.initialized = false; this.initializing = { start: false, end: false }; this.destroyed = false; this.top = 0; this.bottom = 0; this.left = 0; this.right = 0; this.table = table; this.start = { row: void 0, col: void 0 }; this.end = { row: void 0, col: void 0 }; if (this.rangeManager.rowHeader) { this.left = 1; this.right = 1; this.start.col = 1; this.end.col = 1; } this.initElement(); setTimeout(() => { this.initBounds(start, end); }); } initElement() { this.element = document.createElement("div"); this.element.classList.add("tabulator-range"); } initBounds(start, end) { this._updateMinMax(); if (start) { this.setBounds(start, end || start); } } /////////////////////////////////// /////// Boundary Setup /////// /////////////////////////////////// setStart(row, col) { if (this.start.row !== row || this.start.col !== col) { this.start.row = row; this.start.col = col; this.initializing.start = true; this._updateMinMax(); } } setEnd(row, col) { if (this.end.row !== row || this.end.col !== col) { this.end.row = row; this.end.col = col; this.initializing.end = true; this._updateMinMax(); } } setBounds(start, end, visibleRows) { if (start) { this.setStartBound(start); } this.setEndBound(end || start); this.rangeManager.layoutElement(visibleRows); } setStartBound(element) { var row, col; if (element.type === "column") { if (this.rangeManager.columnSelection) { this.setStart(0, element.getPosition() - 1); } } else { row = element.row.position - 1; col = element.column.getPosition() - 1; if (element.column === this.rangeManager.rowHeader) { this.setStart(row, 1); } else { this.setStart(row, col); } } } setEndBound(element) { var rowsCount = this._getTableRows().length, row, col, isRowHeader; if (element.type === "column") { if (this.rangeManager.columnSelection) { if (this.rangeManager.selecting === "column") { this.setEnd(rowsCount - 1, element.getPosition() - 1); } else if (this.rangeManager.selecting === "cell") { this.setEnd(0, element.getPosition() - 1); } } } else { row = element.row.position - 1; col = element.column.getPosition() - 1; isRowHeader = element.column === this.rangeManager.rowHeader; if (this.rangeManager.selecting === "row") { this.setEnd(row, this._getTableColumns().length - 1); } else if (this.rangeManager.selecting !== "row" && isRowHeader) { this.setEnd(row, 0); } else if (this.rangeManager.selecting === "column") { this.setEnd(rowsCount - 1, col); } else { this.setEnd(row, col); } } } _updateMinMax() { this.top = Math.min(this.start.row, this.end.row); this.bottom = Math.max(this.start.row, this.end.row); this.left = Math.min(this.start.col, this.end.col); this.right = Math.max(this.start.col, this.end.col); if (this.initialized) { this.dispatchExternal("rangeChanged", this.getComponent()); } else { if (this.initializing.start && this.initializing.end) { this.initialized = true; this.dispatchExternal("rangeAdded", this.getComponent()); } } } _getTableColumns() { return this.table.columnManager.getVisibleColumnsByIndex(); } _getTableRows() { return this.table.rowManager.getDisplayRows().filter((row) => row.type === "row"); } /////////////////////////////////// /////// Rendering /////// /////////////////////////////////// layout() { var _vDomTop = this.table.rowManager.renderer.vDomTop, _vDomBottom = this.table.rowManager.renderer.vDomBottom, _vDomLeft = this.table.columnManager.renderer.leftCol, _vDomRight = this.table.columnManager.renderer.rightCol, top, bottom, left, right, topLeftCell, bottomRightCell, topLeftCellEl, bottomRightCellEl, topLeftRowEl, bottomRightRowEl; if (this.table.options.renderHorizontal === "virtual" && this.rangeManager.rowHeader) { _vDomRight += 1; } if (_vDomTop == null) { _vDomTop = 0; } if (_vDomBottom == null) { _vDomBottom = Infinity; } if (_vDomLeft == null) { _vDomLeft = 0; } if (_vDomRight == null) { _vDomRight = Infinity; } if (this.overlaps(_vDomLeft, _vDomTop, _vDomRight, _vDomBottom)) { top = Math.max(this.top, _vDomTop); bottom = Math.min(this.bottom, _vDomBottom); left = Math.max(this.left, _vDomLeft); right = Math.min(this.right, _vDomRight); topLeftCell = this.rangeManager.getCell(top, left); bottomRightCell = this.rangeManager.getCell(bottom, right); topLeftCellEl = topLeftCell.getElement(); bottomRightCellEl = bottomRightCell.getElement(); topLeftRowEl = topLeftCell.row.getElement(); bottomRightRowEl = bottomRightCell.row.getElement(); this.element.classList.add("tabulator-range-active"); if (this.table.rtl) { this.element.style.right = topLeftRowEl.offsetWidth - topLeftCellEl.offsetLeft - topLeftCellEl.offsetWidth + "px"; this.element.style.width = topLeftCellEl.offsetLeft + topLeftCellEl.offsetWidth - bottomRightCellEl.offsetLeft + "px"; } else { this.element.style.left = topLeftRowEl.offsetLeft + topLeftCellEl.offsetLeft + "px"; this.element.style.width = bottomRightCellEl.offsetLeft + bottomRightCellEl.offsetWidth - topLeftCellEl.offsetLeft + "px"; } this.element.style.top = topLeftRowEl.offsetTop + "px"; this.element.style.height = bottomRightRowEl.offsetTop + bottomRightRowEl.offsetHeight - topLeftRowEl.offsetTop + "px"; } } atTopLeft(cell) { return cell.row.position - 1 === this.top && cell.column.getPosition() - 1 === this.left; } atBottomRight(cell) { return cell.row.position - 1 === this.bottom && cell.column.getPosition() - 1 === this.right; } occupies(cell) { return this.occupiesRow(cell.row) && this.occupiesColumn(cell.column); } occupiesRow(row) { return this.top <= row.position - 1 && row.position - 1 <= this.bottom; } occupiesColumn(col) { return this.left <= col.getPosition() - 1 && col.getPosition() - 1 <= this.right; } overlaps(left, top, right, bottom) { if (this.left > right || left > this.right || (this.top > bottom || top > this.bottom)) { return false; } return true; } getData() { var data = [], rows2 = this.getRows(), columns = this.getColumns(); rows2.forEach((row) => { var rowData = row.getData(), result = {}; columns.forEach((column) => { result[column.field] = rowData[column.field]; }); data.push(result); }); return data; } getCells(structured, component) { var cells = [], rows2 = this.getRows(), columns = this.getColumns(); if (structured) { cells = rows2.map((row) => { var arr = []; row.getCells().forEach((cell) => { if (columns.includes(cell.column)) { arr.push(component ? cell.getComponent() : cell); } }); return arr; }); } else { rows2.forEach((row) => { row.getCells().forEach((cell) => { if (columns.includes(cell.column)) { cells.push(component ? cell.getComponent() : cell); } }); }); } return cells; } getStructuredCells() { return this.getCells(true, true); } getRows() { return this._getTableRows().slice(this.top, this.bottom + 1); } getColumns() { return this._getTableColumns().slice(this.left, this.right + 1); } clearValues() { var cells = this.getCells(); var clearValue = this.table.options.selectableRangeClearCellsValue; this.table.blockRedraw(); cells.forEach((cell) => { cell.setValue(clearValue); }); this.table.restoreRedraw(); } getBounds(component) { var cells = this.getCells(false, component), output = { start: null, end: null }; if (cells.length) { output.start = cells[0]; output.end = cells[cells.length - 1]; } else { console.warn("No bounds defined on range"); } return output; } getComponent() { if (!this.component) { this.component = new RangeComponent(this); } return this.component; } destroy(notify) { this.destroyed = true; this.element.remove(); if (notify) { this.rangeManager.rangeRemoved(this); } if (this.initialized) { this.dispatchExternal("rangeRemoved", this.getComponent()); } } destroyedGuard(func) { if (this.destroyed) { console.warn("You cannot call the " + func + " function on a destroyed range"); } return !this.destroyed; } } var bindings = { rangeJumpUp: ["ctrl + 38", "meta + 38"], rangeJumpDown: ["ctrl + 40", "meta + 40"], rangeJumpLeft: ["ctrl + 37", "meta + 37"], rangeJumpRight: ["ctrl + 39", "meta + 39"], rangeExpandUp: "shift + 38", rangeExpandDown: "shift + 40", rangeExpandLeft: "shift + 37", rangeExpandRight: "shift + 39", rangeExpandJumpUp: ["ctrl + shift + 38", "meta + shift + 38"], rangeExpandJumpDown: ["ctrl + shift + 40", "meta + shift + 40"], rangeExpandJumpLeft: ["ctrl + shift + 37", "meta + shift + 37"], rangeExpandJumpRight: ["ctrl + shift + 39", "meta + shift + 39"] }; var actions = { rangeJumpLeft: function(e) { this.dispatch("keybinding-nav-range", e, "left", true, false); }, rangeJumpRight: function(e) { this.dispatch("keybinding-nav-range", e, "right", true, false); }, rangeJumpUp: function(e) { this.dispatch("keybinding-nav-range", e, "up", true, false); }, rangeJumpDown: function(e) { this.dispatch("keybinding-nav-range", e, "down", true, false); }, rangeExpandLeft: function(e) { this.dispatch("keybinding-nav-range", e, "left", false, true); }, rangeExpandRight: function(e) { this.dispatch("keybinding-nav-range", e, "right", false, true); }, rangeExpandUp: function(e) { this.dispatch("keybinding-nav-range", e, "up", false, true); }, rangeExpandDown: function(e) { this.dispatch("keybinding-nav-range", e, "down", false, true); }, rangeExpandJumpLeft: function(e) { this.dispatch("keybinding-nav-range", e, "left", true, true); }, rangeExpandJumpRight: function(e) { this.dispatch("keybinding-nav-range", e, "right", true, true); }, rangeExpandJumpUp: function(e) { this.dispatch("keybinding-nav-range", e, "up", true, true); }, rangeExpandJumpDown: function(e) { this.dispatch("keybinding-nav-range", e, "down", true, true); } }; var pasteActions = { range: function(data) { var rows2 = [], range2 = this.table.modules.selectRange.activeRange, singleCell = false, bounds, startCell, startRow, rowWidth, dataLength; dataLength = data.length; if (range2) { bounds = range2.getBounds(); startCell = bounds.start; if (bounds.start === bounds.end) { singleCell = true; } if (startCell) { rows2 = this.table.rowManager.activeRows.slice(); startRow = rows2.indexOf(startCell.row); if (singleCell) { rowWidth = data.length; } else { rowWidth = rows2.indexOf(bounds.end.row) - startRow + 1; } if (startRow > -1) { this.table.blockRedraw(); rows2 = rows2.slice(startRow, startRow + rowWidth); rows2.forEach((row, i) => { row.updateData(data[i % dataLength]); }); this.table.restoreRedraw(); } } } return rows2; } }; var pasteParsers = { range: function(clipboard) { var data = [], rows2 = [], range2 = this.table.modules.selectRange.activeRange, singleCell = false, bounds, startCell, colWidth, columnMap, startCol; if (range2) { bounds = range2.getBounds(); startCell = bounds.start; if (bounds.start === bounds.end) { singleCell = true; } if (startCell) { clipboard = clipboard.split("\n"); clipboard.forEach(function(row) { data.push(row.split(" ")); }); if (data.length) { columnMap = this.table.columnManager.getVisibleColumnsByIndex(); startCol = columnMap.indexOf(startCell.column); if (startCol > -1) { if (singleCell) { colWidth = data[0].length; } else { colWidth = columnMap.indexOf(bounds.end.column) - startCol + 1; } columnMap = columnMap.slice(startCol, startCol + colWidth); data.forEach((item) => { var row = {}; var itemLength = item.length; columnMap.forEach(function(col, i) { row[col.field] = item[i % itemLength]; }); rows2.push(row); }); return rows2; } } } } return false; } }; var columnLookups = { range: function() { var columns = this.modules.selectRange.selectedColumns(); if (this.columnManager.rowHeader) { columns.unshift(this.columnManager.rowHeader); } return columns; } }; var rowLookups = { range: function() { return this.modules.selectRange.selectedRows(); } }; var extensions = { keybindings: { bindings, actions }, clipboard: { pasteActions, pasteParsers }, export: { columnLookups, rowLookups } }; class SelectRange extends Module { constructor(table) { super(table); this.selecting = "cell"; this.mousedown = false; this.ranges = []; this.overlay = null; this.rowHeader = null; this.layoutChangeTimeout = null; this.columnSelection = false; this.rowSelection = false; this.maxRanges = 0; this.activeRange = false; this.blockKeydown = false; this.keyDownEvent = this._handleKeyDown.bind(this); this.mouseUpEvent = this._handleMouseUp.bind(this); this.registerTableOption("selectableRange", false); this.registerTableOption("selectableRangeColumns", false); this.registerTableOption("selectableRangeRows", false); this.registerTableOption("selectableRangeClearCells", false); this.registerTableOption("selectableRangeClearCellsValue", void 0); this.registerTableOption("selectableRangeAutoFocus", true); this.registerTableOption("selectableRangeInitializeDefault", true); this.registerTableOption("selectableRangeBlurEditOnNavigate", void 0); this.registerTableFunction("getRangesData", this.getRangesData.bind(this)); this.registerTableFunction("getRanges", this.getRanges.bind(this)); this.registerTableFunction("addRange", this.addRangeFromComponent.bind(this)); this.registerComponentFunction("cell", "getRanges", this.cellGetRanges.bind(this)); this.registerComponentFunction("row", "getRanges", this.rowGetRanges.bind(this)); this.registerComponentFunction("column", "getRanges", this.colGetRanges.bind(this)); } /////////////////////////////////// /////// Initialization /////// /////////////////////////////////// initialize() { if (this.options("selectableRange")) { if (!this.options("selectableRows")) { this.maxRanges = this.options("selectableRange"); this.initializeTable(); this.initializeWatchers(); } else { console.warn("SelectRange functionality cannot be used in conjunction with row selection"); } if (this.options("columns").findIndex((column) => column.frozen) > 0) { console.warn("Having frozen column in arbitrary position with selectRange option may result in unpredictable behavior."); } if (this.options("columns").filter((column) => column.frozen) > 1) { console.warn("Having multiple frozen columns with selectRange option may result in unpredictable behavior."); } } this.subscribe("edit-nav-disabled", () => { return true; }); } initializeTable() { this.overlay = document.createElement("div"); this.overlay.classList.add("tabulator-range-overlay"); this.rangeContainer = document.createElement("div"); this.rangeContainer.classList.add("tabulator-range-container"); this.activeRangeCellElement = document.createElement("div"); this.activeRangeCellElement.classList.add("tabulator-range-cell-active"); this.overlay.appendChild(this.rangeContainer); this.overlay.appendChild(this.activeRangeCellElement); this.table.rowManager.element.addEventListener("keydown", this.keyDownEvent); this.setDefaultRange(); this.table.rowManager.element.appendChild(this.overlay); this.table.columnManager.element.setAttribute("tabindex", 0); this.table.element.classList.add("tabulator-ranges"); } initializeWatchers() { this.columnSelection = this.options("selectableRangeColumns"); this.rowSelection = this.options("selectableRangeRows"); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("column-mousedown", this.handleColumnMouseDown.bind(this)); this.subscribe("column-mousemove", this.handleColumnMouseMove.bind(this)); this.subscribe("column-resized", this.handleColumnResized.bind(this)); this.subscribe("column-moving", this.handleColumnMoving.bind(this)); this.subscribe("column-moved", this.handleColumnMoved.bind(this)); this.subscribe("column-width", this.layoutChange.bind(this)); this.subscribe("column-height", this.layoutChange.bind(this)); this.subscribe("column-resized", this.layoutChange.bind(this)); this.subscribe("columns-loaded", this.updateHeaderColumn.bind(this)); this.subscribe("cell-height", this.layoutChange.bind(this)); this.subscribe("cell-rendered", this.renderCell.bind(this)); this.subscribe("cell-mousedown", this.handleCellMouseDown.bind(this)); this.subscribe("cell-mousemove", this.handleCellMouseMove.bind(this)); this.subscribe("cell-click", this.handleCellClick.bind(this)); this.subscribe("cell-editing", this.handleEditingCell.bind(this)); this.subscribe("page-changed", this.redraw.bind(this)); this.subscribe("scroll-vertical", this.layoutChange.bind(this)); this.subscribe("scroll-horizontal", this.layoutChange.bind(this)); this.subscribe("data-destroy", this.tableDestroyed.bind(this)); this.subscribe("data-processed", this.setDefaultRange.bind(this)); this.subscribe("table-layout", this.layoutElement.bind(this)); this.subscribe("table-redraw", this.redraw.bind(this)); this.subscribe("table-destroy", this.tableDestroyed.bind(this)); this.subscribe("edit-editor-clear", this.finishEditingCell.bind(this)); this.subscribe("edit-blur", this.restoreFocus.bind(this)); this.subscribe("keybinding-nav-prev", this.keyNavigate.bind(this, "prev")); this.subscribe("keybinding-nav-next", this.keyNavigate.bind(this, "next")); this.subscribe("keybinding-nav-left", this.keyNavigate.bind(this, "left")); this.subscribe("keybinding-nav-right", this.keyNavigate.bind(this, "right")); this.subscribe("keybinding-nav-up", this.keyNavigate.bind(this, "up")); this.subscribe("keybinding-nav-down", this.keyNavigate.bind(this, "down")); this.subscribe("keybinding-nav-range", this.keyNavigateRange.bind(this)); } initializeColumn(column) { if (this.columnSelection && column.definition.headerSort && this.options("headerSortClickElement") !== "icon") { console.warn("Using column headerSort with selectableRangeColumns option may result in unpredictable behavior. Consider using headerSortClickElement: 'icon'."); } } updateHeaderColumn() { var frozenCols; if (this.rowSelection) { this.rowHeader = this.table.columnManager.getVisibleColumnsByIndex()[0]; if (this.rowHeader) { this.rowHeader.definition.cssClass = this.rowHeader.definition.cssClass + " tabulator-range-row-header"; if (this.rowHeader.definition.headerSort) { console.warn("Using column headerSort with selectableRangeRows option may result in unpredictable behavior"); } if (this.rowHeader.definition.editor) { console.warn("Using column editor with selectableRangeRows option may result in unpredictable behavior"); } } } if (this.table.modules.frozenColumns && this.table.modules.frozenColumns.active) { frozenCols = this.table.modules.frozenColumns.getFrozenColumns(); if (frozenCols.length > 1 || frozenCols.length === 1 && frozenCols[0] !== this.rowHeader) { console.warn("Using frozen columns that are not the range header in combination with the selectRange option may result in unpredictable behavior"); } } } /////////////////////////////////// /////// Table Functions /////// /////////////////////////////////// getRanges() { return this.ranges.map((range2) => range2.getComponent()); } getRangesData() { return this.ranges.map((range2) => range2.getData()); } addRangeFromComponent(start, end) { start = start ? start._cell : null; end = end ? end._cell : null; return this.addRange(start, end); } /////////////////////////////////// /////// Component Functions /////// /////////////////////////////////// cellGetRanges(cell) { var ranges = []; if (cell.column === this.rowHeader) { ranges = this.ranges.filter((range2) => range2.occupiesRow(cell.row)); } else { ranges = this.ranges.filter((range2) => range2.occupies(cell)); } return ranges.map((range2) => range2.getComponent()); } rowGetRanges(row) { var ranges = this.ranges.filter((range2) => range2.occupiesRow(row)); return ranges.map((range2) => range2.getComponent()); } colGetRanges(col) { var ranges = this.ranges.filter((range2) => range2.occupiesColumn(col)); return ranges.map((range2) => range2.getComponent()); } /////////////////////////////////// ////////// Event Handlers ///////// /////////////////////////////////// _handleMouseUp(e) { this.mousedown = false; document.removeEventListener("mouseup", this.mouseUpEvent); } _handleKeyDown(e) { if (!this.blockKeydown && (!this.table.modules.edit || this.table.modules.edit && !this.table.modules.edit.currentCell)) { if (e.key === "Enter") { if (this.table.modules.edit && this.table.modules.edit.currentCell) { return; } var activeCell = this.getActiveCell(); if (!activeCell) { return; } this.table.modules.edit.editCell(activeCell); e.preventDefault(); } if ((e.key === "Backspace" || e.key === "Delete") && this.options("selectableRangeClearCells")) { if (this.activeRange) { this.activeRange.clearValues(); } } } } initializeFocus(cell) { var range2; this.restoreFocus(); try { if (document.selection) { range2 = document.body.createTextRange(); range2.moveToElementText(cell.getElement()); range2.select(); } else if (window.getSelection) { range2 = document.createRange(); range2.selectNode(cell.getElement()); window.getSelection().removeAllRanges(); window.getSelection().addRange(range2); } } catch (e) { } } restoreFocus(element) { this.table.rowManager.element.focus(); return true; } /////////////////////////////////// ////// Column Functionality /////// /////////////////////////////////// handleColumnResized(column) { var selected; if (this.selecting !== "column" && this.selecting !== "all") { return; } selected = this.ranges.some((range2) => range2.occupiesColumn(column)); if (!selected) { return; } this.ranges.forEach((range2) => { var selectedColumns = range2.getColumns(true); selectedColumns.forEach((selectedColumn) => { if (selectedColumn !== column) { selectedColumn.setWidth(column.width); } }); }); } handleColumnMoving(_event, column) { this.resetRanges().setBounds(column); this.overlay.style.visibility = "hidden"; } handleColumnMoved(from, _to, _after) { this.activeRange.setBounds(from); this.layoutElement(); } handleColumnMouseDown(event, column) { if (event.button === 2 && (this.selecting === "column" || this.selecting === "all") && this.activeRange.occupiesColumn(column)) { return; } if (this.table.options.movableColumns && this.selecting === "column" && this.activeRange.occupiesColumn(column)) { return; } this.mousedown = true; document.addEventListener("mouseup", this.mouseUpEvent); this.newSelection(event, column); } handleColumnMouseMove(e, column) { if (column === this.rowHeader || !this.mousedown || this.selecting === "all") { return; } this.activeRange.setBounds(false, column, true); } /////////////////////////////////// //////// Cell Functionality /////// /////////////////////////////////// renderCell(cell) { var el = cell.getElement(), rangeIdx = this.ranges.findIndex((range2) => range2.occupies(cell)); el.classList.toggle("tabulator-range-selected", rangeIdx !== -1); el.classList.toggle("tabulator-range-only-cell-selected", this.ranges.length === 1 && this.ranges[0].atTopLeft(cell) && this.ranges[0].atBottomRight(cell)); el.dataset.range = rangeIdx; } handleCellMouseDown(event, cell) { if (event.button === 2 && (this.activeRange.occupies(cell) || (this.selecting === "row" || this.selecting === "all") && this.activeRange.occupiesRow(cell.row))) { return; } this.mousedown = true; document.addEventListener("mouseup", this.mouseUpEvent); this.newSelection(event, cell); } handleCellMouseMove(e, cell) { if (!this.mousedown || this.selecting === "all") { return; } this.activeRange.setBounds(false, cell, true); } handleCellClick(e, cell) { this.initializeFocus(cell); } handleEditingCell(cell) { if (this.activeRange) { this.activeRange.setBounds(cell); } } finishEditingCell() { this.blockKeydown = true; this.table.rowManager.element.focus(); setTimeout(() => { this.blockKeydown = false; }, 10); } /////////////////////////////////// /////// Navigation /////// /////////////////////////////////// keyNavigate(dir, e) { if (this.options("selectableRangeBlurEditOnNavigate")) { const isEditing = this.chain("edit-check-editing"); if (isEditing) { if (dir === "next" || dir === "prev") { this.dispatch("edit-cancel-cell"); } else { return false; } } } if (dir === "prev") { dir = "left"; } else if (dir === "next") { dir = "right"; } if (this.navigate(false, false, dir)) { e.preventDefault(); } } keyNavigateRange(e, dir, jump, expand) { if (this.navigate(jump, expand, dir)) { e.preventDefault(); } } navigate(jump, expand, dir) { var moved = false, range2, rangeEdge, prevRect, nextRow, nextCol, row, column, rowRect, rowManagerRect, columnRect, columnManagerRect; if (this.table.modules.edit && this.table.modules.edit.currentCell) { return false; } if (this.ranges.length > 1) { this.ranges = this.ranges.filter((range3) => { if (range3 === this.activeRange) { range3.setEnd(range3.start.row, range3.start.col); return true; } range3.destroy(); return false; }); } range2 = this.activeRange; prevRect = { top: range2.top, bottom: range2.bottom, left: range2.left, right: range2.right }; rangeEdge = expand ? range2.end : range2.start; nextRow = rangeEdge.row; nextCol = rangeEdge.col; if (jump) { switch (dir) { case "left": nextCol = this.findJumpCellLeft(range2.start.row, rangeEdge.col); break; case "right": nextCol = this.findJumpCellRight(range2.start.row, rangeEdge.col); break; case "up": nextRow = this.findJumpCellUp(rangeEdge.row, range2.start.col); break; case "down": nextRow = this.findJumpCellDown(rangeEdge.row, range2.start.col); break; } } else { if (expand) { if (this.selecting === "row" && (dir === "left" || dir === "right") || this.selecting === "column" && (dir === "up" || dir === "down")) { return; } } switch (dir) { case "left": nextCol = Math.max(nextCol - 1, 0); break; case "right": nextCol = Math.min(nextCol + 1, this.getTableColumns().length - 1); break; case "up": nextRow = Math.max(nextRow - 1, 0); break; case "down": nextRow = Math.min(nextRow + 1, this.getTableRows().length - 1); break; } } if (this.rowHeader && nextCol === 0) { nextCol = 1; } if (!expand) { range2.setStart(nextRow, nextCol); } range2.setEnd(nextRow, nextCol); if (!expand) { this.selecting = "cell"; } moved = prevRect.top !== range2.top || prevRect.bottom !== range2.bottom || prevRect.left !== range2.left || prevRect.right !== range2.right; if (moved) { row = this.getRowByRangePos(range2.end.row); column = this.getColumnByRangePos(range2.end.col); rowRect = row.getElement().getBoundingClientRect(); columnRect = column.getElement().getBoundingClientRect(); rowManagerRect = this.table.rowManager.getElement().getBoundingClientRect(); columnManagerRect = this.table.columnManager.getElement().getBoundingClientRect(); if (!(rowRect.top >= rowManagerRect.top && rowRect.bottom <= rowManagerRect.bottom)) { if (row.getElement().parentNode && column.getElement().parentNode) { this.autoScroll(range2, row.getElement(), column.getElement()); } else { row.getComponent().scrollTo(void 0, false); } } if (!(columnRect.left >= columnManagerRect.left + this.getRowHeaderWidth() && columnRect.right <= columnManagerRect.right)) { if (row.getElement().parentNode && column.getElement().parentNode) { this.autoScroll(range2, row.getElement(), column.getElement()); } else { column.getComponent().scrollTo(void 0, false); } } this.layoutElement(); } return true; } rangeRemoved(removed) { this.ranges = this.ranges.filter((range2) => range2 !== removed); if (this.activeRange === removed) { if (this.ranges.length) { this.activeRange = this.ranges[this.ranges.length - 1]; } else { this.addRange(); } } this.layoutElement(true); } findJumpRow(column, rows2, reverse, emptyStart, emptySide) { if (reverse) { rows2 = rows2.reverse(); } return this.findJumpItem(emptyStart, emptySide, rows2, function(row) { return row.getData()[column.getField()]; }); } findJumpCol(row, columns, reverse, emptyStart, emptySide) { if (reverse) { columns = columns.reverse(); } return this.findJumpItem(emptyStart, emptySide, columns, function(column) { return row.getData()[column.getField()]; }); } findJumpItem(emptyStart, emptySide, items, valueResolver) { var nextItem; for (let currentItem of items) { let currentValue = valueResolver(currentItem); if (emptyStart) { nextItem = currentItem; if (currentValue) { break; } } else { if (emptySide) { nextItem = currentItem; if (currentValue) { break; } } else { if (currentValue) { nextItem = currentItem; } else { break; } } } } return nextItem; } findJumpCellLeft(rowPos, colPos) { var row = this.getRowByRangePos(rowPos), columns = this.getTableColumns(), isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]), isLeftOfStartingCellEmpty = columns[colPos - 1] ? this.isEmpty(row.getData()[columns[colPos - 1].getField()]) : false, targetCols = this.rowHeader ? columns.slice(1, colPos) : columns.slice(0, colPos), jumpCol = this.findJumpCol(row, targetCols, true, isStartingCellEmpty, isLeftOfStartingCellEmpty); if (jumpCol) { return jumpCol.getPosition() - 1; } return colPos; } findJumpCellRight(rowPos, colPos) { var row = this.getRowByRangePos(rowPos), columns = this.getTableColumns(), isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]), isRightOfStartingCellEmpty = columns[colPos + 1] ? this.isEmpty(row.getData()[columns[colPos + 1].getField()]) : false, jumpCol = this.findJumpCol(row, columns.slice(colPos + 1, columns.length), false, isStartingCellEmpty, isRightOfStartingCellEmpty); if (jumpCol) { return jumpCol.getPosition() - 1; } return colPos; } findJumpCellUp(rowPos, colPos) { var column = this.getColumnByRangePos(colPos), rows2 = this.getTableRows(), isStartingCellEmpty = this.isEmpty(rows2[rowPos].getData()[column.getField()]), isTopOfStartingCellEmpty = rows2[rowPos - 1] ? this.isEmpty(rows2[rowPos - 1].getData()[column.getField()]) : false, jumpRow = this.findJumpRow(column, rows2.slice(0, rowPos), true, isStartingCellEmpty, isTopOfStartingCellEmpty); if (jumpRow) { return jumpRow.position - 1; } return rowPos; } findJumpCellDown(rowPos, colPos) { var column = this.getColumnByRangePos(colPos), rows2 = this.getTableRows(), isStartingCellEmpty = this.isEmpty(rows2[rowPos].getData()[column.getField()]), isBottomOfStartingCellEmpty = rows2[rowPos + 1] ? this.isEmpty(rows2[rowPos + 1].getData()[column.getField()]) : false, jumpRow = this.findJumpRow(column, rows2.slice(rowPos + 1, rows2.length), false, isStartingCellEmpty, isBottomOfStartingCellEmpty); if (jumpRow) { return jumpRow.position - 1; } return rowPos; } /////////////////////////////////// /////// Selection /////// /////////////////////////////////// newSelection(event, element) { var range2; if (element.type === "column") { if (!this.columnSelection) { return; } if (element === this.rowHeader) { range2 = this.resetRanges(); this.selecting = "all"; var topLeftCell, bottomRightCell = this.getCell(-1, -1); if (this.rowHeader) { topLeftCell = this.getCell(0, 1); } else { topLeftCell = this.getCell(0, 0); } range2.setBounds(topLeftCell, bottomRightCell); return; } else { this.selecting = "column"; } } else if (element.column === this.rowHeader) { this.selecting = "row"; } else { this.selecting = "cell"; } if (event.shiftKey) { this.activeRange.setBounds(false, element, true); } else if (event.ctrlKey) { this.addRange().setBounds(element, void 0, true); } else { this.resetRanges().setBounds(element, void 0, true); } } autoScroll(range2, row, column) { var tableHolder = this.table.rowManager.element, rect, view, withinHorizontalView, withinVerticalView; if (typeof row === "undefined") { row = this.getRowByRangePos(range2.end.row).getElement(); } if (typeof column === "undefined") { column = this.getColumnByRangePos(range2.end.col).getElement(); } rect = { left: column.offsetLeft, right: column.offsetLeft + column.offsetWidth, top: row.offsetTop, bottom: row.offsetTop + row.offsetHeight }; view = { left: tableHolder.scrollLeft + this.getRowHeaderWidth(), right: Math.ceil(tableHolder.scrollLeft + tableHolder.clientWidth), top: tableHolder.scrollTop, bottom: tableHolder.scrollTop + tableHolder.offsetHeight - this.table.rowManager.scrollbarWidth }; withinHorizontalView = view.left < rect.left && rect.left < view.right && view.left < rect.right && rect.right < view.right; withinVerticalView = view.top < rect.top && rect.top < view.bottom && view.top < rect.bottom && rect.bottom < view.bottom; if (!withinHorizontalView) { if (rect.left < view.left) { tableHolder.scrollLeft = rect.left - this.getRowHeaderWidth(); } else if (rect.right > view.right) { tableHolder.scrollLeft = Math.min(rect.right - tableHolder.clientWidth, rect.left - this.getRowHeaderWidth()); } } if (!withinVerticalView) { if (rect.top < view.top) { tableHolder.scrollTop = rect.top; } else if (rect.bottom > view.bottom) { tableHolder.scrollTop = rect.bottom - tableHolder.clientHeight; } } } /////////////////////////////////// /////// Layout /////// /////////////////////////////////// layoutChange() { this.overlay.style.visibility = "hidden"; clearTimeout(this.layoutChangeTimeout); this.layoutChangeTimeout = setTimeout(this.layoutRanges.bind(this), 200); } redraw(force) { if (force) { this.selecting = "cell"; this.setDefaultRange(); this.layoutElement(); } } layoutElement(visibleRows) { var rows2; if (visibleRows) { rows2 = this.table.rowManager.getVisibleRows(true); } else { rows2 = this.table.rowManager.getRows(); } rows2.forEach((row) => { if (row.type === "row") { this.layoutRow(row); row.cells.forEach((cell) => this.renderCell(cell)); } }); this.getTableColumns().forEach((column) => { this.layoutColumn(column); }); this.layoutRanges(); } layoutRow(row) { var el = row.getElement(), selected = false, occupied = this.ranges.some((range2) => range2.occupiesRow(row)); if (this.selecting === "row") { selected = occupied; } else if (this.selecting === "all") { selected = true; } el.classList.toggle("tabulator-range-selected", selected); el.classList.toggle("tabulator-range-highlight", occupied); } layoutColumn(column) { var el = column.getElement(), selected = false, occupied = this.ranges.some((range2) => range2.occupiesColumn(column)); if (this.selecting === "column") { selected = occupied; } else if (this.selecting === "all") { selected = true; } el.classList.toggle("tabulator-range-selected", selected); el.classList.toggle("tabulator-range-highlight", occupied); } layoutRanges() { var activeCell, activeCellEl, activeRowEl; if (!this.table.initialized) { return; } activeCell = this.getActiveCell(); if (!activeCell) { return; } activeCellEl = activeCell.getElement(); activeRowEl = activeCell.row.getElement(); if (this.table.rtl) { this.activeRangeCellElement.style.right = activeRowEl.offsetWidth - activeCellEl.offsetLeft - activeCellEl.offsetWidth + "px"; } else { this.activeRangeCellElement.style.left = activeRowEl.offsetLeft + activeCellEl.offsetLeft + "px"; } this.activeRangeCellElement.style.top = activeRowEl.offsetTop + "px"; this.activeRangeCellElement.style.width = activeCellEl.offsetWidth + "px"; this.activeRangeCellElement.style.height = activeRowEl.offsetHeight + "px"; this.ranges.forEach((range2) => range2.layout()); this.overlay.style.visibility = "visible"; } /////////////////////////////////// /////// Helper Functions /////// /////////////////////////////////// getCell(rowIdx, colIdx) { var row; if (colIdx < 0) { colIdx = this.getTableColumns().length + colIdx; if (colIdx < 0) { return null; } } if (rowIdx < 0) { rowIdx = this.getTableRows().length + rowIdx; } row = this.table.rowManager.getRowFromPosition(rowIdx + 1); return row ? row.getCells(false, true).filter((cell) => cell.column.visible)[colIdx] : null; } getActiveCell() { if (!this.activeRange) return; return this.getCell(this.activeRange.start.row, this.activeRange.start.col); } getRowByRangePos(pos) { return this.getTableRows()[pos]; } getColumnByRangePos(pos) { return this.getTableColumns()[pos]; } getTableRows() { return this.table.rowManager.getDisplayRows().filter((row) => row.type === "row"); } getTableColumns() { return this.table.columnManager.getVisibleColumnsByIndex(); } addRange(start, end) { var range2; if (this.maxRanges !== true && this.ranges.length >= this.maxRanges) { this.ranges.shift().destroy(); } range2 = new Range(this.table, this, start, end); this.activeRange = range2; this.ranges.push(range2); this.rangeContainer.appendChild(range2.element); return range2; } createDefaultRange() { var range2, cell, visibleCells; range2 = this.addRange(); if (this.table.rowManager.activeRows.length) { visibleCells = this.table.rowManager.activeRows[0].cells.filter((cell2) => cell2.column.visible); cell = visibleCells[this.rowHeader ? 1 : 0]; if (cell) { range2.setBounds(cell); if (this.options("selectableRangeAutoFocus")) { this.initializeFocus(cell); } } } return range2; } clearRanges() { this.ranges.forEach((range2) => range2.destroy()); this.ranges = []; } setDefaultRange() { this.clearRanges(); if (this.options("selectableRangeInitializeDefault")) { this.createDefaultRange(); } } resetRanges() { this.clearRanges(); return this.createDefaultRange(); } tableDestroyed() { document.removeEventListener("mouseup", this.mouseUpEvent); this.table.rowManager.element.removeEventListener("keydown", this.keyDownEvent); } selectedRows(component) { return component ? this.activeRange.getRows().map((row) => row.getComponent()) : this.activeRange.getRows(); } selectedColumns(component) { return component ? this.activeRange.getColumns().map((col) => col.getComponent()) : this.activeRange.getColumns(); } getRowHeaderWidth() { if (!this.rowHeader) { return 0; } return this.rowHeader.getElement().offsetWidth; } isEmpty(value) { return value === null || value === void 0 || value === ""; } } __publicField(SelectRange, "moduleName", "selectRange"); __publicField(SelectRange, "moduleInitOrder", 1); __publicField(SelectRange, "moduleExtensions", extensions); function number(a, b, aRow, bRow, column, dir, params) { var alignEmptyValues = params.alignEmptyValues; var decimal = params.decimalSeparator; var thousand = params.thousandSeparator; var emptyAlign = 0; a = String(a); b = String(b); if (thousand) { a = a.split(thousand).join(""); b = b.split(thousand).join(""); } if (decimal) { a = a.split(decimal).join("."); b = b.split(decimal).join("."); } a = parseFloat(a); b = parseFloat(b); if (isNaN(a)) { emptyAlign = isNaN(b) ? 0 : -1; } else if (isNaN(b)) { emptyAlign = 1; } else { return a - b; } if (alignEmptyValues === "top" && dir === "desc" || alignEmptyValues === "bottom" && dir === "asc") { emptyAlign *= -1; } return emptyAlign; } function string(a, b, aRow, bRow, column, dir, params) { var alignEmptyValues = params.alignEmptyValues; var emptyAlign = 0; var locale; if (!a) { emptyAlign = !b ? 0 : -1; } else if (!b) { emptyAlign = 1; } else { switch (typeof params.locale) { case "boolean": if (params.locale) { locale = this.langLocale(); } break; case "string": locale = params.locale; break; } return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale); } if (alignEmptyValues === "top" && dir === "desc" || alignEmptyValues === "bottom" && dir === "asc") { emptyAlign *= -1; } return emptyAlign; } function datetime(a, b, aRow, bRow, column, dir, params) { var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); var format = params.format || "dd/MM/yyyy HH:mm:ss", alignEmptyValues = params.alignEmptyValues, emptyAlign = 0; if (typeof DT != "undefined") { if (!DT.isDateTime(a)) { if (format === "iso") { a = DT.fromISO(String(a)); } else if (format === "x") { a = DT.fromMillis(a); } else { a = DT.fromFormat(String(a), format); } } if (!DT.isDateTime(b)) { if (format === "iso") { b = DT.fromISO(String(b)); } else if (format === "x") { b = DT.fromMillis(b); } else { b = DT.fromFormat(String(b), format); } } if (!a.isValid) { emptyAlign = !b.isValid ? 0 : -1; } else if (!b.isValid) { emptyAlign = 1; } else { return a - b; } if (alignEmptyValues === "top" && dir === "desc" || alignEmptyValues === "bottom" && dir === "asc") { emptyAlign *= -1; } return emptyAlign; } else { console.error("Sort Error - 'datetime' sorter is dependant on luxon.js"); } } function date(a, b, aRow, bRow, column, dir, params) { if (!params.format) { params.format = "dd/MM/yyyy"; } return datetime.call(this, a, b, aRow, bRow, column, dir, params); } function time(a, b, aRow, bRow, column, dir, params) { if (!params.format) { params.format = "HH:mm"; } return datetime.call(this, a, b, aRow, bRow, column, dir, params); } function boolean(a, b, aRow, bRow, column, dir, params) { var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0; var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0; return el1 - el2; } function array(a, b, aRow, bRow, column, dir, params) { var type = params.type || "length", alignEmptyValues = params.alignEmptyValues, emptyAlign = 0, table = this.table, valueMap; if (params.valueMap) { if (typeof params.valueMap === "string") { valueMap = function(value) { return value.map((item) => { return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, params.valueMap, item); }); }; } else { valueMap = params.valueMap; } } function calc(value) { var result; if (valueMap) { value = valueMap(value); } switch (type) { case "length": result = value.length; break; case "sum": result = value.reduce(function(c, d) { return c + d; }); break; case "max": result = Math.max.apply(null, value); break; case "min": result = Math.min.apply(null, value); break; case "avg": result = value.reduce(function(c, d) { return c + d; }) / value.length; break; case "string": result = value.join(""); break; } return result; } if (!Array.isArray(a)) { emptyAlign = !Array.isArray(b) ? 0 : -1; } else if (!Array.isArray(b)) { emptyAlign = 1; } else { if (type === "string") { return String(calc(a)).toLowerCase().localeCompare(String(calc(b)).toLowerCase()); } else { return calc(b) - calc(a); } } if (alignEmptyValues === "top" && dir === "desc" || alignEmptyValues === "bottom" && dir === "asc") { emptyAlign *= -1; } return emptyAlign; } function exists(a, b, aRow, bRow, column, dir, params) { var el1 = typeof a == "undefined" ? 0 : 1; var el2 = typeof b == "undefined" ? 0 : 1; return el1 - el2; } function alphanum(as, bs, aRow, bRow, column, dir, params) { var a, b, a1, b1, i = 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/; var alignEmptyValues = params.alignEmptyValues; var emptyAlign = 0; if (!as && as !== 0) { emptyAlign = !bs && bs !== 0 ? 0 : -1; } else if (!bs && bs !== 0) { emptyAlign = 1; } else { if (isFinite(as) && isFinite(bs)) return as - bs; a = String(as).toLowerCase(); b = String(bs).toLowerCase(); if (a === b) return 0; if (!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1; a = a.match(rx); b = b.match(rx); L = a.length > b.length ? b.length : a.length; while (i < L) { a1 = a[i]; b1 = b[i++]; if (a1 !== b1) { if (isFinite(a1) && isFinite(b1)) { if (a1.charAt(0) === "0") a1 = "." + a1; if (b1.charAt(0) === "0") b1 = "." + b1; return a1 - b1; } else return a1 > b1 ? 1 : -1; } } return a.length > b.length; } if (alignEmptyValues === "top" && dir === "desc" || alignEmptyValues === "bottom" && dir === "asc") { emptyAlign *= -1; } return emptyAlign; } var defaultSorters = { number, string, date, time, datetime, boolean, array, exists, alphanum }; const _Sort = class _Sort extends Module { constructor(table) { super(table); this.sortList = []; this.changed = false; this.registerTableOption("sortMode", "local"); this.registerTableOption("initialSort", false); this.registerTableOption("columnHeaderSortMulti", true); this.registerTableOption("sortOrderReverse", false); this.registerTableOption("headerSortElement", "<div class='tabulator-arrow'></div>"); this.registerTableOption("headerSortClickElement", "header"); this.registerColumnOption("sorter"); this.registerColumnOption("sorterParams"); this.registerColumnOption("headerSort", true); this.registerColumnOption("headerSortStartingDir"); this.registerColumnOption("headerSortTristate"); } initialize() { this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("table-built", this.tableBuilt.bind(this)); this.registerDataHandler(this.sort.bind(this), 20); this.registerTableFunction("setSort", this.userSetSort.bind(this)); this.registerTableFunction("getSorters", this.getSort.bind(this)); this.registerTableFunction("clearSort", this.clearSort.bind(this)); if (this.table.options.sortMode === "remote") { this.subscribe("data-params", this.remoteSortParams.bind(this)); } } tableBuilt() { if (this.table.options.initialSort) { this.setSort(this.table.options.initialSort); } } remoteSortParams(data, config, silent, params) { var sorters = this.getSort(); sorters.forEach((item) => { delete item.column; }); params.sort = sorters; return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userSetSort(sortList, dir) { this.setSort(sortList, dir); this.refreshSort(); } clearSort() { this.clear(); this.refreshSort(); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// //initialize column header for sorting initializeColumn(column) { var sorter = false, colEl, arrowEl; switch (typeof column.definition.sorter) { case "string": if (_Sort.sorters[column.definition.sorter]) { sorter = _Sort.sorters[column.definition.sorter]; } else { console.warn("Sort Error - No such sorter found: ", column.definition.sorter); } break; case "function": sorter = column.definition.sorter; break; } column.modules.sort = { sorter, dir: "none", params: column.definition.sorterParams || {}, startingDir: column.definition.headerSortStartingDir || "asc", tristate: column.definition.headerSortTristate }; if (column.definition.headerSort !== false) { colEl = column.getElement(); colEl.classList.add("tabulator-sortable"); arrowEl = document.createElement("div"); arrowEl.classList.add("tabulator-col-sorter"); switch (this.table.options.headerSortClickElement) { case "icon": arrowEl.classList.add("tabulator-col-sorter-element"); break; case "header": colEl.classList.add("tabulator-col-sorter-element"); break; default: colEl.classList.add("tabulator-col-sorter-element"); break; } switch (this.table.options.headerSortElement) { case "function": break; case "object": arrowEl.appendChild(this.table.options.headerSortElement); break; default: arrowEl.innerHTML = this.table.options.headerSortElement; } column.titleHolderElement.appendChild(arrowEl); column.modules.sort.element = arrowEl; this.setColumnHeaderSortIcon(column, "none"); if (this.table.options.headerSortClickElement === "icon") { arrowEl.addEventListener("mousedown", (e) => { e.stopPropagation(); }); } (this.table.options.headerSortClickElement === "icon" ? arrowEl : colEl).addEventListener("click", (e) => { var dir = "", sorters = [], match = false; if (column.modules.sort) { if (column.modules.sort.tristate) { if (column.modules.sort.dir == "none") { dir = column.modules.sort.startingDir; } else { if (column.modules.sort.dir == column.modules.sort.startingDir) { dir = column.modules.sort.dir == "asc" ? "desc" : "asc"; } else { dir = "none"; } } } else { switch (column.modules.sort.dir) { case "asc": dir = "desc"; break; case "desc": dir = "asc"; break; default: dir = column.modules.sort.startingDir; } } if (this.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) { sorters = this.getSort(); match = sorters.findIndex((sorter2) => { return sorter2.field === column.getField(); }); if (match > -1) { sorters[match].dir = dir; match = sorters.splice(match, 1)[0]; if (dir != "none") { sorters.push(match); } } else { if (dir != "none") { sorters.push({ column, dir }); } } this.setSort(sorters); } else { if (dir == "none") { this.clear(); } else { this.setSort(column, dir); } } this.refreshSort(); } }); } } refreshSort() { if (this.table.options.sortMode === "remote") { this.reloadData(null, false, false); } else { this.refreshData(true); } } //check if the sorters have changed since last use hasChanged() { var changed = this.changed; this.changed = false; return changed; } //return current sorters getSort() { var self = this, sorters = []; self.sortList.forEach(function(item) { if (item.column) { sorters.push({ column: item.column.getComponent(), field: item.column.getField(), dir: item.dir }); } }); return sorters; } //change sort list and trigger sort setSort(sortList, dir) { var self = this, newSortList = []; if (!Array.isArray(sortList)) { sortList = [{ column: sortList, dir }]; } sortList.forEach(function(item) { var column; column = self.table.columnManager.findColumn(item.column); if (column) { item.column = column; newSortList.push(item); self.changed = true; } else { console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column); } }); self.sortList = newSortList; this.dispatch("sort-changed"); } //clear sorters clear() { this.setSort([]); } //find appropriate sorter for column findSorter(column) { var row = this.table.rowManager.activeRows[0], sorter = "string", field, value; if (row) { row = row.getData(); field = column.getField(); if (field) { value = column.getFieldValue(row); switch (typeof value) { case "undefined": sorter = "string"; break; case "boolean": sorter = "boolean"; break; default: if (!isNaN(Number(value)) && value !== "") { sorter = "number"; } else { if (value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)) { sorter = "alphanum"; } } break; } } } return _Sort.sorters[sorter]; } //work through sort list sorting data sort(data, sortOnly) { var self = this, sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList, sortListActual = [], rowComponents = []; if (this.subscribedExternal("dataSorting")) { this.dispatchExternal("dataSorting", self.getSort()); } if (!sortOnly) { self.clearColumnHeaders(); } if (this.table.options.sortMode !== "remote") { sortList.forEach(function(item, i) { var sortObj; if (item.column) { sortObj = item.column.modules.sort; if (sortObj) { if (!sortObj.sorter) { sortObj.sorter = self.findSorter(item.column); } item.params = typeof sortObj.params === "function" ? sortObj.params(item.column.getComponent(), item.dir) : sortObj.params; sortListActual.push(item); } if (!sortOnly) { self.setColumnHeader(item.column, item.dir); } } }); if (sortListActual.length) { self._sortItems(data, sortListActual); } } else if (!sortOnly) { sortList.forEach(function(item, i) { self.setColumnHeader(item.column, item.dir); }); } if (this.subscribedExternal("dataSorted")) { data.forEach((row) => { rowComponents.push(row.getComponent()); }); this.dispatchExternal("dataSorted", self.getSort(), rowComponents); } return data; } //clear sort arrows on columns clearColumnHeaders() { this.table.columnManager.getRealColumns().forEach((column) => { if (column.modules.sort) { column.modules.sort.dir = "none"; column.getElement().setAttribute("aria-sort", "none"); this.setColumnHeaderSortIcon(column, "none"); } }); } //set the column header sort direction setColumnHeader(column, dir) { column.modules.sort.dir = dir; column.getElement().setAttribute("aria-sort", dir === "asc" ? "ascending" : "descending"); this.setColumnHeaderSortIcon(column, dir); } setColumnHeaderSortIcon(column, dir) { var sortEl = column.modules.sort.element, arrowEl; if (column.definition.headerSort && typeof this.table.options.headerSortElement === "function") { while (sortEl.firstChild) sortEl.removeChild(sortEl.firstChild); arrowEl = this.table.options.headerSortElement.call(this.table, column.getComponent(), dir); if (typeof arrowEl === "object") { sortEl.appendChild(arrowEl); } else { sortEl.innerHTML = arrowEl; } } } //sort each item in sort list _sortItems(data, sortList) { var sorterCount = sortList.length - 1; data.sort((a, b) => { var result; for (var i = sorterCount; i >= 0; i--) { let sortItem = sortList[i]; result = this._sortRow(a, b, sortItem.column, sortItem.dir, sortItem.params); if (result !== 0) { break; } } return result; }); } //process individual rows for a sort function on active data _sortRow(a, b, column, dir, params) { var el1Comp, el2Comp; var el1 = dir == "asc" ? a : b; var el2 = dir == "asc" ? b : a; a = column.getFieldValue(el1.getData()); b = column.getFieldValue(el2.getData()); a = typeof a !== "undefined" ? a : ""; b = typeof b !== "undefined" ? b : ""; el1Comp = el1.getComponent(); el2Comp = el2.getComponent(); return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params); } }; __publicField(_Sort, "moduleName", "sort"); //load defaults __publicField(_Sort, "sorters", defaultSorters); let Sort = _Sort; class GridCalculator { constructor(columns, rows2) { this.columnCount = columns; this.rowCount = rows2; this.columnString = []; this.columns = []; this.rows = []; } genColumns(data) { var colCount = Math.max(this.columnCount, Math.max(...data.map((item) => item.length))); this.columnString = []; this.columns = []; for (let i = 1; i <= colCount; i++) { this.incrementChar(this.columnString.length - 1); this.columns.push(this.columnString.join("")); } return this.columns; } genRows(data) { var rowCount = Math.max(this.rowCount, data.length); this.rows = []; for (let i = 1; i <= rowCount; i++) { this.rows.push(i); } return this.rows; } incrementChar(i) { let char = this.columnString[i]; if (char) { if (char !== "Z") { this.columnString[i] = String.fromCharCode(this.columnString[i].charCodeAt(0) + 1); } else { this.columnString[i] = "A"; if (i) { this.incrementChar(i - 1); } else { this.columnString.push("A"); } } } else { this.columnString.push("A"); } } setRowCount(count) { this.rowCount = count; } setColumnCount(count) { this.columnCount = count; } } class SheetComponent { constructor(sheet) { this._sheet = sheet; return new Proxy(this, { get: function(target, name2, receiver) { if (typeof target[name2] !== "undefined") { return target[name2]; } else { return target._sheet.table.componentFunctionBinder.handle("sheet", target._sheet, name2); } } }); } getTitle() { return this._sheet.title; } getKey() { return this._sheet.key; } getDefinition() { return this._sheet.getDefinition(); } getData() { return this._sheet.getData(); } setData(data) { return this._sheet.setData(data); } clear() { return this._sheet.clear(); } remove() { return this._sheet.remove(); } active() { return this._sheet.active(); } setTitle(title) { return this._sheet.setTitle(title); } setRows(rows2) { return this._sheet.setRows(rows2); } setColumns(columns) { return this._sheet.setColumns(columns); } } class Sheet extends CoreFeature { constructor(spreadsheetManager, definition) { super(spreadsheetManager.table); this.spreadsheetManager = spreadsheetManager; this.definition = definition; this.title = this.definition.title || ""; this.key = this.definition.key || this.definition.title; this.rowCount = this.definition.rows; this.columnCount = this.definition.columns; this.data = this.definition.data || []; this.element = null; this.isActive = false; this.grid = new GridCalculator(this.columnCount, this.rowCount); this.defaultColumnDefinition = { width: 100, headerHozAlign: "center", headerSort: false }; this.columnDefinition = Object.assign(this.defaultColumnDefinition, this.options("spreadsheetColumnDefinition")); this.columnDefs = []; this.rowDefs = []; this.columnFields = []; this.columns = []; this.rows = []; this.scrollTop = null; this.scrollLeft = null; this.initialize(); this.dispatchExternal("sheetAdded", this.getComponent()); } /////////////////////////////////// ///////// Initialization ////////// /////////////////////////////////// initialize() { this.initializeElement(); this.initializeColumns(); this.initializeRows(); } reinitialize() { this.initializeColumns(); this.initializeRows(); } initializeElement() { this.element = document.createElement("div"); this.element.classList.add("tabulator-spreadsheet-tab"); this.element.innerText = this.title; this.element.addEventListener("click", () => { this.spreadsheetManager.loadSheet(this); }); } initializeColumns() { this.grid.setColumnCount(this.columnCount); this.columnFields = this.grid.genColumns(this.data); this.columnDefs = []; this.columnFields.forEach((ref) => { var def = Object.assign({}, this.columnDefinition); def.field = ref; def.title = ref; this.columnDefs.push(def); }); } initializeRows() { var refs; this.grid.setRowCount(this.rowCount); refs = this.grid.genRows(this.data); this.rowDefs = []; refs.forEach((ref, i) => { var def = { "_id": ref }; var data = this.data[i]; if (data) { data.forEach((val, j) => { var field = this.columnFields[j]; if (field) { def[field] = val; } }); } this.rowDefs.push(def); }); } unload() { this.isActive = false; this.scrollTop = this.table.rowManager.scrollTop; this.scrollLeft = this.table.rowManager.scrollLeft; this.data = this.getData(true); this.element.classList.remove("tabulator-spreadsheet-tab-active"); } load() { var wasInactive = !this.isActive; this.isActive = true; this.table.blockRedraw(); this.table.setData([]); this.table.setColumns(this.columnDefs); this.table.setData(this.rowDefs); this.table.restoreRedraw(); if (wasInactive && this.scrollTop !== null) { this.table.rowManager.element.scrollLeft = this.scrollLeft; this.table.rowManager.element.scrollTop = this.scrollTop; } this.element.classList.add("tabulator-spreadsheet-tab-active"); this.dispatchExternal("sheetLoaded", this.getComponent()); } /////////////////////////////////// //////// Helper Functions ///////// /////////////////////////////////// getComponent() { return new SheetComponent(this); } getDefinition() { return { title: this.title, key: this.key, rows: this.rowCount, columns: this.columnCount, data: this.getData() }; } getData(full) { var output = [], rowWidths, outputWidth, outputHeight; this.rowDefs.forEach((rowData) => { var row = []; this.columnFields.forEach((field) => { row.push(rowData[field]); }); output.push(row); }); if (!full && !this.options("spreadsheetOutputFull")) { rowWidths = output.map((row) => row.findLastIndex((val) => typeof val !== "undefined") + 1); outputWidth = Math.max(...rowWidths); outputHeight = rowWidths.findLastIndex((width) => width > 0) + 1; output = output.slice(0, outputHeight); output = output.map((row) => row.slice(0, outputWidth)); } return output; } setData(data) { this.data = data; this.reinitialize(); this.dispatchExternal("sheetUpdated", this.getComponent()); if (this.isActive) { this.load(); } } clear() { this.setData([]); } setTitle(title) { this.title = title; this.element.innerText = title; this.dispatchExternal("sheetUpdated", this.getComponent()); } setRows(rows2) { this.rowCount = rows2; this.initializeRows(); this.dispatchExternal("sheetUpdated", this.getComponent()); if (this.isActive) { this.load(); } } setColumns(columns) { this.columnCount = columns; this.reinitialize(); this.dispatchExternal("sheetUpdated", this.getComponent()); if (this.isActive) { this.load(); } } remove() { this.spreadsheetManager.removeSheet(this); } destroy() { if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.dispatchExternal("sheetRemoved", this.getComponent()); } active() { this.spreadsheetManager.loadSheet(this); } } class Spreadsheet extends Module { constructor(table) { super(table); this.sheets = []; this.element = null; this.registerTableOption("spreadsheet", false); this.registerTableOption("spreadsheetRows", 50); this.registerTableOption("spreadsheetColumns", 50); this.registerTableOption("spreadsheetColumnDefinition", {}); this.registerTableOption("spreadsheetOutputFull", false); this.registerTableOption("spreadsheetData", false); this.registerTableOption("spreadsheetSheets", false); this.registerTableOption("spreadsheetSheetTabs", false); this.registerTableOption("spreadsheetSheetTabsElement", false); this.registerTableFunction("setSheets", this.setSheets.bind(this)); this.registerTableFunction("addSheet", this.addSheet.bind(this)); this.registerTableFunction("getSheets", this.getSheets.bind(this)); this.registerTableFunction("getSheetDefinitions", this.getSheetDefinitions.bind(this)); this.registerTableFunction("setSheetData", this.setSheetData.bind(this)); this.registerTableFunction("getSheet", this.getSheet.bind(this)); this.registerTableFunction("getSheetData", this.getSheetData.bind(this)); this.registerTableFunction("clearSheet", this.clearSheet.bind(this)); this.registerTableFunction("removeSheet", this.removeSheetFunc.bind(this)); this.registerTableFunction("activeSheet", this.activeSheetFunc.bind(this)); } /////////////////////////////////// ////// Module Initialization ////// /////////////////////////////////// initialize() { if (this.options("spreadsheet")) { this.subscribe("table-initialized", this.tableInitialized.bind(this)); this.subscribe("data-loaded", this.loadRemoteData.bind(this)); this.table.options.index = "_id"; if (this.options("spreadsheetData") && this.options("spreadsheetSheets")) { console.warn("You cannot use spreadsheetData and spreadsheetSheets at the same time, ignoring spreadsheetData"); this.table.options.spreadsheetData = false; } this.compatibilityCheck(); if (this.options("spreadsheetSheetTabs")) { this.initializeTabset(); } } } compatibilityCheck() { if (this.options("data")) { console.warn("Do not use the data option when working with spreadsheets, use either spreadsheetData or spreadsheetSheets to pass data into the table"); } if (this.options("pagination")) { console.warn("The spreadsheet module is not compatible with the pagination module"); } if (this.options("groupBy")) { console.warn("The spreadsheet module is not compatible with the row grouping module"); } if (this.options("responsiveCollapse")) { console.warn("The spreadsheet module is not compatible with the responsive collapse module"); } } initializeTabset() { this.element = document.createElement("div"); this.element.classList.add("tabulator-spreadsheet-tabs"); var altContainer = this.options("spreadsheetSheetTabsElement"); if (altContainer && !(altContainer instanceof HTMLElement)) { altContainer = document.querySelector(altContainer); if (!altContainer) { console.warn("Unable to find element matching spreadsheetSheetTabsElement selector:", this.options("spreadsheetSheetTabsElement")); } } if (altContainer) { altContainer.appendChild(this.element); } else { this.footerAppend(this.element); } } tableInitialized() { if (this.sheets.length) { this.loadSheet(this.sheets[0]); } else { if (this.options("spreadsheetSheets")) { this.loadSheets(this.options("spreadsheetSheets")); } else if (this.options("spreadsheetData")) { this.loadData(this.options("spreadsheetData")); } } } /////////////////////////////////// /////////// Ajax Parsing ////////// /////////////////////////////////// loadRemoteData(data, data1, data2) { console.log("data", data, data1, data2); if (Array.isArray(data)) { this.table.dataLoader.clearAlert(); this.dispatchExternal("dataLoaded", data); if (!data.length || Array.isArray(data[0])) { this.loadData(data); } else { this.loadSheets(data); } } else { console.error("Spreadsheet Loading Error - Unable to process remote data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data); } return false; } /////////////////////////////////// ///////// Sheet Management //////// /////////////////////////////////// loadData(data) { var def = { data }; this.loadSheet(this.newSheet(def)); } destroySheets() { this.sheets.forEach((sheet) => { sheet.destroy(); }); this.sheets = []; this.activeSheet = null; } loadSheets(sheets) { if (!Array.isArray(sheets)) { sheets = []; } this.destroySheets(); sheets.forEach((def) => { this.newSheet(def); }); this.loadSheet(this.sheets[0]); } loadSheet(sheet) { if (this.activeSheet !== sheet) { if (this.activeSheet) { this.activeSheet.unload(); } this.activeSheet = sheet; sheet.load(); } } newSheet(definition = {}) { var sheet; if (!definition.rows) { definition.rows = this.options("spreadsheetRows"); } if (!definition.columns) { definition.columns = this.options("spreadsheetColumns"); } sheet = new Sheet(this, definition); this.sheets.push(sheet); if (this.element) { this.element.appendChild(sheet.element); } return sheet; } removeSheet(sheet) { var index = this.sheets.indexOf(sheet), prevSheet; if (this.sheets.length > 1) { if (index > -1) { this.sheets.splice(index, 1); sheet.destroy(); if (this.activeSheet === sheet) { prevSheet = this.sheets[index - 1] || this.sheets[0]; if (prevSheet) { this.loadSheet(prevSheet); } else { this.activeSheet = null; } } } } else { console.warn("Unable to remove sheet, at least one sheet must be active"); } } lookupSheet(key) { if (!key) { return this.activeSheet; } else if (key instanceof Sheet) { return key; } else if (key instanceof SheetComponent) { return key._sheet; } else { return this.sheets.find((sheet) => sheet.key === key) || false; } } /////////////////////////////////// //////// Public Functions ///////// /////////////////////////////////// setSheets(sheets) { this.loadSheets(sheets); return this.getSheets(); } addSheet(sheet) { return this.newSheet(sheet).getComponent(); } getSheetDefinitions() { return this.sheets.map((sheet) => sheet.getDefinition()); } getSheets() { return this.sheets.map((sheet) => sheet.getComponent()); } getSheet(key) { var sheet = this.lookupSheet(key); return sheet ? sheet.getComponent() : false; } setSheetData(key, data) { if (key && !data) { data = key; key = false; } var sheet = this.lookupSheet(key); return sheet ? sheet.setData(data) : false; } getSheetData(key) { var sheet = this.lookupSheet(key); return sheet ? sheet.getData() : false; } clearSheet(key) { var sheet = this.lookupSheet(key); return sheet ? sheet.clear() : false; } removeSheetFunc(key) { var sheet = this.lookupSheet(key); if (sheet) { this.removeSheet(sheet); } } activeSheetFunc(key) { var sheet = this.lookupSheet(key); return sheet ? this.loadSheet(sheet) : false; } } __publicField(Spreadsheet, "moduleName", "spreadsheet"); class Tooltip extends Module { constructor(table) { super(table); this.tooltipSubscriber = null, this.headerSubscriber = null, this.timeout = null; this.popupInstance = null; this.registerTableOption("tooltipDelay", 300); this.registerColumnOption("tooltip"); this.registerColumnOption("headerTooltip"); } initialize() { this.deprecatedOptionsCheck(); this.subscribe("column-init", this.initializeColumn.bind(this)); } deprecatedOptionsCheck() { } initializeColumn(column) { if (column.definition.headerTooltip && !this.headerSubscriber) { this.headerSubscriber = true; this.subscribe("column-mousemove", this.mousemoveCheck.bind(this, "headerTooltip")); this.subscribe("column-mouseout", this.mouseoutCheck.bind(this, "headerTooltip")); } if (column.definition.tooltip && !this.tooltipSubscriber) { this.tooltipSubscriber = true; this.subscribe("cell-mousemove", this.mousemoveCheck.bind(this, "tooltip")); this.subscribe("cell-mouseout", this.mouseoutCheck.bind(this, "tooltip")); } } mousemoveCheck(action, e, component) { var tooltip = action === "tooltip" ? component.column.definition.tooltip : component.definition.headerTooltip; if (tooltip) { this.clearPopup(); this.timeout = setTimeout(this.loadTooltip.bind(this, e, component, tooltip), this.table.options.tooltipDelay); } } mouseoutCheck(action, e, component) { if (!this.popupInstance) { this.clearPopup(); } } clearPopup(action, e, component) { clearTimeout(this.timeout); this.timeout = null; if (this.popupInstance) { this.popupInstance.hide(); } } loadTooltip(e, component, tooltip) { var contentsEl, renderedCallback, coords; function onRendered(callback) { renderedCallback = callback; } if (typeof tooltip === "function") { tooltip = tooltip(e, component.getComponent(), onRendered); } if (tooltip instanceof HTMLElement) { contentsEl = tooltip; } else { contentsEl = document.createElement("div"); if (tooltip === true) { if (component instanceof Cell) { tooltip = component.value; } else { if (component.definition.field) { this.langBind("columns|" + component.definition.field, (value) => { contentsEl.innerHTML = tooltip = value || component.definition.title; }); } else { tooltip = component.definition.title; } } } contentsEl.innerHTML = tooltip; } if (tooltip || tooltip === 0 || tooltip === false) { contentsEl.classList.add("tabulator-tooltip"); contentsEl.addEventListener("mousemove", (e2) => e2.preventDefault()); this.popupInstance = this.popup(contentsEl); if (typeof renderedCallback === "function") { this.popupInstance.renderCallback(renderedCallback); } coords = this.popupInstance.containerEventCoords(e); this.popupInstance.show(coords.x + 15, coords.y + 15).hideOnBlur(() => { this.dispatchExternal("TooltipClosed", component.getComponent()); this.popupInstance = null; }); this.dispatchExternal("TooltipOpened", component.getComponent()); } } } __publicField(Tooltip, "moduleName", "tooltip"); var defaultValidators = { //is integer integer: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } value = Number(value); return !isNaN(value) && isFinite(value) && Math.floor(value) === value; }, //is float float: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } value = Number(value); return !isNaN(value) && isFinite(value) && value % 1 !== 0; }, //must be a number numeric: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return !isNaN(value); }, //must be a string string: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return isNaN(value); }, //must be alphanumeric alphanumeric: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } var reg = new RegExp(/^[a-z0-9]+$/i); return reg.test(value); }, //maximum value max: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return parseFloat(value) <= parameters; }, //minimum value min: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return parseFloat(value) >= parameters; }, //starts with value starts: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return String(value).toLowerCase().startsWith(String(parameters).toLowerCase()); }, //ends with value ends: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return String(value).toLowerCase().endsWith(String(parameters).toLowerCase()); }, //minimum string length minLength: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return String(value).length >= parameters; }, //maximum string length maxLength: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } return String(value).length <= parameters; }, //in provided value list in: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } if (typeof parameters == "string") { parameters = parameters.split("|"); } return parameters.indexOf(value) > -1; }, //must match provided regex regex: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } var reg = new RegExp(parameters); return reg.test(value); }, //value must be unique in this column unique: function(cell, value, parameters) { if (value === "" || value === null || typeof value === "undefined") { return true; } var unique = true; var cellData = cell.getData(); var column = cell.getColumn()._getSelf(); this.table.rowManager.rows.forEach(function(row) { var data = row.getData(); if (data !== cellData) { if (value == column.getFieldValue(data)) { unique = false; } } }); return unique; }, //must have a value required: function(cell, value, parameters) { return value !== "" && value !== null && typeof value !== "undefined"; } }; const _Validate = class _Validate extends Module { constructor(table) { super(table); this.invalidCells = []; this.registerTableOption("validationMode", "blocking"); this.registerColumnOption("validator"); this.registerTableFunction("getInvalidCells", this.getInvalidCells.bind(this)); this.registerTableFunction("clearCellValidation", this.userClearCellValidation.bind(this)); this.registerTableFunction("validate", this.userValidate.bind(this)); this.registerComponentFunction("cell", "isValid", this.cellIsValid.bind(this)); this.registerComponentFunction("cell", "clearValidation", this.clearValidation.bind(this)); this.registerComponentFunction("cell", "validate", this.cellValidate.bind(this)); this.registerComponentFunction("column", "validate", this.columnValidate.bind(this)); this.registerComponentFunction("row", "validate", this.rowValidate.bind(this)); } initialize() { this.subscribe("cell-delete", this.clearValidation.bind(this)); this.subscribe("column-layout", this.initializeColumnCheck.bind(this)); this.subscribe("edit-success", this.editValidate.bind(this)); this.subscribe("edit-editor-clear", this.editorClear.bind(this)); this.subscribe("edit-edited-clear", this.editedClear.bind(this)); } /////////////////////////////////// ///////// Event Handling ////////// /////////////////////////////////// editValidate(cell, value, previousValue) { var valid = this.table.options.validationMode !== "manual" ? this.validate(cell.column.modules.validate, cell, value) : true; if (valid !== true) { setTimeout(() => { cell.getElement().classList.add("tabulator-validation-fail"); this.dispatchExternal("validationFailed", cell.getComponent(), value, valid); }); } return valid; } editorClear(cell, cancelled) { if (cancelled) { if (cell.column.modules.validate) { this.cellValidate(cell); } } cell.getElement().classList.remove("tabulator-validation-fail"); } editedClear(cell) { if (cell.modules.validate) { cell.modules.validate.invalid = false; } } /////////////////////////////////// ////////// Cell Functions ///////// /////////////////////////////////// cellIsValid(cell) { return cell.modules.validate ? cell.modules.validate.invalid || true : true; } cellValidate(cell) { return this.validate(cell.column.modules.validate, cell, cell.getValue()); } /////////////////////////////////// ///////// Column Functions //////// /////////////////////////////////// columnValidate(column) { var invalid = []; column.cells.forEach((cell) => { if (this.cellValidate(cell) !== true) { invalid.push(cell.getComponent()); } }); return invalid.length ? invalid : true; } /////////////////////////////////// ////////// Row Functions ////////// /////////////////////////////////// rowValidate(row) { var invalid = []; row.cells.forEach((cell) => { if (this.cellValidate(cell) !== true) { invalid.push(cell.getComponent()); } }); return invalid.length ? invalid : true; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userClearCellValidation(cells) { if (!cells) { cells = this.getInvalidCells(); } if (!Array.isArray(cells)) { cells = [cells]; } cells.forEach((cell) => { this.clearValidation(cell._getSelf()); }); } userValidate(cells) { var output = []; this.table.rowManager.rows.forEach((row) => { row = row.getComponent(); var valid = row.validate(); if (valid !== true) { output = output.concat(valid); } }); return output.length ? output : true; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnCheck(column) { if (typeof column.definition.validator !== "undefined") { this.initializeColumn(column); } } //validate initializeColumn(column) { var self = this, config = [], validator; if (column.definition.validator) { if (Array.isArray(column.definition.validator)) { column.definition.validator.forEach((item) => { validator = self._extractValidator(item); if (validator) { config.push(validator); } }); } else { validator = this._extractValidator(column.definition.validator); if (validator) { config.push(validator); } } column.modules.validate = config.length ? config : false; } } _extractValidator(value) { var type, params, pos; switch (typeof value) { case "string": pos = value.indexOf(":"); if (pos > -1) { type = value.substring(0, pos); params = value.substring(pos + 1); } else { type = value; } return this._buildValidator(type, params); case "function": return this._buildValidator(value); case "object": return this._buildValidator(value.type, value.parameters); } } _buildValidator(type, params) { var func = typeof type == "function" ? type : _Validate.validators[type]; if (!func) { console.warn("Validator Setup Error - No matching validator found:", type); return false; } else { return { type: typeof type == "function" ? "function" : type, func, params }; } } validate(validators, cell, value) { var self = this, failedValidators = [], invalidIndex = this.invalidCells.indexOf(cell); if (validators) { validators.forEach((item) => { if (!item.func.call(self, cell.getComponent(), value, item.params)) { failedValidators.push({ type: item.type, parameters: item.params }); } }); } if (!cell.modules.validate) { cell.modules.validate = {}; } if (!failedValidators.length) { cell.modules.validate.invalid = false; cell.getElement().classList.remove("tabulator-validation-fail"); if (invalidIndex > -1) { this.invalidCells.splice(invalidIndex, 1); } } else { cell.modules.validate.invalid = failedValidators; if (this.table.options.validationMode !== "manual") { cell.getElement().classList.add("tabulator-validation-fail"); } if (invalidIndex == -1) { this.invalidCells.push(cell); } } return failedValidators.length ? failedValidators : true; } getInvalidCells() { var output = []; this.invalidCells.forEach((cell) => { output.push(cell.getComponent()); }); return output; } clearValidation(cell) { var invalidIndex; if (cell.modules.validate && cell.modules.validate.invalid) { cell.getElement().classList.remove("tabulator-validation-fail"); cell.modules.validate.invalid = false; invalidIndex = this.invalidCells.indexOf(cell); if (invalidIndex > -1) { this.invalidCells.splice(invalidIndex, 1); } } } }; __publicField(_Validate, "moduleName", "validate"); //load defaults __publicField(_Validate, "validators", defaultValidators); let Validate = _Validate; var allModules = /* @__PURE__ */ Object.freeze({ __proto__: null, AccessorModule: Accessor, AjaxModule: Ajax, ClipboardModule: Clipboard, ColumnCalcsModule: ColumnCalcs, DataTreeModule: DataTree, DownloadModule: Download, EditModule: Edit2, ExportModule: Export, FilterModule: Filter, FormatModule: Format, FrozenColumnsModule: FrozenColumns, FrozenRowsModule: FrozenRows, GroupRowsModule: GroupRows, HistoryModule: History, HtmlTableImportModule: HtmlTableImport, ImportModule: Import, InteractionModule: Interaction, KeybindingsModule: Keybindings, MenuModule: Menu, MoveColumnsModule: MoveColumns, MoveRowsModule: MoveRows, MutatorModule: Mutator, PageModule: Page, PersistenceModule: Persistence, PopupModule: Popup2, PrintModule: Print, ReactiveDataModule: ReactiveData, ResizeColumnsModule: ResizeColumns, ResizeRowsModule: ResizeRows, ResizeTableModule: ResizeTable, ResponsiveLayoutModule: ResponsiveLayout, SelectRangeModule: SelectRange, SelectRowModule: SelectRow, SortModule: Sort, SpreadsheetModule: Spreadsheet, TooltipModule: Tooltip, ValidateModule: Validate }); var defaultOptions = { debugEventsExternal: false, //flag to console log events debugEventsInternal: false, //flag to console log events debugInvalidOptions: true, //allow toggling of invalid option warnings debugInvalidComponentFuncs: true, //allow toggling of invalid component warnings debugInitialization: true, //allow toggling of pre initialization function call warnings debugDeprecation: true, //allow toggling of deprecation warnings height: false, //height of tabulator minHeight: false, //minimum height of tabulator maxHeight: false, //maximum height of tabulator columnHeaderVertAlign: "top", //vertical alignment of column headers popupContainer: false, columns: [], //store for colum header info columnDefaults: {}, //store column default props rowHeader: false, data: false, //default starting data autoColumns: false, //build columns from data row structure autoColumnsDefinitions: false, nestedFieldSeparator: ".", //separator for nested data footerElement: false, //hold footer element index: "id", //filed for row index textDirection: "auto", addRowPos: "bottom", //position to insert blank rows, top|bottom headerVisible: true, //hide header renderVertical: "virtual", renderHorizontal: "basic", renderVerticalBuffer: 0, // set virtual DOM buffer size scrollToRowPosition: "top", scrollToRowIfVisible: true, scrollToColumnPosition: "left", scrollToColumnIfVisible: true, rowFormatter: false, rowFormatterPrint: null, rowFormatterClipboard: null, rowFormatterHtmlOutput: null, rowHeight: null, placeholder: false, dataLoader: true, dataLoaderLoading: false, dataLoaderError: false, dataLoaderErrorTimeout: 3e3, dataSendParams: {}, dataReceiveParams: {}, dependencies: {} }; class OptionsList { constructor(table, msgType, defaults = {}) { this.table = table; this.msgType = msgType; this.registeredDefaults = Object.assign({}, defaults); } register(option, value) { this.registeredDefaults[option] = value; } generate(defaultOptions2, userOptions = {}) { var output = Object.assign({}, this.registeredDefaults), warn = this.table.options.debugInvalidOptions || userOptions.debugInvalidOptions === true; Object.assign(output, defaultOptions2); for (let key in userOptions) { if (!output.hasOwnProperty(key)) { if (warn) { console.warn("Invalid " + this.msgType + " option:", key); } output[key] = userOptions.key; } } for (let key in output) { if (key in userOptions) { output[key] = userOptions[key]; } else { if (Array.isArray(output[key])) { output[key] = Object.assign([], output[key]); } else if (typeof output[key] === "object" && output[key] !== null) { output[key] = Object.assign({}, output[key]); } else if (typeof output[key] === "undefined") { delete output[key]; } } } return output; } } class Renderer extends CoreFeature { constructor(table) { super(table); this.elementVertical = table.rowManager.element; this.elementHorizontal = table.columnManager.element; this.tableElement = table.rowManager.tableElement; this.verticalFillMode = "fit"; } /////////////////////////////////// /////// Internal Bindings ///////// /////////////////////////////////// initialize() { } clearRows() { } clearColumns() { } reinitializeColumnWidths(columns) { } renderRows() { } renderColumns() { } rerenderRows(callback) { if (callback) { callback(); } } rerenderColumns(update, blockRedraw) { } renderRowCells(row) { } rerenderRowCells(row, force) { } scrollColumns(left, dir) { } scrollRows(top, dir) { } resize() { } scrollToRow(row) { } scrollToRowNearestTop(row) { } visibleRows(includingBuffer) { return []; } /////////////////////////////////// //////// Helper Functions ///////// /////////////////////////////////// rows() { return this.table.rowManager.getDisplayRows(); } styleRow(row, index) { var rowEl = row.getElement(); if (index % 2) { rowEl.classList.add("tabulator-row-even"); rowEl.classList.remove("tabulator-row-odd"); } else { rowEl.classList.add("tabulator-row-odd"); rowEl.classList.remove("tabulator-row-even"); } } /////////////////////////////////// /////// External Triggers ///////// /////// (DO NOT OVERRIDE) ///////// /////////////////////////////////// clear() { this.clearRows(); this.clearColumns(); } render() { this.renderRows(); this.renderColumns(); } rerender(callback) { this.rerenderRows(); this.rerenderColumns(); } scrollToRowPosition(row, position, ifVisible) { var rowIndex = this.rows().indexOf(row), rowEl = row.getElement(), offset = 0; return new Promise((resolve, reject) => { if (rowIndex > -1) { if (typeof ifVisible === "undefined") { ifVisible = this.table.options.scrollToRowIfVisible; } if (!ifVisible) { if (Helpers.elVisible(rowEl)) { offset = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top; if (offset > 0 && offset < this.elementVertical.clientHeight - rowEl.offsetHeight) { resolve(); return false; } } } if (typeof position === "undefined") { position = this.table.options.scrollToRowPosition; } if (position === "nearest") { position = this.scrollToRowNearestTop(row) ? "top" : "bottom"; } this.scrollToRow(row); switch (position) { case "middle": case "center": if (this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight) { this.elementVertical.scrollTop = this.elementVertical.scrollTop + (rowEl.offsetTop - this.elementVertical.scrollTop) - (this.elementVertical.scrollHeight - rowEl.offsetTop) / 2; } else { this.elementVertical.scrollTop = this.elementVertical.scrollTop - this.elementVertical.clientHeight / 2; } break; case "bottom": if (this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight) { this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.scrollHeight - rowEl.offsetTop) + rowEl.offsetHeight; } else { this.elementVertical.scrollTop = this.elementVertical.scrollTop - this.elementVertical.clientHeight + rowEl.offsetHeight; } break; case "top": this.elementVertical.scrollTop = rowEl.offsetTop; break; } resolve(); } else { console.warn("Scroll Error - Row not visible"); reject("Scroll Error - Row not visible"); } }); } } class BasicHorizontal extends Renderer { constructor(table) { super(table); } renderRowCells(row, inFragment) { const rowFrag = document.createDocumentFragment(); row.cells.forEach((cell) => { rowFrag.appendChild(cell.getElement()); }); row.element.appendChild(rowFrag); if (!inFragment) { row.cells.forEach((cell) => { cell.cellRendered(); }); } } reinitializeColumnWidths(columns) { columns.forEach(function(column) { column.reinitializeWidth(); }); } } class VirtualDomHorizontal extends Renderer { constructor(table) { super(table); this.leftCol = 0; this.rightCol = 0; this.scrollLeft = 0; this.vDomScrollPosLeft = 0; this.vDomScrollPosRight = 0; this.vDomPadLeft = 0; this.vDomPadRight = 0; this.fitDataColAvg = 0; this.windowBuffer = 200; this.visibleRows = null; this.initialized = false; this.isFitData = false; this.columns = []; } initialize() { this.compatibilityCheck(); this.layoutCheck(); this.vertScrollListen(); } compatibilityCheck() { if (this.options("layout") == "fitDataTable") { console.warn("Horizontal Virtual DOM is not compatible with fitDataTable layout mode"); } if (this.options("responsiveLayout")) { console.warn("Horizontal Virtual DOM is not compatible with responsive columns"); } if (this.options("rtl")) { console.warn("Horizontal Virtual DOM is not currently compatible with RTL text direction"); } } layoutCheck() { this.isFitData = this.options("layout").startsWith("fitData"); } vertScrollListen() { this.subscribe("scroll-vertical", this.clearVisRowCache.bind(this)); this.subscribe("data-refreshed", this.clearVisRowCache.bind(this)); } clearVisRowCache() { this.visibleRows = null; } ////////////////////////////////////// ///////// Public Functions /////////// ////////////////////////////////////// renderColumns(row, force) { this.dataChange(); } scrollColumns(left, dir) { if (this.scrollLeft != left) { this.scrollLeft = left; this.scroll(left - (this.vDomScrollPosLeft + this.windowBuffer)); } } calcWindowBuffer() { var buffer = this.elementVertical.clientWidth; this.table.columnManager.columnsByIndex.forEach((column) => { if (column.visible) { var width = column.getWidth(); if (width > buffer) { buffer = width; } } }); this.windowBuffer = buffer * 2; } rerenderColumns(update, blockRedraw) { var old = { cols: this.columns, leftCol: this.leftCol, rightCol: this.rightCol }, colPos = 0; if (update && !this.initialized) { return; } this.clear(); this.calcWindowBuffer(); this.scrollLeft = this.elementVertical.scrollLeft; this.vDomScrollPosLeft = this.scrollLeft - this.windowBuffer; this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer; this.table.columnManager.columnsByIndex.forEach((column) => { var config = {}, width; if (column.visible) { if (!column.modules.frozen) { width = column.getWidth(); config.leftPos = colPos; config.rightPos = colPos + width; config.width = width; if (this.isFitData) { config.fitDataCheck = column.modules.vdomHoz ? column.modules.vdomHoz.fitDataCheck : true; } if (colPos + width > this.vDomScrollPosLeft && colPos < this.vDomScrollPosRight) { if (this.leftCol == -1) { this.leftCol = this.columns.length; this.vDomPadLeft = colPos; } this.rightCol = this.columns.length; } else { if (this.leftCol !== -1) { this.vDomPadRight += width; } } this.columns.push(column); column.modules.vdomHoz = config; colPos += width; } } }); this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; this.tableElement.style.paddingRight = this.vDomPadRight + "px"; this.initialized = true; if (!blockRedraw) { if (!update || this.reinitChanged(old)) { this.reinitializeRows(); } } this.elementVertical.scrollLeft = this.scrollLeft; } renderRowCells(row) { if (this.initialized) { this.initializeRow(row); } else { const rowFrag = document.createDocumentFragment(); row.cells.forEach((cell) => { rowFrag.appendChild(cell.getElement()); }); row.element.appendChild(rowFrag); row.cells.forEach((cell) => { cell.cellRendered(); }); } } rerenderRowCells(row, force) { this.reinitializeRow(row, force); } reinitializeColumnWidths(columns) { for (let i = this.leftCol; i <= this.rightCol; i++) { let col = this.columns[i]; if (col) { col.reinitializeWidth(); } } } ////////////////////////////////////// //////// Internal Rendering ////////// ////////////////////////////////////// deinitialize() { this.initialized = false; } clear() { this.columns = []; this.leftCol = -1; this.rightCol = 0; this.vDomScrollPosLeft = 0; this.vDomScrollPosRight = 0; this.vDomPadLeft = 0; this.vDomPadRight = 0; } dataChange() { var change = false, row, rowEl; if (this.isFitData) { this.table.columnManager.columnsByIndex.forEach((column) => { if (!column.definition.width && column.visible) { change = true; } }); if (change && this.table.rowManager.getDisplayRows().length) { this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer; row = this.chain("rows-sample", [1], [], () => { return this.table.rowManager.getDisplayRows(); })[0]; if (row) { rowEl = row.getElement(); row.generateCells(); this.tableElement.appendChild(rowEl); for (let colEnd = 0; colEnd < row.cells.length; colEnd++) { let cell = row.cells[colEnd]; rowEl.appendChild(cell.getElement()); cell.column.reinitializeWidth(); } rowEl.parentNode.removeChild(rowEl); this.rerenderColumns(false, true); } } } else { if (this.options("layout") === "fitColumns") { this.layoutRefresh(); this.rerenderColumns(false, true); } } } reinitChanged(old) { var match = true; if (old.cols.length !== this.columns.length || old.leftCol !== this.leftCol || old.rightCol !== this.rightCol) { return true; } old.cols.forEach((col, i) => { if (col !== this.columns[i]) { match = false; } }); return !match; } reinitializeRows() { var visibleRows = this.getVisibleRows(), otherRows = this.table.rowManager.getRows().filter((row) => !visibleRows.includes(row)); visibleRows.forEach((row) => { this.reinitializeRow(row, true); }); otherRows.forEach((row) => { row.deinitialize(); }); } getVisibleRows() { if (!this.visibleRows) { this.visibleRows = this.table.rowManager.getVisibleRows(); } return this.visibleRows; } scroll(diff) { this.vDomScrollPosLeft += diff; this.vDomScrollPosRight += diff; if (Math.abs(diff) > this.windowBuffer / 2) { this.rerenderColumns(); } else { if (diff > 0) { this.addColRight(); this.removeColLeft(); } else { this.addColLeft(); this.removeColRight(); } } } colPositionAdjust(start, end, diff) { for (let i = start; i < end; i++) { let column = this.columns[i]; column.modules.vdomHoz.leftPos += diff; column.modules.vdomHoz.rightPos += diff; } } addColRight() { var changes = false, working = true; while (working) { let column = this.columns[this.rightCol + 1]; if (column) { if (column.modules.vdomHoz.leftPos <= this.vDomScrollPosRight) { changes = true; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { var cell = row.getCell(column); row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.rightCol]).getElement().nextSibling); cell.cellRendered(); } }); this.fitDataColActualWidthCheck(column); this.rightCol++; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { row.modules.vdomHoz.rightCol = this.rightCol; } }); if (this.rightCol >= this.columns.length - 1) { this.vDomPadRight = 0; } else { this.vDomPadRight -= column.getWidth(); } } else { working = false; } } else { working = false; } } if (changes) { this.tableElement.style.paddingRight = this.vDomPadRight + "px"; } } addColLeft() { var changes = false, working = true; while (working) { let column = this.columns[this.leftCol - 1]; if (column) { if (column.modules.vdomHoz.rightPos >= this.vDomScrollPosLeft) { changes = true; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { var cell = row.getCell(column); row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.leftCol]).getElement()); cell.cellRendered(); } }); this.leftCol--; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { row.modules.vdomHoz.leftCol = this.leftCol; } }); if (this.leftCol <= 0) { this.vDomPadLeft = 0; } else { this.vDomPadLeft -= column.getWidth(); } let diff = this.fitDataColActualWidthCheck(column); if (diff) { this.scrollLeft = this.elementVertical.scrollLeft = this.elementVertical.scrollLeft + diff; this.vDomPadRight -= diff; } } else { working = false; } } else { working = false; } } if (changes) { this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; } } removeColRight() { var changes = false, working = true; while (working) { let column = this.columns[this.rightCol]; if (column) { if (column.modules.vdomHoz.leftPos > this.vDomScrollPosRight) { changes = true; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { var cell = row.getCell(column); try { row.getElement().removeChild(cell.getElement()); } catch (ex) { console.warn("Could not removeColRight", ex.message); } } }); this.vDomPadRight += column.getWidth(); this.rightCol--; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { row.modules.vdomHoz.rightCol = this.rightCol; } }); } else { working = false; } } else { working = false; } } if (changes) { this.tableElement.style.paddingRight = this.vDomPadRight + "px"; } } removeColLeft() { var changes = false, working = true; while (working) { let column = this.columns[this.leftCol]; if (column) { if (column.modules.vdomHoz.rightPos < this.vDomScrollPosLeft) { changes = true; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { var cell = row.getCell(column); try { row.getElement().removeChild(cell.getElement()); } catch (ex) { console.warn("Could not removeColLeft", ex.message); } } }); this.vDomPadLeft += column.getWidth(); this.leftCol++; this.getVisibleRows().forEach((row) => { if (row.type !== "group") { row.modules.vdomHoz.leftCol = this.leftCol; } }); } else { working = false; } } else { working = false; } } if (changes) { this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; } } fitDataColActualWidthCheck(column) { var newWidth, widthDiff; if (column.modules.vdomHoz.fitDataCheck) { column.reinitializeWidth(); newWidth = column.getWidth(); widthDiff = newWidth - column.modules.vdomHoz.width; if (widthDiff) { column.modules.vdomHoz.rightPos += widthDiff; column.modules.vdomHoz.width = newWidth; this.colPositionAdjust(this.columns.indexOf(column) + 1, this.columns.length, widthDiff); } column.modules.vdomHoz.fitDataCheck = false; } return widthDiff; } initializeRow(row) { if (row.type !== "group") { row.modules.vdomHoz = { leftCol: this.leftCol, rightCol: this.rightCol }; if (this.table.modules.frozenColumns) { this.table.modules.frozenColumns.leftColumns.forEach((column) => { this.appendCell(row, column); }); } for (let i = this.leftCol; i <= this.rightCol; i++) { this.appendCell(row, this.columns[i]); } if (this.table.modules.frozenColumns) { this.table.modules.frozenColumns.rightColumns.forEach((column) => { this.appendCell(row, column); }); } } } appendCell(row, column) { if (column && column.visible) { let cell = row.getCell(column); row.getElement().appendChild(cell.getElement()); cell.cellRendered(); } } reinitializeRow(row, force) { if (row.type !== "group") { if (force || !row.modules.vdomHoz || row.modules.vdomHoz.leftCol !== this.leftCol || row.modules.vdomHoz.rightCol !== this.rightCol) { var rowEl = row.getElement(); while (rowEl.firstChild) rowEl.removeChild(rowEl.firstChild); this.initializeRow(row); } } } } class ColumnManager extends CoreFeature { constructor(table) { super(table); this.blockHozScrollEvent = false; this.headersElement = null; this.contentsElement = null; this.rowHeader = null; this.element = null; this.columns = []; this.columnsByIndex = []; this.columnsByField = {}; this.scrollLeft = 0; this.optionsList = new OptionsList(this.table, "column definition", defaultColumnOptions); this.redrawBlock = false; this.redrawBlockUpdate = null; this.renderer = null; } ////////////// Setup Functions ///////////////// initialize() { this.initializeRenderer(); this.headersElement = this.createHeadersElement(); this.contentsElement = this.createHeaderContentsElement(); this.element = this.createHeaderElement(); this.contentsElement.insertBefore(this.headersElement, this.contentsElement.firstChild); this.element.insertBefore(this.contentsElement, this.element.firstChild); this.initializeScrollWheelWatcher(); this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this)); this.subscribe("scrollbar-vertical", this.padVerticalScrollbar.bind(this)); } padVerticalScrollbar(width) { if (this.table.rtl) { this.headersElement.style.marginLeft = width + "px"; } else { this.headersElement.style.marginRight = width + "px"; } } initializeRenderer() { var renderClass; var renderers = { "virtual": VirtualDomHorizontal, "basic": BasicHorizontal }; if (typeof this.table.options.renderHorizontal === "string") { renderClass = renderers[this.table.options.renderHorizontal]; } else { renderClass = this.table.options.renderHorizontal; } if (renderClass) { this.renderer = new renderClass(this.table, this.element, this.tableElement); this.renderer.initialize(); } else { console.error("Unable to find matching renderer:", this.table.options.renderHorizontal); } } createHeadersElement() { var el = document.createElement("div"); el.classList.add("tabulator-headers"); el.setAttribute("role", "row"); return el; } createHeaderContentsElement() { var el = document.createElement("div"); el.classList.add("tabulator-header-contents"); return el; } createHeaderElement() { var el = document.createElement("div"); el.classList.add("tabulator-header"); el.setAttribute("role", "rowgroup"); if (!this.table.options.headerVisible) { el.classList.add("tabulator-header-hidden"); } return el; } //return containing element getElement() { return this.element; } //return containing contents element getContentsElement() { return this.contentsElement; } //return header containing element getHeadersElement() { return this.headersElement; } //scroll horizontally to match table body scrollHorizontal(left) { this.contentsElement.scrollLeft = left; this.scrollLeft = left; this.renderer.scrollColumns(left); } initializeScrollWheelWatcher() { this.contentsElement.addEventListener("wheel", (e) => { var left; if (e.deltaX) { left = this.contentsElement.scrollLeft + e.deltaX; this.table.rowManager.scrollHorizontal(left); this.table.columnManager.scrollHorizontal(left); } }); } ///////////// Column Setup Functions ///////////// generateColumnsFromRowData(data) { var cols = [], collProgress = {}, rowSample = this.table.options.autoColumns === "full" ? data : [data[0]], definitions = this.table.options.autoColumnsDefinitions; if (data && data.length) { rowSample.forEach((row) => { Object.keys(row).forEach((key, index) => { let value = row[key], col; if (!collProgress[key]) { col = { field: key, title: key, sorter: this.calculateSorterFromValue(value) }; cols.splice(index, 0, col); collProgress[key] = typeof value === "undefined" ? col : true; } else if (collProgress[key] !== true) { if (typeof value !== "undefined") { collProgress[key].sorter = this.calculateSorterFromValue(value); collProgress[key] = true; } } }); }); if (definitions) { switch (typeof definitions) { case "function": this.table.options.columns = definitions.call(this.table, cols); break; case "object": if (Array.isArray(definitions)) { cols.forEach((col) => { var match = definitions.find((def) => { return def.field === col.field; }); if (match) { Object.assign(col, match); } }); } else { cols.forEach((col) => { if (definitions[col.field]) { Object.assign(col, definitions[col.field]); } }); } this.table.options.columns = cols; break; } } else { this.table.options.columns = cols; } this.setColumns(this.table.options.columns); } } calculateSorterFromValue(value) { var sorter; switch (typeof value) { case "undefined": sorter = "string"; break; case "boolean": sorter = "boolean"; break; case "number": sorter = "number"; break; case "object": if (Array.isArray(value)) { sorter = "array"; } else { sorter = "string"; } break; default: if (!isNaN(Number(value)) && value !== "") { sorter = "number"; } else { if (value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)) { sorter = "alphanum"; } else { sorter = "string"; } } break; } return sorter; } setColumns(cols, row) { while (this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild); this.columns = []; this.columnsByIndex = []; this.columnsByField = {}; this.dispatch("columns-loading"); this.dispatchExternal("columnsLoading"); if (this.table.options.rowHeader) { this.rowHeader = new Column(this.table.options.rowHeader === true ? {} : this.table.options.rowHeader, this, true); this.columns.push(this.rowHeader); this.headersElement.appendChild(this.rowHeader.getElement()); this.rowHeader.columnRendered(); } cols.forEach((def, i) => { this._addColumn(def); }); this._reIndexColumns(); this.dispatch("columns-loaded"); if (this.subscribedExternal("columnsLoaded")) { this.dispatchExternal("columnsLoaded", this.getComponents()); } this.rerenderColumns(false, true); this.redraw(true); } _addColumn(definition, before, nextToColumn) { var column = new Column(definition, this), colEl = column.getElement(), index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn; if (before && this.rowHeader && (!nextToColumn || nextToColumn === this.rowHeader)) { before = false; nextToColumn = this.rowHeader; index = 0; } if (nextToColumn && index > -1) { var topColumn = nextToColumn.getTopColumn(); var parentIndex = this.columns.indexOf(topColumn); var nextEl = topColumn.getElement(); if (before) { this.columns.splice(parentIndex, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl); } else { this.columns.splice(parentIndex + 1, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl.nextSibling); } } else { if (before) { this.columns.unshift(column); this.headersElement.insertBefore(column.getElement(), this.headersElement.firstChild); } else { this.columns.push(column); this.headersElement.appendChild(column.getElement()); } } column.columnRendered(); return column; } registerColumnField(col) { if (col.definition.field) { this.columnsByField[col.definition.field] = col; } } registerColumnPosition(col) { this.columnsByIndex.push(col); } _reIndexColumns() { this.columnsByIndex = []; this.columns.forEach(function(column) { column.reRegisterPosition(); }); } //ensure column headers take up the correct amount of space in column groups verticalAlignHeaders() { var minHeight = 0; if (!this.redrawBlock) { this.headersElement.style.height = ""; this.columns.forEach((column) => { column.clearVerticalAlign(); }); this.columns.forEach((column) => { var height = column.getHeight(); if (height > minHeight) { minHeight = height; } }); this.headersElement.style.height = minHeight + "px"; this.columns.forEach((column) => { column.verticalAlign(this.table.options.columnHeaderVertAlign, minHeight); }); this.table.rowManager.adjustTableSize(); } } //////////////// Column Details ///////////////// findColumn(subject) { var columns; if (typeof subject == "object") { if (subject instanceof Column) { return subject; } else if (subject instanceof ColumnComponent) { return subject._getSelf() || false; } else if (typeof HTMLElement !== "undefined" && subject instanceof HTMLElement) { columns = []; this.columns.forEach((column) => { columns.push(column); columns = columns.concat(column.getColumns(true)); }); let match = columns.find((column) => { return column.element === subject; }); return match || false; } } else { return this.columnsByField[subject] || false; } return false; } getColumnByField(field) { return this.columnsByField[field]; } getColumnsByFieldRoot(root) { var matches = []; Object.keys(this.columnsByField).forEach((field) => { var fieldRoot = this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator)[0] : field; if (fieldRoot === root) { matches.push(this.columnsByField[field]); } }); return matches; } getColumnByIndex(index) { return this.columnsByIndex[index]; } getFirstVisibleColumn() { var index = this.columnsByIndex.findIndex((col) => { return col.visible; }); return index > -1 ? this.columnsByIndex[index] : false; } getVisibleColumnsByIndex() { return this.columnsByIndex.filter((col) => col.visible); } getColumns() { return this.columns; } findColumnIndex(column) { return this.columnsByIndex.findIndex((col) => { return column === col; }); } //return all columns that are not groups getRealColumns() { return this.columnsByIndex; } //traverse across columns and call action traverse(callback) { this.columnsByIndex.forEach((column, i) => { callback(column, i); }); } //get definitions of actual columns getDefinitions(active) { var output = []; this.columnsByIndex.forEach((column) => { if (!active || active && column.visible) { output.push(column.getDefinition()); } }); return output; } //get full nested definition tree getDefinitionTree() { var output = []; this.columns.forEach((column) => { output.push(column.getDefinition(true)); }); return output; } getComponents(structured) { var output = [], columns = structured ? this.columns : this.columnsByIndex; columns.forEach((column) => { output.push(column.getComponent()); }); return output; } getWidth() { var width = 0; this.columnsByIndex.forEach((column) => { if (column.visible) { width += column.getWidth(); } }); return width; } moveColumn(from, to, after) { to.element.parentNode.insertBefore(from.element, to.element); if (after) { to.element.parentNode.insertBefore(to.element, from.element); } this.moveColumnActual(from, to, after); this.verticalAlignHeaders(); this.table.rowManager.reinitialize(); } moveColumnActual(from, to, after) { if (from.parent.isGroup) { this._moveColumnInArray(from.parent.columns, from, to, after); } else { this._moveColumnInArray(this.columns, from, to, after); } this._moveColumnInArray(this.columnsByIndex, from, to, after, true); this.rerenderColumns(true); this.dispatch("column-moved", from, to, after); if (this.subscribedExternal("columnMoved")) { this.dispatchExternal("columnMoved", from.getComponent(), this.table.columnManager.getComponents()); } } _moveColumnInArray(columns, from, to, after, updateRows) { var fromIndex = columns.indexOf(from), toIndex, rows2 = []; if (fromIndex > -1) { columns.splice(fromIndex, 1); toIndex = columns.indexOf(to); if (toIndex > -1) { if (after) { toIndex = toIndex + 1; } } else { toIndex = fromIndex; } columns.splice(toIndex, 0, from); if (updateRows) { rows2 = this.chain("column-moving-rows", [from, to, after], null, []) || []; rows2 = rows2.concat(this.table.rowManager.rows); rows2.forEach(function(row) { if (row.cells.length) { var cell = row.cells.splice(fromIndex, 1)[0]; row.cells.splice(toIndex, 0, cell); } }); } } } scrollToColumn(column, position, ifVisible) { var left = 0, offset = column.getLeftOffset(), adjust = 0, colEl = column.getElement(); return new Promise((resolve, reject) => { if (typeof position === "undefined") { position = this.table.options.scrollToColumnPosition; } if (typeof ifVisible === "undefined") { ifVisible = this.table.options.scrollToColumnIfVisible; } if (column.visible) { switch (position) { case "middle": case "center": adjust = -this.element.clientWidth / 2; break; case "right": adjust = colEl.clientWidth - this.headersElement.clientWidth; break; } if (!ifVisible) { if (offset > 0 && offset + colEl.offsetWidth < this.element.clientWidth) { return false; } } left = offset + adjust; left = Math.max(Math.min(left, this.table.rowManager.element.scrollWidth - this.table.rowManager.element.clientWidth), 0); this.table.rowManager.scrollHorizontal(left); this.scrollHorizontal(left); resolve(); } else { console.warn("Scroll Error - Column not visible"); reject("Scroll Error - Column not visible"); } }); } //////////////// Cell Management ///////////////// generateCells(row) { var cells = []; this.columnsByIndex.forEach((column) => { cells.push(column.generateCell(row)); }); return cells; } //////////////// Column Management ///////////////// getFlexBaseWidth() { var totalWidth = this.table.element.clientWidth, fixedWidth = 0; if (this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight) { totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth; } this.columnsByIndex.forEach(function(column) { var width, minWidth, colWidth; if (column.visible) { width = column.definition.width || 0; minWidth = parseInt(column.minWidth); if (typeof width == "string") { if (width.indexOf("%") > -1) { colWidth = totalWidth / 100 * parseInt(width); } else { colWidth = parseInt(width); } } else { colWidth = width; } fixedWidth += colWidth > minWidth ? colWidth : minWidth; } }); return fixedWidth; } addColumn(definition, before, nextToColumn) { return new Promise((resolve, reject) => { var column = this._addColumn(definition, before, nextToColumn); this._reIndexColumns(); this.dispatch("column-add", definition, before, nextToColumn); if (this.layoutMode() != "fitColumns") { column.reinitializeWidth(); } this.redraw(true); this.table.rowManager.reinitialize(); this.rerenderColumns(); resolve(column); }); } //remove column from system deregisterColumn(column) { var field = column.getField(), index; if (field) { delete this.columnsByField[field]; } index = this.columnsByIndex.indexOf(column); if (index > -1) { this.columnsByIndex.splice(index, 1); } index = this.columns.indexOf(column); if (index > -1) { this.columns.splice(index, 1); } this.verticalAlignHeaders(); this.redraw(); } rerenderColumns(update, silent) { if (!this.redrawBlock) { this.renderer.rerenderColumns(update, silent); } else { if (update === false || update === true && this.redrawBlockUpdate === null) { this.redrawBlockUpdate = update; } } } blockRedraw() { this.redrawBlock = true; this.redrawBlockUpdate = null; } restoreRedraw() { this.redrawBlock = false; this.verticalAlignHeaders(); this.renderer.rerenderColumns(this.redrawBlockUpdate); } //redraw columns redraw(force) { if (Helpers.elVisible(this.element)) { this.verticalAlignHeaders(); } if (force) { this.table.rowManager.resetScroll(); this.table.rowManager.reinitialize(); } if (!this.confirm("table-redrawing", force)) { this.layoutRefresh(force); } this.dispatch("table-redraw", force); this.table.footerManager.redraw(); } } class BasicVertical extends Renderer { constructor(table) { super(table); this.verticalFillMode = "fill"; this.scrollTop = 0; this.scrollLeft = 0; this.scrollTop = 0; this.scrollLeft = 0; } clearRows() { var element = this.tableElement; while (element.firstChild) element.removeChild(element.firstChild); element.scrollTop = 0; element.scrollLeft = 0; element.style.minWidth = ""; element.style.minHeight = ""; element.style.display = ""; element.style.visibility = ""; } renderRows() { var element = this.tableElement, onlyGroupHeaders = true, tableFrag = document.createDocumentFragment(), rows2 = this.rows(); rows2.forEach((row, index) => { this.styleRow(row, index); row.initialize(false, true); if (row.type !== "group") { onlyGroupHeaders = false; } tableFrag.appendChild(row.getElement()); }); element.appendChild(tableFrag); rows2.forEach((row) => { row.rendered(); if (!row.heightInitialized) { row.calcHeight(true); } }); rows2.forEach((row) => { if (!row.heightInitialized) { row.setCellHeight(); } }); if (onlyGroupHeaders) { element.style.minWidth = this.table.columnManager.getWidth() + "px"; } else { element.style.minWidth = ""; } } rerenderRows(callback) { this.clearRows(); if (callback) { callback(); } this.renderRows(); if (!this.rows().length) { this.table.rowManager.tableEmpty(); } } scrollToRowNearestTop(row) { var rowTop = Helpers.elOffset(row.getElement()).top; return !(Math.abs(this.elementVertical.scrollTop - rowTop) > Math.abs(this.elementVertical.scrollTop + this.elementVertical.clientHeight - rowTop)); } scrollToRow(row) { var rowEl = row.getElement(); this.elementVertical.scrollTop = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top + this.elementVertical.scrollTop; } visibleRows(includingBuffer) { return this.rows(); } } class VirtualDomVertical extends Renderer { constructor(table) { super(table); this.verticalFillMode = "fill"; this.scrollTop = 0; this.scrollLeft = 0; this.vDomRowHeight = 20; this.vDomTop = 0; this.vDomBottom = 0; this.vDomScrollPosTop = 0; this.vDomScrollPosBottom = 0; this.vDomTopPad = 0; this.vDomBottomPad = 0; this.vDomMaxRenderChain = 90; this.vDomWindowBuffer = 0; this.vDomWindowMinTotalRows = 20; this.vDomWindowMinMarginRows = 5; this.vDomTopNewRows = []; this.vDomBottomNewRows = []; } ////////////////////////////////////// ///////// Public Functions /////////// ////////////////////////////////////// clearRows() { var element = this.tableElement; while (element.firstChild) element.removeChild(element.firstChild); element.style.paddingTop = ""; element.style.paddingBottom = ""; element.style.minHeight = ""; element.style.display = ""; element.style.visibility = ""; this.elementVertical.scrollTop = 0; this.elementVertical.scrollLeft = 0; this.scrollTop = 0; this.scrollLeft = 0; this.vDomTop = 0; this.vDomBottom = 0; this.vDomTopPad = 0; this.vDomBottomPad = 0; this.vDomScrollPosTop = 0; this.vDomScrollPosBottom = 0; } renderRows() { this._virtualRenderFill(); } rerenderRows(callback) { var scrollTop = this.elementVertical.scrollTop; var topRow = false; var topOffset = false; var left = this.table.rowManager.scrollLeft; var rows2 = this.rows(); for (var i = this.vDomTop; i <= this.vDomBottom; i++) { if (rows2[i]) { var diff = scrollTop - rows2[i].getElement().offsetTop; if (topOffset === false || Math.abs(diff) < topOffset) { topOffset = diff; topRow = i; } else { break; } } } rows2.forEach((row) => { row.deinitializeHeight(); }); if (callback) { callback(); } if (this.rows().length) { this._virtualRenderFill(topRow === false ? this.rows.length - 1 : topRow, true, topOffset || 0); } else { this.clear(); this.table.rowManager.tableEmpty(); } this.scrollColumns(left); } scrollColumns(left) { this.table.rowManager.scrollHorizontal(left); } scrollRows(top, dir) { var topDiff = top - this.vDomScrollPosTop; var bottomDiff = top - this.vDomScrollPosBottom; var margin = this.vDomWindowBuffer * 2; var rows2 = this.rows(); this.scrollTop = top; if (-topDiff > margin || bottomDiff > margin) { var left = this.table.rowManager.scrollLeft; this._virtualRenderFill(Math.floor(this.elementVertical.scrollTop / this.elementVertical.scrollHeight * rows2.length)); this.scrollColumns(left); } else { if (dir) { if (topDiff < 0) { this._addTopRow(rows2, -topDiff); } if (bottomDiff < 0) { if (this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer) { this._removeBottomRow(rows2, -bottomDiff); } else { this.vDomScrollPosBottom = this.scrollTop; } } } else { if (bottomDiff >= 0) { this._addBottomRow(rows2, bottomDiff); } if (topDiff >= 0) { if (this.scrollTop > this.vDomWindowBuffer) { this._removeTopRow(rows2, topDiff); } else { this.vDomScrollPosTop = this.scrollTop; } } } } } resize() { this.vDomWindowBuffer = this.table.options.renderVerticalBuffer || this.elementVertical.clientHeight; } scrollToRowNearestTop(row) { var rowIndex = this.rows().indexOf(row); return !(Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex)); } scrollToRow(row) { var index = this.rows().indexOf(row); if (index > -1) { this._virtualRenderFill(index, true); } } visibleRows(includingBuffer) { var topEdge = this.elementVertical.scrollTop, bottomEdge = this.elementVertical.clientHeight + topEdge, topFound = false, topRow = 0, bottomRow = 0, rows2 = this.rows(); if (includingBuffer) { topRow = this.vDomTop; bottomRow = this.vDomBottom; } else { for (var i = this.vDomTop; i <= this.vDomBottom; i++) { if (rows2[i]) { if (!topFound) { if (topEdge - rows2[i].getElement().offsetTop >= 0) { topRow = i; } else { topFound = true; if (bottomEdge - rows2[i].getElement().offsetTop >= 0) { bottomRow = i; } else { break; } } } else { if (bottomEdge - rows2[i].getElement().offsetTop >= 0) { bottomRow = i; } else { break; } } } } } return rows2.slice(topRow, bottomRow + 1); } ////////////////////////////////////// //////// Internal Rendering ////////// ////////////////////////////////////// //full virtual render _virtualRenderFill(position, forceMove, offset) { var element = this.tableElement, holder = this.elementVertical, topPad = 0, rowsHeight = 0, rowHeight = 0, heightOccupied = 0, topPadHeight = 0, i = 0, rows2 = this.rows(), rowsCount = rows2.length, index = 0, row, rowFragment, renderedRows = [], totalRowsRendered = 0, rowsToRender = 0, fixedHeight = this.table.rowManager.fixedHeight, containerHeight = this.elementVertical.clientHeight, avgRowHeight = this.table.options.rowHeight, resized = true; position = position || 0; offset = offset || 0; if (!position) { this.clear(); } else { while (element.firstChild) element.removeChild(element.firstChild); heightOccupied = (rowsCount - position + 1) * this.vDomRowHeight; if (heightOccupied < containerHeight) { position -= Math.ceil((containerHeight - heightOccupied) / this.vDomRowHeight); if (position < 0) { position = 0; } } topPad = Math.min(Math.max(Math.floor(this.vDomWindowBuffer / this.vDomRowHeight), this.vDomWindowMinMarginRows), position); position -= topPad; } if (rowsCount && Helpers.elVisible(this.elementVertical)) { this.vDomTop = position; this.vDomBottom = position - 1; if (fixedHeight || this.table.options.maxHeight) { if (avgRowHeight) { rowsToRender = containerHeight / avgRowHeight + this.vDomWindowBuffer / avgRowHeight; } rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil(rowsToRender)); } else { rowsToRender = rowsCount; } while ((rowsToRender == rowsCount || rowsHeight <= containerHeight + this.vDomWindowBuffer || totalRowsRendered < this.vDomWindowMinTotalRows) && this.vDomBottom < rowsCount - 1) { renderedRows = []; rowFragment = document.createDocumentFragment(); i = 0; while (i < rowsToRender && this.vDomBottom < rowsCount - 1) { index = this.vDomBottom + 1, row = rows2[index]; this.styleRow(row, index); row.initialize(false, true); if (!row.heightInitialized && !this.table.options.rowHeight) { row.clearCellHeight(); } rowFragment.appendChild(row.getElement()); renderedRows.push(row); this.vDomBottom++; i++; } if (!renderedRows.length) { break; } element.appendChild(rowFragment); renderedRows.forEach((row2) => { row2.rendered(); }); const rowsNeedingHeightInit = []; renderedRows.forEach((row2) => { if (!row2.heightInitialized || !row2.getHeight()) { row2.calcHeight(true); rowsNeedingHeightInit.push(row2); } }); rowsNeedingHeightInit.forEach((row2) => { row2.setCellHeight(); }); renderedRows.forEach((row2) => { rowHeight = row2.getHeight() || this.vDomRowHeight; if (totalRowsRendered < topPad) { topPadHeight += rowHeight; } else { rowsHeight += rowHeight; } if (rowHeight > this.vDomWindowBuffer) { this.vDomWindowBuffer = rowHeight * 2; } totalRowsRendered++; }); resized = this.table.rowManager.adjustTableSize(); containerHeight = this.elementVertical.clientHeight; if (resized && (fixedHeight || this.table.options.maxHeight)) { avgRowHeight = rowsHeight / totalRowsRendered; rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil(containerHeight / avgRowHeight + this.vDomWindowBuffer / avgRowHeight)); } } if (!position) { this.vDomTopPad = 0; this.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / totalRowsRendered); this.vDomBottomPad = this.vDomRowHeight * (rowsCount - this.vDomBottom - 1); this.vDomScrollHeight = topPadHeight + rowsHeight + this.vDomBottomPad - containerHeight; } else { this.vDomTopPad = !forceMove ? this.scrollTop - topPadHeight : this.vDomRowHeight * this.vDomTop + offset; this.vDomBottomPad = this.vDomBottom == rowsCount - 1 ? 0 : Math.max(this.vDomScrollHeight - this.vDomTopPad - rowsHeight - topPadHeight, 0); } element.style.paddingTop = this.vDomTopPad + "px"; element.style.paddingBottom = this.vDomBottomPad + "px"; if (forceMove) { this.scrollTop = this.vDomTopPad + topPadHeight + offset - (this.elementVertical.scrollWidth > this.elementVertical.clientWidth ? this.elementVertical.offsetHeight - containerHeight : 0); } this.scrollTop = Math.min(this.scrollTop, this.elementVertical.scrollHeight - containerHeight); if (this.elementVertical.scrollWidth > this.elementVertical.clientWidth && forceMove) { this.scrollTop += this.elementVertical.offsetHeight - containerHeight; } this.vDomScrollPosTop = this.scrollTop; this.vDomScrollPosBottom = this.scrollTop; holder.scrollTop = this.scrollTop; this.dispatch("render-virtual-fill"); } } _addTopRow(rows2, fillableSpace) { var table = this.tableElement, addedRows = [], paddingAdjust = 0, index = this.vDomTop - 1, i = 0, working = true; while (working) { if (this.vDomTop) { let row = rows2[index], rowHeight, initialized; if (row && i < this.vDomMaxRenderChain) { rowHeight = row.getHeight() || this.vDomRowHeight; initialized = row.initialized; if (fillableSpace >= rowHeight) { this.styleRow(row, index); table.insertBefore(row.getElement(), table.firstChild); if (!row.initialized || !row.heightInitialized) { addedRows.push(row); } row.initialize(); if (!initialized) { rowHeight = row.getElement().offsetHeight; if (rowHeight > this.vDomWindowBuffer) { this.vDomWindowBuffer = rowHeight * 2; } } fillableSpace -= rowHeight; paddingAdjust += rowHeight; this.vDomTop--; index--; i++; } else { working = false; } } else { working = false; } } else { working = false; } } for (let row of addedRows) { row.clearCellHeight(); } this._quickNormalizeRowHeight(addedRows); if (paddingAdjust) { this.vDomTopPad -= paddingAdjust; if (this.vDomTopPad < 0) { this.vDomTopPad = index * this.vDomRowHeight; } if (index < 1) { this.vDomTopPad = 0; } table.style.paddingTop = this.vDomTopPad + "px"; this.vDomScrollPosTop -= paddingAdjust; } } _removeTopRow(rows2, fillableSpace) { var removableRows = [], paddingAdjust = 0, i = 0, working = true; while (working) { let row = rows2[this.vDomTop], rowHeight; if (row && i < this.vDomMaxRenderChain) { rowHeight = row.getHeight() || this.vDomRowHeight; if (fillableSpace >= rowHeight) { this.vDomTop++; fillableSpace -= rowHeight; paddingAdjust += rowHeight; removableRows.push(row); i++; } else { working = false; } } else { working = false; } } for (let row of removableRows) { let rowEl = row.getElement(); if (rowEl.parentNode) { rowEl.parentNode.removeChild(rowEl); } } if (paddingAdjust) { this.vDomTopPad += paddingAdjust; this.tableElement.style.paddingTop = this.vDomTopPad + "px"; this.vDomScrollPosTop += this.vDomTop ? paddingAdjust : paddingAdjust + this.vDomWindowBuffer; } } _addBottomRow(rows2, fillableSpace) { var table = this.tableElement, addedRows = [], paddingAdjust = 0, index = this.vDomBottom + 1, i = 0, working = true; while (working) { let row = rows2[index], rowHeight, initialized; if (row && i < this.vDomMaxRenderChain) { rowHeight = row.getHeight() || this.vDomRowHeight; initialized = row.initialized; if (fillableSpace >= rowHeight) { this.styleRow(row, index); table.appendChild(row.getElement()); if (!row.initialized || !row.heightInitialized) { addedRows.push(row); } row.initialize(); if (!initialized) { rowHeight = row.getElement().offsetHeight; if (rowHeight > this.vDomWindowBuffer) { this.vDomWindowBuffer = rowHeight * 2; } } fillableSpace -= rowHeight; paddingAdjust += rowHeight; this.vDomBottom++; index++; i++; } else { working = false; } } else { working = false; } } for (let row of addedRows) { row.clearCellHeight(); } this._quickNormalizeRowHeight(addedRows); if (paddingAdjust) { this.vDomBottomPad -= paddingAdjust; if (this.vDomBottomPad < 0 || index == rows2.length - 1) { this.vDomBottomPad = 0; } table.style.paddingBottom = this.vDomBottomPad + "px"; this.vDomScrollPosBottom += paddingAdjust; } } _removeBottomRow(rows2, fillableSpace) { var removableRows = [], paddingAdjust = 0, i = 0, working = true; while (working) { let row = rows2[this.vDomBottom], rowHeight; if (row && i < this.vDomMaxRenderChain) { rowHeight = row.getHeight() || this.vDomRowHeight; if (fillableSpace >= rowHeight) { this.vDomBottom--; fillableSpace -= rowHeight; paddingAdjust += rowHeight; removableRows.push(row); i++; } else { working = false; } } else { working = false; } } for (let row of removableRows) { let rowEl = row.getElement(); if (rowEl.parentNode) { rowEl.parentNode.removeChild(rowEl); } } if (paddingAdjust) { this.vDomBottomPad += paddingAdjust; if (this.vDomBottomPad < 0) { this.vDomBottomPad = 0; } this.tableElement.style.paddingBottom = this.vDomBottomPad + "px"; this.vDomScrollPosBottom -= paddingAdjust; } } _quickNormalizeRowHeight(rows2) { for (let row of rows2) { row.calcHeight(); } for (let row of rows2) { row.setCellHeight(); } } } class RowManager extends CoreFeature { constructor(table) { super(table); this.element = this.createHolderElement(); this.tableElement = this.createTableElement(); this.heightFixer = this.createTableElement(); this.placeholder = null; this.placeholderContents = null; this.firstRender = false; this.renderMode = "virtual"; this.fixedHeight = false; this.rows = []; this.activeRowsPipeline = []; this.activeRows = []; this.activeRowsCount = 0; this.displayRows = []; this.displayRowsCount = 0; this.scrollTop = 0; this.scrollLeft = 0; this.redrawBlock = false; this.redrawBlockRestoreConfig = false; this.redrawBlockRenderInPosition = false; this.dataPipeline = []; this.displayPipeline = []; this.scrollbarWidth = 0; this.renderer = null; } //////////////// Setup Functions ///////////////// createHolderElement() { var el = document.createElement("div"); el.classList.add("tabulator-tableholder"); el.setAttribute("tabindex", 0); return el; } createTableElement() { var el = document.createElement("div"); el.classList.add("tabulator-table"); el.setAttribute("role", "rowgroup"); el.setAttribute("id", "tabulator-table-body"); return el; } initializePlaceholder() { var placeholder = this.table.options.placeholder; if (typeof placeholder === "function") { placeholder = placeholder.call(this.table); } placeholder = this.chain("placeholder", [placeholder], placeholder, placeholder) || placeholder; if (placeholder) { let el = document.createElement("div"); el.classList.add("tabulator-placeholder"); if (typeof placeholder == "string") { let contents = document.createElement("div"); contents.classList.add("tabulator-placeholder-contents"); contents.innerHTML = placeholder; el.appendChild(contents); this.placeholderContents = contents; } else if (typeof HTMLElement !== "undefined" && placeholder instanceof HTMLElement) { el.appendChild(placeholder); this.placeholderContents = placeholder; } else { console.warn("Invalid placeholder provided, must be string or HTML Element", placeholder); this.el = null; } this.placeholder = el; } } //return containing element getElement() { return this.element; } //return table element getTableElement() { return this.tableElement; } initialize() { this.initializePlaceholder(); this.initializeRenderer(); this.element.appendChild(this.tableElement); this.firstRender = true; this.element.addEventListener("scroll", () => { var left = this.element.scrollLeft, leftDir = this.scrollLeft > left, top = this.element.scrollTop, topDir = this.scrollTop > top; if (this.scrollLeft != left) { this.scrollLeft = left; this.dispatch("scroll-horizontal", left, leftDir); this.dispatchExternal("scrollHorizontal", left, leftDir); this._positionPlaceholder(); } if (this.scrollTop != top) { this.scrollTop = top; this.renderer.scrollRows(top, topDir); this.dispatch("scroll-vertical", top, topDir); this.dispatchExternal("scrollVertical", top, topDir); } }); } ////////////////// Row Manipulation ////////////////// findRow(subject) { if (typeof subject == "object") { if (subject instanceof Row) { return subject; } else if (subject instanceof RowComponent) { return subject._getSelf() || false; } else if (typeof HTMLElement !== "undefined" && subject instanceof HTMLElement) { let match = this.rows.find((row) => { return row.getElement() === subject; }); return match || false; } else if (subject === null) { return false; } } else if (typeof subject == "undefined") { return false; } else { let match = this.rows.find((row) => { return row.data[this.table.options.index] == subject; }); return match || false; } return false; } getRowFromDataObject(data) { var match = this.rows.find((row) => { return row.data === data; }); return match || false; } getRowFromPosition(position) { return this.getDisplayRows().find((row) => { return row.type === "row" && row.getPosition() === position && row.isDisplayed(); }); } scrollToRow(row, position, ifVisible) { return this.renderer.scrollToRowPosition(row, position, ifVisible); } ////////////////// Data Handling ////////////////// setData(data, renderInPosition, columnsChanged) { return new Promise((resolve, reject) => { if (renderInPosition && this.getDisplayRows().length) { if (this.table.options.pagination) { this._setDataActual(data, true); } else { this.reRenderInPosition(() => { this._setDataActual(data); }); } } else { if (this.table.options.autoColumns && columnsChanged && this.table.initialized) { this.table.columnManager.generateColumnsFromRowData(data); } this.resetScroll(); this._setDataActual(data); } resolve(); }); } _setDataActual(data, renderInPosition) { this.dispatchExternal("dataProcessing", data); this._wipeElements(); if (Array.isArray(data)) { this.dispatch("data-processing", data); data.forEach((def, i) => { if (def && typeof def === "object") { var row = new Row(def, this); this.rows.push(row); } else { console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but received:", def); } }); this.refreshActiveData(false, false, renderInPosition); this.dispatch("data-processed", data); this.dispatchExternal("dataProcessed", data); } else { console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data); } } _wipeElements() { this.dispatch("rows-wipe"); this.destroy(); this.adjustTableSize(); this.dispatch("rows-wiped"); } destroy() { this.rows.forEach((row) => { row.wipe(); }); this.rows = []; this.activeRows = []; this.activeRowsPipeline = []; this.activeRowsCount = 0; this.displayRows = []; this.displayRowsCount = 0; } deleteRow(row, blockRedraw) { var allIndex = this.rows.indexOf(row), activeIndex = this.activeRows.indexOf(row); if (activeIndex > -1) { this.activeRows.splice(activeIndex, 1); } if (allIndex > -1) { this.rows.splice(allIndex, 1); } this.setActiveRows(this.activeRows); this.displayRowIterator((rows2) => { var displayIndex = rows2.indexOf(row); if (displayIndex > -1) { rows2.splice(displayIndex, 1); } }); if (!blockRedraw) { this.reRenderInPosition(); } this.regenerateRowPositions(); this.dispatchExternal("rowDeleted", row.getComponent()); if (!this.displayRowsCount) { this.tableEmpty(); } if (this.subscribedExternal("dataChanged")) { this.dispatchExternal("dataChanged", this.getData()); } } addRow(data, pos, index, blockRedraw) { var row = this.addRowActual(data, pos, index, blockRedraw); return row; } //add multiple rows addRows(data, pos, index, refreshDisplayOnly) { var rows2 = []; return new Promise((resolve, reject) => { pos = this.findAddRowPos(pos); if (!Array.isArray(data)) { data = [data]; } if (typeof index == "undefined" && pos || typeof index !== "undefined" && !pos) { data.reverse(); } data.forEach((item, i) => { var row = this.addRow(item, pos, index, true); rows2.push(row); this.dispatch("row-added", row, item, pos, index); }); this.refreshActiveData(refreshDisplayOnly ? "displayPipeline" : false, false, true); this.regenerateRowPositions(); if (this.displayRowsCount) { this._clearPlaceholder(); } resolve(rows2); }); } findAddRowPos(pos) { if (typeof pos === "undefined") { pos = this.table.options.addRowPos; } if (pos === "pos") { pos = true; } if (pos === "bottom") { pos = false; } return pos; } addRowActual(data, pos, index, blockRedraw) { var row = data instanceof Row ? data : new Row(data || {}, this), top = this.findAddRowPos(pos), allIndex = -1, activeIndex, chainResult; if (!index) { chainResult = this.chain("row-adding-position", [row, top], null, { index, top }); index = chainResult.index; top = chainResult.top; } if (typeof index !== "undefined") { index = this.findRow(index); } index = this.chain("row-adding-index", [row, index, top], null, index); if (index) { allIndex = this.rows.indexOf(index); } if (index && allIndex > -1) { activeIndex = this.activeRows.indexOf(index); this.displayRowIterator(function(rows2) { var displayIndex = rows2.indexOf(index); if (displayIndex > -1) { rows2.splice(top ? displayIndex : displayIndex + 1, 0, row); } }); if (activeIndex > -1) { this.activeRows.splice(top ? activeIndex : activeIndex + 1, 0, row); } this.rows.splice(top ? allIndex : allIndex + 1, 0, row); } else { if (top) { this.displayRowIterator(function(rows2) { rows2.unshift(row); }); this.activeRows.unshift(row); this.rows.unshift(row); } else { this.displayRowIterator(function(rows2) { rows2.push(row); }); this.activeRows.push(row); this.rows.push(row); } } this.setActiveRows(this.activeRows); this.dispatchExternal("rowAdded", row.getComponent()); if (this.subscribedExternal("dataChanged")) { this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } if (!blockRedraw) { this.reRenderInPosition(); } return row; } moveRow(from, to, after) { this.dispatch("row-move", from, to, after); this.moveRowActual(from, to, after); this.regenerateRowPositions(); this.dispatch("row-moved", from, to, after); this.dispatchExternal("rowMoved", from.getComponent()); } moveRowActual(from, to, after) { this.moveRowInArray(this.rows, from, to, after); this.moveRowInArray(this.activeRows, from, to, after); this.displayRowIterator((rows2) => { this.moveRowInArray(rows2, from, to, after); }); this.dispatch("row-moving", from, to, after); } moveRowInArray(rows2, from, to, after) { var fromIndex, toIndex, start, end; if (from !== to) { fromIndex = rows2.indexOf(from); if (fromIndex > -1) { rows2.splice(fromIndex, 1); toIndex = rows2.indexOf(to); if (toIndex > -1) { if (after) { rows2.splice(toIndex + 1, 0, from); } else { rows2.splice(toIndex, 0, from); } } else { rows2.splice(fromIndex, 0, from); } } if (rows2 === this.getDisplayRows()) { start = fromIndex < toIndex ? fromIndex : toIndex; end = toIndex > fromIndex ? toIndex : fromIndex + 1; for (let i = start; i <= end; i++) { if (rows2[i]) { this.styleRow(rows2[i], i); } } } } } clearData() { this.setData([]); } getRowIndex(row) { return this.findRowIndex(row, this.rows); } getDisplayRowIndex(row) { var index = this.getDisplayRows().indexOf(row); return index > -1 ? index : false; } nextDisplayRow(row, rowOnly) { var index = this.getDisplayRowIndex(row), nextRow = false; if (index !== false && index < this.displayRowsCount - 1) { nextRow = this.getDisplayRows()[index + 1]; } if (nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")) { return this.nextDisplayRow(nextRow, rowOnly); } return nextRow; } prevDisplayRow(row, rowOnly) { var index = this.getDisplayRowIndex(row), prevRow = false; if (index) { prevRow = this.getDisplayRows()[index - 1]; } if (rowOnly && prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")) { return this.prevDisplayRow(prevRow, rowOnly); } return prevRow; } findRowIndex(row, list2) { var rowIndex; row = this.findRow(row); if (row) { rowIndex = list2.indexOf(row); if (rowIndex > -1) { return rowIndex; } } return false; } getData(active, transform) { var output = [], rows2 = this.getRows(active); rows2.forEach(function(row) { if (row.type == "row") { output.push(row.getData(transform || "data")); } }); return output; } getComponents(active) { var output = [], rows2 = this.getRows(active); rows2.forEach(function(row) { output.push(row.getComponent()); }); return output; } getDataCount(active) { var rows2 = this.getRows(active); return rows2.length; } scrollHorizontal(left) { this.scrollLeft = left; this.element.scrollLeft = left; this.dispatch("scroll-horizontal", left); } registerDataPipelineHandler(handler, priority) { if (typeof priority !== "undefined") { this.dataPipeline.push({ handler, priority }); this.dataPipeline.sort((a, b) => { return a.priority - b.priority; }); } else { console.error("Data pipeline handlers must have a priority in order to be registered"); } } registerDisplayPipelineHandler(handler, priority) { if (typeof priority !== "undefined") { this.displayPipeline.push({ handler, priority }); this.displayPipeline.sort((a, b) => { return a.priority - b.priority; }); } else { console.error("Display pipeline handlers must have a priority in order to be registered"); } } //set active data set refreshActiveData(handler, skipStage, renderInPosition) { var table = this.table, stage = "", index = 0, cascadeOrder = ["all", "dataPipeline", "display", "displayPipeline", "end"]; if (!this.table.destroyed) { if (typeof handler === "function") { index = this.dataPipeline.findIndex((item) => { return item.handler === handler; }); if (index > -1) { stage = "dataPipeline"; if (skipStage) { if (index == this.dataPipeline.length - 1) { stage = "display"; } else { index++; } } } else { index = this.displayPipeline.findIndex((item) => { return item.handler === handler; }); if (index > -1) { stage = "displayPipeline"; if (skipStage) { if (index == this.displayPipeline.length - 1) { stage = "end"; } else { index++; } } } else { console.error("Unable to refresh data, invalid handler provided", handler); return; } } } else { stage = handler || "all"; index = 0; } if (this.redrawBlock) { if (!this.redrawBlockRestoreConfig || this.redrawBlockRestoreConfig && (this.redrawBlockRestoreConfig.stage === stage && index < this.redrawBlockRestoreConfig.index || cascadeOrder.indexOf(stage) < cascadeOrder.indexOf(this.redrawBlockRestoreConfig.stage))) { this.redrawBlockRestoreConfig = { handler, skipStage, renderInPosition, stage, index }; } return; } else { if (Helpers.elVisible(this.element)) { if (renderInPosition) { this.reRenderInPosition(this.refreshPipelines.bind(this, handler, stage, index, renderInPosition)); } else { this.refreshPipelines(handler, stage, index, renderInPosition); if (!handler) { this.table.columnManager.renderer.renderColumns(); } this.renderTable(); if (table.options.layoutColumnsOnNewData) { this.table.columnManager.redraw(true); } } } else { this.refreshPipelines(handler, stage, index, renderInPosition); } this.dispatch("data-refreshed"); } } } refreshPipelines(handler, stage, index, renderInPosition) { this.dispatch("data-refreshing"); if (!handler || !this.activeRowsPipeline[0]) { this.activeRowsPipeline[0] = this.rows.slice(0); } switch (stage) { case "all": //handle case where all data needs refreshing case "dataPipeline": for (let i = index; i < this.dataPipeline.length; i++) { let result = this.dataPipeline[i].handler(this.activeRowsPipeline[i].slice(0)); this.activeRowsPipeline[i + 1] = result || this.activeRowsPipeline[i].slice(0); } this.setActiveRows(this.activeRowsPipeline[this.dataPipeline.length]); case "display": index = 0; this.resetDisplayRows(); case "displayPipeline": for (let i = index; i < this.displayPipeline.length; i++) { let result = this.displayPipeline[i].handler((i ? this.getDisplayRows(i - 1) : this.activeRows).slice(0), renderInPosition); this.setDisplayRows(result || this.getDisplayRows(i - 1).slice(0), i); } case "end": this.regenerateRowPositions(); } if (this.getDisplayRows().length) { this._clearPlaceholder(); } } //regenerate row positions regenerateRowPositions() { var rows2 = this.getDisplayRows(); var index = 1; rows2.forEach((row) => { if (row.type === "row") { row.setPosition(index); index++; } }); } setActiveRows(activeRows) { this.activeRows = this.activeRows = Object.assign([], activeRows); this.activeRowsCount = this.activeRows.length; } //reset display rows array resetDisplayRows() { this.displayRows = []; this.displayRows.push(this.activeRows.slice(0)); this.displayRowsCount = this.displayRows[0].length; } //set display row pipeline data setDisplayRows(displayRows, index) { this.displayRows[index] = displayRows; if (index == this.displayRows.length - 1) { this.displayRowsCount = this.displayRows[this.displayRows.length - 1].length; } } getDisplayRows(index) { if (typeof index == "undefined") { return this.displayRows.length ? this.displayRows[this.displayRows.length - 1] : []; } else { return this.displayRows[index] || []; } } getVisibleRows(chain, viewable) { var rows2 = Object.assign([], this.renderer.visibleRows(!viewable)); if (chain) { rows2 = this.chain("rows-visible", [viewable], rows2, rows2); } return rows2; } //repeat action across display rows displayRowIterator(callback) { this.activeRowsPipeline.forEach(callback); this.displayRows.forEach(callback); this.displayRowsCount = this.displayRows[this.displayRows.length - 1].length; } //return only actual rows (not group headers etc) getRows(type) { var rows2 = []; switch (type) { case "active": rows2 = this.activeRows; break; case "display": rows2 = this.table.rowManager.getDisplayRows(); break; case "visible": rows2 = this.getVisibleRows(false, true); break; default: rows2 = this.chain("rows-retrieve", type, null, this.rows) || this.rows; } return rows2; } ///////////////// Table Rendering ///////////////// //trigger rerender of table in current position reRenderInPosition(callback) { if (this.redrawBlock) { if (callback) { callback(); } else { this.redrawBlockRenderInPosition = true; } } else { this.dispatchExternal("renderStarted"); this.renderer.rerenderRows(callback); if (!this.fixedHeight) { this.adjustTableSize(); } this.scrollBarCheck(); this.dispatchExternal("renderComplete"); } } scrollBarCheck() { var scrollbarWidth = 0; if (this.element.scrollHeight > this.element.clientHeight) { scrollbarWidth = this.element.offsetWidth - this.element.clientWidth; } if (scrollbarWidth !== this.scrollbarWidth) { this.scrollbarWidth = scrollbarWidth; this.dispatch("scrollbar-vertical", scrollbarWidth); } } initializeRenderer() { var renderClass; var renderers = { "virtual": VirtualDomVertical, "basic": BasicVertical }; if (typeof this.table.options.renderVertical === "string") { renderClass = renderers[this.table.options.renderVertical]; } else { renderClass = this.table.options.renderVertical; } if (renderClass) { this.renderMode = this.table.options.renderVertical; this.renderer = new renderClass(this.table, this.element, this.tableElement); this.renderer.initialize(); if ((this.table.element.clientHeight || this.table.options.height) && !(this.table.options.minHeight && this.table.options.maxHeight)) { this.fixedHeight = true; } else { this.fixedHeight = false; } } else { console.error("Unable to find matching renderer:", this.table.options.renderVertical); } } getRenderMode() { return this.renderMode; } renderTable() { this.dispatchExternal("renderStarted"); this.element.scrollTop = 0; this._clearTable(); if (this.displayRowsCount) { this.renderer.renderRows(); if (this.firstRender) { this.firstRender = false; if (!this.fixedHeight) { this.adjustTableSize(); } this.layoutRefresh(true); } } else { this.renderEmptyScroll(); } if (!this.fixedHeight) { this.adjustTableSize(); } this.dispatch("table-layout"); if (!this.displayRowsCount) { this._showPlaceholder(); } this.scrollBarCheck(); this.dispatchExternal("renderComplete"); } //show scrollbars on empty table div renderEmptyScroll() { if (this.placeholder) { this.tableElement.style.display = "none"; } else { this.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px"; } } _clearTable() { this._clearPlaceholder(); this.scrollTop = 0; this.scrollLeft = 0; this.renderer.clearRows(); } tableEmpty() { this.renderEmptyScroll(); this._showPlaceholder(); } checkPlaceholder() { if (this.displayRowsCount) { this._clearPlaceholder(); } else { this.tableEmpty(); } } _showPlaceholder() { if (this.placeholder) { if (this.placeholder && this.placeholder.parentNode) { this.placeholder.parentNode.removeChild(this.placeholder); } this.initializePlaceholder(); this.placeholder.setAttribute("tabulator-render-mode", this.renderMode); this.getElement().appendChild(this.placeholder); this._positionPlaceholder(); this.adjustTableSize(); } } _clearPlaceholder() { if (this.placeholder && this.placeholder.parentNode) { this.placeholder.parentNode.removeChild(this.placeholder); } this.tableElement.style.minWidth = ""; this.tableElement.style.display = ""; } _positionPlaceholder() { if (this.placeholder && this.placeholder.parentNode) { this.placeholder.style.width = this.table.columnManager.getWidth() + "px"; this.placeholderContents.style.width = this.table.rowManager.element.clientWidth + "px"; this.placeholderContents.style.marginLeft = this.scrollLeft + "px"; } } styleRow(row, index) { var rowEl = row.getElement(); if (index % 2) { rowEl.classList.add("tabulator-row-even"); rowEl.classList.remove("tabulator-row-odd"); } else { rowEl.classList.add("tabulator-row-odd"); rowEl.classList.remove("tabulator-row-even"); } } //normalize height of active rows normalizeHeight(force) { this.activeRows.forEach(function(row) { row.normalizeHeight(force); }); } //adjust the height of the table holder to fit in the Tabulator element adjustTableSize() { let initialHeight = this.element.clientHeight, minHeight; let resized = false; if (this.renderer.verticalFillMode === "fill") { let otherHeight = Math.floor(this.table.columnManager.getElement().getBoundingClientRect().height + (this.table.footerManager && this.table.footerManager.active && !this.table.footerManager.external ? this.table.footerManager.getElement().getBoundingClientRect().height : 0)); if (this.fixedHeight) { minHeight = isNaN(this.table.options.minHeight) ? this.table.options.minHeight : this.table.options.minHeight + "px"; const height = "calc(100% - " + otherHeight + "px)"; this.element.style.minHeight = minHeight || "calc(100% - " + otherHeight + "px)"; this.element.style.height = height; this.element.style.maxHeight = height; } else { this.element.style.height = ""; this.element.style.height = this.table.element.clientHeight - otherHeight + "px"; this.element.scrollTop = this.scrollTop; } this.renderer.resize(); if (!this.fixedHeight && initialHeight != this.element.clientHeight) { resized = true; if (!this.redrawing) { this.redrawing = true; if (this.subscribed("table-resize")) { this.dispatch("table-resize"); } else { this.redraw(); } this.redrawing = false; } } this.scrollBarCheck(); } this._positionPlaceholder(); return resized; } //reinitialize all rows reinitialize() { this.rows.forEach(function(row) { row.reinitialize(true); }); } //prevent table from being redrawn blockRedraw() { this.redrawBlock = true; this.redrawBlockRestoreConfig = false; } //restore table redrawing restoreRedraw() { this.redrawBlock = false; if (this.redrawBlockRestoreConfig) { this.refreshActiveData(this.redrawBlockRestoreConfig.handler, this.redrawBlockRestoreConfig.skipStage, this.redrawBlockRestoreConfig.renderInPosition); this.redrawBlockRestoreConfig = false; } else { if (this.redrawBlockRenderInPosition) { this.reRenderInPosition(); } } this.redrawBlockRenderInPosition = false; } //redraw table redraw(force) { this.adjustTableSize(); this.table.tableWidth = this.table.element.clientWidth; if (!force) { this.reRenderInPosition(); this.scrollHorizontal(this.scrollLeft); } else { this.renderTable(); } } resetScroll() { this.element.scrollLeft = 0; this.element.scrollTop = 0; if (this.table.browser === "ie") { var event = document.createEvent("Event"); event.initEvent("scroll", false, true); this.element.dispatchEvent(event); } else { this.element.dispatchEvent(new Event("scroll")); } } } class FooterManager extends CoreFeature { constructor(table) { super(table); this.active = false; this.element = this.createElement(); this.containerElement = this.createContainerElement(); this.external = false; } initialize() { this.initializeElement(); } createElement() { var el = document.createElement("div"); el.classList.add("tabulator-footer"); return el; } createContainerElement() { var el = document.createElement("div"); el.classList.add("tabulator-footer-contents"); this.element.appendChild(el); return el; } initializeElement() { if (this.table.options.footerElement) { switch (typeof this.table.options.footerElement) { case "string": if (this.table.options.footerElement[0] === "<") { this.containerElement.innerHTML = this.table.options.footerElement; } else { this.external = true; this.containerElement = document.querySelector(this.table.options.footerElement); } break; default: this.element = this.table.options.footerElement; break; } } } getElement() { return this.element; } append(element) { this.activate(); this.containerElement.appendChild(element); this.table.rowManager.adjustTableSize(); } prepend(element) { this.activate(); this.element.insertBefore(element, this.element.firstChild); this.table.rowManager.adjustTableSize(); } remove(element) { element.parentNode.removeChild(element); this.deactivate(); } deactivate(force) { if (!this.element.firstChild || force) { if (!this.external) { this.element.parentNode.removeChild(this.element); } this.active = false; } } activate() { if (!this.active) { this.active = true; if (!this.external) { this.table.element.appendChild(this.getElement()); this.table.element.style.display = ""; } } } redraw() { this.dispatch("footer-redraw"); } } class InteractionManager extends CoreFeature { constructor(table) { super(table); this.el = null; this.abortClasses = ["tabulator-headers", "tabulator-table"]; this.previousTargets = {}; this.listeners = [ "click", "dblclick", "contextmenu", "mouseenter", "mouseleave", "mouseover", "mouseout", "mousemove", "mouseup", "mousedown", "touchstart", "touchend" ]; this.componentMap = { "tabulator-cell": "cell", "tabulator-row": "row", "tabulator-group": "group", "tabulator-col": "column" }; this.pseudoTrackers = { "row": { subscriber: null, target: null }, "cell": { subscriber: null, target: null }, "group": { subscriber: null, target: null }, "column": { subscriber: null, target: null } }; this.pseudoTracking = false; } initialize() { this.el = this.table.element; this.buildListenerMap(); this.bindSubscriptionWatchers(); } buildListenerMap() { var listenerMap = {}; this.listeners.forEach((listener) => { listenerMap[listener] = { handler: null, components: [] }; }); this.listeners = listenerMap; } bindPseudoEvents() { Object.keys(this.pseudoTrackers).forEach((key) => { this.pseudoTrackers[key].subscriber = this.pseudoMouseEnter.bind(this, key); this.subscribe(key + "-mouseover", this.pseudoTrackers[key].subscriber); }); this.pseudoTracking = true; } pseudoMouseEnter(key, e, target) { if (this.pseudoTrackers[key].target !== target) { if (this.pseudoTrackers[key].target) { this.dispatch(key + "-mouseleave", e, this.pseudoTrackers[key].target); } this.pseudoMouseLeave(key, e); this.pseudoTrackers[key].target = target; this.dispatch(key + "-mouseenter", e, target); } } pseudoMouseLeave(key, e) { var leaveList = Object.keys(this.pseudoTrackers), linkedKeys = { "row": ["cell"], "cell": ["row"] }; leaveList = leaveList.filter((item) => { var links = linkedKeys[key]; return item !== key && (!links || links && !links.includes(item)); }); leaveList.forEach((key2) => { var target = this.pseudoTrackers[key2].target; if (this.pseudoTrackers[key2].target) { this.dispatch(key2 + "-mouseleave", e, target); this.pseudoTrackers[key2].target = null; } }); } bindSubscriptionWatchers() { var listeners = Object.keys(this.listeners), components = Object.values(this.componentMap); for (let comp of components) { for (let listener of listeners) { let key = comp + "-" + listener; this.subscriptionChange(key, this.subscriptionChanged.bind(this, comp, listener)); } } this.subscribe("table-destroy", this.clearWatchers.bind(this)); } subscriptionChanged(component, key, added) { var listener = this.listeners[key].components, index = listener.indexOf(component), changed = false; if (added) { if (index === -1) { listener.push(component); changed = true; } } else { if (!this.subscribed(component + "-" + key)) { if (index > -1) { listener.splice(index, 1); changed = true; } } } if ((key === "mouseenter" || key === "mouseleave") && !this.pseudoTracking) { this.bindPseudoEvents(); } if (changed) { this.updateEventListeners(); } } updateEventListeners() { for (let key in this.listeners) { let listener = this.listeners[key]; if (listener.components.length) { if (!listener.handler) { listener.handler = this.track.bind(this, key); this.el.addEventListener(key, listener.handler); } } else { if (listener.handler) { this.el.removeEventListener(key, listener.handler); listener.handler = null; } } } } track(type, e) { var path = e.composedPath && e.composedPath() || e.path; var targets = this.findTargets(path); targets = this.bindComponents(type, targets); this.triggerEvents(type, e, targets); if (this.pseudoTracking && (type == "mouseover" || type == "mouseleave") && !Object.keys(targets).length) { this.pseudoMouseLeave("none", e); } } findTargets(path) { var targets = {}; let componentMap = Object.keys(this.componentMap); for (let el of path) { let classList = el.classList ? [...el.classList] : []; let abort = classList.filter((item) => { return this.abortClasses.includes(item); }); if (abort.length) { break; } let elTargets = classList.filter((item) => { return componentMap.includes(item); }); for (let target of elTargets) { if (!targets[this.componentMap[target]]) { targets[this.componentMap[target]] = el; } } } if (targets.group && targets.group === targets.row) { delete targets.row; } return targets; } bindComponents(type, targets) { var keys = Object.keys(targets).reverse(), listener = this.listeners[type], matches = {}, output = {}, targetMatches = {}; for (let key of keys) { let component, target = targets[key], previousTarget = this.previousTargets[key]; if (previousTarget && previousTarget.target === target) { component = previousTarget.component; } else { switch (key) { case "row": case "group": if (listener.components.includes("row") || listener.components.includes("cell") || listener.components.includes("group")) { let rows2 = this.table.rowManager.getVisibleRows(true); component = rows2.find((row) => { return row.getElement() === target; }); if (targets["row"] && targets["row"].parentNode && targets["row"].parentNode.closest(".tabulator-row")) { targets[key] = false; } } break; case "column": if (listener.components.includes("column")) { component = this.table.columnManager.findColumn(target); } break; case "cell": if (listener.components.includes("cell")) { if (matches["row"] instanceof Row) { component = matches["row"].findCell(target); } else { if (targets["row"]) { console.warn("Event Target Lookup Error - The row this cell is attached to cannot be found, has the table been reinitialized without being destroyed first?"); } } } break; } } if (component) { matches[key] = component; targetMatches[key] = { target, component }; } } this.previousTargets = targetMatches; Object.keys(targets).forEach((key) => { let value = matches[key]; output[key] = value; }); return output; } triggerEvents(type, e, targets) { var listener = this.listeners[type]; for (let key in targets) { if (targets[key] && listener.components.includes(key)) { this.dispatch(key + "-" + type, e, targets[key]); } } } clearWatchers() { for (let key in this.listeners) { let listener = this.listeners[key]; if (listener.handler) { this.el.removeEventListener(key, listener.handler); listener.handler = null; } } } } class ComponentFunctionBinder { constructor(table) { this.table = table; this.bindings = {}; } bind(type, funcName, handler) { if (!this.bindings[type]) { this.bindings[type] = {}; } if (this.bindings[type][funcName]) { console.warn("Unable to bind component handler, a matching function name is already bound", type, funcName, handler); } else { this.bindings[type][funcName] = handler; } } handle(type, component, name2) { if (this.bindings[type] && this.bindings[type][name2] && typeof this.bindings[type][name2].bind === "function") { return this.bindings[type][name2].bind(null, component); } else { if (name2 !== "then" && typeof name2 === "string" && !name2.startsWith("_")) { if (this.table.options.debugInvalidComponentFuncs) { console.error("The " + type + " component does not have a " + name2 + " function, have you checked that you have the correct Tabulator module installed?"); } } } } } class DataLoader extends CoreFeature { constructor(table) { super(table); this.requestOrder = 0; this.loading = false; } initialize() { } load(data, params, config, replace, silent, columnsChanged) { var requestNo = ++this.requestOrder; if (this.table.destroyed) { return Promise.resolve(); } this.dispatchExternal("dataLoading", data); if (data && (data.indexOf("{") == 0 || data.indexOf("[") == 0)) { data = JSON.parse(data); } if (this.confirm("data-loading", [data, params, config, silent])) { this.loading = true; if (!silent) { this.alertLoader(); } params = this.chain("data-params", [data, config, silent], params || {}, params || {}); params = this.mapParams(params, this.table.options.dataSendParams); var result = this.chain("data-load", [data, params, config, silent], false, Promise.resolve([])); return result.then((response) => { if (!this.table.destroyed) { if (!Array.isArray(response) && typeof response == "object") { response = this.mapParams(response, this.objectInvert(this.table.options.dataReceiveParams)); } var rowData = this.chain("data-loaded", [response], null, response); if (requestNo == this.requestOrder) { this.clearAlert(); if (rowData !== false) { this.dispatchExternal("dataLoaded", rowData); this.table.rowManager.setData(rowData, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged); } } else { console.warn("Data Load Response Blocked - An active data load request was blocked by an attempt to change table data while the request was being made"); } } else { console.warn("Data Load Response Blocked - Table has been destroyed"); } }).catch((error) => { console.error("Data Load Error: ", error); this.dispatchExternal("dataLoadError", error); if (!silent) { this.alertError(); } setTimeout(() => { this.clearAlert(); }, this.table.options.dataLoaderErrorTimeout); }).finally(() => { this.loading = false; }); } else { this.dispatchExternal("dataLoaded", data); if (!data) { data = []; } this.table.rowManager.setData(data, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged); return Promise.resolve(); } } mapParams(params, map) { var output = {}; for (let key in params) { output[map.hasOwnProperty(key) ? map[key] : key] = params[key]; } return output; } objectInvert(obj) { var output = {}; for (let key in obj) { output[obj[key]] = key; } return output; } blockActiveLoad() { this.requestOrder++; } alertLoader() { var shouldLoad = typeof this.table.options.dataLoader === "function" ? this.table.options.dataLoader() : this.table.options.dataLoader; if (shouldLoad) { this.table.alertManager.alert(this.table.options.dataLoaderLoading || this.langText("data|loading")); } } alertError() { this.table.alertManager.alert(this.table.options.dataLoaderError || this.langText("data|error"), "error"); } clearAlert() { this.table.alertManager.clear(); } } class ExternalEventBus { constructor(table, optionsList, debug) { this.table = table; this.events = {}; this.optionsList = optionsList || {}; this.subscriptionNotifiers = {}; this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this); this.debug = debug; } subscriptionChange(key, callback) { if (!this.subscriptionNotifiers[key]) { this.subscriptionNotifiers[key] = []; } this.subscriptionNotifiers[key].push(callback); if (this.subscribed(key)) { this._notifySubscriptionChange(key, true); } } subscribe(key, callback) { if (!this.events[key]) { this.events[key] = []; } this.events[key].push(callback); this._notifySubscriptionChange(key, true); } unsubscribe(key, callback) { var index; if (this.events[key]) { if (callback) { index = this.events[key].findIndex((item) => { return item === callback; }); if (index > -1) { this.events[key].splice(index, 1); } else { console.warn("Cannot remove event, no matching event found:", key, callback); return; } } else { delete this.events[key]; } } else { console.warn("Cannot remove event, no events set on:", key); return; } this._notifySubscriptionChange(key, false); } subscribed(key) { return this.events[key] && this.events[key].length; } _notifySubscriptionChange(key, subscribed) { var notifiers = this.subscriptionNotifiers[key]; if (notifiers) { notifiers.forEach((callback) => { callback(subscribed); }); } } _dispatch() { var args = Array.from(arguments), key = args.shift(), result; if (this.events[key]) { this.events[key].forEach((callback, i) => { let callResult = callback.apply(this.table, args); if (!i) { result = callResult; } }); } return result; } _debugDispatch() { var args = Array.from(arguments), key = args[0]; args[0] = "ExternalEvent:" + args[0]; if (this.debug === true || this.debug.includes(key)) { console.log(...args); } return this._dispatch(...arguments); } } class InternalEventBus { constructor(debug) { this.events = {}; this.subscriptionNotifiers = {}; this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this); this.chain = debug ? this._debugChain.bind(this) : this._chain.bind(this); this.confirm = debug ? this._debugConfirm.bind(this) : this._confirm.bind(this); this.debug = debug; } subscriptionChange(key, callback) { if (!this.subscriptionNotifiers[key]) { this.subscriptionNotifiers[key] = []; } this.subscriptionNotifiers[key].push(callback); if (this.subscribed(key)) { this._notifySubscriptionChange(key, true); } } subscribe(key, callback, priority = 1e4) { if (!this.events[key]) { this.events[key] = []; } this.events[key].push({ callback, priority }); this.events[key].sort((a, b) => { return a.priority - b.priority; }); this._notifySubscriptionChange(key, true); } unsubscribe(key, callback) { var index; if (this.events[key]) { if (callback) { index = this.events[key].findIndex((item) => { return item.callback === callback; }); if (index > -1) { this.events[key].splice(index, 1); } else { console.warn("Cannot remove event, no matching event found:", key, callback); return; } } } else { console.warn("Cannot remove event, no events set on:", key); return; } this._notifySubscriptionChange(key, false); } subscribed(key) { return this.events[key] && this.events[key].length; } _chain(key, args, initialValue, fallback) { var value = initialValue; if (!Array.isArray(args)) { args = [args]; } if (this.subscribed(key)) { this.events[key].forEach((subscriber, i) => { value = subscriber.callback.apply(this, args.concat([value])); }); return value; } else { return typeof fallback === "function" ? fallback() : fallback; } } _confirm(key, args) { var confirmed = false; if (!Array.isArray(args)) { args = [args]; } if (this.subscribed(key)) { this.events[key].forEach((subscriber, i) => { if (subscriber.callback.apply(this, args)) { confirmed = true; } }); } return confirmed; } _notifySubscriptionChange(key, subscribed) { var notifiers = this.subscriptionNotifiers[key]; if (notifiers) { notifiers.forEach((callback) => { callback(subscribed); }); } } _dispatch() { var args = Array.from(arguments), key = args.shift(); if (this.events[key]) { this.events[key].forEach((subscriber) => { subscriber.callback.apply(this, args); }); } } _debugDispatch() { var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if (this.debug === true || this.debug.includes(key)) { console.log(...args); } return this._dispatch(...arguments); } _debugChain() { var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if (this.debug === true || this.debug.includes(key)) { console.log(...args); } return this._chain(...arguments); } _debugConfirm() { var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if (this.debug === true || this.debug.includes(key)) { console.log(...args); } return this._confirm(...arguments); } } class DeprecationAdvisor extends CoreFeature { constructor(table) { super(table); } _warnUser() { if (this.options("debugDeprecation")) { console.warn(...arguments); } } check(oldOption, newOption, convert) { var msg = ""; if (typeof this.options(oldOption) !== "undefined") { msg = "Deprecated Setup Option - Use of the %c" + oldOption + "%c option is now deprecated"; if (newOption) { msg = msg + ", Please use the %c" + newOption + "%c option instead"; this._warnUser(msg, "font-weight: bold;", "font-weight: normal;", "font-weight: bold;", "font-weight: normal;"); if (convert) { this.table.options[newOption] = this.table.options[oldOption]; } } else { this._warnUser(msg, "font-weight: bold;", "font-weight: normal;"); } return false; } else { return true; } } checkMsg(oldOption, msg) { if (typeof this.options(oldOption) !== "undefined") { this._warnUser("%cDeprecated Setup Option - Use of the %c" + oldOption + " %c option is now deprecated, " + msg, "font-weight: normal;", "font-weight: bold;", "font-weight: normal;"); return false; } else { return true; } } msg(msg) { this._warnUser(msg); } } class DependencyRegistry extends CoreFeature { constructor(table) { super(table); this.deps = {}; this.props = {}; } initialize() { this.deps = Object.assign({}, this.options("dependencies")); } lookup(key, prop, silent) { if (Array.isArray(key)) { for (const item of key) { var match = this.lookup(item, prop, true); if (match) { break; } } if (match) { return match; } else { this.error(key); } } else { if (prop) { return this.lookupProp(key, prop, silent); } else { return this.lookupKey(key, silent); } } } lookupProp(key, prop, silent) { var dependency; if (this.props[key] && this.props[key][prop]) { return this.props[key][prop]; } else { dependency = this.lookupKey(key, silent); if (dependency) { if (!this.props[key]) { this.props[key] = {}; } this.props[key][prop] = dependency[prop] || dependency; return this.props[key][prop]; } } } lookupKey(key, silent) { var dependency; if (this.deps[key]) { dependency = this.deps[key]; } else if (window[key]) { this.deps[key] = window[key]; dependency = this.deps[key]; } else { if (!silent) { this.error(key); } } return dependency; } error(key) { console.error("Unable to find dependency", key, "Please check documentation and ensure you have imported the required library into your project"); } } function fitData(columns, forced) { if (forced) { this.table.columnManager.renderer.reinitializeColumnWidths(columns); } if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { this.table.modules.responsiveLayout.update(); } } function fitDataGeneral(columns, forced) { columns.forEach(function(column) { column.reinitializeWidth(); }); if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { this.table.modules.responsiveLayout.update(); } } function fitDataStretch(columns, forced) { var colsWidth = 0, tableWidth = this.table.rowManager.element.clientWidth, gap = 0, lastCol = false; columns.forEach((column, i) => { if (!column.widthFixed) { column.reinitializeWidth(); } if (this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible) { lastCol = column; } if (column.visible) { colsWidth += column.getWidth(); } }); if (lastCol) { gap = tableWidth - colsWidth + lastCol.getWidth(); if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { lastCol.setWidth(0); this.table.modules.responsiveLayout.update(); } if (gap > 0) { lastCol.setWidth(gap); } else { lastCol.reinitializeWidth(); } } else { if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { this.table.modules.responsiveLayout.update(); } } } function fitColumns(columns, forced) { var totalWidth = this.table.rowManager.element.getBoundingClientRect().width; var fixedWidth = 0; var flexWidth = 0; var flexGrowUnits = 0; var flexColWidth = 0; var flexColumns = []; var fixedShrinkColumns = []; var flexShrinkUnits = 0; var overflowWidth = 0; var gapFill = 0; function calcWidth(width) { var colWidth; if (typeof width == "string") { if (width.indexOf("%") > -1) { colWidth = totalWidth / 100 * parseInt(width); } else { colWidth = parseInt(width); } } else { colWidth = width; } return colWidth; } function scaleColumns(columns2, freeSpace, colWidth, shrinkCols) { var oversizeCols = [], oversizeSpace = 0, remainingSpace = 0, nextColWidth = 0, remainingFlexGrowUnits = flexGrowUnits, gap = 0, changeUnits = 0, undersizeCols = []; function calcGrow(col) { return colWidth * (col.column.definition.widthGrow || 1); } function calcShrink(col) { return calcWidth(col.width) - colWidth * (col.column.definition.widthShrink || 0); } columns2.forEach(function(col, i) { var width = shrinkCols ? calcShrink(col) : calcGrow(col); if (col.column.minWidth >= width) { oversizeCols.push(col); } else { if (col.column.maxWidth && col.column.maxWidth < width) { col.width = col.column.maxWidth; freeSpace -= col.column.maxWidth; remainingFlexGrowUnits -= shrinkCols ? col.column.definition.widthShrink || 1 : col.column.definition.widthGrow || 1; if (remainingFlexGrowUnits) { colWidth = Math.floor(freeSpace / remainingFlexGrowUnits); } } else { undersizeCols.push(col); changeUnits += shrinkCols ? col.column.definition.widthShrink || 1 : col.column.definition.widthGrow || 1; } } }); if (oversizeCols.length) { oversizeCols.forEach(function(col) { oversizeSpace += shrinkCols ? col.width - col.column.minWidth : col.column.minWidth; col.width = col.column.minWidth; }); remainingSpace = freeSpace - oversizeSpace; nextColWidth = changeUnits ? Math.floor(remainingSpace / changeUnits) : remainingSpace; gap = scaleColumns(undersizeCols, remainingSpace, nextColWidth, shrinkCols); } else { gap = changeUnits ? freeSpace - Math.floor(freeSpace / changeUnits) * changeUnits : freeSpace; undersizeCols.forEach(function(column) { column.width = shrinkCols ? calcShrink(column) : calcGrow(column); }); } return gap; } if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { this.table.modules.responsiveLayout.update(); } if (this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight) { totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth; } columns.forEach(function(column) { var width, minWidth, colWidth; if (column.visible) { width = column.definition.width; minWidth = parseInt(column.minWidth); if (width) { colWidth = calcWidth(width); fixedWidth += colWidth > minWidth ? colWidth : minWidth; if (column.definition.widthShrink) { fixedShrinkColumns.push({ column, width: colWidth > minWidth ? colWidth : minWidth }); flexShrinkUnits += column.definition.widthShrink; } } else { flexColumns.push({ column, width: 0 }); flexGrowUnits += column.definition.widthGrow || 1; } } }); flexWidth = totalWidth - fixedWidth; flexColWidth = Math.floor(flexWidth / flexGrowUnits); gapFill = scaleColumns(flexColumns, flexWidth, flexColWidth, false); if (flexColumns.length && gapFill > 0) { flexColumns[flexColumns.length - 1].width += gapFill; } flexColumns.forEach(function(col) { flexWidth -= col.width; }); overflowWidth = Math.abs(gapFill) + flexWidth; if (overflowWidth > 0 && flexShrinkUnits) { gapFill = scaleColumns(fixedShrinkColumns, overflowWidth, Math.floor(overflowWidth / flexShrinkUnits), true); } if (gapFill && fixedShrinkColumns.length) { fixedShrinkColumns[fixedShrinkColumns.length - 1].width -= gapFill; } flexColumns.forEach(function(col) { col.column.setWidth(col.width); }); fixedShrinkColumns.forEach(function(col) { col.column.setWidth(col.width); }); } var defaultModes = { fitData, fitDataFill: fitDataGeneral, fitDataTable: fitDataGeneral, fitDataStretch, fitColumns }; const _Layout = class _Layout extends Module { constructor(table) { super(table, "layout"); this.mode = null; this.registerTableOption("layout", "fitData"); this.registerTableOption("layoutColumnsOnNewData", false); this.registerColumnOption("widthGrow"); this.registerColumnOption("widthShrink"); } //initialize layout system initialize() { var layout = this.table.options.layout; if (_Layout.modes[layout]) { this.mode = layout; } else { console.warn("Layout Error - invalid mode set, defaulting to 'fitData' : " + layout); this.mode = "fitData"; } this.table.element.setAttribute("tabulator-layout", this.mode); this.subscribe("column-init", this.initializeColumn.bind(this)); } initializeColumn(column) { if (column.definition.widthGrow) { column.definition.widthGrow = Number(column.definition.widthGrow); } if (column.definition.widthShrink) { column.definition.widthShrink = Number(column.definition.widthShrink); } } getMode() { return this.mode; } //trigger table layout layout(dataChanged) { var variableHeight = this.table.columnManager.columnsByIndex.find((column) => column.definition.variableHeight || column.definition.formatter === "textarea"); this.dispatch("layout-refreshing"); _Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged); if (variableHeight) { this.table.rowManager.normalizeHeight(true); } this.dispatch("layout-refreshed"); } }; __publicField(_Layout, "moduleName", "layout"); //load defaults __publicField(_Layout, "modes", defaultModes); let Layout = _Layout; var defaultLangs = { "default": { //hold default locale text "groups": { "item": "item", "items": "items" }, "columns": {}, "data": { "loading": "Loading", "error": "Error" }, "pagination": { "page_size": "Page Size", "page_title": "Show Page", "first": "First", "first_title": "First Page", "last": "Last", "last_title": "Last Page", "prev": "Prev", "prev_title": "Prev Page", "next": "Next", "next_title": "Next Page", "all": "All", "counter": { "showing": "Showing", "of": "of", "rows": "rows", "pages": "pages" } }, "headerFilters": { "default": "filter column...", "columns": {} } } }; const _Localize = class _Localize extends Module { constructor(table) { super(table); this.locale = "default"; this.lang = false; this.bindings = {}; this.langList = {}; this.registerTableOption("locale", false); this.registerTableOption("langs", {}); } initialize() { this.langList = Helpers.deepClone(_Localize.langs); if (this.table.options.columnDefaults.headerFilterPlaceholder !== false) { this.setHeaderFilterPlaceholder(this.table.options.columnDefaults.headerFilterPlaceholder); } for (let locale in this.table.options.langs) { this.installLang(locale, this.table.options.langs[locale]); } this.setLocale(this.table.options.locale); this.registerTableFunction("setLocale", this.setLocale.bind(this)); this.registerTableFunction("getLocale", this.getLocale.bind(this)); this.registerTableFunction("getLang", this.getLang.bind(this)); } //set header placeholder setHeaderFilterPlaceholder(placeholder) { this.langList.default.headerFilters.default = placeholder; } //setup a lang description object installLang(locale, lang) { if (this.langList[locale]) { this._setLangProp(this.langList[locale], lang); } else { this.langList[locale] = lang; } } _setLangProp(lang, values) { for (let key in values) { if (lang[key] && typeof lang[key] == "object") { this._setLangProp(lang[key], values[key]); } else { lang[key] = values[key]; } } } //set current locale setLocale(desiredLocale) { desiredLocale = desiredLocale || "default"; function traverseLang(trans, path) { for (var prop in trans) { if (typeof trans[prop] == "object") { if (!path[prop]) { path[prop] = {}; } traverseLang(trans[prop], path[prop]); } else { path[prop] = trans[prop]; } } } if (desiredLocale === true && navigator.language) { desiredLocale = navigator.language.toLowerCase(); } if (desiredLocale) { if (!this.langList[desiredLocale]) { let prefix = desiredLocale.split("-")[0]; if (this.langList[prefix]) { console.warn("Localization Error - Exact matching locale not found, using closest match: ", desiredLocale, prefix); desiredLocale = prefix; } else { console.warn("Localization Error - Matching locale not found, using default: ", desiredLocale); desiredLocale = "default"; } } } this.locale = desiredLocale; this.lang = Helpers.deepClone(this.langList.default || {}); if (desiredLocale != "default") { traverseLang(this.langList[desiredLocale], this.lang); } this.dispatchExternal("localized", this.locale, this.lang); this._executeBindings(); } //get current locale getLocale(locale) { return this.locale; } //get lang object for given local or current if none provided getLang(locale) { return locale ? this.langList[locale] : this.lang; } //get text for current locale getText(path, value) { var fillPath = value ? path + "|" + value : path, pathArray = fillPath.split("|"), text = this._getLangElement(pathArray, this.locale); return text || ""; } //traverse langs object and find localized copy _getLangElement(path, locale) { var root = this.lang; path.forEach(function(level) { var rootPath; if (root) { rootPath = root[level]; if (typeof rootPath != "undefined") { root = rootPath; } else { root = false; } } }); return root; } //set update binding bind(path, callback) { if (!this.bindings[path]) { this.bindings[path] = []; } this.bindings[path].push(callback); callback(this.getText(path), this.lang); } //iterate through bindings and trigger updates _executeBindings() { for (let path in this.bindings) { this.bindings[path].forEach((binding) => { binding(this.getText(path), this.lang); }); } } }; __publicField(_Localize, "moduleName", "localize"); //load defaults __publicField(_Localize, "langs", defaultLangs); let Localize = _Localize; class Comms extends Module { constructor(table) { super(table); } initialize() { this.registerTableFunction("tableComms", this.receive.bind(this)); } getConnections(selectors) { var connections = [], connection; connection = this.table.constructor.registry.lookupTable(selectors); connection.forEach((con) => { if (this.table !== con) { connections.push(con); } }); return connections; } send(selectors, module, action, data) { var connections = this.getConnections(selectors); connections.forEach((connection) => { connection.tableComms(this.table.element, module, action, data); }); if (!connections.length && selectors) { console.warn("Table Connection Error - No tables matching selector found", selectors); } } receive(table, module, action, data) { if (this.table.modExists(module)) { return this.table.modules[module].commsReceived(table, action, data); } else { console.warn("Inter-table Comms Error - no such module:", module); } } } __publicField(Comms, "moduleName", "comms"); var coreModules = /* @__PURE__ */ Object.freeze({ __proto__: null, CommsModule: Comms, LayoutModule: Layout, LocalizeModule: Localize }); const _TableRegistry = class _TableRegistry { static findTable(query) { var results = _TableRegistry.registry.lookupTable(query, true); return Array.isArray(results) && !results.length ? false : results; } }; __publicField(_TableRegistry, "registry", { tables: [], register(table) { _TableRegistry.registry.tables.push(table); }, deregister(table) { var index = _TableRegistry.registry.tables.indexOf(table); if (index > -1) { _TableRegistry.registry.tables.splice(index, 1); } }, lookupTable(query, silent) { var results = [], matches, match; if (typeof query === "string") { matches = document.querySelectorAll(query); if (matches.length) { for (var i = 0; i < matches.length; i++) { match = _TableRegistry.registry.matchElement(matches[i]); if (match) { results.push(match); } } } } else if (typeof HTMLElement !== "undefined" && query instanceof HTMLElement || query instanceof _TableRegistry) { match = _TableRegistry.registry.matchElement(query); if (match) { results.push(match); } } else if (Array.isArray(query)) { query.forEach(function(item) { results = results.concat(_TableRegistry.registry.lookupTable(item)); }); } else { if (!silent) { console.warn("Table Connection Error - Invalid Selector", query); } } return results; }, matchElement(element) { return _TableRegistry.registry.tables.find(function(table) { return element instanceof _TableRegistry ? table === element : table.element === element; }); } }); let TableRegistry = _TableRegistry; const _ModuleBinder = class _ModuleBinder extends TableRegistry { constructor() { super(); } static initializeModuleBinder(defaultModules) { if (!_ModuleBinder.modulesRegistered) { _ModuleBinder.modulesRegistered = true; _ModuleBinder._registerModules(coreModules, true); if (defaultModules) { _ModuleBinder._registerModules(defaultModules); } } } static _extendModule(name2, property, values) { if (_ModuleBinder.moduleBindings[name2]) { var source = _ModuleBinder.moduleBindings[name2][property]; if (source) { if (typeof values == "object") { for (let key in values) { source[key] = values[key]; } } else { console.warn("Module Error - Invalid value type, it must be an object"); } } else { console.warn("Module Error - property does not exist:", property); } } else { console.warn("Module Error - module does not exist:", name2); } } static _registerModules(modules, core) { var mods = Object.values(modules); if (core) { mods.forEach((mod) => { mod.prototype.moduleCore = true; }); } _ModuleBinder._registerModule(mods); } static _registerModule(modules) { if (!Array.isArray(modules)) { modules = [modules]; } modules.forEach((mod) => { _ModuleBinder._registerModuleBinding(mod); _ModuleBinder._registerModuleExtensions(mod); }); } static _registerModuleBinding(mod) { if (mod.moduleName) { _ModuleBinder.moduleBindings[mod.moduleName] = mod; } else { console.error("Unable to bind module, no moduleName defined", mod.moduleName); } } static _registerModuleExtensions(mod) { var extensions2 = mod.moduleExtensions; if (mod.moduleExtensions) { for (let modKey in extensions2) { let ext = extensions2[modKey]; if (_ModuleBinder.moduleBindings[modKey]) { for (let propKey in ext) { _ModuleBinder._extendModule(modKey, propKey, ext[propKey]); } } else { if (!_ModuleBinder.moduleExtensions[modKey]) { _ModuleBinder.moduleExtensions[modKey] = {}; } for (let propKey in ext) { if (!_ModuleBinder.moduleExtensions[modKey][propKey]) { _ModuleBinder.moduleExtensions[modKey][propKey] = {}; } Object.assign(_ModuleBinder.moduleExtensions[modKey][propKey], ext[propKey]); } } } } _ModuleBinder._extendModuleFromQueue(mod); } static _extendModuleFromQueue(mod) { var extensions2 = _ModuleBinder.moduleExtensions[mod.moduleName]; if (extensions2) { for (let propKey in extensions2) { _ModuleBinder._extendModule(mod.moduleName, propKey, extensions2[propKey]); } } } //ensure that module are bound to instantiated function _bindModules() { var orderedStartMods = [], orderedEndMods = [], unOrderedMods = []; this.modules = {}; for (var name2 in _ModuleBinder.moduleBindings) { let mod = _ModuleBinder.moduleBindings[name2]; let module = new mod(this); this.modules[name2] = module; if (mod.prototype.moduleCore) { this.modulesCore.push(module); } else { if (mod.moduleInitOrder) { if (mod.moduleInitOrder < 0) { orderedStartMods.push(module); } else { orderedEndMods.push(module); } } else { unOrderedMods.push(module); } } } orderedStartMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1); orderedEndMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1); this.modulesRegular = orderedStartMods.concat(unOrderedMods.concat(orderedEndMods)); } }; __publicField(_ModuleBinder, "moduleBindings", {}); __publicField(_ModuleBinder, "moduleExtensions", {}); __publicField(_ModuleBinder, "modulesRegistered", false); __publicField(_ModuleBinder, "defaultModules", false); let ModuleBinder = _ModuleBinder; class Alert extends CoreFeature { constructor(table) { super(table); this.element = this._createAlertElement(); this.msgElement = this._createMsgElement(); this.type = null; this.element.appendChild(this.msgElement); } _createAlertElement() { var el = document.createElement("div"); el.classList.add("tabulator-alert"); return el; } _createMsgElement() { var el = document.createElement("div"); el.classList.add("tabulator-alert-msg"); el.setAttribute("role", "alert"); return el; } _typeClass() { return "tabulator-alert-state-" + this.type; } alert(content, type = "msg") { if (content) { this.clear(); this.dispatch("alert-show", type); this.type = type; while (this.msgElement.firstChild) this.msgElement.removeChild(this.msgElement.firstChild); this.msgElement.classList.add(this._typeClass()); if (typeof content === "function") { content = content(); } if (content instanceof HTMLElement) { this.msgElement.appendChild(content); } else { this.msgElement.innerHTML = content; } this.table.element.appendChild(this.element); } } clear() { this.dispatch("alert-hide", this.type); if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.msgElement.classList.remove(this._typeClass()); } } let Tabulator$1 = (_a = class extends ModuleBinder { static extendModule() { _a.initializeModuleBinder(); _a._extendModule(...arguments); } static registerModule() { _a.initializeModuleBinder(); _a._registerModule(...arguments); } constructor(element, options, modules) { super(); _a.initializeModuleBinder(modules); this.options = {}; this.columnManager = null; this.rowManager = null; this.footerManager = null; this.alertManager = null; this.vdomHoz = null; this.externalEvents = null; this.eventBus = null; this.interactionMonitor = false; this.browser = ""; this.browserSlow = false; this.browserMobile = false; this.rtl = false; this.originalElement = null; this.componentFunctionBinder = new ComponentFunctionBinder(this); this.dataLoader = false; this.modules = {}; this.modulesCore = []; this.modulesRegular = []; this.deprecationAdvisor = new DeprecationAdvisor(this); this.optionsList = new OptionsList(this, "table constructor"); this.dependencyRegistry = new DependencyRegistry(this); this.initialized = false; this.destroyed = false; if (this.initializeElement(element)) { this.initializeCoreSystems(options); setTimeout(() => { this._create(); }); } this.constructor.registry.register(this); } initializeElement(element) { if (typeof HTMLElement !== "undefined" && element instanceof HTMLElement) { this.element = element; return true; } else if (typeof element === "string") { this.element = document.querySelector(element); if (this.element) { return true; } else { console.error("Tabulator Creation Error - no element found matching selector: ", element); return false; } } else { console.error("Tabulator Creation Error - Invalid element provided:", element); return false; } } initializeCoreSystems(options) { this.columnManager = new ColumnManager(this); this.rowManager = new RowManager(this); this.footerManager = new FooterManager(this); this.dataLoader = new DataLoader(this); this.alertManager = new Alert(this); this._bindModules(); this.options = this.optionsList.generate(_a.defaultOptions, options); this._clearObjectPointers(); this._mapDeprecatedFunctionality(); this.externalEvents = new ExternalEventBus(this, this.options, this.options.debugEventsExternal); this.eventBus = new InternalEventBus(this.options.debugEventsInternal); this.interactionMonitor = new InteractionManager(this); this.dataLoader.initialize(); this.footerManager.initialize(); this.dependencyRegistry.initialize(); } //convert deprecated functionality to new functions _mapDeprecatedFunctionality() { } _clearSelection() { this.element.classList.add("tabulator-block-select"); if (window.getSelection) { if (window.getSelection().empty) { window.getSelection().empty(); } else if (window.getSelection().removeAllRanges) { window.getSelection().removeAllRanges(); } } else if (document.selection) { document.selection.empty(); } this.element.classList.remove("tabulator-block-select"); } //create table _create() { this.externalEvents.dispatch("tableBuilding"); this.eventBus.dispatch("table-building"); this._rtlCheck(); this._buildElement(); this._initializeTable(); this.initialized = true; this._loadInitialData().finally(() => { this.eventBus.dispatch("table-initialized"); this.externalEvents.dispatch("tableBuilt"); }); } _rtlCheck() { var style = window.getComputedStyle(this.element); switch (this.options.textDirection) { case "auto": if (style.direction !== "rtl") { break; } case "rtl": this.element.classList.add("tabulator-rtl"); this.rtl = true; break; case "ltr": this.element.classList.add("tabulator-ltr"); default: this.rtl = false; } } //clear pointers to objects in default config object _clearObjectPointers() { this.options.columns = this.options.columns.slice(0); if (Array.isArray(this.options.data) && !this.options.reactiveData) { this.options.data = this.options.data.slice(0); } } //build tabulator element _buildElement() { var element = this.element, options = this.options, newElement; if (element.tagName === "TABLE") { this.originalElement = this.element; newElement = document.createElement("div"); var attributes = element.attributes; for (var i in attributes) { if (typeof attributes[i] == "object") { newElement.setAttribute(attributes[i].name, attributes[i].value); } } element.parentNode.replaceChild(newElement, element); this.element = element = newElement; } element.classList.add("tabulator"); element.setAttribute("role", "grid"); element.setAttribute("aria-owns", "tabulator-table-body"); while (element.firstChild) element.removeChild(element.firstChild); if (options.height) { options.height = isNaN(options.height) ? options.height : options.height + "px"; element.style.height = options.height; } if (options.minHeight !== false) { options.minHeight = isNaN(options.minHeight) ? options.minHeight : options.minHeight + "px"; element.style.minHeight = options.minHeight; } if (options.maxHeight !== false) { options.maxHeight = isNaN(options.maxHeight) ? options.maxHeight : options.maxHeight + "px"; element.style.maxHeight = options.maxHeight; } } //initialize core systems and modules _initializeTable() { var element = this.element, options = this.options; this.interactionMonitor.initialize(); this.columnManager.initialize(); this.rowManager.initialize(); this._detectBrowser(); this.modulesCore.forEach((mod) => { mod.initialize(); }); element.appendChild(this.columnManager.getElement()); element.appendChild(this.rowManager.getElement()); if (options.footerElement) { this.footerManager.activate(); } if (options.autoColumns && options.data) { this.columnManager.generateColumnsFromRowData(this.options.data); } this.modulesRegular.forEach((mod) => { mod.initialize(); }); this.columnManager.setColumns(options.columns); this.eventBus.dispatch("table-built"); } _loadInitialData() { return this.dataLoader.load(this.options.data).finally(() => { this.columnManager.verticalAlignHeaders(); }); } //deconstructor destroy() { var element = this.element; this.destroyed = true; this.constructor.registry.deregister(this); this.eventBus.dispatch("table-destroy"); this.rowManager.destroy(); while (element.firstChild) element.removeChild(element.firstChild); element.classList.remove("tabulator"); element.removeAttribute("tabulator-layout"); this.externalEvents.dispatch("tableDestroyed"); } _detectBrowser() { var ua = navigator.userAgent || navigator.vendor || window.opera; if (ua.indexOf("Trident") > -1) { this.browser = "ie"; this.browserSlow = true; } else if (ua.indexOf("Edge") > -1) { this.browser = "edge"; this.browserSlow = true; } else if (ua.indexOf("Firefox") > -1) { this.browser = "firefox"; this.browserSlow = false; } else if (ua.indexOf("Mac OS") > -1) { this.browser = "safari"; this.browserSlow = false; } else { this.browser = "other"; this.browserSlow = false; } this.browserMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(ua) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(ua.slice(0, 4)); } initGuard(func, msg) { var stack, line; if (this.options.debugInitialization && !this.initialized) { if (!func) { stack = new Error().stack.split("\n"); line = stack[0] == "Error" ? stack[2] : stack[1]; if (line[0] == " ") { func = line.trim().split(" ")[1].split(".")[1]; } else { func = line.trim().split("@")[0]; } } console.warn("Table Not Initialized - Calling the " + func + " function before the table is initialized may result in inconsistent behavior, Please wait for the `tableBuilt` event before calling this function." + (msg ? " " + msg : "")); } return this.initialized; } ////////////////// Data Handling ////////////////// //block table redrawing blockRedraw() { this.initGuard(); this.eventBus.dispatch("redraw-blocking"); this.rowManager.blockRedraw(); this.columnManager.blockRedraw(); this.eventBus.dispatch("redraw-blocked"); } //restore table redrawing restoreRedraw() { this.initGuard(); this.eventBus.dispatch("redraw-restoring"); this.rowManager.restoreRedraw(); this.columnManager.restoreRedraw(); this.eventBus.dispatch("redraw-restored"); } //load data setData(data, params, config) { this.initGuard(false, "To set initial data please use the 'data' property in the table constructor."); return this.dataLoader.load(data, params, config, false); } //clear data clearData() { this.initGuard(); this.dataLoader.blockActiveLoad(); this.rowManager.clearData(); } //get table data array getData(active) { return this.rowManager.getData(active); } //get table data array count getDataCount(active) { return this.rowManager.getDataCount(active); } //replace data, keeping table in position with same sort replaceData(data, params, config) { this.initGuard(); return this.dataLoader.load(data, params, config, true, true); } //update table data updateData(data) { var responses = 0; this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if (typeof data === "string") { data = JSON.parse(data); } if (data && data.length > 0) { data.forEach((item) => { var row = this.rowManager.findRow(item[this.options.index]); if (row) { responses++; row.updateData(item).then(() => { responses--; if (!responses) { resolve(); } }).catch((e) => { reject("Update Error - Unable to update row", item, e); }); } else { reject("Update Error - Unable to find row", item); } }); } else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } addData(data, pos, index) { this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if (typeof data === "string") { data = JSON.parse(data); } if (data) { this.rowManager.addRows(data, pos, index).then((rows2) => { var output = []; rows2.forEach(function(row) { output.push(row.getComponent()); }); resolve(output); }); } else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } //update table data updateOrAddData(data) { var rows2 = [], responses = 0; this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if (typeof data === "string") { data = JSON.parse(data); } if (data && data.length > 0) { data.forEach((item) => { var row = this.rowManager.findRow(item[this.options.index]); responses++; if (row) { row.updateData(item).then(() => { responses--; rows2.push(row.getComponent()); if (!responses) { resolve(rows2); } }); } else { this.rowManager.addRows(item).then((newRows) => { responses--; rows2.push(newRows[0].getComponent()); if (!responses) { resolve(rows2); } }); } }); } else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } //get row object getRow(index) { var row = this.rowManager.findRow(index); if (row) { return row.getComponent(); } else { console.warn("Find Error - No matching row found:", index); return false; } } //get row object getRowFromPosition(position) { var row = this.rowManager.getRowFromPosition(position); if (row) { return row.getComponent(); } else { console.warn("Find Error - No matching row found:", position); return false; } } //delete row from table deleteRow(index) { var foundRows = []; this.initGuard(); if (!Array.isArray(index)) { index = [index]; } for (let item of index) { let row = this.rowManager.findRow(item, true); if (row) { foundRows.push(row); } else { console.error("Delete Error - No matching row found:", item); return Promise.reject("Delete Error - No matching row found"); } } foundRows.sort((a, b) => { return this.rowManager.rows.indexOf(a) > this.rowManager.rows.indexOf(b) ? 1 : -1; }); foundRows.forEach((row) => { row.delete(); }); this.rowManager.reRenderInPosition(); return Promise.resolve(); } //add row to table addRow(data, pos, index) { this.initGuard(); if (typeof data === "string") { data = JSON.parse(data); } return this.rowManager.addRows(data, pos, index, true).then((rows2) => { return rows2[0].getComponent(); }); } //update a row if it exists otherwise create it updateOrAddRow(index, data) { var row = this.rowManager.findRow(index); this.initGuard(); if (typeof data === "string") { data = JSON.parse(data); } if (row) { return row.updateData(data).then(() => { return row.getComponent(); }); } else { return this.rowManager.addRows(data).then((rows2) => { return rows2[0].getComponent(); }); } } //update row data updateRow(index, data) { var row = this.rowManager.findRow(index); this.initGuard(); if (typeof data === "string") { data = JSON.parse(data); } if (row) { return row.updateData(data).then(() => { return Promise.resolve(row.getComponent()); }); } else { console.warn("Update Error - No matching row found:", index); return Promise.reject("Update Error - No matching row found"); } } //scroll to row in DOM scrollToRow(index, position, ifVisible) { var row = this.rowManager.findRow(index); if (row) { return this.rowManager.scrollToRow(row, position, ifVisible); } else { console.warn("Scroll Error - No matching row found:", index); return Promise.reject("Scroll Error - No matching row found"); } } moveRow(from, to, after) { var fromRow = this.rowManager.findRow(from); this.initGuard(); if (fromRow) { fromRow.moveToRow(to, after); } else { console.warn("Move Error - No matching row found:", from); } } getRows(active) { return this.rowManager.getComponents(active); } //get position of row in table getRowPosition(index) { var row = this.rowManager.findRow(index); if (row) { return row.getPosition(); } else { console.warn("Position Error - No matching row found:", index); return false; } } /////////////// Column Functions /////////////// setColumns(definition) { this.initGuard(false, "To set initial columns please use the 'columns' property in the table constructor"); this.columnManager.setColumns(definition); } getColumns(structured) { return this.columnManager.getComponents(structured); } getColumn(field) { var column = this.columnManager.findColumn(field); if (column) { return column.getComponent(); } else { console.warn("Find Error - No matching column found:", field); return false; } } getColumnDefinitions() { return this.columnManager.getDefinitionTree(); } showColumn(field) { var column = this.columnManager.findColumn(field); this.initGuard(); if (column) { column.show(); } else { console.warn("Column Show Error - No matching column found:", field); return false; } } hideColumn(field) { var column = this.columnManager.findColumn(field); this.initGuard(); if (column) { column.hide(); } else { console.warn("Column Hide Error - No matching column found:", field); return false; } } toggleColumn(field) { var column = this.columnManager.findColumn(field); this.initGuard(); if (column) { if (column.visible) { column.hide(); } else { column.show(); } } else { console.warn("Column Visibility Toggle Error - No matching column found:", field); return false; } } addColumn(definition, before, field) { var column = this.columnManager.findColumn(field); this.initGuard(); return this.columnManager.addColumn(definition, before, column).then((column2) => { return column2.getComponent(); }); } deleteColumn(field) { var column = this.columnManager.findColumn(field); this.initGuard(); if (column) { return column.delete(); } else { console.warn("Column Delete Error - No matching column found:", field); return Promise.reject(); } } updateColumnDefinition(field, definition) { var column = this.columnManager.findColumn(field); this.initGuard(); if (column) { return column.updateDefinition(definition); } else { console.warn("Column Update Error - No matching column found:", field); return Promise.reject(); } } moveColumn(from, to, after) { var fromColumn = this.columnManager.findColumn(from), toColumn = this.columnManager.findColumn(to); this.initGuard(); if (fromColumn) { if (toColumn) { this.columnManager.moveColumn(fromColumn, toColumn, after); } else { console.warn("Move Error - No matching column found:", toColumn); } } else { console.warn("Move Error - No matching column found:", from); } } //scroll to column in DOM scrollToColumn(field, position, ifVisible) { return new Promise((resolve, reject) => { var column = this.columnManager.findColumn(field); if (column) { return this.columnManager.scrollToColumn(column, position, ifVisible); } else { console.warn("Scroll Error - No matching column found:", field); return Promise.reject("Scroll Error - No matching column found"); } }); } //////////// General Public Functions //////////// //redraw list without updating data redraw(force) { this.initGuard(); this.columnManager.redraw(force); this.rowManager.redraw(force); } setHeight(height) { this.options.height = isNaN(height) ? height : height + "px"; this.element.style.height = this.options.height; this.rowManager.initializeRenderer(); this.rowManager.redraw(true); } setMaxHeight(maxHeight) { this.options.maxHeight = isNaN(maxHeight) ? maxHeight : maxHeight + "px"; this.element.style.maxHeight = this.options.maxHeight; this.rowManager.initializeRenderer(); this.rowManager.redraw(true); } setMinHeight(minHeight) { this.options.minHeight = isNaN(minHeight) ? minHeight : minHeight + "px"; this.element.style.minHeight = this.options.minHeight; this.rowManager.initializeRenderer(); this.rowManager.redraw(true); } //////////////////// Event Bus /////////////////// on(key, callback) { this.externalEvents.subscribe(key, callback); } off(key, callback) { this.externalEvents.unsubscribe(key, callback); } dispatchEvent() { var args = Array.from(arguments); args.shift(); this.externalEvents.dispatch(...arguments); } //////////////////// Alerts /////////////////// alert(contents, type) { this.initGuard(); this.alertManager.alert(contents, type); } clearAlert() { this.initGuard(); this.alertManager.clear(); } ////////////// Extension Management ////////////// modExists(plugin, required) { if (this.modules[plugin]) { return true; } else { if (required) { console.error("Tabulator Module Not Installed: " + plugin); } return false; } } module(key) { var mod = this.modules[key]; if (!mod) { console.error("Tabulator module not installed: " + key); } return mod; } }, //default setup options __publicField(_a, "defaultOptions", defaultOptions), _a); class TabulatorFull extends Tabulator$1 { static extendModule() { Tabulator$1.initializeModuleBinder(allModules); Tabulator$1._extendModule(...arguments); } static registerModule() { Tabulator$1.initializeModuleBinder(allModules); Tabulator$1._registerModule(...arguments); } constructor(element, options, modules) { super(element, options, allModules); } } const DMM_GRAPHQL_URL = "https://api.video.dmm.co.jp/graphql"; const dmmGraphQLKeyword = (code) => { const match = String(code || "").trim().toUpperCase().match(/^([A-Z0-9]{2,10})-(\d{1,6})$/); if (!match) return ""; return `${match[1].toLowerCase()}${match[2].padStart(5, "0")}`; }; const dmmGraphQLRequest = (query) => { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "POST", url: DMM_GRAPHQL_URL, data: JSON.stringify({ query }), timeout: 12e3, headers: { Accept: "application/json", "Content-Type": "application/json", Referer: "https://video.dmm.co.jp/", "Fanza-Device": "BROWSER", "Cache-Control": "no-cache" }, onload: (response) => { if (response.status < 200 || response.status >= 400) { reject(new Error(`DMM GraphQL HTTP ${response.status || 0}`)); return; } try { const json2 = JSON.parse(response.responseText || "{}"); if (Array.isArray(json2.errors) && json2.errors.length) { reject(new Error(json2.errors[0].message || "DMM GraphQL error")); return; } resolve(json2.data || {}); } catch (e) { reject(new Error(`DMM GraphQL JSON parse error: ${e.message}`)); } }, onerror: () => reject(new Error("DMM GraphQL network error")), ontimeout: () => reject(new Error("DMM GraphQL timeout")) }); }); }; const searchDmmContentIdsByGraphQL = async (code) => { const keyword = dmmGraphQLKeyword(code); if (!keyword) return []; const carNumNoHyphen = String(code || "").replace(/-/g, "").toLowerCase(); const results = []; const searchKeywords = [.../* @__PURE__ */ new Set([keyword, `${keyword}#`])]; for (const searchKeyword of searchKeywords) { const query = `{ legacySearchPPV(limit: 10, offset: 0, sort: SALES_RANK_SCORE, floor: AV, queryWord: ${JSON.stringify(searchKeyword)}) { result { contents { contentId serviceCode floorCode } } } }`; const data = await dmmGraphQLRequest(query); const contents = Array.isArray(data && data.legacySearchPPV && data.legacySearchPPV.result && data.legacySearchPPV.result.contents) ? data.legacySearchPPV.result.contents : []; for (const item of contents) { const contentId = String(item.contentId || ""); if (contentId.replace(/[^a-z0-9]/gi, "").toLowerCase().includes(carNumNoHyphen)) { results.push({ serviceCode: item.serviceCode || "digital", floorCode: item.floorCode || "AV", contentId }); } } if (results.length > 0) break; } return results; }; const getDmmGraphQLVideoUrls = async (code) => { const contentItems = await searchDmmContentIdsByGraphQL(code); if (!contentItems.length) return null; const results = await Promise.allSettled( contentItems.map((item) => extractTrailerLinks(item)) ); for (const result of results) { if (result.status === "fulfilled" && result.value) { return result.value; } } return null; }; const extractTrailerLinks = (item) => { const { contentId, serviceCode, floorCode } = item; return new Promise((resolve, reject) => { const trailerPageUrl = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${contentId}/mtype=AhRVShI_/service=${serviceCode}/floor=${floorCode}/mode=/`; _GM_xmlhttpRequest({ method: "GET", url: trailerPageUrl, timeout: 15e3, headers: { "accept-language": "ja-JP,ja;q=0.9", Cookie: "age_check_done=1" }, onload: (response) => { const htmlContent = response.responseText || ""; if (htmlContent.includes("このサービスはお住まいの地域からは")) { reject(new Error("节点不可用,请将DMM域名分流到日本ip")); return; } const match = htmlContent.match(/const\s+args\s+=\s+(.*);/); if (!match) { reject(new Error("未在脚本中找到 const args = ... 变量")); return; } let bitrates; try { bitrates = JSON.parse(match[1]).bitrates; } catch (e) { reject(new Error(`解析播放器脚本 JSON 失败: ${e.message}`)); return; } if (!Array.isArray(bitrates)) { reject(new Error("解析画质链接失败: bitrates 字段不是一个数组或不存在")); return; } const qualityKeys = [ "4ks", "4k", "hhbs", "hhb", "hmb", "mhb", "mmb", "dmb_w", "dmb_s", "dm_s", "sm_s", "mhb_w" ]; const qualityNameRegex = new RegExp(`(${qualityKeys.join("|")})\\.mp4$`); const finalQualityMap = {}; for (const bitrate of bitrates) { const url = bitrate && bitrate.src; if (!url || typeof url !== "string" || !url.endsWith(".mp4")) continue; const qualityMatch = url.match(qualityNameRegex); let qualityKey = ""; if (qualityMatch && qualityMatch[1]) qualityKey = qualityMatch[1]; if (qualityKey && !finalQualityMap[qualityKey]) { finalQualityMap[qualityKey] = url; } } if (Object.keys(finalQualityMap).length === 0) { reject(new Error("未找到匹配要求的预览画质视频")); return; } resolve(finalQualityMap); }, onerror: () => reject(new Error("DMM 播放页请求网络错误")), ontimeout: () => reject(new Error("DMM 播放页请求超时")) }); }); }; const selectDefaultQuality = (dmmVideoQualityList, intendedDefault) => { if (!dmmVideoQualityList || 0 === dmmVideoQualityList.length) return null; const availableSet = new Set(dmmVideoQualityList); if (availableSet.has(intendedDefault)) return intendedDefault; const priorityOrder = qualityOptions.map(((option) => option.quality)).reverse(); for (const quality of priorityOrder) if (availableSet.has(quality)) return quality; return dmmVideoQualityList[0]; }, CACHE_KEY = "jhs_dmm_video"; class DmmVideoFetcher { constructor(carNum2, showErrorMessages = true) { this.carNum = carNum2; this.showErrorMessages = showErrorMessages; } _checkCache() { const cachedData = localStorage.getItem(CACHE_KEY) ? JSON.parse(localStorage.getItem(CACHE_KEY)) : {}; if (cachedData[this.carNum]) { clog.debug("缓存中存在预览视频信息", cachedData[this.carNum]); return cachedData[this.carNum]; } return null; } _updateCache(videoMap) { const cachedData = localStorage.getItem(CACHE_KEY) ? JSON.parse(localStorage.getItem(CACHE_KEY)) : {}; cachedData[this.carNum] = videoMap; clog.debug("成功解析出预览视频并已缓存:", videoMap); localStorage.setItem(CACHE_KEY, JSON.stringify(cachedData)); } async _searchContentIds() { const carNum2 = this.carNum, carNumNoHyphen = carNum2.replace(/-/g, ""), keywordAttempts = [{ keyword: carNum2.replace("-", "00"), name: "00-替换关键词" }, { keyword: carNum2, name: "原始番号关键词" }, { keyword: carNumNoHyphen, name: "无连字符关键词" }], carNumLower = carNum2.toLowerCase(); for (const attempt of keywordAttempts) { const { keyword, name: name2 } = attempt, currentTempCarNumLower = keyword.toLowerCase(), apiUrl2 = `https://api.dmm.com/affiliate/v3/ItemList?${new URLSearchParams({ api_id: "UrwskPfkqQ0DuVry2gYL", affiliate_id: "10278-996", output: "json", site: "FANZA", sort: "match", keyword }).toString()}`; let response; try { response = await gmHttp.get(apiUrl2); } catch (e) { clog.error(`API 请求失败,跳过 ${name2}:`, e); continue; } if (!response || !response.result || !response.result.result_count) { clog.debug(`使用 ${name2} (${keyword}) 进行 API 搜索 返回无结果,尝试下一个关键词。`); continue; } const newItems = []; for (const item of response.result.items) { if (newItems.length >= 2) break; const contentId = item.content_id || "", makerProduct = item.maker_product || ""; if (contentId.includes(currentTempCarNumLower.replace("-", "")) || carNumLower === makerProduct.toLowerCase() || contentId.includes(carNumNoHyphen.toLowerCase())) { newItems.push({ serviceCode: item.service_code, floorCode: item.floor_code, contentId, pageUrl: item.URL }); clog.debug(`[${name2}] cid|makerProduct 匹配成功:`, contentId, makerProduct); } } if (newItems.length > 0) { clog.debug(`--- 成功通过 ${name2} 找到 Content IDs ---`); const $btn2 = $("#fanzaBtn"); let url = `https://www.dmm.co.jp/search/=/searchstr=${keyword}`, type = "single"; if (newItems.length > 1) { $btn2.attr("href", url); $btn2.append('<span class="site-tag" style="top:-15px">多结果</span>'); $btn2.css("backgroundColor", "#7bc73b"); type = "multiple"; } else { url = newItems[0].pageUrl; $btn2.attr("href", url); $btn2.css("backgroundColor", "#7bc73b"); } const dmmCacheKey = "jhs_other_site_dmm", dmmCacheData = localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {}; dmmCacheData[this.carNum] = { type, url }; localStorage.setItem(dmmCacheKey, JSON.stringify(dmmCacheData)); return newItems; } clog.debug(`[${name2}] API 返回结果数 ${response.result.result_count},但无精确匹配的 Content ID。`); } clog.warn("所有关键词尝试均未找到匹配的Content ID, 解析Dmm视频失败"); const $btn = $("#fanzaBtn"); $btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", "#de3333"); return null; } async _extractTrailerLinks({ contentId, serviceCode, floorCode }) { const trailerPageUrl = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${contentId}/mtype=AhRVShI_/service=${serviceCode}/floor=${floorCode}/mode=/`, htmlContent = await gmHttp.get(trailerPageUrl, null, { "accept-language": "ja-JP,ja;q=0.9", Cookie: "age_check_done=1" }); if ("string" != typeof htmlContent) { clog.error(htmlContent); throw new Error("解析播放页内容失败, 非文本内容"); } if (htmlContent.includes("このサービスはお住まいの地域からは")) throw new Error("节点不可用,请将DMM域名分流到日本ip"); const match = htmlContent.match(/const\s+args\s+=\s+(.*);/); if (!match) throw new Error("未在脚本中找到 const args = ... 变量"); let bitrates; try { ({ bitrates } = JSON.parse(match[1])); } catch (e) { throw new Error(`解析播放器脚本 JSON 失败: ${e.message}`); } const finalQualityMap = {}, qualityKeys = qualityOptions.map(((o) => o.quality)).join("|"), qualityNameRegex = new RegExp(`(${qualityKeys})\\.mp4$`); if (!Array.isArray(bitrates)) { clog.error("解析画质链接失败: bitrates 字段不是一个数组或不存在"); throw new Error("解析画质链接失败: bitrates 字段不是一个数组或不存在"); } clog.debug("原始数据返回:", bitrates); for (const item of bitrates) { const url = null == item ? void 0 : item.src; if (!url || "string" != typeof url || !url.endsWith(".mp4")) continue; const qualityMatch = url.match(qualityNameRegex); let qualityKey = ""; qualityMatch && qualityMatch[1] && (qualityKey = qualityMatch[1]); qualityKey && !finalQualityMap[qualityKey] && (finalQualityMap[qualityKey] = url); } if (0 === Object.keys(finalQualityMap).length) throw new Error("未找到匹配要求的预览画质视频"); return finalQualityMap; } async _searchViaGraphQL() { clog.debug("尝试通过 GraphQL API 搜索视频..."); const videoMap = await getDmmGraphQLVideoUrls(this.carNum); if (videoMap && Object.keys(videoMap).length > 0) { this._updateCache(videoMap); clog.debug("GraphQL 成功解析出预览视频:", videoMap); const dmmCacheKey = "jhs_other_site_dmm"; const dmmCacheData = localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {}; dmmCacheData[this.carNum] = { type: "graphql", url: `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}` }; localStorage.setItem(dmmCacheKey, JSON.stringify(dmmCacheData)); return videoMap; } return null; } async fetchVideo() { const cachedResult = this._checkCache(); if (cachedResult) return cachedResult; let contentItems; try { const testCarNum = this.carNum.toLowerCase(); if (testCarNum.startsWith("heyzo") || /^(n\d+|\d+(-\d+)*)$/.test(testCarNum) || /^n\d+$/.test(testCarNum)) throw new Error("无码番号类型, 取消dmm解析"); if (this.carNum.includes("VR-")) throw new Error("VR类型, 取消dmm解析"); contentItems = await this._searchContentIds(); } catch (e) { clog.error("DMM API 搜索失败:", e); } if (!contentItems || 0 === contentItems.length) { clog.warn("Affiliate API 未找到视频,尝试 GraphQL API..."); const graphQLResult = await this._searchViaGraphQL(); if (graphQLResult) return graphQLResult; const $btn = $("#fanzaBtn"); $btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", "#de3333"); return null; } try { const finalVideoMap = await Promise.any(contentItems.map(((item) => this._extractTrailerLinks(item)))); this._updateCache(finalVideoMap); return finalVideoMap; } catch (error) { const errors = error.errors || [error]; clog.error(`Affiliate 解析失败: ${errors[0] && errors[0].message || errors[0]}`, errors); clog.warn("尝试 GraphQL API 作为 fallback..."); const graphQLResult = await this._searchViaGraphQL(); if (graphQLResult) return graphQLResult; if (errors.some(((err) => err.message.includes("节点不可用")))) this.showErrorMessages && show.error("节点不可用,请将DMM域名分流到日本ip"); else { const displayError = errors[0].message || errors[0]; this.showErrorMessages && show.error(`解析失败: ${displayError}`); } const $btn = $("#fanzaBtn"); $btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", "#de3333"); return null; } } } const getDmmVideo = async (carNum2, showErrorMessages = true) => new DmmVideoFetcher(carNum2, showErrorMessages).fetchVideo(); class AutoPagePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "preloadDistance", 500); __publicField2(this, "currentPage", this.getInitialPageNumber()); __publicField2(this, "pageItems", []); } getName() { return "AutoPagePlugin"; } async initCss() { return "\n <style>\n .jhs-scroll {\n text-align: center;\n padding-top: 20px;\n font-size: 14px;\n }\n .jhs-scroll.waterfall-loading { color: #000; }\n .jhs-scroll.waterfall-error { color: #f44336; cursor: pointer; }\n .jhs-scroll.waterfall-no-more { color: #4CAF50; }\n </style>\n "; } async handle() { this.waterfall().then(); } getInitialPageNumber() { if (isJavBus$1) { const match = currentHref$1.match(/\/(page|star\/[^/]+)\/(\d+)/); return match ? parseInt(match[2], 10) : 1; } if (isJavDb$1) { const match = currentHref$1.match(/[?&]page=(\d+)/); return match ? parseInt(match[1], 10) : 1; } return 1; } async waterfall() { if (await this.shouldDisablePaging()) return; const selector = this.getSelector(); this.container = document.querySelector(selector.boxSelector); if (!this.container) { console.error("没有找到容器节点,停止瀑布流!"); return; } this.loader = document.createElement("div"); this.loader.className = "jhs-scroll"; this.container.parentNode.insertBefore(this.loader, this.container.nextSibling); this.pageItems.push({ page: this.currentPage, top: 0, url: window.location.href }); this.loader.addEventListener("click", (() => { this.loader.classList.contains("waterfall-error") && this.loadNextPage().then(); })); window.addEventListener("scroll", (() => { this.checkLoad(); this.checkScrollPosition(); })); const nextLink = document.querySelector(selector.nextPageSelector); this.nextUrl = null == nextLink ? void 0 : nextLink.href; this.hasMore = !!this.nextUrl; this.hasMore || this.setState("waterfall-no-more", "已经到底了"); } async loadNextPage() { var _a2; if (await storageManager.getSetting("autoPage", YES$1) === NO$1) { this.setState("waterfall-loading", ""); return; } if (this.isLoading || !this.nextUrl) return; this.isLoading = true; this.setState("waterfall-loading", "加载中..."); const selector = this.getSelector(); try { const pageNum = utils.getUrlParam(this.nextUrl, "page"); let maxPage = 60; currentHref$1.includes("c11") && (maxPage = 30); if (isJavDb$1 && pageNum > maxPage || currentHref$1.includes("month")) { const beyond60Plugin = this.getBean("Beyond60Plugin"); if (beyond60Plugin) { const { html: html22, nextUrl, hasMore } = await beyond60Plugin.handleBeyond60(this.nextUrl); if (html22) { const pageTop2 = this.container.scrollHeight; this.pageItems.push({ page: this.currentPage + 1, top: pageTop2, url: this.nextUrl }); $(".movie-list").append(html22); } this.hasMore = hasMore; this.nextUrl = nextUrl; const $ul = beyond60Plugin.createPagination(pageNum, hasMore); $(".pagination").html($ul); this.setState("waterfall-loading", ""); this.hasMore || this.setState("waterfall-no-more", "已经到底了"); return; } } const html2 = await gmHttp.get(this.nextUrl); clog.log("请求下一页内容:", this.nextUrl); const $dom = utils.htmlTo$dom(html2); isJavBus$1 && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); let itemList = $dom.find(this.getSelector().requestDomItemSelector); const currentBoxCarInfoList = this.getBoxCarInfoList(), nextPageBoxCarInfoList = this.getBoxCarInfoList(itemList); if (this.checkDuplicateCarNumbers(currentBoxCarInfoList, nextPageBoxCarInfoList)) { this.nextUrl = null; this.hasMore = false; this.setState("waterfall-error", "翻页内容出现重复数据, 可能首页已更新了新视频 或 页码受JavDB限制, 停止瀑布流"); return; } const pageTop = this.container.scrollHeight; this.pageItems.push({ page: this.currentPage + 1, top: pageTop, url: this.nextUrl }); const listPagePlugin = this.getBean("ListPagePlugin"); let coverImgNodeList = $dom.find(this.getSelector().coverImgSelector); listPagePlugin.replaceHdImg(coverImgNodeList); $(this.getSelector().boxSelector).append(itemList); this.nextUrl = null == (_a2 = $dom.find(selector.nextPageSelector)) ? void 0 : _a2.attr("href"); this.hasMore = !!this.nextUrl; let pagination = $dom.find(".pagination"); $(".pagination").replaceWith(pagination); this.setState("waterfall-loading", ""); this.hasMore || this.setState("waterfall-no-more", "已经到底了"); } catch (e) { clog.error("加载失败:", e); this.setState("waterfall-error", "加载失败,点击重试"); } finally { setTimeout((() => { this.isLoading = false; }), 600); } } checkScrollPosition() { const scrollPosition = window.scrollY; for (let i = this.pageItems.length - 1; i >= 0; i--) { const page = this.pageItems[i]; if (scrollPosition >= page.top) { if (this.currentPage !== page.page) { this.currentPage = page.page; this.updatePageUrl(page.url); } break; } } } checkLoad() { if (!this.loader || this.isLoading || !this.hasMore) return; if (window.scrollY < 100) return; const triggerDistance = 200; this.loader.getBoundingClientRect().top < window.innerHeight + triggerDistance && this.loadNextPage().then(); } async shouldDisablePaging() { if (!window.isListPage) return true; await storageManager.getSetting("autoPage", YES$1); return ["search?q", "/rankings/playback", "/rankings/top", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100"].some(((path) => currentHref$1.includes(path))); } updatePageUrl_old(href) { window.history.pushState({}, "", href); if (isJavBus$1) { const match = href.match(/\/(page|star\/.*?)\/(\d+)/), pageNumber = match ? parseInt(match[2], 10) : null; document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁"); } } updatePageUrl(url) { window.history.replaceState({}, "", url); isJavBus$1 && (document.title = document.title.replace(/第\d+頁/, `第${this.currentPage}頁`)); } setState(state, text) { this.loader.className = `jhs-scroll ${state}`; this.loader.textContent = text; } } class AliyunApi { constructor(refresh_token) { this.baseApiUrl = "https://api.aliyundrive.com"; this.refresh_token = refresh_token; this.authorization = null; this.default_drive_id = null; this.backupFolderId = null; } async getDefaultDriveId() { if (this.default_drive_id) return this.default_drive_id; this.userInfo = await this.getUserInfo(); this.default_drive_id = this.userInfo.default_drive_id; return this.default_drive_id; } async getHeaders() { if (this.authorization) return { authorization: this.authorization }; this.authorization = await this.getAuthorization(); return { authorization: this.authorization }; } async getAuthorization() { let url = this.baseApiUrl + "/v2/account/token", data = { refresh_token: this.refresh_token, grant_type: "refresh_token" }; try { return "Bearer " + (await gmHttp.post(url, data)).access_token; } catch (e) { throw e.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : e; } } async getUserInfo() { const headers = await this.getHeaders(); let url = this.baseApiUrl + "/v2/user/get"; return await gmHttp.post(url, {}, headers); } async deleteFile(file_id, drive_id = null) { if (!file_id) throw new Error("未传入file_id"); drive_id || (drive_id = await this.getDefaultDriveId()); let data = { file_id, drive_id }, url = this.baseApiUrl + "/v2/recyclebin/trash"; const headers = await this.getHeaders(); await gmHttp.post(url, data, headers); return {}; } async createFolder(name2, drive_id = null, parent_folder_id = "root") { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/adrive/v2/file/createWithFolders", data = { name: name2, type: "folder", parent_file_id: parent_folder_id, check_name_mode: "auto_rename", content_hash_name: "sha1", drive_id }; const headers = await this.getHeaders(); return await gmHttp.post(url, data, headers); } async getFileList(parent_folder_id = "root", drive_id = null) { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/adrive/v3/file/list"; const data = { drive_id, parent_file_id: parent_folder_id, limit: 200, all: false, url_expire_sec: 14400, image_thumbnail_process: "image/resize,w_256/format,avif", image_url_process: "image/resize,w_1920/format,avif", video_thumbnail_process: "video/snapshot,t_120000,f_jpg,m_lfit,w_256,ar_auto,m_fast", fields: "*", order_by: "updated_at", order_direction: "DESC" }, headers = await this.getHeaders(); return (await gmHttp.post(url, data, headers)).items; } async uploadFile(folder_id, fileName, uploadContent, drive_id = null) { show.info("请求存储空间中..."); let createFileUrl = this.baseApiUrl + "/adrive/v2/file/createWithFolders"; drive_id || (drive_id = await this.getDefaultDriveId()); let data = { drive_id, part_info_list: [{ part_number: 1 }], parent_file_id: folder_id, name: fileName, type: "file", check_name_mode: "auto_rename" }; const headers = await this.getHeaders(), createFileResult = await gmHttp.post(createFileUrl, data, headers), upload_id = createFileResult.upload_id, upload_file_id = createFileResult.file_id, upload_url = createFileResult.part_info_list[0].upload_url; show.info("开始上传文件..."); await this._doUpload(upload_url, uploadContent); await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", data = { drive_id, file_id: upload_file_id, upload_id }, headers); } _doUpload(upload_url, uploadContent) { return new Promise(((resolve, reject) => { $.ajax({ type: "PUT", url: upload_url, data: uploadContent, contentType: " ", processData: false, success: (res, status, xhr) => { 200 === xhr.status ? resolve({}) : reject(xhr); }, error: (xhr) => { clog.error("上传失败", xhr.responseText); reject(xhr); } }); })); } async getDownloadUrl(file_id, drive_id = null) { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/v2/file/get_download_url"; const headers = await this.getHeaders(); let data = { file_id, drive_id }; return (await gmHttp.post(url, data, headers)).url; } async _createBackupFolder(folderName) { const fileList = await this.getFileList(); let folderObj = null; for (let i = 0; i < fileList.length; i++) { let file = fileList[i]; if (file.name === folderName) { folderObj = file; break; } } if (!folderObj) { show.info("不存在备份目录, 进行创建"); folderObj = await this.createFolder(folderName); } this.backupFolderId = folderObj.file_id; } async backup(folderName, fileName, uploadContent) { if (this.backupFolderId) await this.uploadFile(this.backupFolderId, fileName, uploadContent); else { await this._createBackupFolder(folderName); await this.uploadFile(this.backupFolderId, fileName, uploadContent); } } async getBackupList(folderName) { let dataList; if (this.backupFolderId) dataList = await this.getFileList(this.backupFolderId); else { await this._createBackupFolder(folderName); dataList = await this.getFileList(this.backupFolderId); } const fileList = []; dataList.forEach(((data) => { fileList.push({ name: data.name, fileId: data.file_id, createTime: data.created_at, size: data.size }); })); return fileList; } } class WebDavApi { constructor(davUrl, username, password) { this.davUrl = davUrl.endsWith("/") ? davUrl : davUrl + "/"; this.username = username; this.password = password; this.folderName = null; } _getAuthHeaders() { return { Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`, Depth: "1" }; } _sendRequest(method, path, headers = {}, data) { return new Promise(((resolve, reject) => { const url = this.davUrl + path, allHeaders = { ...this._getAuthHeaders(), ...headers }; _GM_xmlhttpRequest({ method, url, headers: allHeaders, data, onload: (response) => { if (response.status >= 200 && response.status < 300) resolve(response); else { console.error(response); reject(new Error(`请求失败 ${response.status}: ${response.statusText}`)); } }, onerror: (response) => { console.error("请求WebDav发生错误:", response); reject(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确")); } }); })); } async backup(folderName, fileName, uploadContent) { await this._sendRequest("MKCOL", folderName); const path = folderName + "/" + fileName; await this._sendRequest("PUT", path, { "Content-Type": "text/plain" }, uploadContent); } async getFileList(folderName) { var _a2, _b, _c; const xmlResponse = (await this._sendRequest("PROPFIND", folderName, { "Content-Type": "application/xml" }, '<?xml version="1.0"?>\n <d:propfind xmlns:d="DAV:">\n <d:prop>\n <d:displayname />\n <d:getcontentlength />\n <d:creationdate />\n <d:getlastmodified />\n <d:iscollection />\n </d:prop>\n </d:propfind>\n ')).responseText, items = new DOMParser().parseFromString(xmlResponse, "text/xml").getElementsByTagNameNS("DAV:", "response"), fileList = []; for (let i = 0; i < items.length; i++) { if (0 === i) continue; let item = items[i]; console.log(item); const name2 = item.getElementsByTagNameNS("DAV:", "displayname")[0].textContent, size = (null == (_a2 = item.getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : _a2.textContent) || "0", createTime = (null == (_b = item.getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : _b.textContent) || (null == (_c = item.getElementsByTagNameNS("DAV:", "getlastmodified")[0]) ? void 0 : _c.textContent) || ""; "0" !== size && fileList.push({ fileId: name2, name: name2, size: Number(size), createTime }); } fileList.reverse(); return fileList; } async deleteFile(fileId) { let path = this.folderName + "/" + encodeURI(fileId); await this._sendRequest("DELETE", path, { "Cache-Control": "no-cache" }); } async getBackupList(folderName) { this.folderName = folderName; await this._sendRequest("MKCOL", folderName); return this.getFileList(folderName); } async getFileContent(filePath) { let path = this.folderName + "/" + filePath; return (await this._sendRequest("GET", path, { Accept: "application/octet-stream" })).responseText; } } const SALT = "x7k9p3"; function simpleEncrypt(str) { return (SALT + str + SALT).split("").map(((char) => { const code = char.codePointAt(0); return String.fromCodePoint(code + 5); })).join(""); } class SettingPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "folderName", "JHS-数据备份"); __publicField2(this, "cacheItems", [{ key: "jhs_dmm_video", text: "🎥 预览视频缓存", title: "预览视频缓存" }, { key: "jhs_other_site", text: "🌍 第三方站点缓存", title: "第三方站点资源检测结果, 如missav,123Av等" }, { key: "jhs_screenShot", text: "🖼️ 缩略图缓存", title: "缩略图缓存" }, { key: "jhs_translate", text: "🆎 标题翻译", title: "标题翻译" }, { key: "jhs_actress_info", text: "👩 演员信息", title: "演员的年龄三围等数据信息" }, { key: "jhs_score_info", text: "⭐ Top250|热播 评分数据", title: "Top250及热播的评分数据" }]); } getName() { return "SettingPlugin"; } async initCss() { const settingObj = await storageManager.getSetting(); let containerWidth = (null == settingObj ? void 0 : settingObj.containerWidth) ?? "100", containerColumns = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == settingObj ? void 0 : settingObj.containerColumns) ?? 5; this.applyImageMode().then(); let containerWidthCss = ` section .container{ max-width: 1000px !important; min-width: ${containerWidth}%; } .movie-list, .movie-list.v{ grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr)); } `; isJavBus$1 && (containerWidthCss = ` .container-fluid .row{ max-width: 1000px !important; min-width: ${containerWidth}%; margin: auto auto; } .container { max-width: 1000px !important; min-width: 80%; margin: auto auto; } .masonry { grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr)); } `); return ` <style> ${containerWidthCss} .nav-btn::after { content:none !important; } #cache-data-display pre { font-family: Consolas, Monaco, 'Andale Mono', monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; color: #333; border: 1px solid #ddd; } .cache-item { transition: all 0.2s ease; } .cache-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); transform: translateY(-2px); } .tooltip-icon { display: inline-block; width: 16px; height: 16px; line-height: 16px; text-align: center; border-radius: 50%; background-color: #ccc; color: white; font-size: 12px; margin-right: 5px; cursor: help; } .setting-item { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 3px; padding: 3px; /*border: 1px solid #ddd; border-radius: 5px;*/ } .simple-setting .setting-item{ align-items:center; } .setting-label { font-size: 14px; min-width: 160px; font-weight: bold; margin-right: 10px; } .form-content{ max-width: 160px; min-width: 160px; } .form-content * { width: 100%; padding: 5px; margin-right: 10px; text-align: center; } .keyword-label { display: inline-flex; align-items: center; padding: 4px 8px; border-radius: 4px; font-size: 14px; position: relative; margin-left: 8px; margin-bottom: 5px; } .keyword-remove { margin-left: 6px; cursor: pointer; font-size: 12px; line-height: 1; } .keyword-input { padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; float:right; } .add-tag-btn { padding: 6px 12px; background-color: #e2e8f0; color: #334155; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-left: 8px; float:right; } .add-tag-btn:hover { background-color: #cbd5e1; } .tag-box { margin-top:15px; } #saveBtn,#moreBtn,#helpBtn,#clean-all { padding: 8px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-top: 10px; } #saveBtn:hover { background-color: #45a049; } #moreBtn { background-color: #5cb85c; color: white; } #moreBtn:hover { background-color: #4cae4c; } #helpBtn { background-color: #e67e22; color: white; } #helpBtn:hover { background-color: #d35400; } .simple-setting, .mini-simple-setting { display: none; background: rgba(255,255,255,1); position: absolute; top: ${isJavDb$1 ? "35px" : "25px"}; right: ${isJavDb$1 ? "-300%" : "0"}; z-index: 1000; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 0; margin-top: 5px; /* 稍微拉开一点距离 */ color: #333; } .mini-switch { appearance: none; -webkit-appearance: none; width: 40px; height: 20px; background: #e0e0e0; border-radius: 20px; position: relative; cursor: pointer; outline: none; /*transition: all 0.2s ease;*/ } .mini-switch:checked { background: #4CAF50; } .mini-switch::before { content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%; background: white; top: 2px; left: 2px; box-shadow: 0 1px 3px rgba(0,0,0,0.2); /*transition: all 0.2s ease;*/ } .mini-switch:checked::before { left: calc(100% - 18px); } .side-menu-item { padding: 12px 12px; cursor: pointer; color: #333; border-left: 3px solid transparent; transition: all 0.2s; display: flex; gap: 5px; } .side-menu-item .icon { height: 24px; width: 24px; } .side-menu-item:hover { background-color: #e9e9e9; } .side-menu-item.active { background-color: #e0e0e0; border-left: 3px solid #5d87c2; font-weight: bold; } .content-panel { display: none; margin-top:20px; padding: 0 10px 10px 0; height: 100%; overflow-x: hidden; overflow-y: auto; } .content-panel.active { display: block; } input[type="checkbox"]:disabled { opacity: 0.6; cursor: default !important; } </style> `; } async handle() { await storageManager.getSetting("enableClog", YES$1) === YES$1 && clog.show(); if (isJavDb$1) { let handleResize2 = function() { if ($(".navbar-search").is(":hidden")) { $(".mini-setting-box").hide(); $(".setting-box").show(); } else { $(".mini-setting-box").show(); $(".setting-box").hide(); } }; $("#navbar-menu-user .navbar-end").prepend('<div class="navbar-item has-dropdown is-hoverable setting-box" style="position:relative;">\n <a id="setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-right:15px !important;">\n 设置\n </a>\n <div class="simple-setting"></div>\n </div>'); utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => { $(".miniHistoryBtnBox").before('\n <div class="navbar-item mini-setting-box" style="position:relative;margin-left: auto;">\n <a id="mini-setting-btn" class="navbar-link nav-btn" style="color: #ff8400 !important;padding-left:0 !important;padding-right:0 !important;">\n 设置\n </a>\n <div class="mini-simple-setting"></div>\n </div>\n '); handleResize2(); })); $(window).resize(handleResize2); } if (isJavBus$1) { utils.loopDetector((() => $("#waitCheckBtn").length), (() => { $("#waitCheckBtn").parent().append('\n <div id="top-right-box" style="position: relative; display: flex; flex-grow: 1;justify-content: flex-end;z-index: 12345679 !important;">\n <div class="setting-box">\n <a id="setting-btn" class="menu-btn main-tab-btn" style="background-color:#6e685e !important;">\n <span>设置</span>\n </a>\n <div class="simple-setting"></div>\n </div>\n </div>\n '); }), 1, 1e4, false); window.isDetailPage && $("h3").before('\n <div class="container-fluid" style="margin-top:20px">\n <div id="top-right-box" style="position: relative; display: flex; flex-grow: 1;justify-content: flex-end;z-index: 12345679 !important;">\n <div class="setting-box">\n <a id="setting-btn" class="menu-btn main-tab-btn" style="background-color:#6e685e !important;">\n <span>设置</span>\n </a>\n <div class="simple-setting"></div>\n </div>\n </div>\n </div>\n '); } $(".main-nav, .container-fluid").on("click", "#setting-btn, #mini-setting-btn", (() => { clog.lowZIndex(); this.openSettingDialog(); })); $(".main-nav, .container-fluid").on("mouseenter", ".setting-box", (async () => { $(".simple-setting").html(await this.simpleSetting()).show(); this.initSimpleSettingForm().then(); clog.lowZIndex(); })).on("mouseleave", ".setting-box", (() => { $(".simple-setting").html("").hide(); })); $(".main-nav, .container-fluid").on("mouseenter", ".mini-setting-box", (async () => { $(".mini-simple-setting").html(await this.simpleSetting()).show(); this.initSimpleSettingForm().then(); clog.lowZIndex(); })).on("mouseleave", ".mini-setting-box", (() => { $(".mini-simple-setting").html("").hide(); })); } async openSettingDialog(defaultActivePanel = "backup-panel", fun) { const buttonsHTML = this.cacheItems.map(((item) => ` <div class="cache-item" style="border: 1px solid #eee; border-radius: 8px; padding: 12px;"> <div style="font-weight: bold; margin-bottom: 8px;">${item.text}</div> <div style="display: flex; gap: 8px;"> <a class="menu-btn clean-btn" data-key="${item.key}" style="background-color:#448cc2; flex:1; text-align:center;" title="${item.title}"> <span>清理</span> </a> <a class="menu-btn view-btn" data-key="${item.key}" style="background-color:#b2bec0; flex:1; text-align:center;" > <span>查看</span> </a> </div> </div> `)).join(""); let videoQualityHtml = ""; qualityOptions.forEach(((option) => { option.canSelect && (videoQualityHtml += `<option value="${option.quality}">${option.text}</option>`); })); const coverButtonPlugin = this.getBean("CoverButtonPlugin"); let settingHtml = ` <div style="display: flex; height: 100%;"> <div style="width: 140px; flex-shrink: 0; padding: 15px 0; background: #f5f5f5; border-right: 1px solid #ddd;"> <div class="side-menu-item ${"backup-panel" === defaultActivePanel ? "active" : ""}" data-panel="backup-panel">💾 数据备份</div> <div class="side-menu-item ${"base-panel" === defaultActivePanel ? "active" : ""}" data-panel="base-panel">⚙️ 基础配置</div> <div class="side-menu-item ${"filter-panel" === defaultActivePanel ? "active" : ""}" data-panel="filter-panel">🚫 屏蔽配置</div> <div class="side-menu-item ${"task-panel" === defaultActivePanel ? "active" : ""}" data-panel="task-panel">📋 定时任务</div> <div class="side-menu-item ${"offline-panel" === defaultActivePanel ? "active" : ""}" data-panel="offline-panel">📂 离线配置</div> <div class="side-menu-item ${"domain-panel" === defaultActivePanel ? "active" : ""}" data-panel="domain-panel" title="第三方视频资源域名配置">🌐 外部网站</div> <div class="side-menu-item ${"mark-delete-panel" === defaultActivePanel ? "active" : ""}" data-panel="mark-delete-panel">🗑️ 标记删除</div> <div class="side-menu-item ${"hotkey-panel" === defaultActivePanel ? "active" : ""}" data-panel="hotkey-panel">⌨️ 快捷键配置</div> <div class="side-menu-item ${"cache-panel" === defaultActivePanel ? "active" : ""}" data-panel="cache-panel">🧹 清理缓存</div> <div class="side-menu-item ${"tip-author-panel" === defaultActivePanel ? "active" : ""}" data-panel="tip-author-panel">💵 打赏作者</div> </div> <div style="flex: 1; display: flex; flex-direction: column; height: 100%; "> <div style="flex: 1; margin: 0 10px; padding-bottom: 20px;overflow: hidden"> <!-- 阿里云盘面板 --> <div id="backup-panel" class="content-panel" style="display: ${"backup-panel" === defaultActivePanel ? "block" : "none"};"> <div style="margin-bottom: 20px"> <a id="importBtn" class="menu-btn" style="background-color:#d25a88"><span>导入数据</span></a> <a id="exportBtn" class="menu-btn" style="background-color:#85d0a3"><span>导出数据</span></a> <a id="getRefreshTokenBtn" class="menu-btn fr-btn" style="background-color:#c4a35e"><span>获取refresh_token</span></a> </div> <div class="setting-item"> <span class="setting-label">阿里云盘备份</span> <div> <a id="backupListBtn" class="menu-btn" style="background-color:#5d87c2"><span>查看备份</span></a> <a id="backupBtn" class="menu-btn" style="background-color:#64bb69"><span>备份数据</span></a> </div> </div> <div class="setting-item"> <span class="setting-label">refresh_token:</span> <div class="form-content"> <input id="refresh_token"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label">WebDav备份</span> <div> <a id="webdavBackupListBtn" class="menu-btn" style="background-color:#5d87c2"><span>查看备份</span></a> <a id="webdavBackupBtn" class="menu-btn" style="background-color:#64bb69"><span>备份数据</span></a> </div> </div> <div class="setting-item"> <span class="setting-label">服务地址:</span> <div class="form-content"> <input id="webDavUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">用户名:</span> <div class="form-content"> <input id="webDavUsername"> </div> </div> <div class="setting-item"> <span class="setting-label">密码:</span> <div class="form-content"> <input id="webDavPassword"> </div> </div> </div> <!-- 基础设置面板 --> <div id="base-panel" class="content-panel" style="display: ${"base-panel" === defaultActivePanel ? "block" : "none"};"> <div class="setting-item"> <span class="setting-label">已鉴定标签展示位置:</span> <div class="form-content"> <select id="tagPosition"> <option value="rightTop">右上</option> <option value="leftTop">左上</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">已鉴定内容处理方式:</span> <div class="form-content"> <select id="movieShowType"> <option value="hide">隐藏</option> <option value="visibility">透明</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> 鉴定补录演员信息 <span data-tip="在列表页进行鉴定是获取不到演员名称的, 开启后, 额外解析详情页补录演员名称, 因发请求解析费时, 会被以往慢1秒左右">❓</span> </span> <div class="form-content"> <input type="checkbox" id="enableSaveActressCarInfo" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item" style="margin-top:10px"> <span class="setting-label"> 列表页功能按钮 </span> </div> <div class="setting-item"> <span class="setting-label">按钮-打开待鉴定:</span> <div class="form-content"> <input type="checkbox" id="showWaitCheckBtn" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">按钮-打开已收藏:</span> <div class="form-content"> <input type="checkbox" id="showWaitDownBtn" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">打开待鉴定|已收藏 窗口数:</span> <div class="form-content"> <input type="number" id="waitCheckCount" min="1" max="20" style="width: 100%;"> </div> </div> <div class="setting-item"> <span class="setting-label">随机打开已收藏:</span> <div class="form-content"> <input type="checkbox" id="randomOpenWaitDown" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item" style="margin-top:10px"> <span class="setting-label"> 封面快捷按钮 </span> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> ${coverButtonPlugin.screenSvg}长缩略图: </span> <div class="form-content"> <input type="checkbox" id="enableScreenSvg" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> ${coverButtonPlugin.videoSvg}预览视频: </span> <div class="form-content"> <input type="checkbox" id="enableVideoSvg" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> ${coverButtonPlugin.handleSvg}鉴定按钮: </span> <div class="form-content"> <input type="checkbox" id="enableHandleSvg" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> ${coverButtonPlugin.siteSvg}第三方跳转: </span> <div class="form-content"> <input type="checkbox" id="enableSiteSvg" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label" style="display:flex; align-items:center; gap:5px"> ${coverButtonPlugin.copySvg}复制按钮: </span> <div class="form-content"> <input type="checkbox" id="enableCopySvg" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label">预览视频默认画质:</span> <div class="form-content"> <select id="videoQuality"> ${videoQualityHtml} </select> </div> </div> <div class="setting-item"> <span class="setting-label">评论区条数:</span> <div class="form-content"> <select id="reviewCount"> <option value="10">10条</option> <option value="20">20条</option> <option value="30">30条</option> <option value="40">40条</option> <option value="50">50条</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label"> 高亮已收藏演员 <span data-tip="详情页, 对已收藏的演员进行边框高亮提醒">❓</span> </span> <div class="form-content"> <input type="checkbox" id="enableFavoriteActresses" class="mini-switch"> </div> </div> <div class="setting-item"> <span id="highlightedTagLabel" class="setting-label"> 分类标签|高亮演员-边框样式: </span> <div class="form-content" style="display: flex; align-items: center;"> <input type="number" id="highlightedTagNumber" min="0" max="20"> <input type="color" id="highlightedTagColor"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label">请求超时时间(毫秒):</span> <div class="form-content"> <input type="number" id="httpTimeout" min="1000" max="10000" style="width: 100%;"> </div> </div> <div class="setting-item"> <span class="setting-label">请求失败重试次数:</span> <div class="form-content"> <input type="number" id="httpRetryCount" min="0" max="10" style="width: 100%;"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label"> 启用控制台日志: </span> <div class="form-content"> <select id="enableClog"> <option value="no">禁用</option> <option value="yes">开启</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">日志最大行数:</span> <div class="form-content"> <input type="number" id="clogMsgCount" min="100" max="3000" style="width: 100%;"> </div> </div> </div> <!-- 定时任务 --> <div id="task-panel" class="content-panel" style="display: ${"task-panel" === defaultActivePanel ? "block" : "none"};"> <div class="setting-item"> <span class="setting-label">请求并发数量:</span> <div class="form-content"> <input type="number" id="checkConcurrencyCount" min="2" max="5" style="width: 100%;"> </div> </div> <div class="setting-item"> <span class="setting-label">请求间隔时间(毫秒):</span> <div class="form-content"> <input type="number" id="checkRequestSleep" min="0" max="3000" style="width: 100%;"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div id="setting-blacklist" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;"> <span style="font-size: 14px; font-weight: bold; padding:3px">自动检测屏蔽黑名单演员</span> <div class="setting-item"> <span class="setting-label"> 任务开关: <span data-tip="变更后, 刷新页面生效">❓</span> </span> <div class="form-content"> <select id="enableCheckBlacklist"> <option value="no">禁用</option> <option value="yes">开启</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">任务间隔时间:</span> <div class="form-content"> <select id="checkBlacklist_intervalTime"> <option value="2">每2小时</option> <option value="3">每3小时</option> <option value="6">每6小时</option> <option value="12">每12小时</option> <option value="24">每24小时</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">检测规则:</span> <div class="form-content"> <select id="checkBlacklist_ruleTime"> <option value="0">全部检测</option> <option value="8760">不检测停更1年以上</option> <option value="17520">不检测停更2年以上</option> <option value="26280">不检测停更3年以上</option> </select> </div> </div> </div> <div id="setting-checkFavoriteActress" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;" class="${isJavDb$1 ? "" : "do-hide"}"> <span style="font-size: 14px; font-weight: bold; padding:3px">自动同步已收藏的演员</span> <div class="setting-item"> <span class="setting-label"> 任务开关: <span data-tip="变更后, 刷新页面生效">❓</span> </span> <div class="form-content"> <select id="enableCheckFavoriteActress"> <option value="no">禁用</option> <option value="yes">开启</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">任务间隔时间:</span> <div class="form-content"> <select id="checkFavoriteActress_IntervalTime"> <option value="12">每12小时</option> <option value="24">每24小时</option> </select> </div> </div> </div> <div id="setting-checkNewVideo" style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;" class="${isJavDb$1 ? "" : "do-hide"}"> <span style="font-size: 14px; font-weight: bold; padding:3px">自动检测已收藏演员的最新作品</span> <div class="setting-item"> <span class="setting-label"> 任务开关: <span data-tip="变更后, 刷新页面生效">❓</span> </span> <div class="form-content"> <select id="enableCheckNewVideo"> <option value="no">禁用</option> <option value="yes">开启</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">任务间隔时间:</span> <div class="form-content"> <select id="checkNewVideo_intervalTime"> <option value="2">每2小时</option> <option value="3">每3小时</option> <option value="6">每6小时</option> <option value="12">每12小时</option> <option value="24">每24小时</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">检测规则:</span> <div class="form-content"> <select id="checkNewVideo_ruleTime"> <option value="0">全部检测</option> <option value="8760">不检测停更1年以上</option> <option value="17520">不检测停更2年以上</option> <option value="26280">不检测停更3年以上</option> </select> </div> </div> </div> </div> <!-- 离线配置面板 --> <div id="offline-panel" class="content-panel" style="display: ${"offline-panel" === defaultActivePanel ? "block" : "none"};"> <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;"> <span style="font-size: 14px; font-weight: bold; padding:3px">115离线文件夹命名</span> <div class="setting-item"> <span class="setting-label"> 启用自定义格式: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enable115OfflineFormat" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">命名格式:</span> <div class="form-content" style="max-width: 600px;"> <input id="offlineFolderFormat" placeholder="例: \${code}" style="width: 100%;"> </div> </div> <div style="margin-top: 10px; padding: 8px; background: #f8f8f8; border-radius: 4px; font-size: 12px; color: #666; line-height: 1.8;"> <div style="font-weight: bold; margin-bottom: 4px;">可用变量(\${} 形式):</div> <div><code>code</code> - 完整番号(如 ABC-123)</div> <div><code>title</code> - 作品标题</div> <div><code>maker</code> - 片商</div> <div><code>actor</code> - 演员(多演员只取第一个)</div> <div><code>date</code> - 当前日期(YYYY-MM-DD)</div> <div style="margin-top: 6px; color: #999;">例: <code>\${actor}/\${code}</code> = 演员目录下的番号子目录</div> <div style="color: #e67e22;">支持 / 分隔的多级目录,离线文件下载到最末级子目录</div> </div> </div> </div> <!-- 域名设置面板 --> <div id="domain-panel" class="content-panel" style="display: ${"domain-panel" === defaultActivePanel ? "block" : "none"};"> <div class="setting-item"> <span class="setting-label">域名 - MissAv:</span> <div class="form-content"> <input id="missAvUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - Jable:</span> <div class="form-content"> <input id="jableUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - JavTrailer:</span> <div class="form-content"> <input id="javTrailersUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - 123Av:</span> <div class="form-content"> <input id="av123Url"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - JavDb:</span> <div class="form-content"> <input id="javDbUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - JavBus:</span> <div class="form-content"> <input id="javBusUrl"> </div> </div> <div class="setting-item"> <span class="setting-label">域名 - SupJav:</span> <div class="form-content"> <input id="supJavUrl"> </div> </div> </div> <!-- 标记删除 --> <div id="mark-delete-panel" class="content-panel" style="display: ${"mark-delete-panel" === defaultActivePanel ? "block" : "none"};"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <span style="font-size: 14px; font-weight: bold;">标记删除列表</span> <button id="batchDeleteBtn" class="menu-btn" style="background-color:#e74c3c; color:white;"> <span>一键清除</span> </button> </div> <div style="margin-bottom: 10px;"> <label style="cursor: pointer;"> <input type="checkbox" id="markDeleteSelectAll" checked> <b>全选</b> </label> <span id="markDeleteCount" style="margin-left: 10px; color: #888; font-size: 13px;"></span> </div> <div id="markDeleteList" style="max-height: 450px; overflow-y: auto; border: 1px solid #e0e0e0; border-radius: 4px;"> <div style="text-align: center; padding: 40px; color: #999;">加载中...</div> </div> <p style="color: #e74c3c; font-size: 12px; margin-top: 10px; text-align: left;">⚠ 作品删除目前暂无法删除115下载记录,如果需要重新下载,请去115云下载记录里删除或清空记录</p> </div> <!-- 快捷键 --> <div id="hotkey-panel" class="content-panel" style="display: ${"hotkey-panel" === defaultActivePanel ? "block" : "none"};"> <p style="color: #c62222; font-size: 14px;font-weight: bold;margin-bottom: 10px;">快捷键修改后, 刷新页面生效</p> <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;"> <div class="setting-item"> <span class="setting-label">🚫 屏蔽:</span> <div class="form-content"> <input id="filterHotKey" placeholder="录入快捷键" data-default-hotkey="a"> </div> </div> <div class="setting-item"> <span class="setting-label">⭐ 收藏:</span> <div class="form-content"> <input id="favoriteHotKey" placeholder="录入快捷键" data-default-hotkey="s"> </div> </div> <div class="setting-item"> <span class="setting-label">📥️ 已下载:</span> <div class="form-content"> <input id="hasDownHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">🔍 已观看:</span> <div class="form-content"> <input id="hasWatchHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label"> <span data-tip="列表页,鼠标放置图片上时可使用快捷键">❓ </span> 对视频列表页启用快捷键: </span> <div class="form-content"> <input type="checkbox" id="enableImageHotKey" class="mini-switch"> </div> </div> </div> <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;"> <div class="setting-item"> <span class="setting-label">⏩ 快进:</span> <div class="form-content"> <input id="speedVideoHotKey" placeholder="录入快捷键" data-default-hotkey="z"> </div> </div> <div class="setting-item"> <span class="setting-label">▲ 折叠:</span> <div class="form-content"> <input id="foldCategoryHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">💻 控制台:</span> <div class="form-content"> <input id="clogHotKey" placeholder="录入快捷键"> </div> </div> </div> <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;"> <span style="font-size: 14px; font-weight: bold; padding:3px">显示已鉴定内容</span> <div class="setting-item"> <span class="setting-label">屏蔽单番号:</span> <div class="form-content"> <input id="showFilterItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">屏蔽演员:</span> <div class="form-content"> <input id="showFilterActorItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">屏蔽关键词:</span> <div class="form-content"> <input id="showFilterKeywordItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">收藏:</span> <div class="form-content"> <input id="showFavoriteItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">已下载:</span> <div class="form-content"> <input id="showHasDownItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">已观看:</span> <div class="form-content"> <input id="showHasWatchItemHotKey" placeholder="录入快捷键"> </div> </div> <div class="setting-item"> <span class="setting-label">显示所有:</span> <div class="form-content"> <input id="showAllItemHotKey" placeholder="录入快捷键"> </div> </div> </div> </div> <!-- 屏蔽设置面板 --> <div id="filter-panel" class="content-panel" style="display: ${"filter-panel" === defaultActivePanel ? "block" : "none"};"> <div class="setting-item"> <span class="setting-label"> 启用划词屏蔽 <span data-tip="视频详情页中, 标题或评论区选中文字, 按右键可快捷加入屏蔽词">❓ </span> </span> <div style="display: flex"> <input type="checkbox" id="enableTitleSelectFilter" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div id="reviewKeywordContainer"> <div class="setting-item"> <span class="setting-label">评论区屏蔽词:</span> <div style="display: flex"> <input type="text" class="keyword-input" placeholder="添加屏蔽词"> <button class="add-tag-btn">添加</button> </div> </div> <div class="tag-box"> </div> </div> <hr style="border: 0; height: 1px; margin:20px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div id="filterKeywordContainer"> <div class="setting-item"> <span class="setting-label">视频标题屏蔽词:</span> <div style="display: flex"> <input type="text" class="keyword-input" placeholder="添加屏蔽词"> <button class="add-tag-btn">添加</button> </div> </div> <div class="tag-box"> </div> </div> </div> <div id="cache-panel" class="content-panel" style="display: ${"cache-panel" === defaultActivePanel ? "block" : "none"};"> <h1 style="text-align:center;font-size: 20px;font-weight: bold">以下操作, 不会对核心数据造成影响</h1> <br/> <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 20px;"> ${buttonsHTML} </div> <div id="cache-data-display" style="margin-top: 20px; display: none;"> <pre style="background: #f5f5f5; padding: 10px; border-radius: 5px; max-height: 400px; overflow: auto;"></pre> </div> </div> <div id="tip-author-panel" class="content-panel" style="display: ${"tip-author-panel" === defaultActivePanel ? "block" : "none"};"> <p style="color: #666; font-size: 0.9em;">如果JHS-enhance给您带来了便捷和价值,请考虑给予一点支持,您的鼓励是我持续创作的最大动力!感谢您的慷慨支持!</p> <div> <div style="display: flex; justify-content: space-around; align-items: flex-start; margin-bottom: 20px; flex-wrap: wrap;"> <div style="text-align: center; margin: 10px; flex: 1 1 30%; min-width: 150px;"> <img src="https://i.imgur.com/ynGK6he.jpeg" alt="SOL二维码" style="width: 350px; height: 350px; border: 1px solid #ddd; padding: 5px; display: block; margin: 0 auto 5px;"> <p>SOL</p> <input type="text" readonly value="5xvRp1jhWkCasT1H7Nh8G5eAuvdCFD5s2Xux2w3rJ8RG" onclick="this.select();document.execCommand('copy');alert('地址已复制!');" style="width: 90%; padding: 5px; margin-top: 5px; border: 1px solid #a99087; background-color: #fff; text-align: center; font-size: 0.8em; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> <p style="font-size: 0.75em; color: #5a504c; margin-top: 4px;">点击地址可复制</p> </div> </div> </div> </div> </div> <div style="flex-shrink: 0; padding: 15px 20px; text-align: right; border-top: 1px solid #eee; background: white;"> <button id="saveBtn">保存设置</button> <button id="clean-all" style="display: none">♾️ 清理全部缓存</button> </div> </div> </div> `; layer.open({ type: 1, title: "设置", content: settingHtml, area: utils.getResponsiveArea(["55%", "90%"]), scrollbar: false, success: (layero, index) => { $(layero).find(".layui-layer-content").css("position", "relative"); this.loadForm(); this.bindClick(); utils.setupEscClose(index); fun && fun(); }, end: () => { this.getBean("CoverButtonPlugin").enableSvgBtn(); } }); } async simpleSetting() { let settingObj = await storageManager.getSetting(); const showFilterItemHotKey = settingObj.showFilterItemHotKey, showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey, showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey, showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey, showHasDownItemHotKey = settingObj.showHasDownItemHotKey, showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey, showAllItemHotKey = settingObj.showAllItemHotKey; return ` <div class="jhs-scrollbar" style="margin-top:20px;max-height:90vh; overflow-y:auto;"> <div style="margin: 0 10px;"> <div class="setting-item"> <span class="setting-label"> 显示已鉴定内容: </span> <div class="form-content" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;"> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">屏蔽单番号${showFilterItemHotKey ? `(${showFilterItemHotKey})` : ""}: </span><input type="checkbox" id="showFilterItem" class="mini-switch"><br/> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">屏蔽演员${showFilterActorItemHotKey ? `(${showFilterActorItemHotKey})` : ""}: </span><input type="checkbox" id="showFilterActorItem" class="mini-switch"><br/> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">屏蔽关键词${showFilterKeywordItemHotKey ? `(${showFilterKeywordItemHotKey})` : ""}: </span><input type="checkbox" id="showFilterKeywordItem" class="mini-switch"><br/> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">收藏${showFavoriteItemHotKey ? `(${showFavoriteItemHotKey})` : ""}: </span><input type="checkbox" id="showFavoriteItem" class="mini-switch"><br/> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">已下载${showHasDownItemHotKey ? `(${showHasDownItemHotKey})` : ""}: </span><input type="checkbox" id="showHasDownItem" class="mini-switch"><br/> <span style="display:inline-block; width: 100px; font-size:13px; font-weight:bold; text-align: left">已观看${showHasWatchItemHotKey ? `(${showHasWatchItemHotKey})` : ""}: </span><input type="checkbox" id="showHasWatchItem" class="mini-switch"><br/> </div> </div> <div class="setting-item"> <span class="setting-label"> <span data-tip="快速显示所有已鉴定内容,减少对以上开关的频繁操作">❓ </span> 显示所有${showAllItemHotKey ? `(${showAllItemHotKey})` : ""}: </span> <div class="form-content" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;"> <input type="checkbox" id="showAllItem" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:10px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label"> <span data-tip="点击封面的打开方式,弹窗|新窗口">❓ </span>弹窗方式打开页面: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="dialogOpenDetail" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">鉴定后立即关闭页面:</span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="needClosePage" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:10px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label"> <span data-tip="使用瀑布流模式, 排序方式将调整为默认">❓ </span>瀑布流模式: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="autoPage" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">启用标题翻译:</span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="translateTitle" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">启用悬浮大图:</span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="hoverBigImg" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label"> <span data-tip="匹配到不规则文件名时自动重命名为标准番号格式,如 mdvr00317-3.mp4 → MDVR-317-3.mp4">❓ </span>不规则重命名:<span style="font-size:11px;color:#999;white-space:nowrap;">(刷新详情页生效,有延迟)</span></span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enable115AutoRename" class="mini-switch"> </div> </div> <div class="setting-item" id="autoRenameScopeRow" style="display:none;"> <span class="setting-label">重命名范围:</span> <div class="form-content" style="text-align: right;"> <select id="autoRenameScope" style="padding: 2px 4px;"> <option value="vr_only">仅VR</option> <option value="all">全部</option> </select> </div> </div> <div class="setting-item"> <span class="setting-label">启用115视频匹配: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enable115Match" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:10px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> ${isJavDb$1 ? '\n <div class="setting-item">\n <span class="setting-label">\n <span data-tip="详情页是否展示女优年龄、三围等信息">❓ </span>加载女优信息:\n </span>\n <div class="form-content" style="text-align: right;">\n <input type="checkbox" id="enableLoadActressInfo" class="mini-switch">\n </div>\n </div>' : ""} <div class="setting-item"> <span class="setting-label"> <span data-tip="详情页第三方资源检测,如missAv,123AV">❓ </span>加载第三方视频资源: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enableLoadOtherSite" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label"> <span data-tip="详情页图片区首列位置加载长缩略图">❓ </span>加载长缩略图: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enableLoadScreenShot" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label"> <span data-tip="详情页解析更多更高画质的预览视频">❓ </span>更高画质预览视频: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enableLoadPreviewVideo" class="mini-switch"> </div> </div> <hr style="border: 0; height: 1px; margin:10px 0;background-image: linear-gradient(to right, rgba(0,0,0,0), rgba(159,137,137,0.75), rgba(0,0,0,0));"/> <div class="setting-item"> <span class="setting-label"> <span data-tip="列数6以上,建议开启竖图">❓ </span>竖图模式: </span> <div class="form-content" style="text-align: right;"> <input type="checkbox" id="enableVerticalModel" class="mini-switch"> </div> </div> <div class="setting-item"> <span class="setting-label">页面列数: <span id="showContainerColumns"></span></span> <div class="form-content"> <input type="range" id="containerColumns" min="2" max="10" step="1" style="padding:5px 0"> </div> </div> <div class="setting-item"> <span class="setting-label">页面宽度: <span id="showContainerWidth"></span></span> <div class="form-content"> <input type="range" id="containerWidth" min="0" max="30" step="1" style="padding:5px 0"> </div> </div> </div> <div style="padding: 0 20px 15px; text-align: right; border-top: 1px solid #eee;"> <button id="helpBtn" style="float:left;">常见问题</button> <button id="moreBtn">更多设置</button> </div> </div> `; } async loadForm() { let settingObj = await storageManager.getSetting(); $("#videoQuality").val(settingObj.videoQuality); $("#reviewCount").val(settingObj.reviewCount || 20); $("#tagPosition").val(settingObj.tagPosition || "rightTop"); $("#movieShowType").val(settingObj.movieShowType || "hide"); $("#waitCheckCount").val(settingObj.waitCheckCount || 5); $("#showWaitCheckBtn").prop("checked", !settingObj.showWaitCheckBtn || settingObj.showWaitCheckBtn === YES$1); $("#showWaitDownBtn").prop("checked", !settingObj.showWaitDownBtn || settingObj.showWaitDownBtn === YES$1); $("#randomOpenWaitDown").prop("checked", !!settingObj.randomOpenWaitDown && settingObj.randomOpenWaitDown === YES$1); $("#checkConcurrencyCount").val(settingObj.checkConcurrencyCount || 2); $("#checkRequestSleep").val(settingObj.checkRequestSleep || 100); $("#enableCheckBlacklist").val(settingObj.enableCheckBlacklist || YES$1); $("#checkBlacklist_intervalTime").val(settingObj.checkBlacklist_intervalTime || 12); $("#checkBlacklist_ruleTime").val(settingObj.checkBlacklist_ruleTime || 8760); $("#enableCheckFavoriteActress").val(settingObj.enableCheckFavoriteActress || YES$1); $("#checkFavoriteActress_IntervalTime").val(settingObj.checkFavoriteActress_IntervalTime || 24); $("#enableCheckNewVideo").val(settingObj.enableCheckNewVideo || YES$1); $("#checkNewVideo_intervalTime").val(settingObj.checkNewVideo_intervalTime || 12); $("#checkNewVideo_ruleTime").val(settingObj.checkNewVideo_ruleTime || 8760); const highlightedTagNumber = settingObj.highlightedTagNumber || 1, highlightedTagColor = settingObj.highlightedTagColor || "#ce2222"; $("#highlightedTagNumber").val(settingObj.highlightedTagNumber || 1); $("#highlightedTagColor").val(settingObj.highlightedTagColor || "#ce2222"); $("#highlightedTagLabel").css("border", `${highlightedTagNumber}px solid ${highlightedTagColor}`); $("#enableClog").val(settingObj.enableClog || YES$1); $("#clogMsgCount").val(settingObj.clogMsgCount || 2e3); $("#refresh_token").val(settingObj.refresh_token || ""); $("#httpTimeout").val(settingObj.httpTimeout || 5e3); $("#httpRetryCount").val(settingObj.httpRetryCount || 3); $("#webDavUrl").val(settingObj.webDavUrl || ""); $("#webDavUsername").val(settingObj.webDavUsername || ""); $("#webDavPassword").val(settingObj.webDavPassword || ""); $("#enableTitleSelectFilter").prop("checked", !settingObj.enableTitleSelectFilter || settingObj.enableTitleSelectFilter === YES$1); $("#enableFavoriteActresses").prop("checked", !settingObj.enableFavoriteActresses || settingObj.enableFavoriteActresses === YES$1); $("#enableSaveActressCarInfo").prop("checked", !!settingObj.enableSaveActressCarInfo && settingObj.enableSaveActressCarInfo === YES$1); $("#enableScreenSvg").prop("checked", !settingObj.enableScreenSvg || settingObj.enableScreenSvg === YES$1); $("#enableVideoSvg").prop("checked", !settingObj.enableVideoSvg || settingObj.enableVideoSvg === YES$1); $("#enableHandleSvg").prop("checked", !settingObj.enableHandleSvg || settingObj.enableHandleSvg === YES$1); $("#enableSiteSvg").prop("checked", !settingObj.enableSiteSvg || settingObj.enableSiteSvg === YES$1); $("#enableCopySvg").prop("checked", !settingObj.enableCopySvg || settingObj.enableCopySvg === YES$1); const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), javTrailersUrl = await otherSitePlugin.getJavTrailersUrl(), av123Url = await otherSitePlugin.getAv123Url(), javDbUrl = await otherSitePlugin.getJavDbUrl(), javBusUrl = await otherSitePlugin.getJavBusUrl(), supJavUrl = await otherSitePlugin.getSupJavUrl(); $("#missAvUrl").val(missAvUrl); $("#jableUrl").val(jableUrl); $("#javTrailersUrl").val(javTrailersUrl); $("#av123Url").val(av123Url); $("#javDbUrl").val(javDbUrl); $("#javBusUrl").val(javBusUrl); $("#supJavUrl").val(supJavUrl); let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(); reviewKeywordList && reviewKeywordList.forEach(((reviewKeyword) => { this.addLabelTag("#reviewKeywordContainer", reviewKeyword); })); filterKeywordList && filterKeywordList.forEach(((reviewKeyword) => { this.addLabelTag("#filterKeywordContainer", reviewKeyword); })); ["#reviewKeywordContainer", "#filterKeywordContainer"].forEach(((containerId) => { $(`${containerId} .add-tag-btn`).on("click", ((event) => this.addKeyword(event, containerId))); $(`${containerId} .keyword-input`).on("keypress", ((event) => { "Enter" === event.key && this.addKeyword(event, containerId); })); })); $("#hotkey-panel [id]").map(((i, el) => el.id)).get().forEach(((containerId) => { const $element = $(`#${containerId}`), defaultValue = void 0 !== settingObj[containerId] ? settingObj[containerId] : $element.attr("data-default-hotkey") || ""; $element.val(defaultValue).on("input", ((event) => { let value = $(event.target).val(); if (/[\u4e00-\u9fa5]/.test(value) || /^Shift[a-zA-Z0-9]+$/.test(value)) { $(event.target).val(""); show.error("非法输入:不能输入中文或输入法转换错误"); } })).on("keydown", ((event) => this.handleHotkeyInput(event, $element))); })); $("#enableImageHotKey").prop("checked", !!settingObj.enableImageHotKey && settingObj.enableImageHotKey === YES$1); $("#enable115OfflineFormat").prop("checked", !!settingObj.enable115OfflineFormat && settingObj.enable115OfflineFormat === YES$1); $("#offlineFolderFormat").val(settingObj.offlineFolderFormat || ""); } handleHotkeyInput(event, $input) { event.preventDefault(); const hotkey = this.parseHotkey(event); "" !== hotkey ? this.isDuplicateHotkey(hotkey, $input.attr("id")) ? show.error("该快捷键已被其他功能使用!") : $input.val(hotkey) : $input.val(""); } parseHotkey(event) { if ("Backspace" === event.key || "Process" === event.key) return ""; const keys = []; event.ctrlKey && keys.push("Ctrl"); event.shiftKey && keys.push("Shift"); event.altKey && keys.push("Alt"); event.metaKey && keys.push("Cmd"); const key = { " ": "Space", Control: "Ctrl", Meta: "Cmd", ArrowUp: "Up", ArrowDown: "Down", ArrowLeft: "Left", ArrowRight: "Right" }[event.key] || (event.key.length > 1 ? event.key.replace("Arrow", "") : event.key); ["Control", "Shift", "Alt", "Meta"].includes(event.key) || keys.push(key); return keys.length > 0 ? keys.join("+") : ""; } isDuplicateHotkey(hotkey, currentInputId) { let isDuplicate = false; $("#hotkey-panel [id]").each(((i, el) => { if (el.id !== currentInputId && hotkey && hotkey === $(el).val()) { isDuplicate = true; return false; } })); return isDuplicate; } async initSimpleSettingForm() { let settingObj = await storageManager.getSetting(); $("#containerColumns").val(settingObj.containerColumns || 5); $("#showContainerColumns").text(settingObj.containerColumns || 5); $("#containerWidth").val((settingObj.containerWidth || 100) - 70); $("#showContainerWidth").text((settingObj.containerWidth || 100) + "%"); $("#dialogOpenDetail").prop("checked", !settingObj.dialogOpenDetail || settingObj.dialogOpenDetail === YES$1); $("#needClosePage").prop("checked", !settingObj.needClosePage || settingObj.needClosePage === YES$1); $("#autoPage").prop("checked", !settingObj.autoPage || settingObj.autoPage === YES$1); $("#translateTitle").prop("checked", !settingObj.translateTitle || settingObj.translateTitle === YES$1); $("#enableLoadActressInfo").prop("checked", !settingObj.enableLoadActressInfo || settingObj.enableLoadActressInfo === YES$1); $("#enableLoadOtherSite").prop("checked", !settingObj.enableLoadOtherSite || settingObj.enableLoadOtherSite === YES$1); $("#containerColumns").on("input", (async (event) => { let columns = $("#containerColumns").val(); $("#showContainerColumns").text(columns); if (isJavDb$1) { document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; } if (isJavBus$1) { document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; } await storageManager.saveSettingItem("containerColumns", columns); this.applyImageMode(); })); $("#containerWidth").on("input", (async (event) => { let containerWidth = parseInt($(event.target).val()); const value = containerWidth + 70 + "%"; $("#showContainerWidth").text(value); if (isJavDb$1) { document.querySelector("section .container").style.minWidth = value; } if (isJavBus$1) { document.querySelector(".container-fluid .row").style.minWidth = value; } storageManager.saveSettingItem("containerWidth", containerWidth + 70); })); $("#dialogOpenDetail").on("change", ((event) => { let dialogOpenDetail = $("#dialogOpenDetail").is(":checked") ? YES$1 : NO$1; storageManager.saveSettingItem("dialogOpenDetail", dialogOpenDetail); })); $("#showFilterItem").prop("checked", !!settingObj.showFilterItem && settingObj.showFilterItem === YES$1); $("#showFilterActorItem").prop("checked", !!settingObj.showFilterActorItem && settingObj.showFilterActorItem === YES$1); $("#showFilterKeywordItem").prop("checked", !!settingObj.showFilterKeywordItem && settingObj.showFilterKeywordItem === YES$1); $("#showFavoriteItem").prop("checked", !settingObj.showFavoriteItem || settingObj.showFavoriteItem === YES$1); $("#showHasDownItem").prop("checked", !settingObj.showHasDownItem || settingObj.showHasDownItem === YES$1); $("#showHasWatchItem").prop("checked", !settingObj.showHasWatchItem || settingObj.showHasWatchItem === YES$1); $("#showFilterItem").on("change", (async (event) => { let showFilterItem = $("#showFilterItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showFilterItem", showFilterItem); window.refresh(); })); $("#showFilterActorItem").on("change", (async (event) => { let showFilterActorItem = $("#showFilterActorItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showFilterActorItem", showFilterActorItem); window.refresh(); })); $("#showFilterKeywordItem").on("change", (async (event) => { let showFilterKeywordItem = $("#showFilterKeywordItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showFilterKeywordItem", showFilterKeywordItem); window.refresh(); })); $("#showFavoriteItem").on("change", (async (event) => { let showFavoriteItem = $("#showFavoriteItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showFavoriteItem", showFavoriteItem); window.refresh(); })); $("#showHasDownItem").on("change", (async (event) => { let showHasDownItem = $("#showHasDownItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showHasDownItem", showHasDownItem); window.refresh(); })); $("#showHasWatchItem").on("change", (async (event) => { let showHasWatchItem = $("#showHasWatchItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showHasWatchItem", showHasWatchItem); window.refresh(); })); const $otherCheckboxes = $("#showFilterItem, #showFilterActorItem, #showFilterKeywordItem, #showFavoriteItem, #showHasDownItem, #showHasWatchItem"), updateOtherCheckboxesState = () => { const isShowAllChecked = $("#showAllItem").is(":checked"); $otherCheckboxes.prop("disabled", isShowAllChecked); isShowAllChecked ? $otherCheckboxes.attr("data-tip", "请先关闭显示所有才可点击") : $otherCheckboxes.removeAttr("data-tip"); }; $("#showAllItem").prop("checked", !!settingObj.showAllItem && settingObj.showAllItem === YES$1); $("#showAllItem").on("change", (async (event) => { let showAllItem = $("#showAllItem").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("showAllItem", showAllItem); updateOtherCheckboxesState(); window.refresh(); })); updateOtherCheckboxesState(); $("#needClosePage").on("change", (async (event) => { await storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? YES$1 : NO$1); window.refresh(); })); $("#autoPage").on("change", (async (event) => { const autoPage = $("#autoPage").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("autoPage", autoPage); autoPage === YES$1 ? ($("#sort-toggle-btn").hide(), $("#one-time-sort-btn").show()) : ($("#sort-toggle-btn").show(), $("#one-time-sort-btn").hide()); })); $("#translateTitle").on("change", (async (event) => { const translateTitle = $("#translateTitle").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("translateTitle", translateTitle); if (translateTitle === YES$1) { await this.getBean("ListPagePlugin").doFilter(); window.isDetailPage && await this.getBean("TranslatePlugin").translate(); } else { await this.getBean("ListPagePlugin").revertTranslation(); $(".translated-title").remove(); } })); $("#hoverBigImg").prop("checked", !!settingObj.hoverBigImg && settingObj.hoverBigImg === YES$1); $("#hoverBigImg").on("change", (async (event) => { const hoverBigImg = $("#hoverBigImg").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("hoverBigImg", hoverBigImg); hoverBigImg === YES$1 ? window.imageHoverPreviewObj = new ImageHoverPreview({ selector: this.getSelector().coverImgSelector }) : window.imageHoverPreviewObj && window.imageHoverPreviewObj.destroy(); })); $("#enableLoadActressInfo").on("change", (async (event) => { const enableLoadActressInfo = $("#enableLoadActressInfo").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enableLoadActressInfo", enableLoadActressInfo); enableLoadActressInfo === YES$1 ? this.getBean("ActressInfoPlugin").loadActressInfo() : $(".actress-info").remove(); })); $("#enableLoadOtherSite").on("change", (async (event) => { const enableLoadOtherSite = $("#enableLoadOtherSite").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enableLoadOtherSite", enableLoadOtherSite); enableLoadOtherSite === YES$1 ? this.getBean("OtherSitePlugin").loadOtherSite().then() : $("#otherSiteBox").remove(); })); $("#enableLoadScreenShot").prop("checked", !settingObj.enableLoadScreenShot || settingObj.enableLoadScreenShot === YES$1); $("#enableLoadScreenShot").on("change", (async (event) => { const enableLoadScreenShot = $("#enableLoadScreenShot").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enableLoadScreenShot", enableLoadScreenShot); enableLoadScreenShot === YES$1 ? this.getBean("ScreenShotPlugin").loadScreenShot().then() : $(".screen-container").remove(); })); $("#enableLoadPreviewVideo").prop("checked", !settingObj.enableLoadPreviewVideo || settingObj.enableLoadPreviewVideo === YES$1); $("#enableLoadPreviewVideo").on("change", (async (event) => { const enableLoadPreviewVideo = $("#enableLoadPreviewVideo").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enableLoadPreviewVideo", enableLoadPreviewVideo); })); $("#enable115AutoRename").prop("checked", !!settingObj.enable115AutoRename && settingObj.enable115AutoRename === YES$1); $("#autoRenameScope").val(settingObj.autoRenameScope || "vr_only"); $("#autoRenameScopeRow").toggle(!!settingObj.enable115AutoRename && settingObj.enable115AutoRename === YES$1); $("#enable115AutoRename").on("change", (async (event) => { const enable115AutoRename = $("#enable115AutoRename").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enable115AutoRename", enable115AutoRename); $("#autoRenameScopeRow").toggle(enable115AutoRename === YES$1); })); $("#autoRenameScope").on("change", (async (event) => { await storageManager.saveSettingItem("autoRenameScope", $("#autoRenameScope").val()); })); $("#enable115Match").prop("checked", !!settingObj.enable115Match && settingObj.enable115Match === YES$1); $("#enable115Match").on("change", (async (event) => { const enable115Match = $("#enable115Match").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enable115Match", enable115Match); let movieList = $(this.getSelector().itemSelector).toArray(); await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList); })); $("#enableVerticalModel").prop("checked", !!settingObj.enableVerticalModel && settingObj.enableVerticalModel === YES$1); $("#enableVerticalModel").on("change", (async (event) => { const enableVerticalModel = $("#enableVerticalModel").is(":checked") ? YES$1 : NO$1; await storageManager.saveSettingItem("enableVerticalModel", enableVerticalModel); this.applyImageMode(); })); $("#moreBtn").on("click", (() => { $(".simple-setting").html("").hide(); this.openSettingDialog("base-panel"); })); $("#helpBtn").on("click", (() => { layer.open({ type: 1, title: "", shadeClose: true, scrollbar: false, content: ` <style> .help-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #333; padding: 15px; max-height: 100%; overflow-y: auto; } .help-section { margin-bottom: 25px; } .help-section summary { font-size: 18px; color: #3498db; margin-bottom: 12px; cursor: pointer; } .help-content { background-color: #f9f9f9; border-radius: 5px; padding: 15px; border-left: 4px solid #3498db; } .help-content p { line-height: 1.6; margin-bottom: 10px; } .help-section img { max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } </style> <div class="help-container"> <h1 style="font-size: 22px; margin-bottom: 20px; color: #2c3e50; border-bottom: 1px solid #eee; padding-bottom: 10px;">使用说明</h1> <details class="help-section"> <summary>1. 无法查看预览视频,提示分流?</summary> <div class="help-content"> <p>JavDB限制日本IP的访问,而预览视频来自DMM,需要日本IP才能访问。</p> <p>这样会导致二者无法同时使用,需要对其一进行代理转发。</p> <p>将 cc3001.dmm.co.jp 及 dmm.co 分流到日本ip。</p> <p><a href="https://youtu.be/wQUK8z_YeU4?t=121" target="_blank">Clash Verge分流规则设置 </a> (如果你是别的代理软件,自行搜索如何分流)</p> </div> </details> <details class="help-section"> <summary>2. 如何屏蔽某一系列的番号?</summary> <div class="help-content"> <p>方法一:设置中-添加视频标题关键词,如: VENX-</p> <p>方法二:进入详情页,选中标题文字,右键可加入</p> <img src="https://i.imgur.com/lVnhK5A.png" alt="进入详情页,选中标题,进行右键"/> </div> </details> <details class="help-section"> <summary>3. 屏蔽某演员,如何只屏蔽单体影片?</summary> <div class="help-content"> <p>屏蔽演员前,先筛选分类,再点屏蔽</p> <img src="https://imgur.com/Ue7eCAi.png" alt="屏蔽演员前,先筛选分类,再点屏蔽"/> </div> </details> <details class="help-section"> <summary>4. 如何多浏览器同时登录115网盘?</summary> <div class="help-content"> <p>① 访问115登录页, 选择JHS-扫码面板, 并扫码登录</p> <img src="https://imgur.com/XbaisWD.png" alt=""/> </div> <div class="help-content"> <p>② 进入网盘后, 右下角悬浮按钮, 复制Cookie</p> <img src="https://imgur.com/GvzJ2Gy.png" alt=""/> </div> <div class="help-content"> <p>③ 打开另一个浏览器(需装JHS脚本), 进入登录页面, 选择JHS-扫码面板, 输入Cookie并回车</p> <img src="https://imgur.com/FX08qdO.png" alt=""/> </div> </details> </div> `, area: utils.getResponsiveArea(["50%", "90%"]) }); })); } async applyImageMode() { $("#verticalImgStyle").remove(); if (await storageManager.getSetting("enableVerticalModel", NO$1) === YES$1) { let imgPosition = "100% 50% !important"; window.location.href.includes("/advanced_search?type=100") && (imgPosition = "50% 50% !important"); const verticalStyle = ` .cover { min-height: 350px !important; overflow: hidden !important; padding-top: 142% !important; } .cover img { object-fit: cover !important; object-position: ${imgPosition}; } /* bus的 */ .masonry .movie-box img { min-height: 500px !important; object-fit: cover !important; object-position: top right; } `; $("<style>").attr("id", "verticalImgStyle").text(verticalStyle).appendTo("head"); } else { const horizontalStyle = "\n .cover {\n min-height:auto !important;\n padding-top: 67% !important;\n }\n .cover img {\n object-fit: contain !important;\n object-position: 50% 50% !important\n }\n \n /* bus的 */\n .masonry .movie-box img {\n min-height:auto !important;\n object-fit: contain !important;\n object-position: top;\n }\n "; $("<style>").attr("id", "verticalImgStyle").text(horizontalStyle).appendTo("head"); } isJavBus$1 && this.getBean("BusImgPlugin").logImageHeightsByRow(); } bindClick() { const self = this; $(".side-menu-item").on("click", (function() { $(".side-menu-item").removeClass("active"); $(this).addClass("active"); $(".content-panel").hide(); const panelId = $(this).data("panel"); $("#" + panelId).show(); if ("cache-panel" === panelId || "mark-delete-panel" === panelId) { $("#saveBtn").hide(); "cache-panel" === panelId ? $("#clean-all").show() : $("#clean-all").hide(); } else { $("#saveBtn").show(); $("#clean-all").hide(); } if ("mark-delete-panel" === panelId) { self.loadMarkDeleteList(); } })); $("#importBtn").on("click", ((event) => this.importData(event))); $("#exportBtn").on("click", ((event) => this.exportData(event))); $("#backupBtn").on("click", ((event) => this.backupData(event))); $("#backupListBtn").on("click", ((event) => this.backupListBtn(event))); $("#webdavBackupBtn").on("click", ((event) => this.backupDataByWebDav(event))); $("#webdavBackupListBtn").on("click", ((event) => this.backupListBtnByWebDav(event))); $("#getRefreshTokenBtn").on("click", ((event) => layer.alert("即将跳转阿里云盘, 请登录后, 点击最右侧悬浮按钮获取refresh_token", { yes: function(index, layero, that) { window.open("https://www.aliyundrive.com/drive/home"); layer.close(index); } }))); $("#saveBtn").on("click", (() => this.saveForm())); $(".clean-btn").on("click", ((event) => { const key = $(event.currentTarget).data("key"), cacheItem = this.cacheItems.find(((item) => item.key === key)); localStorage.removeItem(key); show.ok(`${cacheItem.text} 清理成功`); $("#cache-data-display").hide(); "jhs_dmm_video" === key && localStorage.removeItem("jhs_other_site_dmm"); })); $("#clean-all").on("click", (() => { this.cacheItems.forEach(((item) => localStorage.removeItem(item.key))); show.ok("全部缓存已清理"); $("#cache-data-display").hide(); localStorage.removeItem("jhs_other_site_dmm"); })); $(".view-btn").on("click", ((event) => { const key = $(event.currentTarget).data("key"), data = localStorage.getItem(key), displayDiv = $("#cache-data-display"), pre = displayDiv.find("pre"); displayDiv.show(); if (data) try { const parsedData = JSON.parse(data); pre.text(JSON.stringify(parsedData, null, 2)); } catch { pre.text(data); } else pre.text("无数据"); })); const $widthInput = $("#highlightedTagNumber"), $colorPicker = $("#highlightedTagColor"), $previewBox = $("#highlightedTagLabel"); function updateBorder() { const currentWidth = $widthInput.val(), currentColor = $colorPicker.val(); $previewBox.css("border", `${currentWidth}px solid ${currentColor}`); } $widthInput.on("input", updateBorder); $colorPicker.on("input", updateBorder); $("#markDeleteSelectAll").on("change", (function() { const checked = $(this).prop("checked"); $(".mark-delete-item-cb").prop("checked", checked); })); $("#batchDeleteBtn").on("click", (async () => { const markedList = await storageManager.forage.getItem("markedDeleteList") || []; if (markedList.length === 0) { show.error("没有标记删除的作品"); return; } const checkedItems = []; $(".mark-delete-item-cb:checked").each((function() { const index = parseInt($(this).data("index")); checkedItems.push(markedList[index]); })); if (checkedItems.length === 0) { show.error("请至少选择一个作品"); return; } utils.q(null, `确认一键删除 ${checkedItems.length} 个作品?此操作不可撤销!`, (async () => { let loadObj = loading(); try { const matchPlugin = this.getBean("WangPan115MatchPlugin"); const result = await matchPlugin.batchDeleteMarked(checkedItems, ((current, total, item, status) => { loadObj.close(); loadObj = loading(); $(`#markDeleteProgress`).text(`正在删除: ${current}/${total} - ${item.carNum} ${status}`); })); loadObj.close(); const remaining = markedList.filter((item) => !checkedItems.some((ci) => ci.carNum === item.carNum)); await storageManager.forage.setItem("markedDeleteList", remaining); show.ok(`删除完成: 成功 ${result.deletedCount} 个,失败 ${result.failedCount} 个`); this.loadMarkDeleteList(); } catch (error) { loadObj.close(); console.error("批量删除失败:", error); show.error(`批量删除失败: ${error.message || "网络错误"}`); } })); })); $(document).on("click", ".mark-delete-remove-btn", (async (event) => { event.preventDefault(); const $btn = $(event.currentTarget); const index = parseInt($btn.data("index")); const markedList = await storageManager.forage.getItem("markedDeleteList") || []; if (index >= 0 && index < markedList.length) { const item = markedList[index]; utils.q(null, `确认从标记列表中移除 "${item.title}"?`, (async () => { markedList.splice(index, 1); await storageManager.forage.setItem("markedDeleteList", markedList); show.ok(`已移除: ${item.title}`); const self2 = this; $(`.markDeleteSvg`).each((function() { const $markBtn = $(this); const $box2 = $markBtn.closest(".item"); const { carNum: carNum2 } = self2.getBoxCarInfo($box2); if (carNum2 === item.carNum) { $markBtn.removeClass("marked"); $markBtn.attr("title", "标记删除"); } })); this.loadMarkDeleteList(); })); } })); } async saveForm() { let settingObj = await storageManager.getSetting(); settingObj.videoQuality = $("#videoQuality").val(); settingObj.reviewCount = $("#reviewCount").val(); settingObj.tagPosition = $("#tagPosition").val(); settingObj.movieShowType = $("#movieShowType").val(); settingObj.waitCheckCount = $("#waitCheckCount").val(); settingObj.refresh_token = $("#refresh_token").val(); settingObj.highlightedTagNumber = $("#highlightedTagNumber").val(); settingObj.highlightedTagColor = $("#highlightedTagColor").val(); settingObj.showWaitCheckBtn = $("#showWaitCheckBtn").is(":checked") ? YES$1 : NO$1; settingObj.showWaitCheckBtn === YES$1 ? $("#waitCheckBtn").show() : $("#waitCheckBtn").hide(); settingObj.showWaitDownBtn = $("#showWaitDownBtn").is(":checked") ? YES$1 : NO$1; settingObj.showWaitDownBtn === YES$1 ? $("#waitDownBtn").show() : $("#waitDownBtn").hide(); settingObj.randomOpenWaitDown = $("#randomOpenWaitDown").is(":checked") ? YES$1 : NO$1; settingObj.checkConcurrencyCount = $("#checkConcurrencyCount").val(); settingObj.checkRequestSleep = $("#checkRequestSleep").val(); settingObj.enableCheckBlacklist = $("#enableCheckBlacklist").val(); settingObj.checkBlacklist_intervalTime = $("#checkBlacklist_intervalTime").val(); settingObj.checkBlacklist_ruleTime = $("#checkBlacklist_ruleTime").val(); settingObj.enableCheckFavoriteActress = $("#enableCheckFavoriteActress").val(); settingObj.checkFavoriteActress_IntervalTime = $("#checkFavoriteActress_IntervalTime").val(); settingObj.enableCheckNewVideo = $("#enableCheckNewVideo").val(); settingObj.checkNewVideo_intervalTime = $("#checkNewVideo_intervalTime").val(); settingObj.checkNewVideo_ruleTime = $("#checkNewVideo_ruleTime").val(); settingObj.httpTimeout = $("#httpTimeout").val(); settingObj.httpRetryCount = $("#httpRetryCount").val(); settingObj.enableClog = $("#enableClog").val(); settingObj.enableClog === YES$1 ? clog.show() : clog.hide(); settingObj.clogMsgCount = $("#clogMsgCount").val(); settingObj.webDavUrl = $("#webDavUrl").val(); settingObj.webDavUsername = $("#webDavUsername").val(); settingObj.webDavPassword = $("#webDavPassword").val(); settingObj.missAvUrl = $("#missAvUrl").val().replace(/\/$/, ""); settingObj.jableUrl = $("#jableUrl").val().replace(/\/$/, ""); settingObj.javTrailersUrl = $("#javTrailersUrl").val().replace(/\/$/, ""); settingObj.av123Url = $("#av123Url").val().replace(/\/$/, ""); settingObj.javDbUrl = $("#javDbUrl").val().replace(/\/$/, ""); settingObj.javBusUrl = $("#javBusUrl").val().replace(/\/$/, ""); settingObj.supJavUrl = $("#supJavUrl").val().replace(/\/$/, ""); settingObj.enableTitleSelectFilter = $("#enableTitleSelectFilter").is(":checked") ? YES$1 : NO$1; settingObj.enableFavoriteActresses = $("#enableFavoriteActresses").is(":checked") ? YES$1 : NO$1; settingObj.enableSaveActressCarInfo = $("#enableSaveActressCarInfo").is(":checked") ? YES$1 : NO$1; settingObj.enableScreenSvg = $("#enableScreenSvg").is(":checked") ? YES$1 : NO$1; settingObj.enableVideoSvg = $("#enableVideoSvg").is(":checked") ? YES$1 : NO$1; settingObj.enableHandleSvg = $("#enableHandleSvg").is(":checked") ? YES$1 : NO$1; settingObj.enableSiteSvg = $("#enableSiteSvg").is(":checked") ? YES$1 : NO$1; settingObj.enableCopySvg = $("#enableCopySvg").is(":checked") ? YES$1 : NO$1; $("#hotkey-panel [id]").map(((i, el) => el.id)).get().forEach(((containerId) => { settingObj[containerId] = $(`#${containerId}`).val(); })); settingObj.enableImageHotKey = $("#enableImageHotKey").is(":checked") ? YES$1 : NO$1; settingObj.enable115OfflineFormat = $("#enable115OfflineFormat").is(":checked") ? YES$1 : NO$1; settingObj.offlineFolderFormat = $("#offlineFolderFormat").val(); await storageManager.saveSetting(settingObj); let reviewKeywordList = []; $("#reviewKeywordContainer .keyword-label").toArray().forEach(((item) => { let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim(); reviewKeywordList.push(keyword); })); await storageManager.saveReviewFilterKeyword(reviewKeywordList); let filterKeywordList = []; $("#filterKeywordContainer .keyword-label").toArray().forEach(((item) => { let keyword = $(item).text().replace("×", "").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim(); filterKeywordList.push(keyword); })); await storageManager.saveTitleFilterKeyword(filterKeywordList); show.ok("保存成功"); window.refresh(); const newVideoPlugin = this.getBean("NewVideoPlugin"); newVideoPlugin && newVideoPlugin.resetBtnTip(); this.getBean("BlacklistPlugin").resetBtnTip(); this.getBean("BlacklistPlugin").reloadTable(); } async loadMarkDeleteList() { const markedList = await storageManager.forage.getItem("markedDeleteList") || []; const $list = $("#markDeleteList"); const $count = $("#markDeleteCount"); $count.text(`共 ${markedList.length} 个作品`); if (markedList.length === 0) { $list.html('<div style="text-align: center; padding: 40px; color: #999;">暂无标记删除的作品</div>'); return; } let html2 = ""; for (let i = 0; i < markedList.length; i++) { const item = markedList[i]; const truncatedTitle = item.title && item.title.length > 30 ? item.title.substring(0, 30) + "..." : item.title; const detailUrl = item.url ? item.url.startsWith("http") ? item.url : window.location.origin + item.url : "#"; html2 += `<div style="display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid #eee;"> <input type="checkbox" class="mark-delete-item-cb" data-index="${i}" checked style="margin-right: 10px; flex-shrink: 0;"> <span style="flex: 1; min-width: 0;"> <div style="font-weight: bold; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${item.carNum}</div> <a href="${detailUrl}" target="_blank" title="${item.title}" style="font-size: 12px; color: #2980b9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; text-decoration: none;">${truncatedTitle}</a> <div style="font-size: 11px; color: #999;">${item.publishTime || "-"} | 评分: ${item.score || "-"}</div> </span> <a class="mark-delete-remove-btn" data-index="${i}" style="color: #e74c3c; cursor: pointer; margin-left: 8px; flex-shrink: 0; font-size: 12px;" title="从列表移除">✕</a> </div>`; } html2 += `<div id="markDeleteProgress" style="text-align: center; padding: 8px; color: #e74c3c; font-size: 13px; font-weight: bold;"></div>`; $list.html(html2); $("#markDeleteSelectAll").prop("checked", true); } addLabelTag(containerId, keyword) { const $tagBox = $(`${containerId} .tag-box`); let $label, color2 = "#333"; if (/^[a-z]{2,}-/i.test(keyword) && isJavDb$1) { color2 = "#3477ad"; $label = $(` <a class="keyword-label" data-keyword="${keyword}" style="background-color: #cbd5e1; color: ${color2}" href="/video_codes/${keyword.replace("-", "")}" target="_blank"> ${keyword} <span class="keyword-remove">×</span> </a> `); } else $label = $(` <div class="keyword-label" data-keyword="${keyword}" style="background-color: #cbd5e1; color: ${color2}"> ${keyword} <span class="keyword-remove">×</span> </div> `); $label.find(".keyword-remove").click(((event) => { event.stopPropagation(); event.preventDefault(); const $targetEl = $(event.currentTarget); const dataKeyword = $targetEl.closest(".keyword-label").attr("data-keyword").split(" ")[0]; utils.q(event, `是否移除屏蔽词 ${dataKeyword}?`, (async () => { $targetEl.parent().remove(); })); })); $tagBox.append($label); } addKeyword(event, containerId) { let $keywordInput = $(`${containerId} .keyword-input`); const keyword = $keywordInput.val().trim(); if (keyword) { this.addLabelTag(containerId, keyword); $keywordInput.val(""); } } importData() { try { const input2 = document.createElement("input"); input2.type = "file"; input2.accept = ".json"; input2.onchange = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const content = event.target.result.toString(), updateJsonData = JSON.parse(content); layer.confirm("确定是否要覆盖导入?", { icon: 3, title: "确认覆盖", btn: ["确定", "取消"] }, (async function(index) { await storageManager.importData(updateJsonData); show.ok("数据导入成功"); layer.close(index); location.reload(); })); } catch (err) { console.error(err); show.error("导入失败:文件内容不是有效的JSON格式 " + err); } }; reader.onerror = () => { show.error("读取文件时出错"); }; reader.readAsText(file); }; document.body.appendChild(input2); input2.click(); setTimeout((() => document.body.removeChild(input2)), 1e3); } catch (err) { console.error(err); show.error("导入数据时出错: " + err.message); } } async backupData(event) { let loadObj = loading(); try { const refresh_token = await storageManager.getSetting("refresh_token"); if (!refresh_token) { show.error("请填写refresh_token并保存后, 再试此功能"); return; } show.info("正在整理数据..."); let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData()); uploadContent = simpleEncrypt(uploadContent); const aliyunApi = new AliyunApi(refresh_token); await aliyunApi.backup(this.folderName, fileName, uploadContent); show.ok("备份完成"); } catch (e) { console.error(e); show.error(e.toString()); } finally { loadObj.close(); } } async backupListBtn(event) { const refresh_token = await storageManager.getSetting("refresh_token"); if (!refresh_token) { show.error("请填写refresh_token并保存后, 再试此功能"); return; } let loadObj = loading(); try { const aliyunApi = new AliyunApi(refresh_token), fileList = await aliyunApi.getBackupList(this.folderName); this.openFileListDialog(fileList, aliyunApi, "阿里云盘"); } catch (e) { console.error(e); show.error(`发生错误: ${e ? e.message : e}`); } finally { loadObj.close(); } } async backupDataByWebDav(event) { const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl; if (!webDavUrl) { show.error("请填写webDav服务地址并保存后, 再试此功能"); return; } const webDavUsername = settingObj.webDavUsername; if (!webDavUsername) { show.error("请填写webDav用户名并保存后, 再试此功能"); return; } const webDavPassword = settingObj.webDavPassword; if (!webDavPassword) { show.error("请填写webDav密码并保存后, 再试此功能"); return; } let fileName = utils.getNowStr("_", "_") + ".json", uploadContent = JSON.stringify(await storageManager.exportData()); uploadContent = simpleEncrypt(uploadContent); let loadObj = loading(); try { const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword); await webDavApi.backup(this.folderName, fileName, uploadContent); show.ok("备份完成"); } catch (e) { console.error(e); show.error(e.toString()); } finally { loadObj.close(); } } async backupListBtnByWebDav(event) { const settingObj = await storageManager.getSetting(), webDavUrl = settingObj.webDavUrl; if (!webDavUrl) { show.error("请填写webDav服务地址并保存后, 再试此功能"); return; } const webDavUsername = settingObj.webDavUsername; if (!webDavUsername) { show.error("请填写webDav用户名并保存后, 再试此功能"); return; } const webDavPassword = settingObj.webDavPassword; if (!webDavPassword) { show.error("请填写webDav密码并保存后, 再试此功能"); return; } let loadObj = loading(); try { const webDavApi = new WebDavApi(webDavUrl, webDavUsername, webDavPassword), fileList = await webDavApi.getBackupList(this.folderName); this.openFileListDialog(fileList, webDavApi, "WebDav"); } catch (e) { console.error(e); show.error(`发生错误: ${e ? e.message : e}`); } finally { loadObj.close(); } } openFileListDialog(fileList, api, apiType) { layer.open({ type: 1, title: apiType + "备份文件", content: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="table-container" style="height: calc(100%);"></div>\n </div>\n ', area: ["800px", "70%"], anim: -1, success: (layero) => { const tableObj = new Tabulator("#table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: true, data: fileList, responsiveLayout: "collapse", responsiveLayoutCollapse: true, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, columns: [{ title: "文件名", field: "name", width: 200, headerSort: false, responsive: 0 }, { title: "文件大小", field: "size", responsive: 1, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const units = ["B", "KB", "MB", "GB", "TB", "PB"]; let unitIndex = 0, adjustedSize = cell.getData().size; for (; adjustedSize >= 1024 && unitIndex < units.length - 1; ) { adjustedSize /= 1024; unitIndex++; } return `${adjustedSize % 1 == 0 ? adjustedSize.toFixed(0) : adjustedSize.toFixed(2)} ${units[unitIndex]}`; } }, { title: "备份日期", field: "createTime", responsive: 2, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); return `${utils.getNowStr("-", ":", item.createTime)}`; } }, { title: "操作", minWidth: 250, responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { const deleteButton = cell.getElement().querySelector(".a-danger"), downButton = cell.getElement().querySelector(".a-primary"), importButton = cell.getElement().querySelector(".a-success"); deleteButton && deleteButton.addEventListener("click", ((e) => { utils.q(e, `是否删除 ${item.name} ?`, (async (index) => { let loadObj = loading(); try { await api.deleteFile(item.fileId); let newFileList = await api.getBackupList(this.folderName); tableObj.replaceData(newFileList); "阿里云盘" === apiType ? utils.alert(e, "已移至回收站, 请到阿里云盘回收站二次删除") : utils.alert(e, "删除成功"); } catch (e2) { console.error(e2); show.error(`发生错误: ${e2 ? e2.message : e2}`); } finally { loadObj.close(); } })); })); downButton && downButton.addEventListener("click", (async (e) => { let loadObj = loading(); try { if ("阿里云盘" === apiType) { show.info("获取下载地址..."); const url = await api.getDownloadUrl(item.fileId); show.info("获取文件内容..."); let content = simpleDecrypt(await gmHttp.downloadFileInChunks(url, { Referer: "https://www.aliyundrive.com/" })); utils.download(content, item.name); } else { const content = simpleDecrypt(await api.getFileContent(item.fileId)); utils.download(content, item.name); } } catch (e2) { clog.error(e2); show.error("下载失败: " + e2); } finally { loadObj.close(); } })); importButton && importButton.addEventListener("click", (async (e) => { layer.confirm(`是否将该云备份数据 ${item.name} 导入?`, { icon: 3, title: "提示", btn: ["确定", "取消"] }, (async (index) => { layer.close(index); let loadObj = loading(); try { let respData; if ("阿里云盘" === apiType) { show.info("获取下载地址..."); const downUrl = await api.getDownloadUrl(item.fileId); show.info("获取文件内容..."); respData = await gmHttp.downloadFileInChunks(downUrl, { Referer: "https://www.aliyundrive.com/" }); } else respData = await api.getFileContent(item.fileId); show.info("解密文件内容..."); const content = simpleDecrypt(respData); show.info("解密完成, 开始导入..."); const updateJsonData = JSON.parse(content); await storageManager.importData(updateJsonData); show.ok("导入成功!"); window.location.reload(); } catch (err) { console.error(err); show.error(err); } finally { loadObj.close(); } })); })); })); return '\n <a class="a-danger">删除</a>\n <a class="a-primary">下载</a>\n <a class="a-success">导入</a>\n '; } }], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); } }); } async exportData(event) { try { const backupData = JSON.stringify(await storageManager.exportData()), fileName = `${utils.getNowStr("_", "_")}.json`; utils.download(backupData, fileName); show.ok("数据导出成功"); } catch (err) { console.error(err); show.error("导出数据时出错: " + err.message); } } } class TaskPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "singleTaskKey", "checkNewActressActorFilterCar"); __publicField2(this, "taskConfig", null); __publicField2(this, "storageQueue", new AsyncQueue()); __publicField2(this, "lastCheckFavoriteActressTimeKey", "jhs_time_checkFavoriteActress"); __publicField2(this, "lastCheckBlacklistTimeKey", "jhs_time_checkBlacklist"); __publicField2(this, "lastCheckNewVideoTimeKey", "jhs_time_checkNewVideo"); } getName() { return "TaskPlugin"; } async limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, taskFn) { this.showIsRun(); const activePromises = [], totalItems = taskParamList.length; let processedCount = 0; for (const taskParam of taskParamList) { const p = taskFn(taskParam).finally((() => { activePromises.splice(activePromises.indexOf(p), 1); })); activePromises.push(p); processedCount++; if (activePromises.length >= checkConcurrencyCount) { const remainingToStart = totalItems - processedCount; clog.debug(`剩余任务数: <span style="color: #f40">${remainingToStart}</span>`); await Promise.race(activePromises); await utils.sleep(checkRequestSleep); } } await Promise.all(activePromises); } isUnnecessaryCheck(lastCheckTimeStr, checkIntervalTime) { if (!checkIntervalTime) throw new Error("未传入checkIntervalTime"); checkIntervalTime = parseInt(checkIntervalTime); return utils.getHourDifference(new Date(lastCheckTimeStr), /* @__PURE__ */ new Date()) < checkIntervalTime; } handle() { this.doTask().then(); } showIsRun() { show.info("正在执行检测任务中, 请勿关闭当前窗口", { duration: 3e3 }); } async doTask() { if (window.isListPage) { await this.loadConfig(); this.javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl(); return navigator.locks.request(this.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (lock) { if (window.isListPage) { this.taskConfig.enableCheckBlacklist === YES$1 ? await this.checkBlacklist() : clog.warn("自动检测屏蔽黑名单-禁用"); if (!isJavBus$1) { if (this.taskConfig.enableCheckFavoriteActress === YES$1) { const lastCheckFavoriteActressTimeStr = localStorage.getItem(this.lastCheckFavoriteActressTimeKey), checkFavoriteActress_IntervalTime = this.taskConfig.checkFavoriteActress_IntervalTime, isUnCheck = lastCheckFavoriteActressTimeStr && this.isUnnecessaryCheck(lastCheckFavoriteActressTimeStr, checkFavoriteActress_IntervalTime), isLogin = $('a[href*="/users/profile"]').length > 0; isUnCheck && clog.debug(`检测同步演员, 上次检测时间: ${lastCheckFavoriteActressTimeStr} 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时 未到时间`); !isUnCheck && isLogin && await this.checkFavoriteActress(); } else clog.warn("自动同步已收藏的演员-禁用"); this.taskConfig.enableCheckNewVideo === YES$1 ? await this.checkNewVideo() : clog.warn("自动检测已收藏演员的最新作品-禁用"); } } } else clog.debug("争夺任务锁失败, 跳过执行"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })).finally((() => { setTimeout((() => { this.doTask(); }), 3e5); })); } } async loadConfig() { const settingObj = await storageManager.getSetting(); this.taskConfig = { checkConcurrencyCount: settingObj.checkConcurrencyCount ? Number(settingObj.checkConcurrencyCount) : 2, checkRequestSleep: settingObj.checkRequestSleep ? Number(settingObj.checkRequestSleep) : 100, enableCheckBlacklist: settingObj.enableCheckBlacklist || YES$1, checkBlacklist_intervalTime: settingObj.checkBlacklist_intervalTime ? Number(settingObj.checkBlacklist_intervalTime) : 12, checkBlacklist_ruleTime: settingObj.checkBlacklist_ruleTime ? Number(settingObj.checkBlacklist_ruleTime) : 8760, enableCheckFavoriteActress: settingObj.enableCheckFavoriteActress || YES$1, checkFavoriteActress_IntervalTime: settingObj.checkFavoriteActress_IntervalTime ? Number(settingObj.checkFavoriteActress_IntervalTime) : 24, enableCheckNewVideo: settingObj.enableCheckNewVideo || YES$1, checkNewVideo_intervalTime: settingObj.checkNewVideo_intervalTime ? Number(settingObj.checkNewVideo_intervalTime) : 12, checkNewVideo_ruleTime: settingObj.checkNewVideo_ruleTime ? Number(settingObj.checkNewVideo_ruleTime) : 8760 }; } async checkBlacklist(isManual) { let blacklist = await storageManager.getBlacklist(); if (0 === blacklist.length) return; blacklist = blacklist.sort(((a, b) => a.createTime < b.createTime ? 1 : a.createTime > b.createTime ? -1 : 0)); const checkConcurrencyCount = this.taskConfig.checkConcurrencyCount, checkRequestSleep = this.taskConfig.checkRequestSleep, checkBlacklist_intervalTime = this.taskConfig.checkBlacklist_intervalTime, checkBlacklist_ruleTime = this.taskConfig.checkBlacklist_ruleTime, lastCheckBlacklistTimeStr = localStorage.getItem(this.lastCheckBlacklistTimeKey); if (!isManual && lastCheckBlacklistTimeStr && this.isUnnecessaryCheck(lastCheckBlacklistTimeStr, checkBlacklist_intervalTime)) { clog.debug(`检测黑名单, 上次检测时间: ${lastCheckBlacklistTimeStr} 检测间隔时间: ${checkBlacklist_intervalTime}小时 未到时间`); return; } const taskParamList = [], msgList = []; for (const blacklistItem of blacklist) { let name2 = blacklistItem.name, checkTime = blacklistItem.checkTime, lastPublishTime = blacklistItem.lastPublishTime, url = blacklistItem.url; if (new URL(window.location.href).hostname === new URL(url).hostname) { if (isManual || !checkTime || !this.isUnnecessaryCheck(checkTime, checkBlacklist_intervalTime)) if (!lastPublishTime || 0 === checkBlacklist_ruleTime || this.isUnnecessaryCheck(lastPublishTime, checkBlacklist_ruleTime)) taskParamList.push(blacklistItem); else { let msg = `检测黑名单: ${name2} ${lastPublishTime} 停更超过${checkBlacklist_ruleTime / 24 / 365}年,跳过检测`; msgList.push(msg); $("#checkBlacklistMsg").text(msg); } } else clog.log("黑名单地址非同域名,跳过", url); } if (0 === taskParamList.length) return; msgList.forEach(((msg) => { clog.log(msg); })); clog.log(`<span style='color: #f40'>检测屏蔽黑名单, 总任务数: ${taskParamList.length}, 并发限制:${checkConcurrencyCount}, 请求间隔时间:${checkRequestSleep}ms</span>`); const blacklistPlugin = this.getBean("BlacklistPlugin"); await this.limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, (async (task) => { let { starId, name: name2, url } = task; try { clog.log("正在检屏黑名单演员:", name2, url); $("#checkBlacklistMsg").text(`正在检屏黑名单演员: ${name2} ${url}`); const html2 = await gmHttp.get(url), $dom = utils.htmlTo$dom(html2); this.storageQueue.addTask((async () => { let { lastPublishTime } = await blacklistPlugin.parseAndSaveFilterInfo($dom, name2, starId); await storageManager.updateBlacklistItem({ starId, name: name2, checkTime: utils.getNowStr(), lastPublishTime }); })); } catch (e) { $("#checkBlacklistMsg").text(`检测屏蔽演员信息, 发生错误: ${url}`); clog.error("检测屏蔽演员信息, 发生错误:", url, e); show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right"); } })); await this.storageQueue.waitAllFinished(); const updateLastCheckBlacklistTimeStr = utils.getNowStr(); localStorage.setItem(this.lastCheckBlacklistTimeKey, updateLastCheckBlacklistTimeStr); clog.log('<span style="color: #f40">-------- END 检测屏蔽黑名单 END --------</span>'); $("#checkBlacklistMsg").text("检测屏蔽黑名单, 结束"); this.getBean("BlacklistPlugin").resetBtnTip().then(); } async checkFavoriteActress() { const checkUrl = `${this.javDbUrl}/users/collection_actors`, actorInfoList = []; await this.scrapeActorInfo(checkUrl, actorInfoList); clog.log("所有演员信息已收集, 总计数量:", actorInfoList.length); $("#checkNewVideoMsg").text("同步完成"); if (actorInfoList.length > 0) { await storageManager.addFavoriteActressList(actorInfoList); localStorage.setItem(this.lastCheckFavoriteActressTimeKey, utils.getNowStr()); this.getBean("NewVideoPlugin").resetBtnTip().then(); } } async scrapeActorInfo(currentUrl, actorInfoList) { clog.log(`正在抓取页面: ${currentUrl}`); $("#checkNewVideoMsg").text(`正在解析已收藏的演员: ${currentUrl}`); try { const html2 = await gmHttp.get(currentUrl), $dom = utils.htmlTo$dom(html2); $dom.find("#actors .actor-box a").each(((i, element) => { const $a = $(element), title = $a.attr("title"), href = $a.attr("href"); if (title && href) { const allName = title.split(",").map(((name3) => name3.trim())).filter(((name3) => name3.length > 0)), name2 = allName[0] || "", segments = new URL(href, this.javDbUrl).pathname.split("/").filter(((s) => s.length > 0)); let starId = ""; segments.length > 0 && (starId = segments[segments.length - 1]); let actressType = "censored"; const avatar = $a.find("img").attr("src"), $infoSpan = $a.find(".info"); $infoSpan.length && $infoSpan.text().trim().includes("無碼") && (actressType = "uncensored"); actorInfoList.push({ starId, name: name2, allName, avatar, actressType, lastCheckTime: null, lastUpdateTime: null }); } })); const nextRelativeUrl = $dom.find(".pagination-next").attr("href"); if (nextRelativeUrl) { const nextAbsoluteUrl = new URL(nextRelativeUrl, this.javDbUrl).href; await this.scrapeActorInfo(nextAbsoluteUrl, actorInfoList); } } catch (error) { clog.error(`抓取 ${currentUrl} 时发生错误:`, error); } } async checkNewVideo(isManual) { const dataActressInfoList = await storageManager.getFavoriteActressList(), actressInfoList = utils.genericSort(dataActressInfoList, [{ key: (item) => { var _a2; return (null == (_a2 = item.newVideoList) ? void 0 : _a2.length) ?? 0; }, order: "desc" }, { key: "lastPublishTime", order: "desc" }]), checkConcurrencyCount = this.taskConfig.checkConcurrencyCount, checkRequestSleep = this.taskConfig.checkRequestSleep, checkNewVideo_intervalTime = this.taskConfig.checkNewVideo_intervalTime, checkNewVideo_ruleTime = this.taskConfig.checkNewVideo_ruleTime, lastCheckNewVideoTimeStr = localStorage.getItem(this.lastCheckNewVideoTimeKey); if (!isManual && lastCheckNewVideoTimeStr && this.isUnnecessaryCheck(lastCheckNewVideoTimeStr, checkNewVideo_intervalTime)) { clog.debug(`检测新作品, 上次检测时间: ${lastCheckNewVideoTimeStr} 检测间隔时间: ${checkNewVideo_intervalTime}小时 未到时间`); return; } const taskParamList = [], msgList = []; for (const actress of actressInfoList) { const { lastCheckTime, lastPublishTime, name: name2 } = actress; !isManual && lastCheckTime && this.isUnnecessaryCheck(lastCheckTime, checkNewVideo_intervalTime) || (!lastPublishTime || 0 === checkNewVideo_ruleTime || this.isUnnecessaryCheck(lastPublishTime, checkNewVideo_ruleTime) ? taskParamList.push(actress) : msgList.push(`检测新作品: ${name2} ${lastPublishTime} 停更超过${checkNewVideo_ruleTime / 24 / 365}年,跳过检测`)); } if (0 === taskParamList.length) return; msgList.forEach(((msg) => { clog.log(msg); })); clog.log(`<span style='color: #f40'>检测最新作品, 总任务数: ${taskParamList.length}, 并发限制:${checkConcurrencyCount}, 请求间隔时间:${checkRequestSleep}ms</span>`); const filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorActressCarList = await storageManager.getBlacklistCarList(), filterActorActressCarNumList = new Set(filterActorActressCarList.map(((car) => car.carNum))); await this.limitConcurrency(taskParamList, checkConcurrencyCount, checkRequestSleep, (async (task) => { const { lastCheckTime, name: name2, starId } = task; let url = `${this.javDbUrl}/actors/${starId}?t=d`; try { clog.log("正在检测最新作品, 演员:", name2, url); $("#checkNewVideoMsg").text(`正在检测最新作品, 演员: ${name2}`); const html2 = await gmHttp.get(url), $dom = utils.htmlTo$dom(html2); this.storageQueue.addTask((async () => { await this.parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList); })); } catch (e) { clog.error("检测屏蔽演员信息, 发生错误:", url, e); console.error("检测屏蔽演员信息, 发生错误:", url, e); show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right"); } })); await this.storageQueue.waitAllFinished(); localStorage.setItem(this.lastCheckNewVideoTimeKey, utils.getNowStr()); clog.log('<span style="color: #f40">检测最新作品---结束</span>'); $("#checkNewVideoMsg").text("检测完毕"); const newVideoPlugin = this.getBean("NewVideoPlugin"); newVideoPlugin.loadData(); newVideoPlugin.resetBtnTip().then(); } async parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList) { let movieList, nextPageLink, tempIsJavBus = false, selectorType = "javdb"; if ($dom.text().includes("javbus")) { tempIsJavBus = true; selectorType = "javbus"; } tempIsJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); movieList = $dom.find(this.getSelector(selectorType).requestDomItemSelector); nextPageLink = $dom.find(this.getSelector(selectorType).nextPageSelector).attr("href"); if (nextPageLink && 0 === movieList.length) { clog.error("新作品检测-解析列表失败"); show.error("新作品检测-解析列表失败"); throw new Error("新作品检测-解析列表失败"); } let carNumList = [], lastPublishTime = null; for (const element of movieList) { const $item = $(element), { carNum: carNum2, url, title, publishTime } = this.getBoxCarInfo($item); if (!carNum2) continue; if (!filterKeywordList.find(((kw) => title.includes(kw) || carNum2.startsWith(kw))) && !filterActorActressCarNumList.has(carNum2)) { lastPublishTime || (lastPublishTime = publishTime); carNumList.push(carNum2); } } const carList = await storageManager.getCarList(), storageCarNumList = new Set(carList.map(((car) => car.carNum))), nonExistingCarNumList = carNumList.filter(((carNum2) => !storageCarNumList.has(carNum2))); nonExistingCarNumList.length > 0 && clog.log(`<span style='color: #f40'>检测出新作品, ${name2}, 共${nonExistingCarNumList.length}部</span>`); await storageManager.updateFavoriteActress({ starId, lastCheckTime: utils.getNowStr(), newVideoList: nonExistingCarNumList, lastPublishTime }); } async checkOneNewVideo(actress) { const filterKeywordList = await storageManager.getTitleFilterKeyword(), filterActorActressCarList = await storageManager.getBlacklistCarList(), filterActorActressCarNumList = new Set(filterActorActressCarList.map(((car) => car.carNum))), { lastCheckTime, name: name2, starId } = actress; let url = `${this.javDbUrl}/actors/${starId}?t=d`; const $checkNewVideoMsg = $("#checkNewVideoMsg"); try { clog.log("正在检测最新作品, 演员:", name2, url); $checkNewVideoMsg.text(`正在检测最新作品, 演员: ${name2}`); const html2 = await gmHttp.get(url), $dom = utils.htmlTo$dom(html2); await this.parsePage($dom, starId, name2, filterKeywordList, filterActorActressCarNumList); clog.log('<span style="color: #f40">检测最新作品---结束</span>'); $checkNewVideoMsg.text("检测完毕"); this.getBean("NewVideoPlugin").loadData(); } catch (e) { clog.error("检测屏蔽演员信息, 发生错误:", url, e); show.error("检测屏蔽演员信息, 发生错误:" + e, "bottom", "right"); $checkNewVideoMsg.text(`检测屏蔽演员信息, 发生错误: ${url}`); } } } const CDN_SOURCES = [{ name: "jsDelivr (全球CDN)", json: "https://cdn.jsdelivr.net/gh/gfriends/gfriends/Filetree.json", base: "https://cdn.jsdelivr.net/gh/gfriends/gfriends/Content/" }, { name: "GitHub Raw (备用)", json: "https://raw.githubusercontent.com/gfriends/gfriends/master/Filetree.json", base: "https://raw.githubusercontent.com/gfriends/gfriends/master/Content/" }]; let currentCdnIndex$1 = parseInt(localStorage.getItem("jhs_img_cdn_index") || "0", 10); (currentCdnIndex$1 >= CDN_SOURCES.length || currentCdnIndex$1 < 0) && (currentCdnIndex$1 = 0); window.G_FRIENDS_JSON_URL = CDN_SOURCES[currentCdnIndex$1].json; window.window.CDN_BASE_URL = CDN_SOURCES[currentCdnIndex$1].base; const STORE_NAME = "filetreeStore", dbHelper = { db: null, async open() { return this.db ? this.db : new Promise(((resolve, reject) => { const request = indexedDB.open("GfriendsAvatarDB", 1); request.onupgradeneeded = (event) => { this.db = event.target.result; this.db.objectStoreNames.contains(STORE_NAME) || this.db.createObjectStore(STORE_NAME); }; request.onsuccess = (event) => { this.db = event.target.result; resolve(this.db); }; request.onerror = (event) => { console.error("IndexedDB open error:", event.target.errorCode); reject(new Error("Failed to open IndexedDB")); }; })); }, async get(key) { await this.open(); return new Promise(((resolve) => { const request = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).get(key); request.onsuccess = () => resolve(request.result); request.onerror = () => resolve(null); })); }, async set(key, value) { await this.open(); return new Promise(((resolve, reject) => { const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put(value, key); request.onsuccess = () => resolve(); request.onerror = (event) => { console.error("IndexedDB set error:", event.target.errorCode); reject(new Error("Failed to write to IndexedDB")); }; })); } }; window.window.G_FRIENDS_DATA_CACHE = null; window.window.G_FRIENDS_AVATAR_MAP = null; class NewVideoPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "currentPage", 1); __publicField2(this, "pageSize", 30); } getName() { return "NewVideoPlugin"; } async initCss() { return "\n <style>\n #actress-card-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(243px, 1fr)); /* 响应式3-5列 */\n gap: 20px;\n padding-bottom: 20px;\n padding-right: 10px;\n background: #f9f9f9;\n border-radius: 5px;\n overflow-y: auto;\n }\n .actress-card {\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);\n padding: 15px;\n text-align: center;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n position: relative;\n overflow: hidden;\n }\n .actress-card:hover {\n box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);\n }\n .actress-card-name {\n font-size: 1.2em;\n font-weight: bold;\n color: #007bff;\n margin-top: 10px;\n }\n .actress-card-allname {\n font-size: 0.9em;\n color: #999;\n margin-top: 5px;\n height: 30px; /* 保证高度一致性 */\n overflow: hidden;\n white-space: nowrap; /* 防止文字换行 */\n text-overflow: ellipsis; /* 当文本溢出时,显示省略号 */\n }\n .actress-card-avatar {\n width: 100px;\n height: 100px;\n border-radius: 50%;\n object-fit: contain;\n margin: 0 auto;\n border: 4px solid #f0f0f0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n \n .card-tag {\n position: absolute;\n top: 15px; /* 调整标签距离顶部的距离 */\n right: -50px; /* 调整标签距离右侧的距离,负值让它移到外面一点 */\n \n width: 150px; /* 标签的宽度,影响斜角长度 */\n padding: 5px 0; /* 上下内边距 */\n text-align: center;\n \n background-color: #ff4757; /* 标签颜色 */\n color: white; /* 文字颜色 */\n font-size: 14px;\n font-weight: bold;\n z-index: 10; /* 确保标签在其他内容之上 */\n \n /* 3. 核心:旋转标签,使其倾斜 */\n transform: rotate(45deg); /* 45度斜角 */\n \n /* 可选:添加一些阴影或边框效果 */\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);\n }\n \n .card-new-count-tag {\n position: absolute;\n top: 5px;\n text-align: center;\n font-size: 14px;\n font-weight: bold;\n z-index: 10;\n }\n \n #actress-pagination {\n padding-top: 10px;\n text-align: center;\n border-top: 1px solid #ddd;\n }\n @media (max-width: 600px) {\n .page-number-btn {\n display: none !important;\n }\n }\n \n \n .card-btn {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n justify-content: center;\n align-items: center;\n text-decoration: none;\n border: none;\n cursor: pointer;\n background: linear-gradient(145deg, #e0e0e0 0%, #f7f7f7 100%);\n box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.08),\n -8px -8px 16px rgba(255, 255, 255, 1.0);\n transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n }\n \n .card-btn svg,\n .card-btn svg path {\n transition: fill 0.3s ease;\n }\n \n .card-btn:hover {\n box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1),\n inset -5px -5px 10px rgba(255, 255, 255, 0.9);\n transform: scale(0.97);\n background: #e0e0e0;\n }\n \n .btn-check-actress svg path {\n fill: #4CAF50;\n }\n .btn-check-actress:hover svg path {\n fill: #388E3C;\n }\n \n .btn-edit-actress svg path {\n fill: #FFC107;\n }\n .btn-edit-actress:hover svg path {\n fill: #FFB300;\n }\n \n .btn-delete-actress svg path {\n fill: #F44336;\n }\n .btn-delete-actress:hover svg path {\n fill: #D32F2F;\n }\n </style>\n "; } async showNewVideoCount() { const totalNewVideoCount = (await storageManager.getFavoriteActressList()).reduce(((accumulator, actress) => { var _a2; return accumulator + ((null == (_a2 = actress.newVideoList) ? void 0 : _a2.length) ?? 0); }), 0); $("#newVideoCount").text(`${totalNewVideoCount}`); } async resetBtnTip() { const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime; $("#checkFavoriteActress").attr("data-tip", `上次自动同步时间: ${lastCheckTimeStr}; 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时`); $("#checkNewVideo").attr("data-tip", `上次检测时间: ${lastCheckNewVideoTimeStr}; 检测间隔时间: ${checkNewVideo_intervalTime}小时`); } async openDialog() { const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime; let html2 = ` <div class="newVideoToolBox" style="display: flex; flex-direction: column; height: 100%; overflow: hidden; padding:10px"> <div style="margin-bottom: 15px;display: flex; justify-content: space-between;"> <div> <a class="a-danger" id="checkFavoriteActress" data-tip="上次自动同步时间: ${lastCheckTimeStr}; 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时">${this.actressSvg} 手动同步演员</a> <a class="a-warning" id="checkNewVideo" data-tip="上次检测时间: ${lastCheckNewVideoTimeStr}; 检测间隔时间: ${checkNewVideo_intervalTime}小时">${this.newSvg} 手动检测最新作品</a> <a class="a-info" id="toSetting">${this.settingSvg} 配置</a> <span id="checkNewVideoMsg"></span> </div> <div style="display: flex; align-items: flex-start;"> <select id="paramActressType" style="text-align: center; height: 100%; min-width: 150px; border: 1px solid #ddd; margin-right: 10px"> <option value="all" selected>所有</option> <option value="uncensored">无码</option> <option value="censored">有码</option> <option value="">未知</option> </select> <a class="a-normal" id="reLoad">${this.refreshSvg} 刷新</a> </div> </div> <div id="actress-card-container" class="jhs-scrollbar"></div> <div id="actress-pagination"></div> </div> `; layer.open({ type: 1, title: '<span style="padding: 0 10px;" data-tip="数据来源: 女优页面首页,含磁链分类">新作品检测 ❓</span>', content: html2, scrollbar: false, area: utils.getResponsiveArea(["80%", "90%"]), anim: -1, success: async (layero, index) => { this.loadData(); this.bindClick(); utils.setupEscClose(index); } }); } bindClick() { const taskPlugin = this.getBean("TaskPlugin"); $("#reLoad").on("click", ((event) => { this.loadData(); $("#checkNewVideoMsg").text(""); })); $("#toSetting").on("click", ((event) => { this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => { $("#setting-checkFavoriteActress").css({ border: "1px solid #f40" }); $("#setting-checkNewVideo").css({ border: "1px solid #f40" }); })); })); $("#checkFavoriteActress").on("click", ((event) => { utils.q({ clientX: event.clientX, clientY: event.clientY + 20 }, "是否手动同步演员?", (() => { navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (!lock) { show.error("当前有定时任务在后台执行中, 无法发起手动任务"); return; } if ($('a[href*="/users/profile"]').length > 0) { await taskPlugin.checkFavoriteActress(); this.loadData(); } else show.error("未登录JavDb, 同步失败"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); })); $("#checkNewVideo").on("click", ((event) => { utils.q({ clientX: event.clientX, clientY: event.clientY + 20 }, "是否手动检测最新作品?", (() => { navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { lock ? await taskPlugin.checkNewVideo(true) : show.error("当前有定时任务在后台执行中, 无法发起手动任务"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); })); $("#paramActressType").on("change", ((event) => { this.loadData(); })); } loadData() { this.currentPage = 1; this.renderActressCards().then(); } async renderActressCards() { const $actressCardContainer = $("#actress-card-container"); if (!$actressCardContainer.length) return; let favoriteActressesList = await storageManager.getFavoriteActressList(); const paramActressType = $("#paramActressType").val(); "all" !== paramActressType && (favoriteActressesList = favoriteActressesList.filter(((actress) => actress.actressType === paramActressType))); const fullActressList = utils.genericSort(favoriteActressesList, [{ key: (item) => { var _a2; return (null == (_a2 = item.newVideoList) ? void 0 : _a2.length) ?? 0; }, order: "desc" }, { key: "lastPublishTime", order: "desc" }]), totalCount = fullActressList.length, totalPages = Math.ceil(totalCount / this.pageSize), startIndex = (this.currentPage - 1) * this.pageSize, endIndex = startIndex + this.pageSize, pagedData = fullActressList.slice(startIndex, endIndex), javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl(), taskPlugin = this.getBean("TaskPlugin"), checkNewVideo_ruleTime = await storageManager.getSetting("checkNewVideo_ruleTime") || 8760, cardsHtml = pagedData.map(((data) => { var _a2, _b, _c; const allNamesStr = Array.isArray(data.allName) ? data.allName.join(",") : ""; Array.isArray(data.newVideoList) && data.newVideoList.join(","); const detailLink = `${javDbUrl}/actors/${data.starId}?t=d`; let isUnCheck = false; data.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(data.lastPublishTime, checkNewVideo_ruleTime)); let actressTypeText = "未知", actressTypeBgc = "#9E9E9E"; if ("uncensored" === data.actressType) { actressTypeText = "无码"; actressTypeBgc = "#4CAF50"; } else if ("censored" === data.actressType) { actressTypeText = "有码"; actressTypeBgc = "#FF9800"; } let cardUnnecessaryBtnCss = ""; isUnCheck && (cardUnnecessaryBtnCss = "background: linear-gradient(145deg, #e0e0e0 0%, #cabdbd 100%);box-shadow: none"); return ` <div class="actress-card" data-starId="${data.starId}" style="${isUnCheck ? "background: #d4cece;" : ""} min-height: 370px;"> <a href="${detailLink}" target="_blank" style="text-decoration: none; color: inherit; display: block; flex-grow: 1;"> <img src="${data.avatar || "https://c0.jdbstatic.com/images/actor_unknow.jpg"}" alt="${allNamesStr}" class="actress-card-avatar"> </a> <div> <a href="${detailLink}" target="_blank" style="text-decoration: none; color: inherit; display: block; flex-grow: 1;"> <div class="actress-card-name">${data.name}</div> </a> <div class="actress-card-allname" title="${allNamesStr}">${allNamesStr}</div> </div> <div style="font-size: 0.8em; margin-top: 5px;"> <span>上次检测: ${data.lastCheckTime || ""}</span> </div> <div style="font-size: 0.8em; margin-top: 5px;"> <span>最后发行作品: ${data.lastPublishTime || ""}</span> </div> <div style="font-size: 0.7em; color: #cc4444; margin-top: 5px; min-height: 18px"> <span>${isUnCheck ? "停更" + checkNewVideo_ruleTime / 24 / 365 + "年以上, 下轮任务不再进行检测" : ""}</span> </div> <div style="font-size: 0.8em; margin-top: 5px; color: #3765c5; min-height: 10px"> <span>${data.remark || ""}</span> </div> <div style="margin-top: 10px;display: flex; justify-content:center; gap: 10px;"> <a title="编辑" class="card-btn btn-edit-actress" style="${cardUnnecessaryBtnCss}" data-starId="${data.starId}">${this.editSvg}</a> <a title="取消收藏" class="card-btn btn-delete-actress" style="${cardUnnecessaryBtnCss}" data-starId="${data.starId}">${this.deleteSvg}</a> <a title="重新检测该演员" class="card-btn btn-check-actress" style="${cardUnnecessaryBtnCss}" data-starId="${data.starId}">${this.checkSvg}</a> </div> <div class="card-tag" style="background-color:${actressTypeBgc}">${actressTypeText}</div> <div class="card-new-count-tag" data-tip="最新作品数量: ${(null == (_a2 = data.newVideoList) ? void 0 : _a2.length) || 0}" style="${(null == (_b = data.newVideoList) ? void 0 : _b.length) > 0 ? "color: #4CAF50;" : ""}"> 🔔 ${(null == (_c = data.newVideoList) ? void 0 : _c.length) || 0} </div> </div> `; })).join(""); $actressCardContainer.html(cardsHtml); $(".btn-delete-actress").off("click").on("click", ((e) => { e.preventDefault(); const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find(((item) => item.starId === starId)); utils.q(e, `是否取消收藏 ${actress.name}?`, (async () => { let deleteActressUrl = `${await this.getBean("OtherSitePlugin").getJavDbUrl()}/actors/${starId}/uncollect`; const csrfToken = document.querySelector("meta[name=csrf-token]").content, res = await gmHttp.post(deleteActressUrl, null, { "x-csrf-token": csrfToken }); if (res.includes("removeClass")) { await storageManager.removeFavoriteActress(starId); this.loadData(); } else { show.error("移除失败"); clog.error("移除失败,返回值:", res); } })); })); $(".btn-edit-actress").off("click").on("click", ((e) => { e.preventDefault(); const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find(((item) => item.starId === starId)); actress ? this.editActress(actress) : show.error(`未找到 starId 为 ${starId} 的女优记录。`); })); $(".btn-check-actress").off("click").on("click", ((e) => { e.preventDefault(); navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (!lock) { show.error("当前有定时任务在后台执行中, 无法发起手动任务"); return; } const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find(((item) => item.starId === starId)); await taskPlugin.checkOneNewVideo(actress); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); this.renderPagination(totalCount, totalPages); show.ok("加载完成"); } async editActress(actress) { const initialName = actress.name, initialAvatar = actress.avatar, initialRemark = actress.remark || "", initialAllName = Array.isArray(actress.allName) ? actress.allName.join(",") : "", initialNewVideoList = Array.isArray(actress.newVideoList) ? actress.newVideoList.join(",") : "", starId = actress.starId, textareaStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", initActressType = actress.actressType || "", editFormHtml = ` <div style="padding: 20px;"> <div style="margin-bottom: 15px; text-align: center;"> <img id="edit-avatar-preview" src="${initialAvatar}" alt="Avatar Preview" style="width: 100px; height: 100px; border-radius: 50%; object-fit: cover; margin-bottom: 10px; border: 2px solid #ddd;"> <div style="text-align: left"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">头像链接:</label> <input type="text" id="edit-actress-avatar" value="${initialAvatar}" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"> <div style="display: flex; gap: 5px; margin-top: 5px;"> <button type="button" id="search-avatar-btn" style="flex-grow: 1; padding: 8px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;"> 搜索头像 </button> <button type="button" id="select-cdn-btn" style="width: 100px; padding: 8px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;"> 选择 CDN 源 </button> </div> </div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">主名称:</label> <input type="text" id="edit-actress-name" value="${initialName}" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">所有别名(用逗号隔开):</label> <textarea id="edit-actress-allname" style="${textareaStyle}">${initialAllName}</textarea> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">演员类别:</label> <select id="actressType" style="width: 100%; padding: 10px; border: 1px solid #ddd;"> <option value="" ${"" === initActressType ? "selected" : ""}>未知</option> <option value="censored" ${"censored" === initActressType ? "selected" : ""}>有码</option> <option value="uncensored" ${"uncensored" === initActressType ? "selected" : ""}>无码</option> </select> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">最新作品(用逗号隔开):</label> <textarea id="edit-actress-newvideolist" style="${textareaStyle}">${initialNewVideoList}</textarea> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">备注:</label> <textarea id="edit-remark" style="${textareaStyle}">${initialRemark}</textarea> </div> </div> `; layer.open({ type: 1, title: `编辑女优: ${initialName} (${starId})`, area: ["500px", "750px"], content: editFormHtml, btn: ["保存", "取消"], success: (layero, index) => { const autoResizeTextarea = ($textarea) => { $textarea.css("height", "auto"); $textarea.css("height", $textarea[0].scrollHeight + 15 + "px"); }; $("#edit-actress-avatar").on("input", (function() { const newUrl = $(this).val(); $("#edit-avatar-preview").attr("src", newUrl); })); const $allNameTextarea = $("#edit-actress-allname"); $allNameTextarea.on("input", (function() { autoResizeTextarea($(this)); })); autoResizeTextarea($allNameTextarea); const $videoListTextarea = $("#edit-actress-newvideolist"); $videoListTextarea.on("input", (function() { autoResizeTextarea($(this)); })); autoResizeTextarea($videoListTextarea); $("#search-avatar-btn").on("click", (async () => { await this.searchAvatar(); })); $("#select-cdn-btn").on("click", (async () => { await (async function() { const initialIndex = currentCdnIndex, radioOptions = CDN_SOURCES.map(((source, index2) => ` <div style="margin-bottom: 10px;"> <input type="radio" id="cdn-${index2}" name="cdn-source" value="${index2}" ${index2 === initialIndex ? "checked" : ""} style="margin-right: 10px;"> <label for="cdn-${index2}">${source.name} ${source.json.includes("jsdelivr") ? "(推荐)" : ""}</label> </div> `)).join(""), cdnSelectHtml = ` <div style="padding: 20px;"> <p style="margin-bottom: 15px; font-weight: bold; color: #333;">请选择头像数据源 (当前: ${CDN_SOURCES[initialIndex].name}):</p> ${radioOptions} <p style="margin-top: 20px; color: #555; font-size: 12px;">切换源会清除本地缓存的数据,并在下次搜索时重新加载。</p> </div> `; layer.open({ type: 1, title: "选择 CDN 源", area: ["400px", "auto"], content: cdnSelectHtml, btn: ["确定", "取消"], success: (layero2, index2) => { utils.setupEscClose(index2); }, yes: async (index2) => { const newIndexStr = $('input[name="cdn-source"]:checked').val(), newIndex = parseInt(newIndexStr, 10); if (newIndex !== currentCdnIndex) { currentCdnIndex = newIndex; localStorage.setItem("jhs_img_cdn_index", newIndex.toString()); window.G_FRIENDS_JSON_URL = CDN_SOURCES[newIndex].json; window.CDN_BASE_URL = CDN_SOURCES[newIndex].base; window.G_FRIENDS_DATA_CACHE = null; window.G_FRIENDS_AVATAR_MAP = null; try { await dbHelper.set("filetree_data", null); } catch (e) { clog.error("清除 IndexedDB 缓存失败:", e); } show.ok(`CDN 源已切换为: ${CDN_SOURCES[newIndex].name}`); layer.close(index2); } else layer.close(index2); } }); })(); })); utils.setupEscClose(index); }, yes: async (index) => { const newAvatar = $("#edit-actress-avatar").val().trim(), newName = $("#edit-actress-name").val().trim(), newAllNameStr = $("#edit-actress-allname").val().trim(), newVideoListStr = $("#edit-actress-newvideolist").val().trim(), newRemark = $("#edit-remark").val().trim(), newActressType = $("#actressType").val(); if (!newName) { show.error("主名称不能为空"); return false; } const newAllName = newAllNameStr.split(/[\uff0c,]/).map(((n) => n.trim())).filter(((n) => n.length > 0)), newVideoList = newVideoListStr.split(/[\uff0c,]/).map(((n) => n.trim())).filter(((n) => n.length > 0)); actress.avatar = newAvatar; actress.name = newName; actress.allName = newAllName; actress.newVideoList = newVideoList; actress.actressType = newActressType; actress.remark = newRemark; if (await storageManager.updateFavoriteActress(actress)) show.error("修改失败"); else { show.ok(`女优 ${newName} 信息已更新`); await this.renderActressCards(); layer.close(index); } } }); } renderPagination(totalCount, totalPages) { const currentPage = this.currentPage; let paginationHtml = ""; const $actressPagination = $("#actress-pagination"); if (0 === totalPages) { paginationHtml = '<span style="color: #666;">共 0 条记录</span>'; $actressPagination.html(paginationHtml); return; } currentPage > 1 && totalPages > 5 && (paginationHtml += '<button class="pagination-btn" data-page="1" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">首页</button>'); currentPage > 1 && (paginationHtml += `<button class="pagination-btn" data-page="${currentPage - 1}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">上一页</button>`); let startPage = Math.max(1, currentPage - Math.floor(2.5)), endPage = Math.min(totalPages, startPage + 5 - 1); endPage - startPage < 4 && (startPage = Math.max(1, endPage - 5 + 1)); for (let i = startPage; i <= endPage; i++) { paginationHtml += `<button class="pagination-btn page-number-btn ${i === currentPage ? "active" : ""}" data-page="${i}" style="padding: 8px 12px; margin: 0 3px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; ${i === currentPage ? "background: #007bff; color: white; border-color: #007bff;" : ""}">${i}</button>`; } currentPage < totalPages && (paginationHtml += `<button class="pagination-btn" data-page="${currentPage + 1}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">下一页</button>`); currentPage < totalPages && totalPages > 5 && (paginationHtml += `<button class="pagination-btn" data-page="${totalPages}" style="padding: 8px 12px; margin: 0 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">尾页</button>`); paginationHtml += `<span style="margin-left: 20px; color: #666;">共 ${totalCount} 条记录 (第 ${currentPage}/${totalPages} 页)</span>`; $actressPagination.html(paginationHtml); $(".pagination-btn").off("click").on("click", ((e) => { if ($(e.currentTarget).is("[disabled]")) return; const newPage = parseInt($(e.currentTarget).data("page")); if (newPage >= 1 && newPage <= totalPages && newPage !== this.currentPage) { this.currentPage = newPage; this.renderActressCards(); } })); } async searchAvatar() { const $mainNameInput = $("#edit-actress-name"), $aliasInput = $("#edit-actress-allname"), currentName = $mainNameInput.val().trim(), searchNames = $aliasInput.val().trim().split(/[\uff0c,]/).map(((n) => n.trim())).filter(((n) => n.length > 0)); currentName && searchNames.unshift(currentName); if (0 === searchNames.length) { show.error("请先填写女优主名称或别名进行搜索。"); return; } const loadObj = loading("正在搜索头像..."); let imageLinks = []; try { imageLinks = await searchActorAvatars(searchNames); } catch (e) { show.error(`头像数据加载或搜索失败: ${e.message || e}`); return; } finally { loadObj.close(); } if (0 === imageLinks.length) { show.error(`未找到与 '${searchNames.join("、")}' 相关的头像。请检查名称。`); return; } const imageItems = imageLinks.map(((url, index) => ` <div id="wrapper-${index}" class="gfriends-image-item-wrapper"> <img alt="" src="${url}" data-url="${url}" class="gfriends-selectable-img" data-wrapper-id="wrapper-${index}" > <div class="gfriends-size-tag" data-size-for="wrapper-${index}">...</div> </div> `)).join(""), imageListHtml = ` <style> /* 保持上一个回答的美化样式 */ #gfriends-image-list-container { padding: 15px; height: 100%; box-sizing: border-box; background-color: #f8f9fa; } #gfriends-prompt { color: #555; font-weight: 500; border-bottom: 1px solid #eee; padding-bottom: 10px; } #gfriends-image-list { display: flex; flex-wrap: wrap; gap: 15px; justify-content: center; } .gfriends-image-item-wrapper { width: 160px; height: 225px; /* 增加高度以容纳尺寸标签 */ overflow: hidden; border-radius: 6px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: pointer; position: relative; padding-bottom: 25px; /* 为尺寸标签留出空间 */ } .gfriends-selectable-img { width: 100%; height: 200px; /* 固定图片高度 */ object-fit: cover; border: 3px solid transparent; border-radius: 6px; transition: border 0.2s ease; } .gfriends-image-item-wrapper:hover { transform: translateY(-4px) scale(1.02); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } .gfriends-selectable-img.is-selected { border-color: #ff6347; box-shadow: 0 0 0 3px #ff6347; } /* 新增:尺寸标签样式 */ .gfriends-size-tag { position: absolute; bottom: 0; /* 定位到图片容器底部 */ left: 0; right: 0; height: 25px; line-height: 25px; text-align: center; background-color: rgba(0, 0, 0, 0.7); /* 半透明背景 */ color: #fff; font-size: 11px; font-weight: bold; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; user-select: none; } </style> <div id="gfriends-image-list-container"> <p id="gfriends-prompt" style="text-align: center; font-size: 15px; margin-bottom: 15px;"> 点击图片即可选择(初始共 ${imageLinks.length} 张) </p> <div style="overflow-y: auto; height: calc(100% - 40px);"> <div id="gfriends-image-list"> ${imageItems} </div> </div> </div> `; let errorCount = 0; layer.open({ type: 1, title: `选择女优头像 (${imageLinks.length} 张)`, area: utils.getResponsiveArea(["900px", "85%"]), content: imageListHtml, btn: ["关闭"], success: (selectionLayero, selectionIndex) => { const $container = $(selectionLayero), $images = $container.find(".gfriends-selectable-img"), $prompt = $container.find("#gfriends-prompt"); $images.each((function() { const $img = $(this), wrapperId = $img.data("wrapper-id"), $wrapper = $container.find(`#${wrapperId}`), $sizeTag = $container.find(`.gfriends-size-tag[data-size-for="${wrapperId}"]`); $img.on("load", (function() { const width = this.naturalWidth, height = this.naturalHeight; $sizeTag.text(`${width} x ${height}`); })); $img.on("error", (function() { $wrapper.remove(); errorCount++; const validCount = imageLinks.length - errorCount; $prompt.text(`点击图片即可选择(已移除 ${errorCount} 张错误图片,剩余 ${validCount} 张)`); if (0 === validCount) { show.error("所有搜索到的头像链接均已失效,无法选择。"); layer.close(selectionIndex); } })); this.complete && (this.naturalWidth > 0 ? $img.trigger("load") : $img.trigger("error")); })); $images.on("click", (function() { const $clickedImg = $(this), selectedUrl = $clickedImg.data("url"); $("#edit-actress-avatar").val(selectedUrl); $("#edit-avatar-preview").attr("src", selectedUrl); $images.removeClass("is-selected"); $clickedImg.addClass("is-selected"); setTimeout((() => { layer.close(selectionIndex); }), 150); })); utils.setupEscClose(selectionIndex); } }); } } class HistoryPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "tableObj", null); } getName() { return "HistoryPlugin"; } async initCss() { return "\n <style>\n /* 下拉菜单容器(相对定位) */\n .sub-btns {\n position: relative;\n display: inline-block;\n }\n \n /* 下拉菜单内容(默认隐藏) */\n .sub-btns-menu {\n display: none;\n position: absolute;\n right: 80px;\n top:-10px;\n background: white;\n padding:10px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 100;\n border-radius: 4px;\n overflow: hidden;\n }\n \n \n /* 点击后显示菜单(JS 控制) */\n .sub-btns-menu.show {\n display: flex !important;\n flex-direction: column;\n }\n \n .table-link-param {\n cursor: pointer;\n }\n </style\n "; } handleResize() { if ($(".navbar-search").is(":hidden")) { $(".historyBtnBox").show(); $(".miniHistoryBtnBox").hide(); } else { $(".historyBtnBox").hide(); $(".miniHistoryBtnBox").show(); } } handle() { if (isJavDb$1) { $(".navbar-end").prepend('<div class="navbar-item has-sub-btns is-hoverable historyBtnBox">\n <a id="historyBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-right:15px !important;">\n 鉴定记录\n </a>\n </div>'); $(".navbar-search").css("margin-left", "0").before('\n <div class="navbar-item miniHistoryBtnBox">\n <a id="miniHistoryBtn" class="navbar-link nav-btn" style="color: #aade66 !important;padding-left:0 !important;padding-right:0 !important;">\n 鉴定记录\n </a>\n </div>\n '); this.handleResize(); $(window).resize((() => { this.handleResize(); })); $("#historyBtn,#miniHistoryBtn").on("click", ((event) => this.openHistory())); } isJavBus$1 && utils.loopDetector((() => $("#setting-btn").length), (() => { $("#top-right-box").append('\n <a id="historyBtn" class="menu-btn main-tab-btn" style="background-color:#b68625 !important;">\n 鉴定记录\n </a>\n '); $("#historyBtn,#miniHistoryBtn").on("click", ((event) => this.openHistory())); }), 1, 1e4, false); this.bindClick(); } openHistory() { layer.open({ type: 1, title: "鉴定记录", content: '\n <div style="padding: 10px 20px; height: 100%;overflow:hidden;"> \n <div id="filterBox" style="display: flex;gap: 5px;">\n <select id="dataType" style="text-align: center;min-width: 150px;">\n <option value="all" selected>所有</option>\n <option value="filter">🚫 已屏蔽</option>\n <option value="favorite">⭐ 已收藏</option>\n <option value="hasDown">📥️ 已下载</option>\n <option value="hasWatch">🔍 已观看</option>\n </select>\n <input id="searchCarNum" type="text" placeholder="搜索番号|演员" style="padding: 4px 5px;">\n <a id="clearSearchbtn" class="a-info" style="margin-left: 0">重置</a>\n </div>\n <div id="allSelectBox" style="margin-top: 8px;display: none">\n <a class="menu-btn multiple-history-deleteBtn" style="background-color:#8c8080; color:white; margin-bottom: 5px;"> <span>✂️ 移除</span> </a>\n <a class="menu-btn multiple-history-hasWatchBtn" style="background-color:#d7a80c;margin-bottom: 5px">🔍 已观看</a>\n <a class="menu-btn multiple-history-hasDownBtn" style="background-color:#7bc73b;margin-bottom: 5px">📥️ 已下载</a>\n <a class="menu-btn multiple-history-favoriteBtn" style="background-color:#25b1dc;margin-bottom: 5px">⭐ 收藏</a>\n <a class="menu-btn multiple-history-filterBtn" style="background-color:#de3333;margin-bottom: 5px">🚫 屏蔽</a>\n </div>\n <div id="table-container" style="margin-top:20px !important; height: calc(100% - 50px); overflow-x:hidden;"></div>\n </div>\n ', scrollbar: false, shadeClose: true, area: utils.getResponsiveArea(["70%", "90%"]), anim: -1, success: async (layero) => { await this.loadTableData(); $(".layui-layer-content").on("click", "#clearSearchbtn", (async (event) => { $("#searchCarNum").val(""); $("#dataType").val("all"); await this.reloadTable(); $("#allSelectBox").hide(); })).on("focusout keydown", "#searchCarNum", (async (event) => { if ("focusout" === event.type || "Enter" === event.key) { "Enter" === event.key && event.preventDefault(); if ("keydown" === event.type && "Enter" !== event.key) return; await this.reloadTable(); } })).on("click", ".table-link-param", (async (event) => { let $targetEl = $(event.currentTarget); $("#searchCarNum").val($targetEl.text()); await this.reloadTable(); })).on("change", "#dataType", (async () => { await this.reloadTable(); })); }, end: () => { if (this.tableObj) { this.tableObj.destroy(); this.tableObj = null; } window.refresh(); } }); } async reloadTable() { this.tableObj.deselectRow(); this.tableObj.setPage(1); } bindClick() { document.addEventListener("click", (function(e) { if (e.target.closest(".sub-btns-toggle")) { const menu = e.target.closest(".sub-btns").querySelector(".sub-btns-menu"); document.querySelectorAll(".sub-btns-menu.show").forEach(((openMenu) => { openMenu !== menu && openMenu.classList.remove("show"); })); menu.classList.toggle("show"); } else document.querySelectorAll(".sub-btns-menu.show").forEach(((menu) => { menu.classList.remove("show"); })); })); $(document).on("click", ".history-deleteBtn, .history-filterBtn, .history-favoriteBtn, .history-hasDownBtn, .history-hasWatchBtn, .history-detailBtn", ((event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget), $menuBox = $btn.closest(".action-btns"), carNum2 = $menuBox.attr("data-car-num"), aHref = $menuBox.attr("data-href"), handleAction = async (status) => { await storageManager.saveCar({ carNum: carNum2, url: aHref, names: null, actionType: status }); window.refresh(); await this.reloadTable(); }; $btn.hasClass("history-filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER$1))) : $btn.hasClass("history-favoriteBtn") ? handleAction(Status_FAVORITE$1).then() : $btn.hasClass("history-hasDownBtn") ? handleAction(Status_HAS_DOWN$1).then() : $btn.hasClass("history-hasWatchBtn") ? handleAction(Status_HAS_WATCH$1).then() : $btn.hasClass("history-deleteBtn") ? this.handleDelete(event, carNum2) : $btn.hasClass("history-detailBtn") && this.handleClickDetail(event, { carNum: carNum2, url: aHref }).then(); })); $(document).on("click", ".multiple-history-deleteBtn, .multiple-history-filterBtn, .multiple-history-favoriteBtn, .multiple-history-hasDownBtn, .multiple-history-hasWatchBtn", ((event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); let selectedRows = this.tableObj.getSelectedData(), handleText = "", handleStatus = ""; if ($btn.hasClass("multiple-history-filterBtn")) { handleText = "屏蔽"; handleStatus = Status_FILTER$1; } else if ($btn.hasClass("multiple-history-favoriteBtn")) { handleText = "收藏"; handleStatus = Status_FAVORITE$1; } else if ($btn.hasClass("multiple-history-hasDownBtn")) { handleText = "已下载"; handleStatus = Status_HAS_DOWN$1; } else if ($btn.hasClass("multiple-history-hasWatchBtn")) { handleText = "已观看"; handleStatus = Status_HAS_WATCH$1; } else if ($btn.hasClass("multiple-history-deleteBtn")) { handleText = "移除"; handleStatus = "delete"; } utils.q(event, `当前已勾选${selectedRows.length}条数据, 是否全标记为 ${handleText}?`, (async () => { let loadObj = loading(); try { if ("delete" === handleStatus) { const deleteCarNumList = selectedRows.map(((row) => row.carNum)), removedCount = await storageManager.batchRemoveCars(deleteCarNumList); removedCount > 0 ? show.ok(`已成功删除 ${removedCount} 个番号`) : false === removedCount && show.error("提供的番号中没有一个存在于列表中。"); } else { const carRecords = JSON.parse(JSON.stringify(selectedRows)); carRecords.forEach(((record) => { record.actionType = handleStatus; })); await storageManager.saveCarList(carRecords); show.ok("操作成功"); } this.tableObj.deselectRow(); this.reloadTable().then(); } catch (e) { console.error(e); } finally { loadObj.close(); } })); })); } async getDataList(page, size, sort) { let dataList = await storageManager.getCarList(); this.allCount = dataList.length; this.filterCount = 0; this.favoriteCount = 0; this.hasDownCount = 0; this.hasWatchCount = 0; dataList.forEach(((item) => { switch (item.status) { case Status_FILTER$1: this.filterCount++; break; case Status_FAVORITE$1: this.favoriteCount++; break; case Status_HAS_DOWN$1: this.hasDownCount++; break; case Status_HAS_WATCH$1: this.hasWatchCount++; } })); $('#dataType option[value="all"]').text(`所有 (${this.allCount})`); $('#dataType option[value="filter"]').text(`🚫 已屏蔽 (${this.filterCount})`); $('#dataType option[value="favorite"]').text(`⭐ 已收藏 (${this.favoriteCount})`); $('#dataType option[value="hasDown"]').text(`📥️ 已下载 (${this.hasDownCount})`); $('#dataType option[value="hasWatch"]').text(`🔍 已观看 (${this.hasWatchCount})`); const dataType = $("#dataType").val(); let filterDataList = "all" === dataType ? dataList : dataList.filter(((item) => item.status === dataType)); const searchCarNum = $("#searchCarNum").val().trim(); if (searchCarNum) { let tempCarNum = searchCarNum.toLowerCase().replace("-c", "").replace("-uc", "").replace("-4k", ""); filterDataList = filterDataList.filter(((item) => { const result1 = item.carNum.toLowerCase().includes(tempCarNum); const result2 = (item.names ? item.names : "").toLowerCase().includes(tempCarNum); return result1 || result2; })); } if (sort && sort.length > 0) { const sorter = sort[0], field = sorter.field, dir = sorter.dir; filterDataList.sort(((a, b) => { const valA = a[field], valB = b[field], isValANullish = null == valA || "" === valA, isValBNullish = null == valB || "" === valB; return isValANullish && !isValBNullish ? 1 : !isValANullish && isValBNullish ? -1 : isValANullish && isValBNullish ? 0 : valA < valB ? "asc" === dir ? -1 : 1 : valA > valB ? "asc" === dir ? 1 : -1 : 0; })); } const totalCount = filterDataList.length, maxPage = Math.ceil(totalCount / size), startIndex = (page - 1) * size, endIndex = startIndex + size; filterDataList = filterDataList.slice(startIndex, endIndex); return { maxPage, dataList: filterDataList, totalCount }; } async loadTableData() { this.tableObj = new Tabulator("#table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: true, pagination: true, paginationMode: "remote", sortMode: "remote", ajaxURL: "queryRealm", dataLoader: false, ajaxRequestFunc: async (url, ajaxConfig, params) => { const page = (null == params ? void 0 : params.page) || 1, size = (null == params ? void 0 : params.size) || 50, sort = (null == params ? void 0 : params.sort) || []; return await this.getDataList(page, size, sort); }, dataReceiveParams: { last_page: "maxPage", last_row: "totalCount", data: "dataList" }, paginationSize: 50, paginationSizeSelector: [50, 100, 1e3, 99999], paginationCounter: (pageSize, currentRow, currentPage, totalRows, totalPages) => `共 ${totalRows} 条记录`, responsiveLayout: "collapse", responsiveLayoutCollapse: true, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, selectableRowsPersistence: false, index: "carNum", columns: [{ formatter: "rowSelection", titleFormatter: "rowSelection", hozAlign: "center", headerSort: false, responsive: 0, width: 40, titleFormatterParams: { rowRange: "active" }, cellClick: (e, cell) => { cell.getRow().toggleSelect(); } }, { title: "番号", field: "carNum", width: 120, sorter: "string", responsive: 0, formatter: (cell, formatterParams, onRendered) => { const carNumString = cell.getData().carNum, hyphenIndex = carNumString.indexOf("-"); if (-1 === hyphenIndex) return carNumString; return `<a class="table-link-param">${carNumString.substring(0, hyphenIndex + 1)}</a>${carNumString.substring(hyphenIndex + 1)}`; } }, { title: "演员", field: "names", minWidth: 200, sorter: "string", responsive: 5, headerSort: true, formatter: (cell, formatterParams, onRendered) => (cell.getData().names || "").split(" ").filter(((name2) => "" !== name2.trim())).map(((name2) => `<a class="table-link-param">${name2}</a>`)).join(" ") }, { title: "创建时间", field: "createDate", width: 170, sorter: "string", responsive: 4 }, { title: "修改时间", field: "updateDate", width: 170, sorter: "string", responsive: 4 }, { title: "发行时间", field: "publishTime", width: 170, sorter: "string", responsive: 4 }, { title: "来源", field: "url", width: 80, sorter: "string", responsive: 5, hozAlign: "left", formatter: (cell, formatterParams, onRendered) => { let url = cell.getData().url; return url ? url.includes("javdb") ? '<span style="color:#d34f9e">Javdb</span>' : url.includes("javbus") ? '<span style="color:#eaa813">JavBus</span>' : url.includes("123av") ? '<span style="color:#eaa813">123Av</span>' : `<span style="color:#050505">${url}</span>` : ""; } }, { title: "状态", field: "status", width: 100, sorter: "string", responsive: 1, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const statusValue = cell.getData().status; let color2 = "", text = ""; switch (statusValue) { case "filter": color2 = "#de3333"; text = "🚫 屏蔽"; break; case "favorite": color2 = "#25b1dc"; text = "⭐ 收藏"; break; case "hasDown": color2 = "#7bc73b"; text = "📥️ 已下载"; break; case "hasWatch": color2 = "#d7a80c"; text = "🔍 已观看"; break; default: text = statusValue; } return `<span style="color:${color2}">${text}</span>`; } }, { title: "备注", field: "remark", width: 100, sorter: "string", responsive: 6 }, { title: "操作", sorter: "string", minWidth: 150, cssClass: "action-cell-dropdown", responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { var _a2; null == (_a2 = cell.getElement().querySelector(".history-editBtn")) || _a2.addEventListener("click", ((e) => { this.editRecord(item); })); })); return ` <div class="action-btns" style="display: flex; gap: 5px;justify-content:center" data-car-num="${item.carNum}" data-href="${item.url ? item.url : ""}"> <div class="sub-btns"> <a class="menu-btn sub-btns-toggle" style="background-color:#c59d36; color:white; margin-bottom: 5px;"> <span>✏️ 变更</span> </a> <div class="sub-btns-menu"> <a class="menu-btn history-editBtn" style="background-color:#007bff; color:white; margin-bottom: 5px;"> <span>✏️ 编辑</span> </a> <a class="menu-btn history-deleteBtn" style="background-color:#8c8080; color:white; margin-bottom: 5px;"> <span>✂️ 移除</span> </a> <a class="menu-btn history-hasWatchBtn" style="background-color:#d7a80c;margin-bottom: 5px">🔍 已观看</a> <a class="menu-btn history-hasDownBtn" style="background-color:#7bc73b;margin-bottom: 5px">📥️ 已下载</a> <a class="menu-btn history-favoriteBtn" style="background-color:#25b1dc;margin-bottom: 5px">⭐ 收藏</a> <a class="menu-btn history-filterBtn" style="background-color:#de3333;margin-bottom: 5px">🚫 屏蔽</a> </div> </div> <a class="menu-btn history-detailBtn" style="background-color:#3397de; color:white; margin-bottom: 5px;"> <span>📄 详情页</span> </a> </div> `; } }], initialSort: [{ column: "updateDate", dir: "desc" }], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); this.tableObj.on("rowSelectionChanged", ((data, rows2, selected, deselected) => { const $allSelectBox = $("#allSelectBox"), $filterBox = $("#filterBox"); if (data && data.length > 0) { $filterBox.hide(); $allSelectBox.show(); } else { $filterBox.show(); $allSelectBox.hide(); } })); this.tableObj.on("rowDblClick", (function(e, row) { row.toggleSelect(); })); this.tableObj.on("tableBuilt", (async () => { })); } handleDelete(event, carNum2) { utils.q(event, `是否移除${carNum2}?`, (async () => { await storageManager.removeCar(carNum2); this.reloadTable().then(); })); } async handleClickDetail(event, data) { if (isJavDb$1) if (data.carNum.includes("FC2-")) { const movieId = this.parseMovieId(data.url); this.getBean("Fc2Plugin").openFc2Dialog(movieId, data.carNum, data.url); } else { if (!data.url) { window.open("/search?q=" + data.carNum, "_blank"); return; } utils.openPage(data.url, data.carNum, false, event); } if (isJavBus$1) { let url = data.url; if (url.includes("javdb")) if (data.carNum.includes("FC2-")) { const movieId = this.parseMovieId(url); await this.getBean("Fc2Plugin").openFc2Page(movieId, data.carNum, url); } else window.open(url, "_blank"); else utils.openPage(data.url, data.carNum, false, event); } } async editRecord(item) { const initialCarNum = item.carNum, initialNames = item.names || "", initialUrl = item.url || "", initialStatus = item.status, initialRemark = item.remark || "", textareaStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", inputStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;", editFormHtml = ` <div style="padding: 20px;"> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">番号:</label> <input type="text" id="edit-carNum" value="${initialCarNum}" style="${inputStyle} background-color: #f0f0f0;" readonly> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">演员 (用空格隔开):</label> <textarea id="edit-names" style="${textareaStyle}">${initialNames}</textarea> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">状态:</label> <select id="edit-status" style="width: 100%; padding: 10px; border: 1px solid #ddd;"> <option value="" ${"" === initialStatus ? "selected" : ""}>-- 请选择 --</option> ${[{ value: Status_FILTER$1, text: "🚫 屏蔽" }, { value: Status_FAVORITE$1, text: "⭐ 收藏" }, { value: Status_HAS_DOWN$1, text: "📥️ 已下载" }, { value: Status_HAS_WATCH$1, text: "🔍 已观看" }].map(((option) => ` <option value="${option.value}" ${initialStatus === option.value ? "selected" : ""}>${option.text}</option> `)).join("")} </select> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">链接:</label> <input type="text" id="edit-url" value="${initialUrl}" style="${inputStyle}"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">备注:</label> <textarea id="edit-remark" style="${textareaStyle}">${initialRemark}</textarea> </div> </div> `; layer.open({ type: 1, title: `编辑记录: ${initialCarNum}`, area: ["500px", "650px"], content: editFormHtml, btn: ["保存", "取消"], success: (layero, index) => { const autoResizeTextarea = ($textarea) => { $textarea.css("height", "auto"); $textarea.css("height", $textarea[0].scrollHeight + 15 + "px"); }, $namesTextarea = $("#edit-names"); $namesTextarea.on("input", (function() { autoResizeTextarea($(this)); })); autoResizeTextarea($namesTextarea); const $remarkTextarea = $("#edit-remark"); $remarkTextarea.on("input", (function() { autoResizeTextarea($(this)); })); autoResizeTextarea($remarkTextarea); }, yes: async (index) => { const newNames = $("#edit-names").val().trim(), newStatus = $("#edit-status").val(), newUrl = $("#edit-url").val().trim(), newRemark = $("#edit-remark").val().trim(), updatedRecord = { ...item, names: newNames, actionType: newStatus, url: newUrl, remark: newRemark }; await storageManager.updateCarInfo(updatedRecord); this.tableObj.setData(); layer.close(index); } }); } } const _unsafeWindow$1 = /* @__PURE__ */ (() => typeof unsafeWindow !== "undefined" ? unsafeWindow : window)(); class DetailPagePlugin extends BasePlugin { getName() { return "DetailPagePlugin"; } constructor() { super(); } handle() { if (window.isDetailPage) { $(".video-meta-panel a").each((function() { const href = $(this).attr("href"); href && (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("/")) && $(this).attr("target", "_blank"); })); this.handleFancyBox(); } } handleFancyBox() { document.addEventListener("click", (function(e) { if (e.target.closest(".fancybox-button--thumbs")) { const isVisible = !$(".fancybox-thumbs").is(":hidden"); localStorage.setItem("jhs_fancyboxThumbs", isVisible.toString()); _unsafeWindow$1.$.fancybox.defaults.thumbs.autoStart = isVisible; } })); if (void 0 !== _unsafeWindow$1.$.fancybox) { const savedState = localStorage.getItem("jhs_fancyboxThumbs"); _unsafeWindow$1.$.fancybox.defaults.thumbs.autoStart = "true" === savedState; } } } class PreviewVideoPlugin extends BasePlugin { getName() { return "PreviewVideoPlugin"; } async initCss() { return "\n .video-control-btn {\n min-width:120px;\n padding: 7px 12px;\n font-size: 12px;\n background: rgba(0,0,0,0.7);\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n .video-control-btn.active {\n background-color: #1890ff;\n color: white;\n font-weight: bold;\n border: 2px solid #096dd9;\n }\n "; } async handle() { if (!window.isDetailPage) return; let settingObj = await storageManager.getSetting(); this.filterHotKey = settingObj.filterHotKey; this.favoriteHotKey = settingObj.favoriteHotKey; this.speedVideoHotKey = settingObj.speedVideoHotKey; let $preview = $(".preview-video-container"); $preview.on("click", ((event) => { utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => { this.handleVideo().then(); })); })); await storageManager.getSetting("enableLoadPreviewVideo", YES$1) !== YES$1 || currentHref$1.includes("autoPlay=1") || this.initDmm().then(); let href = window.location.href; (href.includes("gallery-1") || href.includes("gallery-2")) && utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => { $(".fancybox-content #preview-video").length > 0 && this.handleVideo().then(); })); href.includes("autoPlay=1") && $preview.length > 0 && $preview[0].click(); } async initDmm() { try { const dmmVideoMap = await getDmmVideo(this.getPageInfo().carNum, false); if (!dmmVideoMap) return; let settingDefaultVideoQuality = await storageManager.getSetting("videoQuality"); clog.debug("解析其它画质预览视频", "设置-期望画质", settingDefaultVideoQuality); const defaultVideoUrl = dmmVideoMap[selectDefaultQuality(Object.keys(dmmVideoMap), settingDefaultVideoQuality)]; clog.log("切换其它画质预览视频: ", defaultVideoUrl); const $previewVideoEL = $("#preview-video"), videoEl = $previewVideoEL.length ? $previewVideoEL[0] : null, isVideoElementHidden = !videoEl || utils.isHidden($previewVideoEL); if ($previewVideoEL.length) { if (videoEl) { const currentTime = videoEl.currentTime; $previewVideoEL.attr("src", defaultVideoUrl); if (!isVideoElementHidden) { clog.debug("播放器已手动打开, 变更进度条"); videoEl.currentTime = currentTime; videoEl.play(); } } } else { clog.debug("JavDB没有视频播放元素, 开始创建..."); const videoCoverSrc = $(".column-video-cover img").attr("src"); $(".preview-images").prepend(` <a class="preview-video-container" data-fancybox="gallery" href="#preview-video"> <span>预告片</span> <img src="${videoCoverSrc}" class="video-cover" style="width: 150px; height: auto;" alt=""> </a> `); $(".preview-video-container").on("click", ((event) => { utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (async () => { await this.handleVideo(); })); })); } } catch (error) { clog.error("预加载dmm失败:", error); } } async handleVideo() { if (await storageManager.getSetting("enableLoadPreviewVideo", YES$1) === NO$1) return; const $videoEl = $("#preview-video"); if (!$videoEl.length) return; const $videoContainer = $videoEl.parent(); $videoContainer.css("position", "relative"); const videoEl = $videoEl[0]; let carNum2 = this.getPageInfo().carNum; const jhs_videoMuted = localStorage.getItem("jhs_videoMuted"); if (jhs_videoMuted) videoEl.muted = "yes" === jhs_videoMuted; videoEl.addEventListener("volumechange", (function() { localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no"); })); const CACHE_KEY2 = "jhs_dmm_video"; let dmmErrorRetryCount = 0; const attachVideoErrorHandler = () => { videoEl.addEventListener("error", async function onVideoError(e) { if (dmmErrorRetryCount >= 2) return; dmmErrorRetryCount++; clog.warn(`预览视频加载失败(第${dmmErrorRetryCount}次),清除缓存后重试...`); const cachedData = localStorage.getItem(CACHE_KEY2) ? JSON.parse(localStorage.getItem(CACHE_KEY2)) : {}; delete cachedData[carNum2]; localStorage.setItem(CACHE_KEY2, JSON.stringify(cachedData)); const freshMap = await getDmmVideo(carNum2); if (!freshMap) { show.error("预览视频获取失败,请稍后重试"); return; } const storedQuality = await storageManager.getSetting("videoQuality"); const quality = selectDefaultQuality(Object.keys(freshMap), storedQuality); const freshUrl = freshMap[quality]; const savedTime = videoEl.currentTime; $videoEl.attr("src", freshUrl); videoEl.load(); if (savedTime > 0 && Number.isFinite(savedTime)) { videoEl.currentTime = savedTime; } videoEl.play(); }, { once: false }); }; attachVideoErrorHandler(); videoEl.play(); const dmmVideoMap = await getDmmVideo(carNum2); let $bottomToolbar = $("<div></div>").attr("id", "video-bottom-toolbar").css({ display: "flex", gap: "5px", "align-items": "center", "flex-wrap": "wrap" }), $qualityButtonGroup = $("<div></div>").css({ display: "flex", gap: "5px", "align-items": "center" }), defaultVideoQuality = null; if (dmmVideoMap) { let storedQuality = await storageManager.getSetting("videoQuality"); defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), storedQuality); let defaultVideoUrl = dmmVideoMap[defaultVideoQuality]; if ($videoEl.attr("src") !== defaultVideoUrl) { $videoEl.attr("src", defaultVideoUrl); videoEl.load(); videoEl.play(); } qualityOptions.forEach(((option) => { let dmmVideoUrl = dmmVideoMap[option.quality]; if (dmmVideoUrl) { const isActive = defaultVideoQuality === option.quality; let qualityButton = $(` <button class="video-control-btn${isActive ? " active" : ""}" id="${option.id}" data-quality="${option.quality}" data-video-src="${dmmVideoUrl}" style="min-width: 40px; border: 1px solid #ccc; background-color: ${isActive ? "#007bff" : "#fff"}; color: ${isActive ? "white" : "black"};"> ${option.text} </button> `); $qualityButtonGroup.append(qualityButton); } })); } $bottomToolbar.append($qualityButtonGroup); let $functionButtonGroup = $("<div></div>").css({ display: "flex", gap: "5px", "align-items": "center", "margin-left": "auto" }), filterButton = $(`<button class="menu-btn" id="video-filterBtn" style="min-width: 120px; background-color:#de3333;">屏蔽 ${this.filterHotKey ? "(" + this.filterHotKey + ")" : ""}</button>`); $functionButtonGroup.append(filterButton); let favoriteButton = $(`<button class="menu-btn" id="video-favoriteBtn" style="min-width: 120px; background-color:#25b1dc;">收藏 ${this.favoriteHotKey ? "(" + this.favoriteHotKey + ")" : ""}</button>`); $functionButtonGroup.append(favoriteButton); let speedButton = $(`<button class="menu-btn" id="speed-btn" style="min-width: 120px; background-color:#76b45d;">快进 ${this.speedVideoHotKey ? "(" + this.speedVideoHotKey + ")" : ""}</button>`); $functionButtonGroup.append(speedButton); $bottomToolbar.append($functionButtonGroup); $videoContainer.append($bottomToolbar); $bottomToolbar.on("click", ".video-control-btn", (async (e) => { const $button = $(e.currentTarget), videoSrc = $button.data("video-src"); if (!$button.hasClass("active")) try { const currentTime = videoEl.currentTime; $videoEl.attr("src", videoSrc); videoEl.load(); if (currentTime > 0 && Number.isFinite(currentTime)) { videoEl.currentTime = currentTime; } await videoEl.play(); $bottomToolbar.find(".video-control-btn").removeClass("active").css({ "background-color": "#fff", color: "black" }); $button.addClass("active").css({ "background-color": "#007bff", color: "white" }); } catch (error) { console.error("切换画质失败:", error); } })); $("#speed-btn").on("click", (() => { this.getBean("DetailPageButtonPlugin").speedVideo(); })); utils.rightClick(document.body, "#speed-btn", ((event) => { this.getBean("DetailPageButtonPlugin").filterOne(event); })); $("#video-filterBtn").on("click", ((event) => { this.getBean("DetailPageButtonPlugin").filterOne(event); })); $("#video-favoriteBtn").on("click", ((event) => { this.getBean("DetailPageButtonPlugin").favoriteOne(event); })); } } const _HotkeyManager = class _HotkeyManager2 { constructor() { if (new.target === _HotkeyManager2) throw new Error("HotkeyManager cannot be instantiated."); } static registerHotkey(hotkeyString, callback, keyupCallback = null) { if (Array.isArray(hotkeyString)) { let id_list = []; hotkeyString.forEach(((hotkey) => { if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误"); let id = this.recordHotkey(hotkey, callback, keyupCallback); id_list.push(id); })); return id_list; } if (!this.isHotkeyFormat(hotkeyString)) throw new Error("快捷键格式错误"); return this.recordHotkey(hotkeyString, callback, keyupCallback); } static recordHotkey(hotkeyString, callback, keyupCallback) { let id = Math.random().toString(36).substr(2); this.registerHotKeyMap.set(id, { hotkeyString, callback, keyupCallback }); return id; } static unregisterHotkey(id) { this.registerHotKeyMap.has(id) && this.registerHotKeyMap.delete(id); } static isHotkeyFormat(hotkeyString) { return hotkeyString.toLowerCase().split("+").map(((k) => k.trim())).every(((k) => ["ctrl", "shift", "alt"].includes(k) || 1 === k.length)); } static judgeHotkey(hotkeyString, event) { const keyList = hotkeyString.toLowerCase().split("+").map(((k) => k.trim())), ctrl = keyList.includes("ctrl"), shift = keyList.includes("shift"), alt = keyList.includes("alt"), key = keyList.find(((k) => "ctrl" !== k && "shift" !== k && "alt" !== k)); return (this.isMac ? event.metaKey : event.ctrlKey) === ctrl && event.shiftKey === shift && event.altKey === alt && event.key.toLowerCase() === key; } }; __publicField2(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac")); __publicField2(_HotkeyManager, "registerHotKeyMap", /* @__PURE__ */ new Map()); __publicField2(_HotkeyManager, "handleKeydown", ((event) => { for (const [id, data] of _HotkeyManager.registerHotKeyMap) { let hotkeyString = data.hotkeyString, callback = data.callback; _HotkeyManager.judgeHotkey(hotkeyString, event) && callback(event); } })); __publicField2(_HotkeyManager, "handleKeyup", ((event) => { for (const [id, data] of _HotkeyManager.registerHotKeyMap) { let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback; keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event) && keyupCallback(event)); } })); let HotkeyManager = _HotkeyManager; document.addEventListener("keydown", ((event) => { HotkeyManager.handleKeydown(event); })); document.addEventListener("keyup", ((event) => { HotkeyManager.handleKeyup(event); })); class JavTrailersPlugin extends BasePlugin { getName() { return "JavTrailersPlugin"; } constructor() { super(); this.hasBand = false; } handle() { let href = window.location.href; if (!href.includes("handle=1")) return; if ($("h1:contains('Page not found')").length) { console.log("番号无法匹配, 跳搜索"); let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-"); window.location.href = "/search/" + encodeURIComponent(keyword) + window.location.search; return; } let findList = $(".videos-list .video-link").toArray(); if (findList.length) { const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find(((el) => $(el).find(".vid-title").text().toLowerCase().includes(keyword))); if (matchedLink) { window.location.href = $(matchedLink).attr("href") + window.location.search; return; } } this.handlePlayJavTrailers(); $("#videoPlayerContainer").on("click", (() => { this.handlePlayJavTrailers(); })); window.addEventListener("message", ((event) => { let videoEl = document.getElementById("vjs_video_3_html5_api"); videoEl && (videoEl.currentTime += 5); })); const urlParams = new URLSearchParams(window.location.search), filterHotKey = urlParams.get("filterHotKey"), favoriteHotKey = urlParams.get("favoriteHotKey"), speedVideoHotKey = urlParams.get("speedVideoHotKey"); filterHotKey && HotkeyManager.registerHotkey(filterHotKey, (() => window.parent.postMessage(filterHotKey, "*"))); favoriteHotKey && HotkeyManager.registerHotkey(favoriteHotKey, (() => window.parent.postMessage(favoriteHotKey, "*"))); speedVideoHotKey && HotkeyManager.registerHotkey(speedVideoHotKey, (() => { const videoEl = document.getElementById("vjs_video_3_html5_api"); videoEl && (videoEl.currentTime += 5); })); } handlePlayJavTrailers() { if (!this.hasBand) { utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => { setTimeout((() => { this.hasBand = true; let videoEl = document.getElementById("vjs_video_3_html5_api"); console.log(videoEl); videoEl.play(); videoEl.currentTime = 5; videoEl.addEventListener("timeupdate", (function() { videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.currentTime += 2); })); $("#vjs_video_3_html5_api").css({ position: "fixed", width: "100vw", height: "100vh", objectFit: "cover", zIndex: "999999999" }); $(".vjs-control-bar").css({ position: "fixed", bottom: "20px", zIndex: "999999999" }); }), 100); })); utils.loopDetector((() => $("#vjs_video_3 canvas").length > 0), (() => { 0 !== $("#vjs_video_3 canvas").length && $("#vjs_video_3 canvas").css({ position: "fixed", width: "100vw", height: "100vh", objectFit: "cover", top: "0", right: "0", zIndex: "999999998" }); })); } } } class SubTitleCatPlugin extends BasePlugin { getName() { return "SubTitleCatPlugin"; } handle() { $(".t-banner-inner").hide(); $("#navbar").hide(); let keyword = new URLSearchParams(window.location.search).get("search").toLowerCase(), findList = $(".sub-table tr td a").toArray(), visibleCount = 0; findList.forEach(((el) => { let item = $(el); item.text().toLowerCase().includes(keyword) ? visibleCount++ : item.parent().parent().hide(); })); 0 === visibleCount && show.error("该番号无字幕!"); const $secTitle = $(".sec-title"), newHTML = $secTitle.html().replace(/^\d+/, visibleCount); $secTitle.html(newHTML); } } class JavPackSubtitle { static escapeHtml(value = "") { return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); } static sanitizeName(value = "") { return String(value).replace(/[\\/:*?"<>|]/g, "").replace(/\s+/g, " ").trim(); } static isSubtitleFile(file = {}) { return /\.(srt|ass|ssa|vtt|sub)$/i.test(file.n || ""); } static buildDefaultKeyword(details = {}) { return this.sanitizeName(details.code || "") || this.sanitizeName([details.code, details.title].filter(Boolean).join(" ")) || document.title; } static buildSearchKeywords(details = {}) { return [this.buildDefaultKeyword(details)].filter(Boolean); } static buildSubtitleBaseName(details = {}) { var _a2; if ((_a2 = window.JavPackMatch115Console) == null ? void 0 : _a2.buildRename) { return window.JavPackMatch115Console.buildRename(details, [{ n: `${details.code || "subtitle"}.mp4` }]); } return this.sanitizeName([details.code, details.title].filter(Boolean).join(" ")) || this.buildDefaultKeyword(details); } static buildSubtitleFilename(details = {}, item = {}) { const ext = this.sanitizeName(item.ext || "srt").replace(/^\.+/, "") || "srt"; return `${this.buildSubtitleBaseName(details)}.${ext}`; } static clearPreviewCache() { this.previewCache.clear(); } static async checkSubInCloud(req115, cid) { if (!req115 || !cid) return false; const res = await req115.filesAll(cid); return ((res == null ? void 0 : res.data) || []).some((file) => this.isSubtitleFile(file)); } static getReq115() { return window.Req115 || null; } static getTargetCid() { var _a2, _b; return ((_a2 = document.querySelector(".x-match-cont .zymatch-item [data-cid]")) == null ? void 0 : _a2.dataset.cid) || ((_b = document.querySelector(".x-match-cont .zymatch-item")) == null ? void 0 : _b.dataset.cid) || ""; } static async find115Cid(code) { if (!code) return ""; const req115 = this.getReq115(); if (!req115) return ""; try { const { data } = await req115.filesSearchAllFolders(code); if (!(data == null ? void 0 : data.length)) return ""; const exact = data.find((f) => f.n === code); if (exact) return exact.cid; const upper = data.find((f) => f.n.toUpperCase() === code.toUpperCase()); if (upper) return upper.cid; const contains = data.find((f) => f.n.toUpperCase().includes(code.toUpperCase())); return (contains == null ? void 0 : contains.cid) || ""; } catch (e) { console.warn("[JavPackSubtitle] 115搜索目录失败:", e.message); return ""; } } static async preload115Matches(code) { this.cached115Matches = []; this.current115Cids = []; if (!code) return; const req115 = this.getReq115(); if (!req115) return; try { const { data } = await req115.filesSearchAllFolders(code); if (!(data == null ? void 0 : data.length)) return; const codeUpper = code.toUpperCase(); const matches = data.filter((f) => f.n.toUpperCase().includes(codeUpper)).map((f) => ({ cid: f.cid, name: f.n })).sort((a, b) => { const aExact = a.name.toUpperCase() === codeUpper; const bExact = b.name.toUpperCase() === codeUpper; if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; return a.name.localeCompare(b.name); }); this.cached115Matches = matches; this.current115Cids = matches.length === 1 ? [matches[0].cid] : matches.map((m) => m.cid); this.last115Code = code; } catch (e) { console.warn("[JavPackSubtitle] 115预加载目录失败:", e.message); } } static ensureDetailButton({ details, getTargetCid, getSubtitleEntries } = {}) { const panel = document.querySelector(".movie-panel-info"); const buttons = panel == null ? void 0 : panel.querySelector(".panel-block:last-child .buttons"); if (!buttons || document.getElementById(this.BTN_ID)) return; const btn = document.createElement("button"); btn.id = this.BTN_ID; btn.type = "button"; btn.className = "button is-small is-info x-subtitle-search"; btn.textContent = "字幕搜索"; const refreshSubtitleFileTip = () => { const groups = /* @__PURE__ */ new Map(); ((getSubtitleEntries == null ? void 0 : getSubtitleEntries()) || []).forEach(({ directory, files = [] }) => { const path = String(directory || "").trim(); if (!path) return; if (!groups.has(path)) groups.set(path, /* @__PURE__ */ new Set()); files.map((file) => String((file == null ? void 0 : file.n) || (file == null ? void 0 : file.name) || file || "").trim()).filter(Boolean).forEach((name2) => groups.get(path).add(name2)); }); btn.title = groups.size ? ["已有字幕文件:", ...[...groups].flatMap(([path, files]) => [ `目录:${path}`, ...[...files].map((name2) => ` - ${name2}`) ])].join("\n") : "暂无已匹配的字幕文件"; btn.textContent = groups.size ? "已有字幕" : "字幕搜索"; btn.classList.toggle("is-success", Boolean(groups.size)); btn.classList.toggle("is-info", !groups.size); }; btn.addEventListener("mouseenter", refreshSubtitleFileTip); btn.addEventListener("focus", refreshSubtitleFileTip); btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); this.openSearchModal({ details, getTargetCid }); }); buttons.appendChild(btn); refreshSubtitleFileTip(); const matchRoot = document.querySelector(".x-match-cont"); if (matchRoot) new MutationObserver(refreshSubtitleFileTip).observe(matchRoot, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-has-subtitle", "data-subtitle-files"] }); } static modalTemplate(defaultKw) { return ` <div class="pdb-sub-modal"> <div class="pdb-sub-header"> <div class="pdb-sub-search-wrap"> <span class="pdb-sub-title">字幕检索:</span> <input type="text" id="sub-search-input" value="${this.escapeHtml(defaultKw)}" class="pdb-sub-input" placeholder="输入检索词..." /> <button id="sub-search-btn" class="pdb-sub-btn">重新搜索</button> </div> <div class="pdb-sub-115-wrap" id="sub-115-wrap" style="display:none;"> <span class="pdb-sub-title">115目录:</span> <div id="sub-115-select" class="pdb-sub-checkbox-group"></div> </div> <span class="pdb-sub-close" id="sub-close-btn">×</span> </div> <div class="pdb-sub-body"> <div class="pdb-sub-content"></div> <div class="pdb-sub-preview-wrap"> <div class="pdb-sub-preview-header"> <span>字幕内容预览</span><span id="preview-status" class="pdb-sub-preview-status">暂无预览</span> </div> <textarea class="pdb-sub-textarea" readonly></textarea> </div> </div> </div> `; } static openSearchModal({ details = {}, getTargetCid } = {}) { var _a2; const defaultKw = this.buildDefaultKeyword(details); this.currentDetails = details; (_a2 = document.getElementById(this.MODAL_ID)) == null ? void 0 : _a2.remove(); const overlay = document.createElement("div"); overlay.id = this.MODAL_ID; overlay.className = "pdb-sub-overlay"; overlay.innerHTML = this.modalTemplate(defaultKw); document.body.appendChild(overlay); const contentWrap = overlay.querySelector(".pdb-sub-content"); const previewBox = overlay.querySelector(".pdb-sub-textarea"); const statusNode = overlay.querySelector("#preview-status"); const input2 = overlay.querySelector("#sub-search-input"); const dirWrap = overlay.querySelector("#sub-115-wrap"); const dirSelect = overlay.querySelector("#sub-115-select"); const refresh115DirUI = () => { if (!this.cached115Matches.length) { dirWrap.style.display = "none"; return; } dirWrap.style.display = ""; dirSelect.innerHTML = this.cached115Matches.map((m) => { const checked = this.current115Cids.includes(m.cid) ? " checked" : ""; const displayName = m.name.length > 26 ? m.name.slice(0, 26) + "..." : m.name; return `<label class="pdb-sub-checkbox-label"><input type="checkbox" class="pdb-sub-checkbox" value="${m.cid}"${checked}><span title="${this.escapeHtml(m.name)}">${this.escapeHtml(displayName)}</span></label>`; }).join(""); }; dirSelect.addEventListener("change", () => { this.current115Cids = Array.from(dirSelect.querySelectorAll("input:checked")).map((cb) => cb.value); }); if (this.last115Code === details.code && this.cached115Matches.length) { refresh115DirUI(); } else { this.preload115Matches(details.code).then(refresh115DirUI); } const closeModal = () => { this.clearPreviewCache(); previewBox.value = ""; overlay.remove(); }; overlay.querySelector("#sub-close-btn").addEventListener("click", closeModal); overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); }); const performSearch = async (kw) => { if (!kw) return; contentWrap.innerHTML = '<div class="pdb-sub-msg">正在搜索字幕来源,请稍候...</div>'; previewBox.value = ""; statusNode.textContent = "暂无预览"; try { const results = await Promise.allSettled([ this.searchXunlei(kw), this.searchSubtitleCat(kw) ]); const dataList = results.flatMap((result) => result.status === "fulfilled" ? result.value : []); const chineseResults = this.sortResults(dataList, kw, details); if (!chineseResults.length) { contentWrap.innerHTML = '<div class="pdb-sub-msg">未找到简中或繁中字幕,请尝试使用完整番号搜索</div>'; return; } this.renderTable({ container: contentWrap, dataList: chineseResults, previewBox, statusNode, overlay, details, getTargetCid, kw }); if (kw.toUpperCase() !== (details.code || "").toUpperCase()) { this.preload115Matches(kw).then(refresh115DirUI); } } catch (e) { console.error("字幕搜索失败:", e); contentWrap.innerHTML = '<div class="pdb-sub-msg">搜索失败,请稍后重试</div>'; } }; overlay.querySelector("#sub-search-btn").addEventListener("click", () => performSearch(input2.value.trim())); input2.addEventListener("keypress", (e) => { if (e.key === "Enter") performSearch(input2.value.trim()); }); performSearch(defaultKw); } static requestText(url) { return window.gmHttp.get(url); } static async searchXunlei(kw) { const root = await this.requestText(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?name=${encodeURIComponent(kw)}`); if (!root || root.code !== 0 || !Array.isArray(root.data)) return []; const results = root.data.slice(0, 20).map((item) => this.normalizeXunleiResult(item)); const labeledResults = await Promise.all(results.map((item) => this.labelXunleiChineseSubtitle(item))); return labeledResults.filter(Boolean); } static normalizeXunleiResult(item = {}) { const rawLanguages = item.languages ?? item.Languages ?? item.langs ?? item.Langs ?? item.language ?? item.Language ?? []; return { ...item, name: item.name || item.Name || item.extra_name || item.Title || "", languages: Array.isArray(rawLanguages) ? rawLanguages : rawLanguages ? [String(rawLanguages)] : [], ext: item.ext || item.Ext || item.format || item.Format || "srt", url: item.url || item.Url || "", provider: "迅雷" }; } static async labelXunleiChineseSubtitle(item) { if (this.isWantedChineseSubtitle(item)) return item; if (!item.url) return null; try { const language = this.detectChineseSubtitleLanguage(this.decodeSubtitle(await this.fetchBinaryCached(item.url))); return language ? { ...item, languages: [language] } : null; } catch (_) { return null; } } static detectChineseSubtitleLanguage(text = "") { const sample = String(text).slice(0, 12e3); if (!/[\u3400-\u9fff]/.test(sample)) return ""; const simplifiedCount = (sample.match(/[为与个么开关东风头后发这边过还进时样气应实话说问题现经体万台农国门书车云]/g) || []).length; const traditionalCount = (sample.match(/[為與個麼開關東風頭後發這邊過還進時樣氣應實話說問題現經體萬臺農國門書車雲]/g) || []).length; if (simplifiedCount === 0 && traditionalCount === 0) return ""; return traditionalCount > simplifiedCount ? "繁中" : "简中"; } static async searchSubtitleCat(kw) { const searchUrl = `${this.SUBTITLECAT_ORIGIN}/index.php?search=${encodeURIComponent(kw)}`; const searchHtml = await this.requestText(searchUrl); const entries = this.parseSubtitleCatSearch(searchHtml).slice(0, 20); const languageGroups = await Promise.all(entries.map(async (entry) => { try { return this.parseSubtitleCatLanguages(await this.requestText(entry.detailUrl), entry); } catch (_) { return []; } })); return languageGroups.flat(); } static parseSubtitleCatSearch(html2) { const doc = new DOMParser().parseFromString(html2, "text/html"); return [...doc.querySelectorAll("table.sub-table tbody tr")].flatMap((row) => { var _a2; const link2 = row.querySelector('a[href*="subs/"]'); const name2 = (_a2 = link2 == null ? void 0 : link2.textContent) == null ? void 0 : _a2.trim(); if (!link2 || !name2) return []; return [{ name: name2, detailUrl: new URL(link2.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href }]; }); } static parseSubtitleCatLanguages(html2, entry) { const doc = new DOMParser().parseFromString(html2, "text/html"); return [...doc.querySelectorAll(".sub-single")].flatMap((node) => { var _a2; const languageCode = ((_a2 = node.querySelector("img.flag")) == null ? void 0 : _a2.getAttribute("alt")) || ""; const language = this.SUBTITLECAT_CHINESE_LANGUAGES.get(languageCode); const link2 = node.querySelector("a.green-link[href$='.srt']"); if (!language || !link2) return []; return [{ name: entry.name, languageCode, languages: [language], ext: "srt", provider: "SubtitleCat", url: new URL(link2.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href }]; }); } static isWantedChineseSubtitle(item = {}) { if (item.provider === "SubtitleCat") return this.SUBTITLECAT_CHINESE_LANGUAGES.has(item.languageCode); const language = Array.isArray(item.languages) ? item.languages.join(" ") : String(item.languages || ""); const name2 = `${item.name || ""} ${item.extra_name || ""}`; return /(zh[-_]?cn|zh[-_]?tw|chs|cht|chinese\s*(simplified|traditional)|简体|繁体|繁體|简中|繁中)/i.test(`${language} ${name2}`); } static sortResults(dataList, kw = "", details = {}) { const kwClean = kw.toLowerCase().replace(/[-_.\s]/g, ""); const tokens = kw.toLowerCase().split(/[-_.\s]+/).filter((word) => word.length > 1); const codeClean = String(details.code || kw).toLowerCase().replace(/[-_.\s]/g, ""); return [...dataList].filter((item) => this.isWantedChineseSubtitle(item)).sort((a, b) => this.scoreResult(b, kwClean, tokens, codeClean) - this.scoreResult(a, kwClean, tokens, codeClean)); } static scoreResult(item, kwClean, tokens, codeClean = "") { const name2 = (item.name || item.extra_name || "").toLowerCase(); const compactName = name2.replace(/[-_.\s]/g, ""); const lang = (item.languages && item.languages[0] || "").toLowerCase(); let score = 0; if (codeClean && compactName.includes(codeClean)) score += 800; tokens.forEach((token) => { if (name2.includes(token)) score += 50; }); if (kwClean && compactName.includes(kwClean)) score += 500; if (/(zh|cn|chs|cht|chinese|简中|繁中)/i.test(lang) || /(zh|cn|chs|cht|chinese|简中|繁中)/i.test(name2)) score += 100; if (item.ext === "srt" || item.ext === "ass") score += 20; return score; } static renderTable({ container, dataList, previewBox, statusNode, overlay, details, getTargetCid, kw = "" }) { const words = kw.split(/[-_.\s]+/).filter((word) => word.length > 1); const highlightRegex = words.length ? new RegExp(`(${words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "gi") : null; container.innerHTML = ` <table class="pdb-sub-table"> <thead> <tr> <th class="pdb-sub-th" style="width: 90px;">来源</th> <th class="pdb-sub-th">原始字幕名称</th> <th class="pdb-sub-th" style="width: 80px;">语言</th> <th class="pdb-sub-th" style="width: 60px;">格式</th> <th class="pdb-sub-th" style="width: 170px; text-align:center;">操作</th> </tr> </thead> <tbody> ${dataList.map((item, index) => this.renderRow(item, index, highlightRegex)).join("")} </tbody> </table> `; container.querySelectorAll(".sub-action-btn").forEach((btn) => { btn.addEventListener("click", () => this.handleAction({ btn, item: dataList[btn.dataset.idx], previewBox, statusNode, overlay, details, getTargetCid })); }); } static renderRow(item, index, highlightRegex) { var _a2; const lang = ((_a2 = item.languages) == null ? void 0 : _a2[0]) || "未知"; const name2 = item.name || item.extra_name || "未知字幕"; const provider = item.provider || "迅雷"; const providerClass = provider === "SubtitleCat" ? "pdb-sub-provider--subtitlecat" : "pdb-sub-provider--xunlei"; const displayName = highlightRegex ? this.escapeHtml(name2).replace(highlightRegex, '<span style="color:#e74c3c; font-weight:bold;">$1</span>') : this.escapeHtml(name2); return ` <tr class="pdb-sub-tr"> <td class="pdb-sub-td-provider"><span class="pdb-sub-provider ${providerClass}">${this.escapeHtml(provider)}</span></td> <td class="pdb-sub-td">${displayName}</td> <td class="pdb-sub-td-lang">${this.escapeHtml(lang)}</td> <td class="pdb-sub-td-ext">${this.escapeHtml(item.ext || "srt")}</td> <td class="pdb-sub-td-actions"> <button class="sub-action-btn pdb-sub-action-btn pdb-sub-btn-preview" data-action="preview" data-idx="${index}">预览</button> <button class="sub-action-btn pdb-sub-action-btn pdb-sub-btn-download" data-action="download" data-idx="${index}">下载</button> <button class="sub-action-btn pdb-sub-action-btn pdb-sub-btn-upload" data-action="upload" data-idx="${index}">115直传</button> </td> </tr> `; } static async handleAction({ btn, item, previewBox, statusNode, overlay, details, getTargetCid }) { const action = btn.dataset.action; const oldText = btn.textContent; const url = item.url; if (!url) return alert("无效的字幕下载直链"); const filename = this.buildSubtitleFilename(details, item); btn.textContent = "获取中..."; btn.style.opacity = "0.6"; try { const buffer = await this.fetchBinaryCached(url); const text = this.decodeSubtitle(buffer); if (text.includes("<?xml") && text.includes("<Code>NoSuchKey</Code>")) throw new Error("该字幕在迅雷云端已失效丢失 (NoSuchKey)"); if (action === "preview") { previewBox.value = text; statusNode.textContent = filename; this.setActivePreviewRow(overlay, btn); } else if (action === "download") { this.downloadBuffer(buffer, filename); } else if (action === "upload") { const jdbCid = (getTargetCid == null ? void 0 : getTargetCid()) || this.getTargetCid(); let cids = jdbCid ? [jdbCid] : [...this.current115Cids]; const req115 = this.getReq115(); if (!cids.length || !req115) throw new Error("未在 115 找到匹配目录,请确认作品已下载到 115"); btn.textContent = "直传中..."; for (const cid of cids) { await this.uploadSubtitle({ req115, cid, filename, buffer }); } show.ok(`字幕已上传到 115(${cids.length}个目录)`); const detailBtn = document.getElementById(this.BTN_ID); if (detailBtn) { detailBtn.classList.add("is-success"); detailBtn.textContent = "已有字幕"; } window.dispatchEvent(new CustomEvent("JavDB_SubtitleUploaded", { detail: { code: details.code || "", cid: String(cids[0]), subtitle: { n: filename, s: buffer.byteLength } } })); overlay.remove(); this.clearPreviewCache(); } } catch (err) { alert(`执行中止: ${err.message}`); } finally { btn.textContent = oldText; btn.style.opacity = "1"; } } static setActivePreviewRow(overlay, btn) { overlay.querySelectorAll(".pdb-sub-tr.is-previewing").forEach((row2) => { row2.classList.remove("is-previewing"); row2.removeAttribute("aria-current"); }); const row = btn.closest(".pdb-sub-tr"); if (!row) return; row.classList.add("is-previewing"); row.setAttribute("aria-current", "true"); } static decodeSubtitle(buffer) { let text = new TextDecoder("utf-8").decode(buffer); if ((text.match(/\uFFFD/g) || []).length > 3) text = new TextDecoder("gbk").decode(buffer); return text; } static fetchBinaryCached(url) { if (this.previewCache.has(url)) return Promise.resolve(this.previewCache.get(url)); return this.fetchBinary(url).then((buffer) => { this.previewCache.set(url, buffer); return buffer; }); } static fetchBinary(url) { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "GET", url, responseType: "arraybuffer", onload: (res) => { if (res.status === 200 && res.response) resolve(res.response); else reject(new Error(`字幕流获取失败,HTTP_CODE: ${res.status}`)); }, onerror: () => reject(new Error("跨域网络请求被阻断")) }); }); } static downloadBuffer(buffer, filename) { const blob = new Blob([buffer], { type: "application/octet-stream" }); const link2 = document.createElement("a"); link2.href = URL.createObjectURL(blob); link2.download = filename; link2.click(); URL.revokeObjectURL(link2.href); } static uploadSubtitle(args) { return args.req115.queueMutation("上传字幕", () => this.uploadSubtitleNow(args)); } static async uploadSubtitleNow({ req115, cid, filename, buffer }) { const blob = new Blob([buffer], { type: "application/octet-stream" }); const file = new File([blob], filename, { type: "application/octet-stream" }); const initRes = await req115.sampleInitUpload({ filename, filesize: file.size, cid }); if (!initRes || !initRes.host && initRes.status !== 2 && initRes.statuscode !== 0) { throw new Error((initRes == null ? void 0 : initRes.error_msg) || "获取115上传安全凭证被拦截"); } if (!initRes.host) return initRes; let uploadRes = null; for (let retry = 0; retry < 3; retry++) { uploadRes = await req115.upload({ ...initRes, filename, file }); if (uploadRes && uploadRes.state !== false) return uploadRes; await new Promise((resolve) => setTimeout(resolve, 1500)); } throw new Error((uploadRes == null ? void 0 : uploadRes.error_msg) || (uploadRes == null ? void 0 : uploadRes.error) || "115 服务器拒绝接收回调"); } } __publicField(JavPackSubtitle, "BTN_ID", "x-subtitle-search-btn"); __publicField(JavPackSubtitle, "MODAL_ID", "x-subtitle-modal"); __publicField(JavPackSubtitle, "previewCache", /* @__PURE__ */ new Map()); __publicField(JavPackSubtitle, "cached115Matches", []); __publicField(JavPackSubtitle, "current115Cids", []); __publicField(JavPackSubtitle, "last115Code", ""); __publicField(JavPackSubtitle, "SUBTITLECAT_ORIGIN", "https://www.subtitlecat.com"); __publicField(JavPackSubtitle, "SUBTITLECAT_CHINESE_LANGUAGES", /* @__PURE__ */ new Map([ ["zh-CN", "简中"], ["zh-TW", "繁中"] ])); window.JavPackSubtitle = JavPackSubtitle; class Req { static _serializeToFormData(formData, data, parentKey = "") { Object.entries(data).forEach(([key, val]) => { const fullKey = parentKey ? `${parentKey}[${key}]` : key; if (val === void 0) return; if (val !== null && typeof val === "object" && !(val instanceof Blob || val instanceof Date)) { this._serializeToFormData(formData, val, fullKey); } else { formData.append(fullKey, val instanceof Date ? val.toISOString() : val); } }); } static request(config) { let options; if (typeof config === "string") { options = { url: config }; } else if (config !== null && typeof config === "object") { options = { ...config }; } else { return Promise.reject(new TypeError("Request config must be a string or an object")); } if (!options.url) return Promise.reject(new Error("Request URL is required")); options.method = (options.method || this.DEFAULT_METHOD).toUpperCase(); options.timeout || (options.timeout = this.DEFAULT_TIMEOUT); const { params, method, data, signal } = options; try { const urlInstance = new URL(options.url, location.origin); if (params) { const cleanParams = this._isPlainObject(params) ? Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null)) : params; new URLSearchParams(cleanParams).forEach((v, k) => urlInstance.searchParams.set(k, v)); } options.url = urlInstance.toString(); } catch { return Promise.reject(new Error(`Invalid Request URL: ${options.url}`)); } if (method === "POST") { options.responseType ?? (options.responseType = this.DEFAULT_POST_RESPONSE_TYPE); if (this._isPlainObject(data) && !this._isRawBody(data)) { const formData = new FormData(); this._serializeToFormData(formData, data); options.data = formData; } } else if (method === "GET") { options.responseType ?? (options.responseType = options.url.split(/[?#]/)[0].endsWith(".json") ? "json" : this.DEFAULT_GET_RESPONSE_TYPE); } return new Promise((resolve, reject) => { if (signal == null ? void 0 : signal.aborted) return reject(new DOMException("Aborted", "AbortError")); const { url: targetUrl } = options; let xhrHandler; const handleAbort = () => xhrHandler == null ? void 0 : xhrHandler.abort(); const finalize = (settleFn, ...args) => { signal == null ? void 0 : signal.removeEventListener("abort", handleAbort); settleFn(...args); }; xhrHandler = _GM_xmlhttpRequest({ onabort: () => reject(new DOMException("Aborted", "AbortError")), ontimeout: () => finalize(reject, new Error(`Timeout: ${targetUrl}`)), onerror: () => finalize(reject, new Error(`Network Error: ${targetUrl}`)), onload: ({ status, finalUrl, response }) => status >= 400 ? finalize(reject, new Error(`HTTP ${status}: ${targetUrl}`)) : finalize(resolve, method === "HEAD" ? finalUrl : response), ...options, params: void 0 }); signal == null ? void 0 : signal.addEventListener("abort", handleAbort, { once: true }); }); } static async tasks(initial, steps, signal) { let ctx = initial; for (const step of steps) { if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError"); if (typeof ctx === "string" || (ctx == null ? void 0 : ctx.url)) ctx = await this.request(typeof ctx === "string" ? { url: ctx, signal } : { ...ctx, signal }); ctx = await step(ctx); } return ctx; } } __publicField(Req, "DEFAULT_GET_RESPONSE_TYPE", "document"); __publicField(Req, "DEFAULT_POST_RESPONSE_TYPE", "json"); __publicField(Req, "DEFAULT_TIMEOUT", 3e4); __publicField(Req, "DEFAULT_METHOD", "GET"); __publicField(Req, "_isRawBody", (v) => v instanceof FormData || v instanceof Blob || v instanceof ArrayBuffer || v instanceof URLSearchParams); __publicField(Req, "_isPlainObject", (v) => Object.prototype.toString.call(v) === "[object Object]"); class Drive115 extends Req { static files(cid = "0", params = {}) { return this.request({ url: "https://webapi.115.com/files", params: { cid, ...params } }); } static filesSearch(search_value, params = {}) { return this.request({ url: "https://webapi.115.com/files/search", params: { search_value, ...params } }); } static lixianTaskLists(page = 1) { return this.request({ url: "https://115.com/web/lixian/", params: { ct: "lixian", ac: "task_lists", page } }); } static labelList() { return this.request({ url: "https://webapi.115.com/label/list", params: { keyword: "", limit: 11150 } }); } /** * @connect 115vod.com */ static filesVideo(pickcode) { return this.request({ url: "https://115vod.com/webapi/files/video", params: { pickcode, local: 1 } }); } static post(details) { return this.request({ method: "POST", ...details }); } static filesAdd(cname, pid) { return this.post({ url: "https://webapi.115.com/files/add", data: { cname, pid } }); } static lixianAddTaskUrl(url, wp_path_id) { return this.post({ url: "https://115.com/web/lixian/", params: { ct: "lixian", ac: "add_task_url" }, data: { url, wp_path_id } }); } /** * Bulk delete offline tasks and source files * @param {string[]} hash Array of info_hashes */ static lixianTaskDel(hash) { return this.post({ url: "https://115.com/web/lixian/", params: { ct: "lixian", ac: "task_del" }, data: { hash } }); } /** * Bulk delete files * @param {string[]} fid Array of file IDs * @param {string} pid Parent folder ID */ static rbDelete(fid, pid = "") { return this.post({ url: "https://webapi.115.com/rb/delete", data: { fid, pid, ignore_warn: 1 } }); } /** * Batch move files * @param {string[]} fid Array of file IDs * @param {string} pid Destination folder ID */ static filesMove(fid, pid) { return this.post({ url: "https://webapi.115.com/files/move", data: { fid, pid, move_proid: "" } }); } /** * Bulk label files * @param {string} file_ids fid1,fid2,fid3... * @param {string} file_label label_id1,label_id2,label_id3... */ static filesBatchLabel(file_ids, file_label, action = "add") { return this.post({ url: "https://webapi.115.com/files/batch_label", data: { file_ids, file_label, action } }); } /** * Bulk rename files * @param {object} files_new_name { [fid]: rename } */ static filesBatchRename(files_new_name) { return this.post({ url: "https://webapi.115.com/files/batch_rename", data: { files_new_name } }); } static sampleInitUpload({ filename, filesize, cid }) { return this.post({ url: "https://uplb.115.com/3.0/sampleinitupload.php", data: { filename, filesize, target: `U_1_${cid}` } }); } /** * @connect aliyuncs.com */ static upload({ host: url, filename: name2, object: key, policy, accessid: OSSAccessKeyId, callback, signature, file }) { return this.post({ url, data: { name: name2, key, policy, OSSAccessKeyId, success_action_status: "200", callback, signature, file } }); } static filesEdit(fid, fid_cover) { return this.post({ url: "https://webapi.115.com/files/edit", data: { fid, fid_cover } }); } } __publicField(Drive115, "DEFAULT_GET_RESPONSE_TYPE", "json"); class Req115 extends Drive115 { static async filesAll(cid, params = {}) { const res = await this.files(cid, params); const { count, page_size, data } = res; return count > page_size && data.length ? this.files(cid, { ...params, limit: count }) : res; } static filesAllVideos(cid, params = {}) { return this.filesAll(cid, { ...params, type: 4 }); } static filesAllSRTs(cid, params = {}) { return this.filesAll(cid, { ...params, suffix: "srt" }); } static async filesSearchAll(search_value, params = {}) { const res = await this.filesSearch(search_value, params); const { count, page_size, data } = res; return count > page_size && data.length ? this.filesSearch(search_value, { ...params, limit: count }) : res; } static filesSearchAllVideos(search_value, params = {}) { return this.filesSearchAll(search_value, { ...params, type: 4 }); } static filesSearchAllFolders(search_value, params = {}) { return this.filesSearchAll(search_value, { ...params, fc: 1 }); } static async handleDir(routes) { var _a2; if (routes.length === 1 && /^\d{5,}$/.test(routes[0])) return routes[0]; let cid; const routesStr = routes.join("/"); const cachedCid = localStorage.getItem(routesStr); if (cachedCid) { const res = await this.files(cachedCid); if ((_a2 = res == null ? void 0 : res.path) == null ? void 0 : _a2.length) { const path = res.path.slice(1).map((p) => p.name); if (path.join("/") === routesStr) cid = cachedCid; } } if (!cid) { cid = "0"; for (const route of routes) { const { data } = await this.filesSearchAllFolders(route, { cid }); let folder = data.find((folder2) => folder2.n === route); if (!folder) folder = await this.filesAdd(route, cid); cid = folder == null ? void 0 : folder.cid; if (!cid) break; } } const month = (/* @__PURE__ */ new Date()).getMonth().toString(); if (localStorage.getItem("115_CD") !== month) { localStorage.clear(); localStorage.setItem("115_CD", month); } if (cid) localStorage.setItem(routesStr, cid); return cid; } static async handleVerify(info_hash, { regex, codes }, { max, filter }) { const sleep = () => { return new Promise((r) => { setTimeout(r, 1e3); }); }; let file_id = ""; let videos = []; for (let index = 0; index < max; index++) { if (index) await sleep(); const { tasks } = await this.lixianTaskLists(); const task = tasks.find((task2) => task2.info_hash === info_hash); if (!task || task.status === -1) break; file_id = task.file_id; if (file_id) break; } if (!file_id) return { file_id, videos }; for (let index = 0; index < max; index++) { if (index) await sleep(); const { data } = await this.filesAllVideos(file_id); videos = data.filter((item) => regex.test(item.n)); if (videos.length) break; } if (!videos.length) { const { tasks } = await this.lixianTaskLists(); const task = tasks.find((task2) => task2.info_hash === info_hash); if (task.status === 2) { const { data } = await this.filesAllVideos(file_id); codes = codes.map((code) => code.toUpperCase()); videos = data.filter((item) => { const name2 = item.n.toUpperCase(); return codes.some((code) => name2.includes(code)); }); } } return { videos: videos.filter(filter), file_id }; } static async handleClean(keepFiles, cid) { const needMove = keepFiles.filter((file) => file.cid !== cid).map((file) => file.fid); if (needMove.length) await this.filesMove(needMove, cid); const { data } = await this.filesAll(cid); const needRemove = data.filter((item) => !keepFiles.some((file) => file.fid === item.fid)).map((item) => item.fid ?? item.cid); if (needRemove.length) return this.rbDelete(needRemove, cid); } static async handleTags(files, tags) { var _a2; const { data } = await this.labelList(); if (!((_a2 = data == null ? void 0 : data.list) == null ? void 0 : _a2.length)) return; const { list: list2 } = data; const labels = []; tags.forEach((tag) => { const item = list2.find((item2) => item2.name === tag); if (item) labels.push(item.id); }); if (labels.length) return this.filesBatchLabel(files.map((it) => it.fid).toString(), labels.toString()); } static handleRename(files, cid, { rename, renameTxt, zh, crack }) { rename = rename.replaceAll("$zh", zh ? renameTxt.zh : ""); rename = rename.replaceAll("$crack", crack ? renameTxt.crack : ""); rename = rename.split("$sep").filter(Boolean).join(renameTxt.sep); rename = rename.trim(); const renameObj = { [cid]: rename }; if (files.length === 1) { const { fid, ico } = files[0]; renameObj[fid] = `${rename}.${ico}`; return this.filesBatchRename(renameObj); } const icoMap = files.reduce((acc, { ico, ...item }) => { acc[ico] ?? (acc[ico] = []); acc[ico].push(item); return acc; }, {}); const noTxt = renameTxt.no; for (const [ico, items] of Object.entries(icoMap)) { if (items.length === 1) { renameObj[items[0].fid] = `${rename}.${ico}`; continue; } items.toSorted((a, b) => a.n.localeCompare(b.n)).forEach(({ fid }, idx) => { const no = noTxt.replaceAll(`\${no}`, `${idx + 1}`.padStart(2, "0")); renameObj[fid] = `${rename}${no}.${ico}`; }); } return this.filesBatchRename(renameObj); } static async handleCover(url, cid, filename) { const file = await this.request({ url, timeout: 6e4, responseType: "blob" }); if (!file) return; const res = await this.sampleInitUpload({ cid, filename, filesize: file.size }); if (res == null ? void 0 : res.host) return this.upload({ ...res, filename, file }); } static async handleOffline({ dir, regex, codes, verifyOptions, code, rename, renameTxt, tags, clean, cover }, magnets) { const res = { status: "error", msg: `获取目录失败: ${dir.join("/")}` }; const cid = await this.handleDir(dir); if (!cid) return res; for (let index = 0, { length } = magnets; index < length; index++) { const { url, zh, crack } = magnets[index]; const { state, error_msg, errcode, info_hash } = await this.lixianAddTaskUrl(url, cid); if (!state) { res.msg = error_msg; res.status = "error"; res.currIdx = index; if (errcode === 10008) continue; if (errcode === 911) res.status = "warn"; break; } const { videos, file_id } = await this.handleVerify(info_hash, { regex, codes }, verifyOptions); if (!videos.length) { if (verifyOptions.clean) this.lixianTaskDel([info_hash]); if (file_id && clean) this.rbDelete([file_id], cid); res.msg = `${code} 离线验证失败`; res.status = "error"; continue; } const { data: srts = [] } = await this.filesAllSRTs(file_id); const files = [...videos, ...srts]; if (clean) await this.handleClean(files, file_id); if (tags.length) this.handleTags(videos, tags); if (rename) this.handleRename(files, file_id, { rename, renameTxt, zh: zh || srts.length, crack }); if (cover) { try { const { data } = await this.handleCover(cover, file_id, `${code}.cover.jpg`); if (data == null ? void 0 : data.file_id) this.filesEdit(file_id, data.file_id); } catch (err) { console.warn("[Req115.handleCover]", err == null ? void 0 : err.message); } } res.msg = `${code} 离线任务成功`; res.status = "success"; break; } return res; } static async queueMutation(name2, fn) { return fn(); } } window.Req115 = Req115; class Fc2Plugin extends BasePlugin { getName() { return "Fc2Plugin"; } async initCss() { return "\n <style>\n /* 弹层样式 */\n .movie-detail-layer .layui-layer-title {\n font-size: 18px;\n color: #333;\n background: #f8f8f8;\n }\n \n \n /* 容器样式 */\n .movie-detail-container {\n margin: 40px;\n height: 100%;\n background: #fff;\n }\n \n .movie-poster-container {\n flex: 0 0 60%;\n padding: 15px;\n }\n \n .right-box {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n }\n \n /* 预告片iframe */\n .movie-trailer {\n width: 100%;\n height: 100%;\n min-height: 400px;\n background: #000;\n border-radius: 4px;\n }\n \n /* 电影信息样式 */\n .movie-title {\n font-size: 24px;\n margin-bottom: 15px;\n color: #333;\n }\n \n .movie-meta {\n margin-bottom: 20px;\n color: #666;\n }\n \n .movie-meta span {\n margin-right: 15px;\n }\n \n /* 演员列表 */\n .actor-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 10px;\n }\n \n .actor-tag {\n padding: 4px 12px;\n background: #f0f0f0;\n border-radius: 15px;\n font-size: 12px;\n color: #555;\n }\n \n /* 图片列表 */\n .image-list {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 10px;\n }\n \n .movie-image-thumb {\n width: 120px;\n height: 80px;\n object-fit: cover;\n border-radius: 4px;\n cursor: pointer;\n transition: transform 0.3s;\n }\n \n .movie-image-thumb:hover {\n transform: scale(1.05);\n }\n \n /* 加载中和错误状态 */\n .search-loading, .movie-error {\n padding: 40px;\n text-align: center;\n color: #999;\n }\n \n .movie-error {\n color: #f56c6c;\n }\n \n .fancybox-container{\n z-index:99999999\n }\n \n \n /* 错误提示样式 */\n .movie-not-found, .movie-error {\n text-align: center;\n padding: 30px;\n color: #666;\n }\n \n .movie-not-found h3, .movie-error h3 {\n color: #f56c6c;\n margin: 15px 0;\n }\n \n .icon-warning, .icon-error {\n font-size: 50px;\n color: #e6a23c;\n }\n \n .icon-error {\n color: #f56c6c;\n }\n \n .fc2-movie-panel-info .panel-block {\n padding: 0 !important;\n }\n </style>\n "; } handle() { let fc2Url = "/advanced_search?type=3&score_min=0&d=1"; $('.navbar-item:contains("FC2")').attr("href", fc2Url); $('.tabs a:contains("FC2")').attr("href", fc2Url); if (currentHref$1.includes("advanced_search?type=3")) { $("h2.section-title").contents().first().replaceWith("Fc2PPV"); $(".section .container > .box").remove(); } if (currentHref$1.includes("collection_codes?movieId")) { $("section").html(""); const urlParams = new URLSearchParams(window.location.search); let movieId = urlParams.get("movieId"), carNum2 = urlParams.get("carNum"), url = urlParams.get("url"); movieId && carNum2 && url && this.openFc2Dialog(movieId, carNum2, url); } } openFc2Dialog(movieId, carNum2, href) { let tempCarNum = carNum2.replace("FC2-", ""); if (href.includes("123av")) { this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum2, href); return; } layer.open({ type: 1, title: carNum2, content: '\n <div class="movie-detail-container">\n <!--<div class="movie-poster-container">\n <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe>\n </div>-->\n <!-- <div class="right-box">-->\n <div class="movie-info-container">\n <div class="search-loading">加载中...</div>\n </div>\n \n <div class="movie-panel-info fc2-movie-panel-info" style="margin-top:20px"><strong>第三方资源: </strong></div>\n \n <div style="margin: 30px 0">\n <a id="filterBtn" class="menu-btn" style="background-color:#de3333"><span>🚫 屏蔽</span></a>\n <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>⭐ 收藏</span></a>\n <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>📥️ 已下载</span></a>\n <a id="hasWatchBtn" class="menu-btn" style="background-color:#d7a80c;"><span>🔍 已观看</span></a>\n \n <a id="subtitleSearchBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="search-subtitle-btn" class="menu-btn fr-btn" style="background:linear-gradient(to bottom, #8d5656, rgb(196,159,91))">\n <span>字幕 (SubTitleCat)</span>\n </a>\n <a id="xunLeiSubtitleBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)">\n <span>字幕 (迅雷)</span>\n </a>\n -->\n <a id="magnetSearchBtn" class="menu-btn fr-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n </div>\n <div class="message video-panel" style="margin-top:20px">\n <div id="magnets-content" class="magnet-links" style="margin: 0 0.75rem">\n <div class="search-loading">加载中...</div>\n </div>\n </div>\n <div id="reviews-content">\n </div>\n <div id="related-content">\n </div>\n <span id="data-actress" style="display: none"></span>\n <!--</div>-->\n </div>\n ', area: utils.getResponsiveArea(["70%", "90%"]), skin: "movie-detail-layer", scrollbar: false, success: (layero, index) => { this.loadData(movieId, carNum2); $("#favoriteBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FAVORITE$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#filterBtn").on("click", ((event) => { utils.q(event, `是否屏蔽${carNum2}?`, (async () => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FILTER$1, publishTime }); window.refresh(); layer.closeAll(); window.location.href.includes("collection_codes?movieId") && utils.closePage(); })); })); $("#hasDownBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_DOWN$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#hasWatchBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_WATCH$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({ details: { code: carNum2 } }); })); window.JavPackSubtitle.preload115Matches(carNum2); $("#magnetSearchBtn").on("click", (() => { let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2); layer.open({ type: 1, title: "磁力搜索", content: '<div id="magnetHubBox"></div>', area: utils.getResponsiveArea(["60%", "80%"]), scrollbar: false, success: () => { $("#magnetHubBox").append(magnetHub); } }); })); this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); utils.setupEscClose(index); }, end() { window.location.href.includes("collection_codes?movieId") && utils.closePage(); } }); } loadData(movieId, carNum2) { let tempCarNum = carNum2.replace("FC2-", ""); this.handleMovieDetail(movieId); this.handleLongImg(tempCarNum); this.handleMagnets(movieId); this.getBean("ReviewPlugin").showReview(movieId, $("#reviews-content")).then(); this.getBean("RelatedPlugin").showRelated($("#related-content"), movieId).then(); } handleMovieDetail(movieId) { javDbApi.getMovieDetail(movieId).then(((res) => { const actors = res.actors || [], imgList = res.imgList || []; let actorsHtml = ""; if (actors.length > 0) { let actress = ""; for (let i = 0; i < actors.length; i++) { let actor = actors[i]; actorsHtml += `<span class="actor-tag"><a href="/actors/${actor.id}" target="_blank">${actor.name}</a></span>`; 0 === actor.gender && (actress += actor.name + " "); } $("#data-actress").text(actress); } else actorsHtml = '<span class="no-data">暂无演员信息</span>'; let imagesHtml = ""; imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => ` <a href="${img}" data-fancybox="movie-gallery" data-caption="剧照 ${index + 1}"> <img src="${img}" class="movie-image-thumb" alt=""/> </a> `)).join("") : '<div class="no-data">暂无剧照</div>'; $(".movie-info-container").html(` <h3 class="movie-title"><strong class="current-title">${res.title || "无标题"}</strong></h3> <div class="movie-meta"> <span><strong>番号: </strong>${res.carNum || "未知"}</span> <span><strong>年份: </strong>${res.releaseDate || "未知"}</span> <span><strong>评分: </strong>${res.score || "无"}</span> <span><strong>时长: </strong>${res.duration + " m" || "无"}</span> </div> <div class="movie-meta"> <span> <strong>站点: </strong> <a href="https://fc2ppvdb.com/articles/${res.carNum.replace("FC2-", "")}" target="_blank">fc2ppvdb</a> <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${res.carNum.replace("FC2-", "")}/" target="_blank">fc2电子市场</a> </span> </div> <div class="movie-actors"> <div class="actor-list"><strong>主演: </strong>${actorsHtml}</div> </div> <div class="movie-gallery" style="margin-top:10px"> <strong>剧照: </strong> <div class="image-list">${imagesHtml}</div> </div> <div id="data-releaseDate" style="display: none">${res.releaseDate || ""}</div> `); this.getBean("TranslatePlugin").translate(res.carNum, false).then(); })).catch(((err) => { console.error(err); $(".movie-info-container").html(` <div class="movie-error">加载失败: ${err.message}</div> `); })); } handleLongImg(carNum2) { utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => { $(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> '); const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2); imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl, carNum2); })); } handleMagnets(movieId) { javDbApi.getMagnets(movieId).then(((magnetList) => { let magnetsHtml = ""; if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) { let magnet = magnetList[i], oddClass = ""; i % 2 == 0 && (oddClass = "odd"); magnetsHtml += ` <div class="item columns is-desktop ${oddClass}"> <div class="magnet-name column is-four-fifths"> <a href="magnet:?xt=urn:btih:${magnet.hash}" title="右鍵點擊並選擇「複製鏈接地址」"> <span class="name">${magnet.name}</span> <br> <span class="meta"> ${(magnet.size / 1024).toFixed(2)}GB, ${magnet.files_count}個文件 </span> <br> <div class="tags"> ${magnet.hd ? '<span class="tag is-primary is-small is-light">高清</span>' : ""} ${magnet.cnsub ? '<span class="tag is-warning is-small is-light">字幕</span>' : ""} </div> </a> </div> <div class="buttons column"> <button class="button is-info is-small copy-to-clipboard" data-clipboard-text="magnet:?xt=urn:btih:${magnet.hash}" type="button"> 複製 </button> </div> <div class="date column"><span class="time">${magnet.created_at}</span></div> </div> `; } else magnetsHtml = '<span class="no-data">暂无磁力信息</span>'; $("#magnets-content").html(magnetsHtml); $(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => { $(el).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async (event) => { event.stopPropagation(); event.preventDefault(); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask($(el).attr("data-clipboard-text")); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } }))); })); })).catch(((err) => { console.error(err); $("#magnets-content").html(` <div class="movie-error">加载失败: ${err.message}</div> `); })); } async openFc2Page(movieId, carNum2, url) { const otherSitePlugin = this.getBean("OtherSitePlugin"); let javDbUrl = await otherSitePlugin.getJavDbUrl(); window.open(`${javDbUrl}/users/collection_codes?movieId=${movieId}&carNum=${carNum2}&url=${url}`); } } class HighlightMagnetPlugin extends BasePlugin { getName() { return "HighlightMagnetPlugin"; } parseDateFromAttr(dateStr) { if (!dateStr) return 0; if (dateStr.length === 8) { const y = parseInt(dateStr.substring(0, 4), 10); const m = parseInt(dateStr.substring(4, 6), 10) - 1; const d2 = parseInt(dateStr.substring(6, 8), 10); const date2 = new Date(y, m, d2); return isNaN(date2.getTime()) ? 0 : date2.getTime(); } const d = new Date(dateStr); return isNaN(d.getTime()) ? 0 : d.getTime(); } parseSizeToBytes(sizeText) { if (!sizeText) return 0; const s = sizeText.trim().toUpperCase(); const num = parseFloat(s); if (isNaN(num)) return 0; if (s.includes("TB")) return num * 1024 * 1024 * 1024 * 1024; if (s.includes("GB")) return num * 1024 * 1024 * 1024; if (s.includes("MB")) return num * 1024 * 1024; if (s.includes("KB")) return num * 1024; return num; } getRowInfo(row) { const $row = $(row); const nameText = $row.find(".name").text().toLowerCase(); const tags = []; $row.find(".tags .tag").each((_, tag) => tags.push($(tag).text().toLowerCase())); const sizeMB = parseInt($row.attr("data-size"), 10) || 0; const files = parseInt($row.attr("data-files"), 10) || 0; const dateStr = $row.attr("data-date") || ""; return { row, $row, nameText, date: this.parseDateFromAttr(dateStr), dateStr, size: sizeMB * 1024 * 1024, files, tags, is4K: nameText.includes("4k") || nameText.includes("[4k]") || tags.includes("4k"), isHD: nameText.includes("高清") || tags.includes("高清"), isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi") || tags.includes("字幕"), isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc") || tags.includes("无码") || tags.includes("無碼") }; } getRowInfoForBus(row) { const $row = $(row); const $nameCell = $row.find("td:first-child"); const nameText = ($nameCell.find("a:first-child").text() || "").toLowerCase().trim(); const sizeText = ($row.find("td:nth-child(2)").text() || "").trim(); const dateText = ($row.find("td:nth-child(3)").text() || "").trim(); const hasHDButton = $nameCell.find("a.btn-primary").text().includes("高清"); return { row, $row, nameText, date: this.parseDateFromAttr(dateText), dateStr: dateText, size: this.parseSizeToBytes(sizeText), files: 0, tags: [], is4K: nameText.includes("4k") || nameText.includes("[4k]"), isHD: hasHDButton || nameText.includes("高清"), isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi"), isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc") }; } async applySortAndFilter(settings) { if (isJavBus$1) { return this.applySortAndFilterForBus(settings); } if (!isJavDb$1) return; const $container = $("#magnets-content"); const $rows = $container.find(".item[data-rank]"); if (0 === $rows.length) return; const rowInfos = $rows.toArray().map((row) => this.getRowInfo(row)); const { filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder } = settings; const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored; let visibleInfos = rowInfos; if (anyFilterEnabled) { visibleInfos = rowInfos.filter((info) => { if (filterHD && info.isHD) return true; if (filter4K && info.is4K) return true; if (filterSubtitle && info.isSubtitle) return true; if (filterUncensored && info.isUncensored) return true; return false; }); } if (sortOrder && sortOrder.length > 0) { visibleInfos.sort((a, b) => { for (const key of sortOrder) { let cmp = 0; switch (key) { case "date": cmp = b.date - a.date; break; case "size": cmp = b.size - a.size; break; case "files": cmp = a.files - b.files; break; } if (0 !== cmp) return cmp; } return 0; }); } const container = document.getElementById("magnets-content"); if (!container) return; const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info)); const reviewsContainer = document.getElementById("reviewsContainer"); const insertRef = reviewsContainer && reviewsContainer.parentNode === container ? reviewsContainer.previousElementSibling || reviewsContainer : null; const fragment = document.createDocumentFragment(); visibleInfos.forEach((info) => fragment.appendChild(info.row)); hiddenInfos.forEach((info) => fragment.appendChild(info.row)); if (insertRef) { container.insertBefore(fragment, insertRef); } else { container.appendChild(fragment); } visibleInfos.forEach((info) => { if (info.is4K) { info.$row.addClass("magnet-4k-highlight"); } }); hiddenInfos.forEach((info) => $(info.row).hide()); } applySortAndFilterForBus(settings) { const $table = $("#magnet-table"); if (!$table.length) return; const allRows = $table.find("tr").toArray(); const headerRow = allRows.find((r) => $(r).find("td").length === 0 || $(r).css("font-weight") === "bold" || $(r).css("fontWeight") === "bold" || $(r).css("font-weight") === "700"); const dataRows = headerRow ? allRows.filter((r) => r !== headerRow) : allRows.slice(1); if (0 === dataRows.length) return; const rowInfos = dataRows.map((row) => this.getRowInfoForBus(row)); const { filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder } = settings; const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored; let visibleInfos = rowInfos; if (anyFilterEnabled) { visibleInfos = rowInfos.filter((info) => { if (filterHD && info.isHD) return true; if (filter4K && info.is4K) return true; if (filterSubtitle && info.isSubtitle) return true; if (filterUncensored && info.isUncensored) return true; return false; }); } if (sortOrder && sortOrder.length > 0) { visibleInfos.sort((a, b) => { for (const key of sortOrder) { let cmp = 0; switch (key) { case "date": cmp = b.date - a.date; break; case "size": cmp = b.size - a.size; break; } if (0 !== cmp) return cmp; } return 0; }); } const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info)); const fragment = document.createDocumentFragment(); visibleInfos.forEach((info) => fragment.appendChild(info.row)); hiddenInfos.forEach((info) => fragment.appendChild(info.row)); const insertAnchor = headerRow || $table.find("tr").first()[0]; if (insertAnchor && insertAnchor.nextSibling) { $(insertAnchor).after(fragment); } else { $table.append(fragment); } visibleInfos.forEach((info) => { if (info.is4K) { info.$row.addClass("magnet-4k-highlight"); } }); hiddenInfos.forEach((info) => $(info.row).hide()); } showAll() { if (isJavDb$1) { $("#magnets-content .item[data-rank]").toArray().forEach(((el) => { $(el).show(); $(el).removeClass("magnet-4k-highlight"); })); } if (isJavBus$1) { $("#magnet-table tr").toArray().forEach(((el) => { $(el).show(); $(el).removeClass("magnet-4k-highlight"); })); } } handleBus() { isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => { const rows2 = $("#magnet-table tr"), QUALITY_KEYWORDS = ["4k", "-c", "-u", "-uc"]; let hasHighQuality = false; rows2.each(((_, row) => { const $row = $(row), $firstTd = $row.find("td:first-child"), $firstLink = $firstTd.find("a:first-child"), $secondLink = $firstTd.find("a:nth-child(2)"), linkText = $firstLink.text().toLowerCase(); linkText.includes("4k") && $firstLink.css("color", "#f40"); if (QUALITY_KEYWORDS.some(((keyword) => linkText.includes(keyword))) || $secondLink.length && $secondLink.text().includes("字幕")) { hasHighQuality = true; $row.addClass("high-quality"); } })); hasHighQuality ? rows2.each(((_, row) => { const $row = $(row); $row.hasClass("high-quality") || $row.hide(); })) : $("#enable-magnets-filter").addClass("do-hide"); })); } } class FoldCategoryPlugin extends BasePlugin { getName() { return "FoldCategoryPlugin"; } async initCss() { const settingObj = await storageManager.getSetting(); return ` <style> #tags a.tag, .tags a.tag { position:relative; } .highlight-btn { position: absolute; top: -10px; right: -10px; background-color: #4CAF50; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; font-size: 14px; line-height: 24px; text-align: center; cursor: pointer; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); display: none; z-index: 999; } /* 当父元素被高亮时,按钮变为其他颜色 */ .highlighted .highlight-btn { background-color: #FF5722; } /* 高亮状态下的标签样式 */ .highlighted { /* 浅黄色 */ border: ${settingObj.highlightedTagNumber || 1}px solid ${settingObj.highlightedTagColor || "#ce2222"} !important; } </style> `; } async handle() { if (window.isListPage && !currentHref$1.includes("advanced_search")) { this.highlightTag(); utils.loopDetector((() => $("#waitCheckBtn").length), (() => { this.createFoldBtn(); }), 1, 1e4, true); $("#tags .tag-category .tag-expand").each(((index, el) => { $(el).parent().hasClass("collapse") && el.click(); })); } } highlightTag() { (async () => { const tags = await storageManager.getHighlightedTags(); tags && tags.forEach(((text) => { $(`#tags a.tag:contains(${text})`).addClass("highlighted"); $(`.tags a.tag:contains(${text})`).addClass("highlighted"); })); })().then(); $("#tags a.tag, .tags a.tag").hover((function() { const $tag = $(this), button = $('<button class="highlight-btn" title="高亮显示">★</button>'); $tag.append(button); button.fadeIn(0); }), (function() { $(this).find(".highlight-btn").fadeOut(0, (function() { $(this).remove(); })); })); $(document).on("click", ".highlight-btn", (async function(e) { e.stopPropagation(); e.preventDefault(); const $tag = $(this).closest("a.tag"), clonedTag = $tag.clone(); clonedTag.find(".highlight-btn").remove(); const tagText = clonedTag.text().trim().replace(/\s*\(\d+\)$/, ""); let highlightedTags = await storageManager.getHighlightedTags(); if (highlightedTags.includes(tagText)) { highlightedTags = highlightedTags.filter(((item) => item !== tagText)); $tag.removeClass("highlighted"); } else { highlightedTags.push(tagText); $tag.addClass("highlighted"); } await storageManager.setHighlightedTags(highlightedTags); })); } async createFoldBtn() { const foldCategoryHotKey = await storageManager.getSetting("foldCategoryHotKey"); let $subTags = $("#tags"), checkTagStr = $("#tags dl div.tag.is-info").map((function() { return $(this).text().replaceAll("\n", "").replaceAll(" ", ""); })).get().join(" "); if (!checkTagStr) return; $(".tabs").append(` <div style="display: flex;align-items: center;flex-grow:1;justify-content: flex-end;"> <div>已选分类: <span id="jhs-check-tag">${checkTagStr}</span></div> <a class="menu-btn main-tab-btn" id="foldCategoryBtn" style="background-color:#d23e60 !important;"> <span></span> ${foldCategoryHotKey ? ` (${foldCategoryHotKey})` : ""} <i style="margin-left: 10px"></i> </a> </div> `); let $section = $("h2.section-title"); if ($section.length > 0) { $section.append('\n <div id="foldCategoryBtn">\n <a class="menu-btn" style="background-color:#d23e60 !important;margin-left: 20px;border-bottom:none !important;border-radius:3px;">\n <span></span>\n <i style="margin-left: 10px"></i>\n </a>\n </div>\n '); $subTags = $("section > div > div.box"); } if (!$subTags) return; let $foldCategoryBtn = $("#foldCategoryBtn"), isFolded = localStorage.getItem("jhs_foldCategory") === YES$1, [newText, newIcon] = isFolded ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; $foldCategoryBtn.find("span").text(newText).end().find("i").attr("class", newIcon); window.location.href.includes("noFold=1") || $subTags[isFolded ? "hide" : "show"](); $foldCategoryBtn.on("click", (async (event) => { event.preventDefault(); isFolded = !isFolded; localStorage.setItem("jhs_foldCategory", isFolded ? YES$1 : NO$1); const [newText2, newIcon2] = isFolded ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; $foldCategoryBtn.find("span").text(newText2).end().find("i").attr("class", newIcon2); $subTags[isFolded ? "hide" : "show"](); })); } } class ActressInfoPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "apiUrl", "https://ja.wikipedia.org/wiki/"); } getName() { return "ActressInfoPlugin"; } async handle() { "yes" === await storageManager.getSetting("enableLoadActressInfo", "yes") && this.loadActressInfo(); } loadActressInfo() { this.handleDetailPage().then(); this.handleStarPage().then(); } async initCss() { return "\n <style>\n .info-tag {\n background-color: #ecf5ff;\n display: inline-block;\n height: 32px;\n padding: 0 10px;\n line-height: 30px;\n font-size: 12px;\n color: #409eff;\n border: 1px solid #d9ecff;\n border-radius: 4px;\n box-sizing: border-box;\n white-space: nowrap;\n }\n </style>\n "; } async handleDetailPage() { if ($(".actress-info").length > 0) return; let nameList = $(".female").prev().map(((i, el) => $(el).text().trim())).get(); if (!nameList.length) return; const cacheKey = "jhs_actress_info", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; let result = null, infoHtml = ""; for (let i = 0; i < nameList.length; i++) { let name2 = nameList[i]; result = cacheData[name2]; if (!result) try { result = await this.searchInfo(name2); result && (cacheData[name2] = result); } catch (e) { console.error("该名称查询失败,尝试其它名称"); } let contentHtml = ""; contentHtml = result ? ` <div class="panel-block actress-info"> <strong>${name2}:</strong> <a href="${result.url}" style="margin-left: 5px" target="_blank"> <span class="info-tag">${result.birthday} ${result.age}</span> <span class="info-tag">${result.height} ${result.weight}</span> <span class="info-tag">${result.threeSizeText} ${result.braSize}</span> </a> </div> ` : `<div class="panel-block actress-info"><a href="${this.apiUrl + name2}" target="_blank"><strong>${name2}:</strong></a></div> `; infoHtml += contentHtml; } $('strong:contains("演員")').parent().after(infoHtml); localStorage.setItem(cacheKey, JSON.stringify(cacheData)); } async handleStarPage() { if ($(".actress-info").length > 0) return; let nameList = [], $actor = $(".actor-section-name"); $actor.length && $actor.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let $sectionMeta = $(".section-meta:not(:contains('影片'))"); $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); if (!nameList.length) return; const cacheKey = "jhs_actress_info", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; let result = null; for (let i = 0; i < nameList.length; i++) { let name2 = nameList[i]; result = cacheData[name2]; if (result) break; try { result = await this.searchInfo(name2); } catch (e) { console.error("该名称查询失败,尝试其它名称"); } if (result) break; } result && nameList.forEach(((name2) => { cacheData[name2] = result; })); let contentHtml = '<div class="actress-info" style="font-size: 17px; font-weight: normal; margin-top: 5px;">无此相关演员信息</div>'; result && (contentHtml = ` <a class="actress-info" href="${result.url}" target="_blank"> <div style="font-size: 17px; font-weight: normal; margin-top: 5px;"> <div style="display: flex; margin-bottom: 10px;"> <span style="width: 300px;">出生日期: ${result.birthday}</span> <span style="width: 200px;">年龄: ${result.age}</span> <span style="width: 200px;">身高: ${result.height}</span> </div> <div style="display: flex; margin-bottom: 10px;"> <span style="width: 300px;">体重: ${result.weight}</span> <span style="width: 200px;">三围: ${result.threeSizeText}</span> <span style="width: 200px;">罩杯: ${result.braSize}</span> </div> </div> </a> `); $actor.parent().append(contentHtml); localStorage.setItem(cacheKey, JSON.stringify(cacheData)); } async searchInfo(name2) { "三上悠亞" === name2 && (name2 = "三上悠亜"); let url = this.apiUrl + name2; const html2 = await gmHttp.get(url), parser = new DOMParser(), $dom = $(parser.parseFromString(html2, "text/html")); let birthday = $dom.find('a[title="誕生日"]').parent().parent().find("td").text().trim(), age = $dom.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt($dom.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", height = $dom.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", weight = $dom.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim(); "― kg" === weight && (weight = ""); return { birthday, age, height, weight, threeSizeText: $dom.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(), braSize: $dom.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(), url }; } } class AliyunPanPlugin extends BasePlugin { getName() { return "AliyunPanPlugin"; } handle() { $("body").append('<a class="a-success" id="refresh-token-btn" style="position:fixed; right: 0; top:50%;z-index:99999">获取refresh_token</a>'); $("#refresh-token-btn").on("click", ((event) => { let tokenStr = localStorage.getItem("token"); if (!tokenStr) { alert("请先登录!"); return; } let refresh_token = JSON.parse(tokenStr).refresh_token; navigator.clipboard.writeText(refresh_token).then((() => { alert("已复制到剪切板 如失败, 请手动复制: " + refresh_token); })).catch(((err) => { console.error("Failed to copy refresh token: ", err); })); })); } } class HitShowPlugin extends BasePlugin { constructor() { super(); __publicField2(this, "$contentBox", $(".section .container")); } getName() { return "HitShowPlugin"; } handle() { $('a[href*="rankings/playback"]').on("click", ((event) => { event.preventDefault(); event.stopPropagation(); window.location.href = "/rankings/playback"; })); this.handlePlayback().then(); } hookPage() { let $h2 = $("h2.section-title"); $h2.contents().first().replaceWith("热播"); $h2.css("marginBottom", "0"); $(".empty-message").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'); this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>'); } async handlePlayback() { if (!window.location.href.includes("rankings/playback")) return; let period = new URLSearchParams(window.location.search).get("period"); this.toolBar(period); this.hookPage(); let $movieBox = $(".movie-list"); $movieBox.html(""); let loadObj = loading(); try { const movies = await javDbApi.playback(period); let moviesHtml = javDbApi.markDataListHtml(movies); $movieBox.html(moviesHtml); this.pluginManager.getBean("ListPagePlugin").doFilter(); } catch (e) { clog.error("发生错误:", e); } finally { loadObj.close(); } } toolBar(period) { let conditionHtml = ` <div class="button-group" style="margin-top:18px"> <div class="buttons has-addons" id="conditionBox"> <a style="padding:18px 18px !important;" class="button is-small ${"daily" === period ? "is-info" : ""}" href="/rankings/playback?period=daily">日榜</a> <a style="padding:18px 18px !important;" class="button is-small ${"weekly" === period ? "is-info" : ""}" href="/rankings/playback?period=weekly">周榜</a> <a style="padding:18px 18px !important;" class="button is-small ${"monthly" === period ? "is-info" : ""}" href="/rankings/playback?period=monthly">月榜</a> </div> </div> `; this.$contentBox.append(conditionHtml); } getStarRating(score) { let stars = ""; const fullStars = Math.floor(score); for (let i = 0; i < fullStars; i++) stars += '<i class="icon-star"></i>'; for (let i = 0; i < 5 - fullStars; i++) stars += '<i class="icon-star gray"></i>'; return stars; } loadScore(movies) { if (0 === movies.length) return; (async () => { for (const movie of movies) try { const movieId = movie.id; if (!$(`#score_${movieId}`).length) return; if ($(`#${movieId}`).is(":hidden")) continue; const cacheData = localStorage.getItem("jhs_score_info") ? JSON.parse(localStorage.getItem("jhs_score_info")) : {}, cached = cacheData[movieId]; if (cached) { this.appendScoreHtml(movieId, cached); continue; } for (; !document.hasFocus(); ) await new Promise(((r) => setTimeout(r, 500))); const res = await javDbApi.getMovieDetail(movieId); let score = res.score, watchedCount = res.watchedCount, html2 = ` <span class="value"> <span class="score-stars">${this.getStarRating(score)}</span> ${score}分,由${watchedCount}人評價 </span> `; this.appendScoreHtml(movieId, html2); cacheData[movieId] = html2; localStorage.setItem("jhs_score_info", JSON.stringify(cacheData)); await new Promise(((r) => setTimeout(r, 500))); } catch (err) { clog.error(`🚨 解析评分数据失败 | 编号: ${movie.number} `, `错误详情: ${err.message} `, err.stack ? `调用栈: ${err.stack}` : ""); } })(); } appendScoreHtml(movieId, scoreHtml) { let $scoreBox = $(`#score_${movieId}`); $scoreBox.length && "" === $scoreBox.html().trim() && $scoreBox.slideUp(0, (function() { $(this).html(scoreHtml).slideDown(500); })); } } class TOP250Plugin extends BasePlugin { constructor() { super(); __publicField2(this, "has_cnsub", ""); __publicField2(this, "$contentBox", $(".section .container")); __publicField2(this, "movies", []); } getName() { return "TOP250Plugin"; } handle() { $('.main-tabs ul li:contains("猜你喜歡")').html('<a href="/rankings/top"><span>Top250</span></a>'); $('a[href*="rankings/top"]').on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $target = $(event.target), href = ($target.is("a") ? $target : $target.closest("a")).attr("href"); let queryString = href.includes("?") ? href.split("?")[1] : href; const urlParams = new URLSearchParams(queryString); this.checkLogin(event, urlParams); })); this.handleTop().then(); } hookPage() { $("h2.section-title").contents().first().replaceWith("Top250"); $(".empty-message").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'); this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>'); this.renderPagination(); } renderPagination() { const urlParams = new URLSearchParams(window.location.search); let currentPage = parseInt(urlParams.get("page")) || 1; this.$contentBox.append(((page) => { const isNextDisabled = page >= 5; let paginationListHTML = ""; for (let i = 1; i <= 5; i++) { paginationListHTML += `<li><a class="pagination-link ${page === i ? "is-current" : ""}" data-page="${i}">${i}</a></li>`; } return ` <nav class="pagination"> <a class="pagination-previous ${page <= 1 ? "do-hide" : ""}" data-page="${page - 1}">上一頁</a> <a class="pagination-next ${isNextDisabled ? "do-hide" : ""}" data-page="${page + 1}">下一頁</a> <ul class="pagination-list"> ${paginationListHTML} </ul> </nav> `; })(currentPage)); this.$contentBox.on("click", ".pagination-link, .pagination-previous, .pagination-next", ((event) => { event.preventDefault(); const newPage = parseInt($(event.currentTarget).data("page")); !isNaN(newPage) && newPage > 0 && ((newPage2) => { urlParams.set("page", newPage2); window.history.pushState({}, "", "?" + urlParams.toString()); window.location.reload(); })(newPage); })); } async handleTop() { if (!window.location.href.includes("/rankings/top")) return; const urlParams = new URLSearchParams(window.location.search); let t = urlParams.get("t") || "", type = "all", type_value = ""; if (/^y\d+$/.test(t)) { type = "year"; type_value = t.substring(1); } else if ("" !== t) { type = "video_type"; type_value = t; } this.has_cnsub = urlParams.get("has_cnsub") || ""; let page = urlParams.get("page") || 1; this.toolBar(type, type_value, page); this.hookPage(); let $movieBox = $(".movie-list"); $movieBox.html(""); let loadObj = loading(); try { const res = await javDbApi.top250(type, type_value, page, 50); let successFlag = res.success, message = res.message, action = res.action; if (1 === successFlag) { let dataList = res.data.movies; if (0 === dataList.length) { show.error("无数据"); loadObj.close(); return; } this.movies = dataList; const filter_movies = dataList.filter(((item) => "1" === this.has_cnsub ? item.has_cnsub : "0" !== this.has_cnsub || !item.has_cnsub)); let moviesHtml = javDbApi.markDataListHtml(filter_movies); $movieBox.html(moviesHtml); this.pluginManager.getBean("ListPagePlugin").doFilter(); } else { clog.error(res); $movieBox.html(`<h3>${message}</h3>`); show.error(message); if ("JWTVerificationError" === action) { await localStorage.removeItem("jhs_appAuthorization"); await this.checkLogin(null, new URLSearchParams(window.location.search)); } } } catch (e) { clog.error("发生错误:", e); } finally { loadObj.close(); } } toolBar(type, type_value, currentPage) { "5" === currentPage.toString() && $(".pagination-next").remove(); $(".pagination-ellipsis").closest("li").remove(); $(".pagination-list li a").each((function() { parseInt($(this).text()) > 5 && $(this).closest("li").remove(); })); let yearHtml = ""; for (let year = (/* @__PURE__ */ new Date()).getFullYear(); year >= 2008; year--) yearHtml += ` <a style="padding:18px 18px !important;" class="button is-small ${type_value === year.toString() ? "is-info" : ""}" href="/rankings/top?t=y${year}&has_cnsub=${this.has_cnsub}"> ${year} </a> `; let conditionHtml = ` <div class="button-group"> <div class="buttons has-addons" id="conditionBox" style="margin-bottom: 0!important;"> <a style="padding:18px 18px !important;" class="button is-small ${"all" === type ? "is-info" : ""}" href="/rankings/top?has_cnsub=${this.has_cnsub}">全部</a> <a style="padding:18px 18px !important;" class="button is-small ${"0" === type_value ? "is-info" : ""}" href="/rankings/top?t=0&has_cnsub=${this.has_cnsub}">有码</a> <a style="padding:18px 18px !important;" class="button is-small ${"1" === type_value ? "is-info" : ""}" href="/rankings/top?t=1&has_cnsub=${this.has_cnsub}">无码</a> <a style="padding:18px 18px !important;" class="button is-small ${"2" === type_value ? "is-info" : ""}" href="/rankings/top?t=2&has_cnsub=${this.has_cnsub}">欧美</a> <a style="padding:18px 18px !important;" class="button is-small ${"3" === type_value ? "is-info" : ""}" href="/rankings/top?t=3&has_cnsub=${this.has_cnsub}">Fc2</a> <a style="padding:18px 18px !important;margin-left: 50px" class="button is-small ${"1" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="1">含中字磁鏈</a> <a style="padding:18px 18px !important;" class="button is-small ${"0" === this.has_cnsub ? "is-info" : ""}" data-cnsub-value="0">无字幕</a> <a style="padding:18px 18px !important;" class="button is-small" data-cnsub-value="">重置</a> </div> <div class="buttons has-addons" id="conditionBox"> ${yearHtml} </div> </div> `; this.$contentBox.append(conditionHtml); $("a[data-cnsub-value]").on("click", ((event) => { const cnsubValue = $(event.currentTarget).data("cnsub-value"); this.has_cnsub = cnsubValue.toString(); $("a[data-cnsub-value]").removeClass("is-info"); $(event.currentTarget).addClass("is-info"); $(".toolbar a.button").not("[data-cnsub-value]").each(((index, element) => { const $link = $(element), url = new URL($link.attr("href"), window.location.origin); url.searchParams.set("has_cnsub", cnsubValue); $link.attr("href", url.toString()); })); const filter_movies = this.movies.filter(((item) => "1" === this.has_cnsub ? item.has_cnsub : "0" !== this.has_cnsub || !item.has_cnsub)); let moviesHtml = javDbApi.markDataListHtml(filter_movies); $(".movie-list").html(moviesHtml); })); } async checkLogin(event, urlParams) { if (!localStorage.getItem("jhs_appAuthorization")) { show.error("该类别依赖移动端接口,请先完成登录"); this.openLoginDialog(); return; } let type = "all", type_value = "", t = urlParams.get("t") || ""; if (/^y\d+$/.test(t)) { type = "year"; type_value = t.substring(1); } else if ("" !== t) { type = "video_type"; type_value = t; } let tParam = "year" === type ? `y${type_value}` : "all" === type ? "" : type_value; let url = `/rankings/top${tParam ? `?t=${tParam}` : ""}`; event && (event.ctrlKey || event.metaKey) ? _GM_openInTab(window.location.origin + url, { insert: 0 }) : window.location.href = url; } openLoginDialog() { layer.open({ type: 1, title: "JavDB", closeBtn: 1, area: ["360px", "auto"], shadeClose: false, content: ` <div style="padding: 30px; font-family: 'Helvetica Neue', Arial, sans-serif;"> <div style="margin-bottom: 25px;"> <input type="text" id="username" name="username" style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; box-sizing: border-box; transition: all 0.3s; font-size: 14px; background: #f9f9f9; color: #333;" placeholder="用户名 | 邮箱" onfocus="this.style.borderColor='#4a8bfc'; this.style.background='#fff'" onblur="this.style.borderColor='#e0e0e0'; this.style.background='#f9f9f9'"> </div> <div style="margin-bottom: 15px;"> <input type="password" id="password" name="password" style="width: 100%; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 4px; box-sizing: border-box; transition: all 0.3s; font-size: 14px; background: #f9f9f9; color: #333;" placeholder="密码" onfocus="this.style.borderColor='#4a8bfc'; this.style.background='#fff'" onblur="this.style.borderColor='#e0e0e0'; this.style.background='#f9f9f9'"> </div> <button id="loginBtn" style="width: 100%; padding: 12px; background: #4a8bfc; color: white; border: none; border-radius: 4px; font-size: 15px; cursor: pointer; transition: background 0.3s;" onmouseover="this.style.background='#3a7be0'" onmouseout="this.style.background='#4a8bfc'"> 登录 </button> </div> `, success: (layero, index) => { $("#loginBtn").click((function() { const username = $("#username").val(), password = $("#password").val(); if (!username || !password) { show.error("请输入用户名和密码"); return; } let loadObj = loading(); javDbApi.login(username, password).then((async (res) => { let success = res.success; if (0 === success) show.error(res.message); else { if (1 !== success) { clog.error("登录失败", res); throw new Error(res.message); } { let token = res.data.token; await localStorage.setItem("jhs_appAuthorization", token); show.ok("登录成功"); layer.close(index); window.location.href = "/rankings/top"; } } })).catch(((err) => { clog.error("登录异常:", err); show.error(err.message); })).finally((() => { loadObj.close(); })); })); } }); } } class NavBarPlugin extends BasePlugin { getName() { return "NavBarPlugin"; } async initCss() { return "\n .highlight-red {\n /* 核心要求:高亮红色文本 */\n color: red !important; \n \n /* 建议:增加字体加粗,效果更明显 */\n font-weight: bold;\n \n /* 建议:增加背景色,效果更突出 */\n /* background-color: yellow; */ \n}\n "; } handle() { this.margeNav(); this.hookSearch(); this.hookOldSearch(); this.toggleOtherNavItem(); $(window).resize(this.toggleOtherNavItem); if (window.location.href.includes("/search")) { const urlParams = new URLSearchParams(window.location.search); let q = urlParams.get("q"), f = urlParams.get("f"); $("#search-keyword").val(q); f && $("#search-type").val(f); q && this.highlightKeyword(q); } } highlightKeyword(keyword) { const trimedKeyword = keyword.trim(); if (!trimedKeyword) return; const lowerCaseKeyword = trimedKeyword.toLowerCase(); $(".video-title strong, .actor-box strong").each((function() { const $strongElement = $(this); $strongElement.text().toLowerCase().includes(lowerCaseKeyword) && $strongElement.addClass("highlight-red"); })); } hookSearch() { $("#navbar-menu-hero").after('\n <div class="navbar-menu" id="search-box">\n <div class="navbar-start" style="display: flex; align-items: center; gap: 5px;">\n <select id="search-type" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; background-color: #333; color: #eee; font-size: 14px; outline: none;">\n <option value="all">影片</option>\n <option value="actor">演員</option>\n <option value="series">系列</option>\n <option value="maker">片商</option>\n <option value="director">導演</option>\n <option value="code">番號</option>\n <option value="list">清單</option>\n </select>\n <input id="search-keyword" type="text" placeholder="輸入影片番號,演員名等關鍵字進行檢索" style="padding: 8px 12px; border: 1px solid #555; border-radius: 4px; flex-grow: 1; font-size: 14px; background-color: #333; color: #eee; outline: none;">\n <a href="/advanced_search?noFold=1" title="進階檢索" style="padding: 6px 12px; background-color: #444; border-radius: 4px; text-decoration: none; color: #ddd; font-size: 14px; border: 1px solid #555;"><span>...</span></a>\n <a id="search-img-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">识图</a>\n <a id="search-btn" style="padding: 6px 16px; background-color: #444; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 500; cursor: pointer; border: 1px solid #555;">檢索</a>\n </div>\n </div>\n '); $("#search-keyword").on("paste", ((event) => { const items = event.originalEvent.clipboardData.items; for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) { const blob = items[i].getAsFile(); $("#search-keyword").blur(); const imageRecognitionPlugin = this.getBean("ImageRecognitionPlugin"); imageRecognitionPlugin.open((() => { imageRecognitionPlugin.handleImageFile(blob); imageRecognitionPlugin.resetSearchUI(); })); return; } setTimeout((() => { $("#search-btn").click(); }), 0); })).on("keypress", ((event) => { "Enter" === event.key && setTimeout((() => { $("#search-btn").click(); }), 0); })); $("#search-btn").on("click", ((event) => { let keyword = $("#search-keyword").val(), searchCurrentType = $("#search-type option:selected").val(); "" !== keyword && (window.location.href.includes("/search") ? window.location.href = "/search?q=" + keyword + "&f=" + searchCurrentType : window.open("/search?q=" + keyword + "&f=" + searchCurrentType)); })); $("#search-img-btn").on("click", (() => { this.getBean("ImageRecognitionPlugin").open(); })); } hookOldSearch() { const searchImage = document.querySelector(".search-image"); if (!searchImage) return; const clonedImage = searchImage.cloneNode(true); searchImage.parentNode.replaceChild(clonedImage, searchImage); $("#button-search-image").attr("data-tooltip", "以图识图"); $(".search-image").on("click", ((event) => { this.getBean("ImageRecognitionPlugin").open(); })); } margeNav() { $('a[href*="/feedbacks/new"]').remove(); $('a[href*="theporndude.com"]').remove(); $('a.navbar-link[href="/makers"]').parent().after('\n <div class="navbar-item has-dropdown is-hoverable">\n <a class="navbar-link">其它</a>\n <div class="navbar-dropdown is-boxed">\n <a class="navbar-item" href="/feedbacks/new" target="_blank" >反饋</a>\n <a class="navbar-item" rel="nofollow noopener" target="_blank" href="https://theporndude.com/zh">ThePornDude</a>\n </div>\n </div>\n '); } toggleOtherNavItem() { let $searchBox = $("#search-box"), $oldSearchBox = $("#search-bar-container"); if ($(window).width() < 1600 && $(window).width() > 1023) { $searchBox.hide(); $oldSearchBox.show(); } if ($(window).width() > 1600) { $searchBox.show(); $oldSearchBox.hide(); } } } class BusDetailPagePlugin extends BasePlugin { getName() { return "BusDetailPagePlugin"; } async initCss() { if (!window.isDetailPage) return ""; $("h4:contains('推薦')").hide(); } async handle() { if (window.location.href.includes("/star/")) { const $avatarBox = $(".avatar-box"); if ($avatarBox.length > 0) { let parent2 = $avatarBox.parent(); parent2.css("position", "initial"); parent2.insertBefore(parent2.parent()); } } $(".genre a").each((function() { const href2 = $(this).attr("href"); href2 && (href2.startsWith("http://") || href2.startsWith("https://") || href2.startsWith("/")) && $(this).attr("target", "_blank"); })); this.addCopyCarNumBtn(); } addCopyCarNumBtn() { let headerSpan = null; const headerSpans = document.querySelectorAll("span.header"); for (const span of headerSpans) if ("識別碼:" === span.textContent.trim()) { headerSpan = span; break; } if (headerSpan) { const targetSpan = headerSpan.nextElementSibling; if (targetSpan && "SPAN" === targetSpan.tagName) { const identifierText = targetSpan.textContent.trim(), copyButton = document.createElement("button"); copyButton.textContent = "复制"; copyButton.style.marginLeft = "10px"; copyButton.style.padding = "0 10px"; copyButton.style.cursor = "pointer"; copyButton.style.border = "1px solid #ccc"; copyButton.style.borderRadius = "5px"; copyButton.style.backgroundColor = "#f0f0f0"; copyButton.style.fontSize = "12px"; copyButton.addEventListener("click", (function(event) { event.preventDefault(); const copyAction = (text) => { this.textContent = "已复制"; setTimeout((() => { this.textContent = "复制"; }), 1500); }; navigator.clipboard && navigator.clipboard.writeText && navigator.clipboard.writeText(identifierText).then((() => copyAction())).catch(((err) => { console.error("无法通过标准API复制:", err); alert("复制失败,请手动复制: " + identifierText); })); })); targetSpan.parentNode.insertBefore(copyButton, targetSpan.nextSibling); } } } } class BusPreviewVideoPlugin extends BasePlugin { getName() { return "BusPreviewVideoPlugin"; } async initCss() { return "\n /* 弹窗/Modal 样式 */\n .bus-preview-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.95); \n /* 关键修改:更新 z-index */\n z-index: 12345699; \n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0; \n visibility: hidden; \n transition: opacity 0.2s ease;\n }\n .bus-preview-modal.is-open {\n opacity: 1;\n visibility: visible;\n }\n /* 垂直排列视频和按钮,并居中 */\n .bus-preview-modal-content {\n position: relative;\n max-width: 95%; \n max-height: 95%;\n display: flex; \n flex-direction: column; \n align-items: center; \n gap: 15px; \n }\n \n /* 移除 .bus-preview-close-btn 的样式 */\n\n /* 视频播放器容器 */\n .video-player-wrapper {\n /* 关键修改:更新 width 和 max-height */\n width: 80vw; \n max-height: 85vh; \n aspect-ratio: 16 / 9; \n position: relative; \n background-color: black; \n max-width: 100%; \n }\n /* 视频元素 */\n .video-player-wrapper #preview-video {\n position: absolute; \n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: block;\n }\n\n /* 画质控制盒 (底部按钮) */\n .video-control-box {\n display: flex;\n flex-direction: row; \n justify-content: center; \n flex-wrap: wrap; \n gap: 10px;\n padding: 10px 0; \n }\n\n /* 按钮样式 (保留) */\n .video-control-btn {\n min-width:80px;\n padding: 6px 12px;\n background: rgba(255,255,255,0.2);\n color: white;\n border: 1px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n cursor: pointer;\n text-align: center;\n font-size: 14px;\n transition: background-color 0.2s, border-color 0.2s;\n }\n .video-control-btn:hover {\n background: rgba(255,255,255,0.4);\n }\n .video-control-btn.active {\n background-color: #1890ff; \n color: white;\n font-weight: bold;\n border: 1px solid #096dd9;\n }\n "; } initModal() { if (0 === $("#bus-preview-modal").length) { $("body").append('\n <div id="bus-preview-modal" class="bus-preview-modal">\n <div class="bus-preview-modal-content">\n </div>\n </div>\n '); const $modal = $("#bus-preview-modal"); $modal.on("click", ((e) => { "bus-preview-modal" === e.target.id && this.closeVideoModal(); })); $(document).on("keydown", ((e) => { "Escape" === e.key && $modal.hasClass("is-open") && this.closeVideoModal(); })); } } closeVideoModal() { const $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { const videoEl = $previewVideo[0]; videoEl.pause(); } $("#bus-preview-modal").removeClass("is-open"); } async handle() { if (!window.isDetailPage) return; this.initModal(); const firstImageSrc = $("#sample-waterfall .sample-box .photo-frame img:first").attr("src"), videoPreview = $(` <a class="preview-video-container sample-box" style="cursor: pointer"> <div class="photo-frame" style="position:relative;"> <img src="${firstImageSrc}" class="video-cover" alt=""> <div class="play-icon" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:white; font-size:40px; text-shadow:0 0 10px rgba(0,0,0,0.5);"> ▶ </div> </div> </a>`); $("#sample-waterfall").prepend(videoPreview); "yes" === await storageManager.getSetting("enableLoadPreviewVideo", "yes") && getDmmVideo(this.getPageInfo().carNum, false).then(); let isHandlingVideo = false, $preview = $(".preview-video-container"); $preview.on("click", (async (event) => { event.preventDefault(); event.stopPropagation(); if (isHandlingVideo) show.info("正在加载中, 勿重复点击"); else { isHandlingVideo = true; try { await this.handleVideo(); } finally { isHandlingVideo = false; } } })); window.location.href.includes("autoPlay=1") && $preview.trigger("click"); } async handleVideo() { const $modal = $("#bus-preview-modal"), $modalContent = $modal.find(".bus-preview-modal-content"); let $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { $modal.addClass("is-open"); const videoEl = $previewVideo[0]; videoEl.play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e))); return; } let carNum2 = this.getPageInfo().carNum; const dmmVideoMap = await getDmmVideo(carNum2); if (dmmVideoMap && 0 !== Object.keys(dmmVideoMap).length) { await this.createVideoPlayerAndControls(dmmVideoMap, $modalContent); $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { $modal.addClass("is-open"); $previewVideo[0].play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e))); } else show.error("视频播放器创建失败。"); } else show.error("未找到可用的视频源。"); } async createVideoPlayerAndControls(dmmVideoMap, $container) { let defaultVideoQuality = await storageManager.getSetting("videoQuality"); defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality); let defaultVideoUrl = dmmVideoMap[defaultVideoQuality]; $container.html(` <div class="video-player-wrapper"> <video id="preview-video" controls playsinline> <source src="${defaultVideoUrl}" /> </video> </div> <div class="video-control-box"> </div> `); const $videoEl = $("#preview-video"), $qualityControlsBox = $container.find(".video-control-box"); if (!$videoEl.length) return; const videoEl = $videoEl[0]; const jhs_videoMuted = localStorage.getItem("jhs_videoMuted"); videoEl.muted = !jhs_videoMuted || "yes" === jhs_videoMuted; videoEl.addEventListener("volumechange", (function() { localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no"); })); let buttonsHtml = ""; qualityOptions.forEach(((option) => { let dmmVideoUrl = dmmVideoMap[option.quality]; if (dmmVideoUrl) { const isActive = defaultVideoQuality === option.quality; buttonsHtml += ` <button class="video-control-btn${isActive ? " active" : ""}" data-quality="${option.quality}" data-video-src="${dmmVideoUrl}"> ${option.text} </button> `; } })); $qualityControlsBox.html(buttonsHtml); const $buttons = $qualityControlsBox.find(".video-control-btn"); $qualityControlsBox.off("click").on("click", ".video-control-btn", (async (e) => { try { const $button = $(e.currentTarget); if ($button.hasClass("active")) return; let videoSrc = $button.attr("data-video-src"); const currentTime = videoEl.currentTime; videoEl.src = videoSrc; videoEl.load(); if (currentTime > 0 && Number.isFinite(currentTime)) { videoEl.currentTime = currentTime; } await videoEl.play(); $buttons.removeClass("active"); $button.addClass("active"); } catch (error) { console.error("切换画质失败:", error); } })); } } const HLS_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js"; let hlsLoadPromise = null; const getHlsClass = () => { const HlsClass = window.Hls || globalThis.Hls || (typeof Hls !== "undefined" ? Hls : null); return HlsClass && HlsClass.isSupported && HlsClass.isSupported() ? HlsClass : null; }; const binaryTextToArrayBuffer = (value) => { const text = String(value || ""); const bytes = new Uint8Array(text.length); for (let index = 0; index < text.length; index += 1) { bytes[index] = text.charCodeAt(index) & 255; } return bytes.buffer; }; const loadHlsLibrary = () => { const readyHls = getHlsClass(); if (readyHls) return Promise.resolve(readyHls); if (hlsLoadPromise) return hlsLoadPromise; const loadByGm = () => new Promise((resolve) => { _GM_xmlhttpRequest({ method: "GET", url: HLS_SCRIPT_URL, timeout: 15e3, onload: (r) => { if (r.status >= 200 && r.status < 300 && r.responseText) { try { Function(`${r.responseText} //# sourceURL=${HLS_SCRIPT_URL}`).call(globalThis); } catch (err) { clog.error("HLS: hls.js 执行失败", err); } } resolve(getHlsClass()); }, onerror: () => resolve(getHlsClass()), ontimeout: () => resolve(getHlsClass()) }); }); hlsLoadPromise = new Promise((resolve) => { const existing = document.querySelector('script[data-jhs-hls="1"]'); if (existing) { existing.addEventListener("load", () => resolve(getHlsClass()), { once: true }); existing.addEventListener("error", () => loadByGm().then(resolve), { once: true }); setTimeout(() => { if (!getHlsClass()) loadByGm().then(resolve); }, 4e3); return; } const hlsScript = document.createElement("script"); hlsScript.src = HLS_SCRIPT_URL; hlsScript.async = true; hlsScript.dataset.jhsHls = "1"; hlsScript.onload = () => resolve(getHlsClass()); hlsScript.onerror = () => loadByGm().then(resolve); document.head.appendChild(hlsScript); }).then((HlsClass) => { if (!HlsClass) hlsLoadPromise = null; return HlsClass; }); return hlsLoadPromise; }; const createHlsLoader = () => { class GMHlsLoader { constructor(config) { this.config = config; this.context = null; this.callbacks = null; this.loader = null; this.stats = this.createStats(); } createStats() { return { aborted: false, loaded: 0, retry: 0, total: 0, chunkCount: 0, bwEstimate: 0, trequest: 0, tfirst: 0, tload: 0, loading: { start: 0, first: 0, end: 0 }, parsing: { start: 0, end: 0 }, buffering: { start: 0, first: 0, end: 0 } }; } destroy() { this.abort(); } abort() { if (this.stats) this.stats.aborted = true; if (this.loader) { try { this.loader.abort(); } catch (e) { } } this.loader = null; } load(context, config, callbacks) { this.context = context; this.callbacks = callbacks; const requestUrl = context.url; const wantsArrayBuffer = context.responseType === "arraybuffer" || /\.(?:ts|m4s|mp4|key)(?:[?#]|$)/i.test(requestUrl); const startedAt = performance.now(); const stats = this.stats = this.createStats(); stats.trequest = startedAt; stats.tfirst = startedAt; stats.tload = startedAt; stats.loading.start = startedAt; this.loader = _GM_xmlhttpRequest({ method: "GET", url: requestUrl, responseType: "text", overrideMimeType: wantsArrayBuffer ? "text/plain; charset=x-user-defined" : void 0, timeout: config && config.timeout ? config.timeout : 2e4, headers: { Accept: wantsArrayBuffer ? "*/*" : "application/vnd.apple.mpegurl, application/x-mpegURL, */*" }, onprogress: (event) => { stats.loaded = Number(event && event.loaded ? event.loaded : stats.loaded || 0); stats.total = Number(event && event.total ? event.total : stats.total || stats.loaded || 0); if (!stats.loading.first && stats.loaded > 0) { stats.loading.first = performance.now(); } }, onload: (r) => { const status = Number(r.status || 0); const response = { code: status, text: r.statusText || "", url: r.finalUrl || requestUrl }; stats.tfirst = stats.tfirst || performance.now(); stats.tload = performance.now(); stats.loading.first = stats.loading.first || stats.tload; stats.loading.end = stats.tload; if (status < 200 || status >= 300) { callbacks.onError && callbacks.onError(response, context, null, stats); return; } const responseText = r.responseText || r.response || ""; const data = wantsArrayBuffer ? binaryTextToArrayBuffer(responseText) : responseText; stats.loaded = data && data.byteLength ? data.byteLength : data && data.length ? data.length : stats.loaded || 0; stats.total = stats.total || stats.loaded; stats.bwEstimate = stats.loading.end > stats.loading.first ? Math.round(stats.total * 8e3 / (stats.loading.end - stats.loading.first)) : 0; callbacks.onSuccess && callbacks.onSuccess({ data, url: response.url }, stats, context, response); }, onerror: () => { callbacks.onError && callbacks.onError({ code: 0, text: "network error", url: requestUrl }, context, null, stats); }, ontimeout: () => { stats.tload = performance.now(); stats.loading.end = stats.tload; callbacks.onTimeout && callbacks.onTimeout(stats, context, null); } }); } } return GMHlsLoader; }; const isM3U8Url = (url) => /\.m3u8(?:[?#].*)?$/i.test(String(url || "")); const attachHlsToVideo = (videoEl, src) => { return new Promise((resolve, reject) => { if (!videoEl || !src) { reject(new Error("video element or source missing")); return; } const HlsClass = getHlsClass(); if (!HlsClass) { videoEl.src = src; videoEl.load && videoEl.load(); resolve(false); return; } const hls = new HlsClass({ enableWorker: false, lowLatencyMode: true, loader: createHlsLoader(), autoStartLoad: true, startPosition: 0, capLevelToPlayerSize: true, testBandwidth: false, preferManagedMediaSource: false, maxBufferLength: 6, maxMaxBufferLength: 12, backBufferLength: 30, maxBufferHole: 0.5, nudgeOffset: 0.1, manifestLoadingMaxRetry: 2, levelLoadingMaxRetry: 2, fragLoadingMaxRetry: 2, manifestLoadingTimeOut: 12e3, levelLoadingTimeOut: 12e3, fragLoadingTimeOut: 12e3, abrEwmaFastLive: 3, abrEwmaSlowLive: 9 }); hls.on(HlsClass.Events.MANIFEST_PARSED, () => { hls.startLoad(0); videoEl.play().catch(() => { }); resolve(true); }); hls.on(HlsClass.Events.ERROR, (_, data) => { if (!data || !data.fatal) return; clog.error("HLS: 播放失败", data); if (data.type === HlsClass.ErrorTypes.MEDIA_ERROR) { if (videoEl.readyState >= 2) { try { hls.recoverMediaError && hls.recoverMediaError(); } catch (e) { } return; } } reject(new Error(data.details || "HLS fatal error")); }); hls.loadSource(src); hls.attachMedia(videoEl); videoEl._hls = hls; }); }; const destroyHls = (videoEl) => { if (videoEl && videoEl._hls) { try { videoEl._hls.destroy(); } catch (e) { } videoEl._hls = null; } }; const COVER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 70, 74, 83, 64, 87].map((v) => String.fromCharCode(v ^ 37)).join(""); const TRAILER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 81, 87, 68, 76, 73, 64, 87].map((v) => String.fromCharCode(v ^ 37)).join(""); const BASE_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95].map((v) => String.fromCharCode(v ^ 37)).join(""); const COVER_API_TOKEN = [18, 28, 19, 19, 65, 65, 70, 28, 64, 29, 23, 21, 17, 29, 67, 70, 18, 64, 21, 22, 64, 16, 65, 29, 22, 19, 16, 28, 16, 17, 64, 29].map((v) => String.fromCharCode(v ^ 37)).join(""); const getCover = async (code) => { var _a2; const query = String(code || "").trim(); if (!query) return null; try { const r = await gmHttp.get(`${COVER_API_URL}/${encodeURIComponent(query)}`, null, { "Authorization": `Bearer ${COVER_API_TOKEN}` }, { timeout: 15e3 }); if ((_a2 = r == null ? void 0 : r.cover) == null ? void 0 : _a2.large) { return r.cover.large; } return null; } catch (e) { clog.error("获取封面失败:", e.message); return null; } }; const getTrailer = async (code) => { const query = String(code || "").trim(); if (!query) return null; try { const r = await gmHttp.get(`${TRAILER_API_URL}/${encodeURIComponent(query)}`, null, { "Authorization": `Bearer ${COVER_API_TOKEN}` }, { timeout: 15e3 }); if ((r == null ? void 0 : r.trailers) && Array.isArray(r.trailers) && r.trailers.length > 0) { const qualityMap = {}; r.trailers.forEach((item) => { if (item.proxy && item.quality) { qualityMap[item.quality] = `${BASE_URL}${item.proxy}`; } }); if (Object.keys(qualityMap).length > 0) { clog.log("预告片播放地址:", JSON.stringify(qualityMap)); return qualityMap; } } return null; } catch (e) { clog.error("获取预告片失败:", e.message); return null; } }; class JavopenPreviewVideoPlugin extends BasePlugin { getName() { return "JavopenPreviewVideoPlugin"; } _attachVideoSrc(videoEl, src) { if (!videoEl || !src) return; destroyHls(videoEl); if (isM3U8Url(src)) { const HlsClass = getHlsClass(); if (HlsClass) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 加载失败:", err); }); } else { loadHlsLibrary().then((HlsClass2) => { if (videoEl && videoEl.isConnected && HlsClass2) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 延迟加载失败:", err); }); } }); } } else { videoEl.src = src; videoEl.load && videoEl.load(); } } _selectDefaultQuality(qualityList, intendedDefault) { if (!qualityList || qualityList.length === 0) return null; const availableSet = new Set(qualityList); if (availableSet.has(intendedDefault)) return intendedDefault; const priorityOrder = qualityOptions.map((o) => o.quality).reverse(); for (const q of priorityOrder) if (availableSet.has(q)) return q; return qualityList[0]; } async initCss() { return ` .jhs-javopen-video-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 12345700; display: flex; justify-content: center; align-items: center; opacity: 0; visibility: hidden; transition: opacity 0.2s; } .jhs-javopen-video-modal.is-open { opacity: 1; visibility: visible; } .jhs-javopen-video-modal-inner { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 90vw; max-height: 90vh; } .jhs-javopen-video-wrapper { width: 80vw; max-height: 80vh; aspect-ratio: 16/9; background: #000; position: relative; } .jhs-javopen-video-wrapper video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .jhs-javopen-quality-bar { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; } .jhs-javopen-quality-btn { min-width: 60px; padding: 5px 10px; font-size: 13px; background: rgba(255,255,255,0.2); color: #fff; border: 1px solid rgba(255,255,255,0.5); border-radius: 4px; cursor: pointer; transition: background 0.2s; } .jhs-javopen-quality-btn:hover { background: rgba(255,255,255,0.4); } .jhs-javopen-quality-btn.active { background: #1890ff; border-color: #096dd9; font-weight: bold; } .jhs-javopen-loading { color: #fff; font-size: 16px; text-align: center; } `; } async handle() { if (!window.isDetailPage) return; const carNum2 = this.getPageInfo().carNum; if (!carNum2) return; this._addJavopenButton(carNum2); } _addJavopenButton(carNum2) { let $target = null; if (isJavDb$1) { $target = $('a[title="複製番號"]'); } else if (isJavBus$1) { const headerSpans = document.querySelectorAll("span.header"); for (const span of headerSpans) { if (span.textContent.trim() === "識別碼:") { const nextSpan = span.nextElementSibling; if (nextSpan && nextSpan.tagName === "SPAN") { const copyBtn = nextSpan.nextElementSibling; if (copyBtn && copyBtn.tagName === "BUTTON" && copyBtn.textContent.trim() === "复制") { $target = $(copyBtn); } else { $target = $(nextSpan); } } break; } } } if (!$target || !$target.length) return; const $btn = $(`<a class="site-btn" style="min-width: auto; padding: 0 5px; margin-left: 6px; margin-bottom: 0; background-color: #1890ff; vertical-align: middle; cursor: pointer; font-size: 11px;"><span>视频预览</span></a>`); $btn.on("click", async (e) => { e.preventDefault(); e.stopPropagation(); $btn.css("pointer-events", "none").find("span").text("..."); try { await this._openJavopenPlayer(carNum2); } finally { $btn.css("pointer-events", "").find("span").text("视频预览"); } }); $target.after($btn); } async _openJavopenPlayer(carNum2) { if ($("#jhs-javopen-video-modal").length === 0) { $("body").append(` <div id="jhs-javopen-video-modal" class="jhs-javopen-video-modal"> <div class="jhs-javopen-video-modal-inner"> <div class="jhs-javopen-video-wrapper"> <video id="jhs-javopen-video" controls playsinline></video> </div> <div class="jhs-javopen-quality-bar"></div> </div> </div> `); const $modal2 = $("#jhs-javopen-video-modal"); $modal2.on("click", (e) => { if (e.target.id === "jhs-javopen-video-modal") { $modal2.removeClass("is-open"); const videoEl2 = document.getElementById("jhs-javopen-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); } } }); $(document).on("keydown", (e) => { if (e.key === "Escape" && $modal2.hasClass("is-open")) { $modal2.removeClass("is-open"); const videoEl2 = document.getElementById("jhs-javopen-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); } } }); } const $modal = $("#jhs-javopen-video-modal"); const $wrapper = $modal.find(".jhs-javopen-video-wrapper"); const $qualityBar = $modal.find(".jhs-javopen-quality-bar"); $wrapper.html('<div class="jhs-javopen-loading">视频预览加载中...</div>'); $qualityBar.empty(); $modal.addClass("is-open"); let videoMap = null; try { videoMap = await getTrailer(carNum2); } catch (err) { $wrapper.html(`<div class="jhs-javopen-loading">视频预览请求失败: ${err.message}</div>`); return; } if (!videoMap || Object.keys(videoMap).length === 0) { $wrapper.html('<div class="jhs-javopen-loading">未找到可用的视频源</div>'); return; } const qualityList = Object.keys(videoMap); const defaultQuality = this._selectDefaultQuality(qualityList, "720p"); const defaultUrl = videoMap[defaultQuality]; $wrapper.html(`<video id="jhs-javopen-video" controls playsinline> <source src="${defaultUrl}" /> </video>`); const videoEl = document.getElementById("jhs-javopen-video"); if (!videoEl) return; if (isM3U8Url(defaultUrl)) { this._attachVideoSrc(videoEl, defaultUrl); } let buttonsHtml = ""; qualityOptions.forEach((option) => { const url = videoMap[option.quality]; if (url) { buttonsHtml += `<button class="jhs-javopen-quality-btn${option.quality === defaultQuality ? " active" : ""}" data-quality="${option.quality}" data-video-src="${url}">${option.text}</button>`; } }); $qualityBar.html(buttonsHtml); $qualityBar.off("click").on("click", ".jhs-javopen-quality-btn", (e) => { const $btn = $(e.currentTarget); if ($btn.hasClass("active")) return; const src = $btn.attr("data-video-src"); const currentTime = videoEl.currentTime; destroyHls(videoEl); this._attachVideoSrc(videoEl, src); videoEl.load(); if (currentTime > 0 && Number.isFinite(currentTime)) { videoEl.currentTime = currentTime; } videoEl.play().catch(() => { }); $qualityBar.find(".jhs-javopen-quality-btn").removeClass("active"); $btn.addClass("active"); }); videoEl.play().catch((e) => console.warn("视频预览播放失败:", e)); } } class ImageRecognitionPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "siteList", [{ name: "Google旧版", url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d", ico: "https://www.google.com/favicon.ico" }, { name: "Google", url: "https://lens.google.com/uploadbyurl?url={占位符}", ico: "https://www.google.com/favicon.ico" }, { name: "Yandex", url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}", ico: "https://yandex.ru/favicon.ico" }]); __publicField2(this, "isUploading", false); } getName() { return "ImageRecognitionPlugin"; } async initCss() { return "\n <style>\n #upload-area {\n border: 2px dashed #85af68;\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n margin-bottom: 20px;\n transition: all 0.3s;\n background-color: #f9f9f9;\n }\n #upload-area:hover {\n border-color: #76b947;\n background-color: #f0f0f0;\n }\n /* 拖拽进入 */\n #upload-area.highlight {\n border-color: #2196F3;\n background-color: #e3f2fd;\n }\n \n \n #select-image-btn {\n background-color: #4CAF50;\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n transition: background-color 0.3s;\n }\n #select-image-btn:hover {\n background-color: #45a049;\n }\n \n \n #handle-btn, #cancel-btn {\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n border: none;\n transition: opacity 0.3s;\n }\n #handle-btn {\n background-color: #2196F3;\n color: white;\n }\n #handle-btn:hover {\n opacity: 0.9;\n }\n #cancel-btn {\n background-color: #f44336;\n color: white;\n }\n #cancel-btn:hover {\n opacity: 0.9;\n }\n \n .search-img-site-btns-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 15px;\n }\n .search-img-site-btn {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: #f5f5f5;\n border-radius: 4px;\n text-decoration: none;\n color: #333;\n transition: all 0.2s;\n font-size: 14px;\n border: 1px solid #ddd;\n }\n .search-img-site-btn:hover {\n background-color: #e0e0e0;\n transform: translateY(-2px);\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n .search-img-site-btn img {\n width: 16px;\n height: 16px;\n margin-right: 6px;\n }\n .search-img-site-btn span {\n white-space: nowrap;\n }\n </style>\n "; } open(onOpenFun) { layer.open({ type: 1, title: "以图识图", content: '\n <div style="padding: 20px">\n <div id="upload-area">\n <div style="color: #555;margin-bottom: 15px;">\n <p>拖拽图片到此处 或 点击按钮选择图片</p>\n <p>也可以直接 Ctrl+V 粘贴图片或 图片URL</p>\n </div>\n <button id="select-image-btn">选择图片</button>\n <input type="file" style="display: none" id="image-file" accept="image/*">\n </div>\n \n <div id="url-input-container" style="margin-top: 15px;display: none;">\n <input type="text" id="image-url" placeholder="粘贴图片URL地址..." style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">\n </div>\n \n <div id="preview-area" style="margin-bottom: 20px; text-align: center; display: none;">\n <img id="preview-image" alt="" src="" style="max-width: 100%; max-height: 300px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">\n <div style="margin-top: 15px; display: flex; justify-content: center; gap: 10px;" id="action-btns">\n <button id="handle-btn">搜索图片</button>\n <button id="cancel-btn">取消</button>\n </div>\n \n <div id="search-results" style="display: none;">\n <p style="margin: 20px auto">请选择识图网站:<a id="openAll" style="cursor: pointer">全部打开</a></p>\n <div class="search-img-site-btns-container" id="search-img-site-btns-container"></div>\n </div>\n </div>\n \n </div>\n ', area: utils.isMobile() ? utils.getResponsiveArea() : ["40%", "80%"], success: async (layero) => { this.initEventListeners(); onOpenFun && onOpenFun(); }, end: () => { $(document).off("paste.searchImg"); } }); } initEventListeners() { const $uploadArea = $("#upload-area"), $fileInput = $("#image-file"), $selectBtn = $("#select-image-btn"), $previewArea = $("#preview-area"), $previewImage = $("#preview-image"), $actionBtns = $("#action-btns"), $searchImage = $("#handle-btn"), $cancelBtn = $("#cancel-btn"), $urlInputContainer = $("#url-input-container"), $imageUrlInput = $("#image-url"), $searchResults = $("#search-results"), $siteBtnsContainer = $("#search-img-site-btns-container"); $uploadArea.on("dragover", ((e) => { e.preventDefault(); $uploadArea.addClass("highlight"); })).on("dragleave", (() => { $uploadArea.removeClass("highlight"); })).on("drop", ((e) => { e.preventDefault(); $uploadArea.removeClass("highlight"); if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) { this.handleImageFile(e.originalEvent.dataTransfer.files[0]); this.resetSearchUI(); } })); $selectBtn.on("click", (() => { $fileInput.trigger("click"); })); $fileInput.on("change", ((e) => { if (e.target.files && e.target.files[0]) { this.handleImageFile(e.target.files[0]); this.resetSearchUI(); } })); $(document).on("paste.searchImg", (async (e) => { const items = e.originalEvent.clipboardData.items; for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) { const blob = items[i].getAsFile(); this.handleImageFile(blob); this.resetSearchUI(); return; } const text = e.originalEvent.clipboardData.getData("text"); if (text && utils.isUrl(text)) { $urlInputContainer.show(); $imageUrlInput.val(text); $previewImage.attr("src", text); $previewArea.show(); this.resetSearchUI(); } })); $searchImage.on("click", (async () => { const imageSrc = $previewImage.attr("src"); if (imageSrc) { if (!this.isUploading) { this.isUploading = true; try { const imgUrl = await this.searchByImage(imageSrc); $actionBtns.hide(); $searchResults.show(); $siteBtnsContainer.empty(); const key = "jhs_selectedSites", selectedSites = JSON.parse(localStorage.getItem(key) || "{}"); this.siteList.forEach(((site) => { const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl)), isChecked = false !== selectedSites[site.name]; $siteBtnsContainer.append(` <a href="${siteUrl}" class="search-img-site-btn" target="_blank" title="${site.name}"> <input type="checkbox" class="site-checkbox" data-site-name="${site.name}" style="margin-right: 5px" ${isChecked ? "checked" : ""}> <img src="${site.ico}" alt="${site.name}"> <span>${site.name}</span> </a> `); })); $siteBtnsContainer.on("change", ".site-checkbox", (function() { const siteName = $(this).data("site-name"); selectedSites[siteName] = $(this).is(":checked"); localStorage.setItem(key, JSON.stringify(selectedSites)); })); $siteBtnsContainer.show(); } finally { this.isUploading = false; } } } else show.info("请粘贴或上传图片"); })); $cancelBtn.on("click", (() => { $previewArea.hide(); $urlInputContainer.hide(); $fileInput.val(""); $imageUrlInput.val(""); })); $imageUrlInput.on("change", (() => { if (utils.isUrl($imageUrlInput.val())) { $previewImage.attr("src", $imageUrlInput.val()); $previewArea.show(); } })); $("#openAll").on("click", (() => { $(".search-img-site-btn").each((function() { $(this).find(".site-checkbox").is(":checked") && window.open($(this).attr("href")); })); })); } resetSearchUI() { $("#action-btns").show(); $("#search-results").hide(); $("#search-img-site-btns-container").hide().empty(); } handleImageFile(file) { const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container"); if (!file.type.match("image.*")) { show.info("请选择图片文件"); return; } const reader = new FileReader(); reader.onload = (e) => { previewImage.src = e.target.result; previewArea.style.display = "block"; urlInputContainer.style.display = "none"; $("#handle-btn")[0].click(); }; reader.readAsDataURL(file); } async searchByImage(imageSrc) { let loadObj = loading(); try { let imageUrl = imageSrc; if (imageSrc.startsWith("data:")) { show.info("开始上传图片..."); const imgurUrl = await (async function(base64Data) { var _a2; const matches = base64Data.match(/^data:(.+);base64,(.+)$/); if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据"); const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i); const byteArray = new Uint8Array(byteNumbers), blob = new Blob([byteArray], { type: mimeType }), formData = new FormData(); formData.append("image", blob); const idKey = "jhs_imgurClientId"; let clientId = localStorage.getItem(idKey); if (!clientId) { clientId = window.prompt("请输入您自己的Imgur Client-ID(可前往 https://api.imgur.com/oauth2/addclient 免费申请)用于图片上传搜索:"); if (!clientId) throw new Error("未提供Imgur Client-ID,无法上传图片"); localStorage.setItem(idKey, clientId); } const response = await fetch("https://api.imgur.com/3/image", { method: "POST", headers: { Authorization: `Client-ID ${clientId}` }, body: formData }), data = await response.json(); if (data.success && data.data && data.data.link) return data.data.link; throw new Error((null == (_a2 = data.data) ? void 0 : _a2.error) || "上传到Imgur失败"); })(imageSrc); if (!imgurUrl) { show.error("上传到失败"); return; } imageUrl = imgurUrl; } return imageUrl; } catch (error) { show.error(`搜索失败: ${error.message}`); console.error("搜索失败:", error); } finally { loadObj.close(); } } } class BusNavBarPlugin extends BasePlugin { getName() { return "BusNavBarPlugin"; } handle() { $("#navbar > div > div > span").append('\n <button class="btn btn-default" style="color: #0d9488" id="search-img-btn">识图</button>\n '); $("#search-img-btn").on("click", (() => { this.getBean("ImageRecognitionPlugin").open(); })); } } class RelatedPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "floorIndex", 1); __publicField2(this, "isInit", false); } getName() { return "RelatedPlugin"; } async showRelated($eleBox, movieId) { const enableLoadRelated = await storageManager.getSetting("enableLoadRelated", NO$1), $magnets = $eleBox; if (movieId) { $magnets.append(` <div style="display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px;"> <span style="flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent);"></span> <span style="padding: 0 10px;">相关清单</span> <a id="relatedFold" style="margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center;"> <span class="toggle-text">${enableLoadRelated === YES$1 ? "折叠" : "展开"}</span> <span class="toggle-icon" style="margin-left: 4px;">${enableLoadRelated === YES$1 ? "▲" : "▼"}</span> </a> <span style="flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent);"></span> </div> `); $("#relatedFold").on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $text = $("#relatedFold .toggle-text"), $icon = $("#relatedFold .toggle-icon"), isFolded = "展开" === $text.text(); $text.text(isFolded ? "折叠" : "展开"); $icon.text(isFolded ? "▲" : "▼"); if (isFolded) { $("#relatedContainer").show(); $("#relatedFooter").show(); if (!this.isInit) { this.fetchAndDisplayRelateds(movieId); this.isInit = true; } storageManager.saveSettingItem("enableLoadRelated", YES$1); } else { $("#relatedContainer").hide(); $("#relatedFooter").hide(); storageManager.saveSettingItem("enableLoadRelated", NO$1); } })); $magnets.append('<div id="relatedContainer"></div>'); $magnets.append('<div id="relatedFooter"></div>'); enableLoadRelated === YES$1 && await this.fetchAndDisplayRelateds(movieId); } else show.error("未传入movieId"); } async fetchAndDisplayRelateds(movieId) { const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter"); $relatedContainer.append('<div id="relatedLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取清单中...</div>'); let dataList = null; try { dataList = await javDbApi.related(movieId, 1, 20); } catch (e) { console.error("获取清单失败:", e); } finally { $("#relatedLoading").remove(); } if (dataList) if (0 !== dataList.length) { this.displayRelateds(dataList, $relatedContainer); if (20 === dataList.length) { $relatedFooter.html('\n <button id="loadMoreRelateds" style="width:100%; background-color: #e1f5fe; border:none; padding:10px; margin-top:10px; cursor:pointer; color:#0277bd; font-weight:bold; border-radius:4px;">\n 加载更多清单\n </button>\n <div id="relatedEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>\n '); let currentPage = 1, $loadMoreRelateds = $("#loadMoreRelateds"); $loadMoreRelateds.on("click", (async () => { $loadMoreRelateds.text("加载中...").prop("disabled", true); currentPage++; let moreData; try { moreData = await javDbApi.related(movieId, currentPage, 20); } catch (e) { console.error("加载更多清单失败:", e); } finally { $loadMoreRelateds.text("加载失败, 请点击重试").prop("disabled", false); } if (moreData) { this.displayRelateds(moreData, $relatedContainer); if (moreData.length < 20) { $loadMoreRelateds.remove(); $("#relatedEnd").show(); } else $loadMoreRelateds.text("加载更多清单").prop("disabled", false); } })); } else $relatedFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部清单</div>'); } else $relatedContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无清单</div>'); else { $relatedContainer.append('\n <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n 获取清单失败\n <a id="retryFetchRelateds" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n </div>\n '); $("#retryFetchRelateds").on("click", (async () => { $("#retryFetchRelateds").parent().remove(); await this.fetchAndDisplayRelateds(movieId); })); } } displayRelateds(dataList, $container) { dataList.length && dataList.forEach(((item) => { let commentHtml = ` <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;position:relative;"> <span style="position:absolute;top:5px;right:10px;color:#999;font-size:12px;">#${this.floorIndex++}</span> <span style="position:absolute;bottom:5px;right:10px;color:#999;font-size:12px;">创建时间: ${item.createTime}</span> <p><a href="/lists/${item.relatedId}" target="_blank" style="color:#2e8abb">${item.name}</a></p> <p style="margin-top: 5px;">视频个数: ${item.movieCount}</p> <p style="margin-top: 5px;">收藏次数: ${item.collectionCount} 被查看次数: ${item.viewCount}</p> </div> `; $container.append(commentHtml); })); } } class WantAndWatchedVideosPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "type", null); } getName() { return "WantAndWatchedVideosPlugin"; } async handle() { if (window.location.href.includes("/want_watch_videos")) { $("h3").append('<a class="a-primary" id="wantWatchBtn" style="padding:10px;">导入至 JHS</a>'); $("#wantWatchBtn").on("click", ((event) => { this.type = Status_FAVORITE$1; this.importWantWatchVideos(event, "是否将 想看的影片 导入到 JHS-收藏?"); })); } if (window.location.href.includes("/watched_videos")) { $("h3").append('<a class="a-success" id="wantWatchBtn" style="padding:10px;">导入至 JHS</a>'); $("#wantWatchBtn").on("click", ((event) => { this.type = Status_HAS_DOWN$1; this.importWantWatchVideos(event, "是否将 看过的影片 导入到 JHS-已下载?"); })); } } importWantWatchVideos(event, title) { utils.q(null, `${title} <br/> <span style='color: #f40'>执行此功能前请记得备份数据</span>`, (async () => { let loadObj = loading(); try { await this.parseMovieList(); } catch (e) { console.error(e); } finally { loadObj.close(); } })); } async parseMovieList($dom) { let movieList, nextPageLink; if ($dom) { movieList = $dom.find(this.getSelector().itemSelector); nextPageLink = $dom.find(".pagination-next").attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(".pagination-next").attr("href"); } for (const element of movieList) { const item = $(element), href = item.find("a").attr("href"), carNum2 = item.find(".video-title strong").text().trim(), publishTime = item.find(".meta").text().trim(); if (href && carNum2) try { if (await storageManager.getCar(carNum2)) { show.info(`${carNum2} 已存在, 跳过`); continue; } await storageManager.saveCar({ carNum: carNum2, url: href, names: null, actionType: this.type, publishTime }); } catch (error) { console.error(`保存失败 [${carNum2}]:`, error); } } if (nextPageLink) { show.info("发现下一页,正在解析:", nextPageLink); await new Promise(((resolve) => setTimeout(resolve, 1e3))); $.ajax({ url: nextPageLink, method: "GET", success: (html2) => { const parser = new DOMParser(), next$dom = $(parser.parseFromString(html2, "text/html")); this.parseMovieList(next$dom); }, error: function(err) { console.error(err); show.error("加载下一页失败:" + err.message); } }); } else { show.ok("导入结束!"); window.refresh(); } } } function showCopiedFeedback($btn) { const originalText = $btn.text(); $btn.addClass("copied").text("已复制"); setTimeout((() => { $btn.removeClass("copied").text(originalText); }), 2e3); } function fallbackCopy(text, $btn) { const textarea2 = document.createElement("textarea"); textarea2.value = text; textarea2.style.position = "fixed"; document.body.appendChild(textarea2); textarea2.select(); try { document.execCommand("copy"); showCopiedFeedback($btn); } catch (err) { console.error("复制失败:", err); alert("复制失败,请手动复制链接"); } document.body.removeChild(textarea2); } function parseSize(sizeStr) { if (!sizeStr || sizeStr === "未知") return 0; const match = String(sizeStr).replace(/,/g, "").match(/([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB|B)?/i); if (!match) return 0; const num = parseFloat(match[1]); const unit = (match[2] || "B").toUpperCase(); const multipliers = { TIB: 1099511627776, TB: 1099511627776, GIB: 1073741824, GB: 1073741824, MIB: 1048576, MB: 1048576, KIB: 1024, KB: 1024, B: 1 }; return Number.isFinite(num) ? num * (multipliers[unit] || 1) : 0; } function parseDate(dateStr) { if (!dateStr || dateStr === "未知") return 0; const ts = Date.parse(dateStr.replace(/\//g, "-")); return isNaN(ts) ? 0 : ts; } function formatMagnetDate(dateStr) { if (!dateStr) return ""; const match = String(dateStr).match(/(\d{4})[-/](\d{1,2})[-/](\d{1,2})/); if (match) return `${match[1]}-${match[2].padStart(2, "0")}-${match[3].padStart(2, "0")}`; const ts = parseDate(dateStr); return ts ? new Date(ts).toISOString().slice(0, 10) : dateStr; } function classifyQuality(title) { const text = String(title || "").replace(/【[^】]*(?:APP|夸克|UC搜|(?:[a-z0-9-]+\.)+(?:com|net|org|cc|sbs|top))[^】]*】/gi, " "); const hasCJK = /[\u4e00-\u9fff]/.test(text); const hasJP = /[\u3040-\u309f\u30a0-\u30ff]/.test(text); const isChinese = /(?:[^A-Za-z]|^)FHDC(?:[^A-Za-z]|$)/i.test(text) || /[-_](?:UC|CH?)(?:[^A-Za-z]|$)/.test(text) || /(?:\u4e2d\u5b57|\u4e2d\u6587|\u5b57\u5e55|\u4e2d\u6587\u5b57\u5e55|\u7e41\u9ad4\u4e2d\u5b57|\u7e41\u4f53\u4e2d\u5b57|\u7e41\u9ad4\u4e2d\u6587|\u7e41\u4f53\u4e2d\u6587|\u7e41\u9ad4\u5b57\u5e55|\u7e41\u4f53\u5b57\u5e55|\u7e41\u4e2d|\u7e41\u5b57|\u81ea\u63d0|\u5f81\u7528|\u5fb5\u7528|\u6f22\u5316|\u6c49\u5316|\u5167\u5d4c|\u5185\u5d4c|\u5167\u5c01|\u5185\u5c01|\u96d9\u8a9e|\u53cc\u8bed)/.test(text) || hasCJK && !hasJP; const is4K = /(?:[^A-Za-z0-9]|^)(?:4K(?:UHD)?|2160P)(?:[^A-Za-z0-9]|$)/i.test(text); const isCracked = /(?:\u7834\u89e3|\u7834\u574f|\u7834\u58de|\u7834\u58ca|\u65e0\u7801|\u7121\u78bc)/.test(text) || /\b(?:uncensored|mosaic)\b/i.test(text); return { isChinese, is4K, isCracked }; } function gmFetch(url, opts = {}) { return new Promise(((resolve) => { _GM_xmlhttpRequest({ method: opts.method || "GET", url, headers: opts.headers || {}, data: opts.data, onload: (r) => { resolve({ loadstuts: r.status >= 200 && r.status < 300, status: r.status, responseText: r.responseText, finalUrl: r.finalUrl || url }); }, onerror: () => resolve({ loadstuts: false, status: 0, responseText: "", finalUrl: url }) }); })); } function parseHTML(html2) { const parser = new DOMParser(); return parser.parseFromString(html2, "text/html"); } class MagnetHubPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "currentEngine", null); __publicField2(this, "searchEngines", [ { name: "Sukebei", id: "Sukebei", url: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", targetPage: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", parseHtml: this.parseSukebei }, { name: "U9A9", id: "u9a9", url: "https://u9a9.com/?type=2&search={keyword}", targetPage: "https://u9a9.com/?type=2&search={keyword}", parseHtml: this.parseU9A9 }, { name: "U3C3", id: "u3c3", url: "https://u3c3.com/?search={keyword}", targetPage: "https://u3c3.com/?search={keyword}", parseHtml: this.parseU3C3 }, { name: "BTSearch", id: "btsearch", url: "https://btsearch.love/api/search?keyword={keyword}&limit=10&offset=0&sort=size&sort_type=desc", targetPage: "https://btsearch.love/search?keyword={keyword}", parseJson: this.parseBTSearch }, { name: "SoKitty", id: "sokitty", url: "https://w1.sokitty.me/search?key={keyword}", targetPage: "https://w1.sokitty.me/search?key={keyword}", parseHtml: this.parseSokitty }, { name: "ØMagnet", id: "omag", url: "https://xn--mag-zna.net/search?q={keyword}", targetPage: "https://xn--mag-zna.net/search?q={keyword}", parseHtml: this.parseOmag } ]); } getName() { return "MagnetHubPlugin"; } async initCss() { return ` <style> .magnet-container { margin: 12px auto; width: 100%; font-family: Arial, sans-serif; } .magnet-tabs { display: flex; border-bottom: 1px solid #ddd; margin-bottom: 0; justify-content: space-between; align-items: center; } .magnet-tab { padding: 4px 10px; cursor: pointer; border: 1px solid transparent; border-bottom: none; margin-right: 3px; background: #f5f5f5; border-radius: 5px 5px 0 0; font-size: 12px; transition: background .16s; } .magnet-tab.active { background: #fff; border-color: #ddd; border-bottom: 1px solid #fff; margin-bottom: -1px; font-weight: bold; } .magnet-tab:hover:not(.active) { background: #e9e9e9; } .magnet-controls { display: flex; align-items: center; gap: 6px; padding: 6px 10px; background: #fafafa; border-bottom: 1px solid #eee; } .magnet-sort-select { height: 24px; padding: 1px 18px 1px 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 11px; color: #333; background: #fff; cursor: pointer; } .magnet-sort-select:disabled { opacity: .5; cursor: not-allowed; } .magnet-results { min-height: 160px; } .magnet-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 12px; } .magnet-table th { padding: 4px; background: #f8f8f8; border-bottom: 2px solid #e0e0e0; font-weight: 600; color: #555; font-size: 11px; text-align: left; } .magnet-table th.col-name { max-width: 0; } .magnet-table th.col-size { width: 60px; text-align: center; } .magnet-table th.col-date { width: 75px; text-align: center; } .magnet-table th.col-actions { width: 170px; text-align: center; } .magnet-table td { padding: 5px 4px; border-bottom: 1px solid #eee; vertical-align: middle; } .magnet-table tr:hover td { background-color: #f9f9f9; } .magnet-table td.col-size { text-align: center; white-space: nowrap; } .magnet-table td.col-date { text-align: center; white-space: nowrap; font-size: 11px; color: #888; } .magnet-table td.col-actions { text-align: center; white-space: nowrap; } .magnet-name-cell { display: flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; } .magnet-name-cell > a { flex: 1 1 auto; min-width: 0; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #243b67; font-weight: 650; text-decoration: none; line-height: 1.4; } .magnet-name-cell > a:hover { color: #1d4ed8; text-decoration: underline; } .magnet-badge { display: inline-flex; align-items: center; flex-shrink: 0; padding: 1px 4px; border-radius: 3px; font-size: 10px; font-weight: 800; line-height: 1.4; color: #fff; } .magnet-badge.mg-subtitle { background: #16a34a; } .magnet-badge.mg-four-k { background: #2563eb; } .magnet-badge.mg-cracked { background: #be123c; } .magnet-loading { text-align: center; padding: 14px; color: #666; font-size: 12px; } .magnet-error { color: #f44336; padding: 8px; font-size: 12px; } .magnet-empty { text-align: center; padding: 14px; color: #999; font-size: 12px; } .magnet-empty a { color: #e74c3c; font-weight: bold; cursor: pointer; } .magnet-hub-btn { display: inline-block; min-width: 36px; padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; font-size: 11px; text-align: center; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,.12); text-shadow: 0 1px 1px rgba(0,0,0,.15); border: none; line-height: 1.3; margin-left: 3px; } .magnet-hub-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 6px rgba(0,0,0,.18); opacity: .92; } .magnet-hub-btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0,0,0,.1); } .magnet-hub-btn.copied { background-color: #66bb6a; } .magnet-hub-btn.copy-btn { background-color: #8b5cf6; } .magnet-hub-btn.check-btn { background-color: #e11d48; } .magnet-hub-btn.down-btn { background-color: #14b8a6; } .whatslink-overlay { position: fixed; inset: 0; z-index: 2147483700; display: flex; align-items: center; justify-content: center; padding: 22px; background: rgba(15,23,42,.66); backdrop-filter: blur(8px); } .whatslink-modal { position: relative; width: min(1140px, calc(100vw - 36px)); max-width: 1140px; max-height: calc(100vh - 36px); background: transparent; border: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .whatslink-gallery-scene { display: grid; grid-template-rows: minmax(0, 1fr) auto auto; row-gap: 3px; width: 100%; align-items: center; justify-items: center; } .whatslink-gallery-visual { position: relative; z-index: 1; width: 100%; display: grid; place-items: center; overflow: hidden; } .whatslink-gallery-hero { width: 100%; height: 100%; min-height: 0; object-fit: contain; display: block; } .whatslink-gallery-close { position: absolute; top: 12px; right: 14px; z-index: 4; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: 0; border-radius: 50%; color: #fff; background: rgba(42,45,49,.76); cursor: pointer; padding: 0 0 2px; font-family: Arial, sans-serif; font-size: 23px; line-height: 1; } .whatslink-gallery-close:hover { background: rgba(22,25,28,.92); } .whatslink-gallery-arrow { position: absolute; top: 50%; z-index: 3; width: 34px; height: 34px; display: flex; align-items: center; justify-content: center; transform: translateY(-50%); border: 0; border-radius: 50%; color: #fff; background: rgba(38,42,45,.76); cursor: pointer; padding: 0 0 3px; font-family: Arial, sans-serif; font-size: 27px; line-height: 1; } .whatslink-gallery-arrow:hover { background: rgba(22,25,28,.92); } .whatslink-gallery-prev { left: 16px; } .whatslink-gallery-next { right: 16px; } .whatslink-gallery-info { position: relative; z-index: 3; display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 4px; color: #fff; background: rgba(38,40,42,.68); font: 11px ui-monospace, SFMono-Regular, Consolas, monospace; white-space: nowrap; } .whatslink-gallery-info span + span::before { content: " · "; margin-right: 8px; color: rgba(255,255,255,.42); } .whatslink-gallery-thumbs { position: relative; z-index: 3; display: flex; justify-content: center; gap: 3px; max-width: 100%; padding: 3px; border-radius: 4px; background: rgba(29,31,34,.68); } .whatslink-gallery-thumb { width: 88px; height: 50px; padding: 0; overflow: hidden; border: 2px solid transparent; border-radius: 3px; background: transparent; cursor: pointer; } .whatslink-gallery-thumb.active { border-color: #e3a05b; } .whatslink-gallery-thumb img { width: 100%; height: 100%; display: block; object-fit: cover; } .whatslink-gallery-empty { position: absolute; z-index: 2; display: none; width: min(420px, 72%); text-align: center; color: rgba(255,255,255,.74); } .whatslink-modal.no-shots .whatslink-gallery-empty { display: block; } .whatslink-modal.no-shots .whatslink-gallery-visual { height: min(460px, calc(100vh - 100px)); } .whatslink-modal.no-shots .whatslink-gallery-hero, .whatslink-modal.no-shots .whatslink-gallery-arrow, .whatslink-modal.no-shots .whatslink-gallery-thumbs { display: none; } .whatslink-gallery-empty-icon { width: 56px; height: 56px; margin: 0 auto 14px; display: grid; place-items: center; border: 1px solid rgba(255,255,255,.24); border-radius: 50%; color: #e3a05b; font-size: 24px; } .whatslink-gallery-empty-title { margin-bottom: 7px; color: #fff; font-size: 17px; font-weight: 800; } .whatslink-gallery-empty-text { margin: 0; font-size: 13px; line-height: 1.6; } .whatslink-loading { padding: 28px; text-align: center; color: #475569; font-size: 14px; } </style> `; } createMagnetHub(keyword) { keyword = keyword.replace("FC2-", ""); const $container = $('<div class="magnet-container"></div>'); const $tabs = $('<div class="magnet-tabs"></div>'); const key = "jhs_magnetHub_selectedEngine"; const savedEngineId = localStorage.getItem(key); let defaultEngineIndex = 0; const $tabBox = $('<div style="display: flex; align-items: center;"></div>'); this.searchEngines.forEach(((engine, index) => { const $tab = $(`<div class="magnet-tab" data-engine="${engine.id}">${engine.name}</div>`); if (savedEngineId && engine.id === savedEngineId) { $tab.addClass("active"); this.currentEngine = engine; defaultEngineIndex = index; } else if (!savedEngineId && engine.id === "Sukebei") { $tab.addClass("active"); this.currentEngine = engine; } $tabBox.append($tab); })); const sortMode = localStorage.getItem("jhs_magnetSort") || "size"; const $sortSelect = $(`<select class="magnet-sort-select" disabled style="margin-left:auto;margin-right:4px;"> <option value="size" ${sortMode === "size" ? "selected" : ""}>按大小</option> <option value="newest" ${sortMode === "newest" ? "selected" : ""}>按最新</option> <option value="oldest" ${sortMode === "oldest" ? "selected" : ""}>按最旧</option> </select>`); $tabBox.append($sortSelect); $tabs.append($tabBox); $tabs.append(`<a style="margin-right: 20px;margin-top:3px" id="targetBox" href="${this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword))}" target="_blank">原网页</a>`); $container.append($tabs); const $resultsContainer = $('<div class="magnet-results"></div>'); $container.append($resultsContainer); $container.on("click", ".magnet-tab", ((e) => { const engineId = $(e.target).data("engine"); this.currentEngine = this.searchEngines.find(((engine) => engine.id === engineId)); this.resolveUrl(this.currentEngine, keyword).then(((url) => { $("#targetBox").attr("href", url); })); localStorage.setItem(key, engineId); $container.find(".magnet-tab").removeClass("active"); $(e.target).addClass("active"); this.searchEngine($resultsContainer, $sortSelect, this.currentEngine, keyword); })); $sortSelect.on("change", () => { localStorage.setItem("jhs_magnetSort", $sortSelect.val()); RerenderSort($resultsContainer, $sortSelect.val()); }); $container.on("click", ".copy-btn", function() { const $btn = $(this), magnet = $btn.data("magnet"); const dnCode = $btn.data("dncode") || ""; const magWithDn = magnet + (dnCode ? `&dn=${encodeURIComponent(dnCode)}` : ""); navigator.clipboard ? navigator.clipboard.writeText(magWithDn).then((() => { showCopiedFeedback($btn); })).catch((() => { fallbackCopy(magWithDn, $btn); })) : fallbackCopy(magWithDn, $btn); }); $container.on("click", ".down-115", (async (event) => { event.stopPropagation(); const magnet = $(event.currentTarget).data("magnet"); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })); $container.on("click", ".check-btn", (async (event) => { event.stopPropagation(); const magnet = $(event.currentTarget).data("magnet"); this.checkWhatslink(magnet); })); this.searchEngine($resultsContainer, $sortSelect, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword); this.resolveUrl(this.currentEngine || this.searchEngines[defaultEngineIndex], keyword).then(((url) => { $("#targetBox").attr("href", url); })); return $container; } searchEngine($container, $sortSelect, engine, keyword) { this.currentEngine = engine; this.currentKeyword = keyword; $container.html(`<div class="magnet-loading">正在从 ${engine.name} 搜索 "${keyword}"...</div>`); const cacheKey = `${engine.name}_${keyword}`; sessionStorage.getItem(cacheKey); this.resolveUrl(engine, keyword).then(((url) => { const requestOpts = { method: "GET", url }; if (engine.parseHtml) { _GM_xmlhttpRequest(Object.assign(requestOpts, { method: "GET", url, onload: async (response) => { try { const results = await engine.parseHtml.call(this, response.responseText, keyword); results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); $container.data("_magnetData", results); $sortSelect.prop("disabled", results.length === 0); this.displayResults($container, results, engine.name); } catch (e) { this.showError($container, `解析 ${engine.name} 结果失败: ${e.message}`); } }, onerror: (error) => { this.showError($container, `从 ${engine.name} 获取数据失败: ${error.statusText}`); } })); } if (engine.parseJson) { engine.parseJson.call(this, $container, $sortSelect, engine, keyword, cacheKey); } })); } showError($container, message) { $container.html(`<div class="magnet-error">${message} <a id="retrySearch" href="#">🔄 刷新</a></div>`); const $select = $container.closest(".magnet-container").find(".magnet-sort-select"); $container.find("#retrySearch").on("click", ((e) => { e.preventDefault(); this.searchEngine($container, $select, this.currentEngine, this.currentKeyword); })); } displayResults($container, results, engineName) { $container.empty(); const sortMode = localStorage.getItem("jhs_magnetSort") || "size"; if (0 !== results.length) { results = this.sortMagnetData(results, sortMode); const $table = $(`<table class="magnet-table"> <thead><tr> <th class="col-name">名称</th> <th class="col-size">大小</th> <th class="col-date">日期</th> <th class="col-actions">操作</th> </tr></thead> <tbody></tbody> </table>`); const $tbody = $table.find("tbody"); results.forEach(((result) => { const quality = classifyQuality(result.title); let badges = ""; if (quality.is4K) badges += '<span class="magnet-badge mg-four-k">4K</span>'; if (quality.isChinese) badges += '<span class="magnet-badge mg-subtitle">中字</span>'; if (quality.isCracked) badges += '<span class="magnet-badge mg-cracked">破解</span>'; const displayDate = formatMagnetDate(result.date); const dnCode = extractCode(result.title); const $row = $(`<tr> <td> <div class="magnet-name-cell"> ${badges} <a href="${result.magnet}" title="${result.title}">${result.title}</a> </div> </td> <td class="col-size">${result.size || "未知"}</td> <td class="col-date">${displayDate || "未知"}</td> <td class="col-actions"> <button class="magnet-hub-btn copy-btn" data-magnet="${result.magnet}" data-dncode="${dnCode || ""}">复制</button> <button class="magnet-hub-btn check-btn" data-magnet="${result.magnet}">验车</button> <button class="magnet-hub-btn down-btn down-115" data-magnet="${result.magnet}">115离线</button> </td> </tr>`); $tbody.append($row); })); $container.append($table); } else { $container.append(`<div class="magnet-empty">无搜索结果 <a id="retrySearch" href="#">🔄 刷新</a></div>`); $("#retrySearch").on("click", ((e) => { e.preventDefault(); this.searchEngine($container, $container.closest(".magnet-container").find(".magnet-sort-select"), this.currentEngine, this.currentKeyword); })); } } sortMagnetData(data, mode) { return [...data].sort((a, b) => { const sizeDelta = parseSize(b.size) - parseSize(a.size); if (mode === "size") return sizeDelta; const aTime = parseDate(a.date); const bTime = parseDate(b.date); if (!aTime && !bTime) return sizeDelta; if (!aTime) return 1; if (!bTime) return -1; const timeDelta = mode === "oldest" ? aTime - bTime : bTime - aTime; return timeDelta || sizeDelta; }); } async resolveUrl(engine, keyword) { return engine.url.replace("{keyword}", encodeURIComponent(keyword)); } async checkWhatslink(magnet) { var _a2; (_a2 = document.querySelector(".whatslink-overlay")) == null ? void 0 : _a2.remove(); const overlay = document.createElement("div"); overlay.className = "whatslink-overlay"; overlay.innerHTML = '<div class="whatslink-modal no-shots"><div class="whatslink-loading">正在验车...</div></div>'; document.body.appendChild(overlay); try { const url = `https://whatslink.info/api/v1/link?url=${encodeURIComponent(magnet)}`; const r = await gmFetch(url, { timeout: 2e4 }); if (!r.loadstuts) throw new Error("WhatsLink 请求失败"); const data = JSON.parse(r.responseText || "{}"); overlay.remove(); this.showWhatslinkModal(data, magnet); } catch (e) { overlay.remove(); this.showWhatslinkModal({ error: e.message || "查询失败", name: "查询失败", type: "-", file_type: "-", size: 0, count: "-", screenshots: [] }, magnet); } } showWhatslinkModal(payload, magnet) { var _a2; (_a2 = document.querySelector(".whatslink-overlay")) == null ? void 0 : _a2.remove(); const shots = Array.isArray(payload == null ? void 0 : payload.screenshots) ? payload.screenshots.map((item) => item == null ? void 0 : item.screenshot).filter(Boolean) : []; let index = 0; const resourceName = (payload == null ? void 0 : payload.name) || "未知资源"; const resourceCount = (payload == null ? void 0 : payload.count) ?? "-"; const overlay = document.createElement("div"); overlay.className = "whatslink-overlay"; const modal = document.createElement("section"); modal.className = `whatslink-modal${shots.length ? "" : " no-shots"}`; modal.innerHTML = `<div class="whatslink-gallery-scene"> <div class="whatslink-gallery-visual"> <button class="whatslink-gallery-close" type="button" aria-label="关闭">×</button> <button class="whatslink-gallery-arrow whatslink-gallery-prev" type="button" aria-label="上一张">‹</button> <img class="whatslink-gallery-hero" alt="截图预览"> <button class="whatslink-gallery-arrow whatslink-gallery-next" type="button" aria-label="下一张">›</button> <div class="whatslink-gallery-empty"> <div class="whatslink-gallery-empty-icon">?</div> <div class="whatslink-gallery-empty-title">暂无截图</div> <p class="whatslink-gallery-empty-text">当前资源没有可展示的截图,可以结合资源名称和文件数量判断。</p> </div> </div> <div class="whatslink-gallery-info"> <span class="whatslink-gallery-name"></span> <span class="whatslink-gallery-count"></span> <span class="whatslink-gallery-index"></span> </div> <div class="whatslink-gallery-thumbs"></div> </div>`; overlay.appendChild(modal); document.body.appendChild(overlay); modal.querySelector(".whatslink-gallery-name").textContent = resourceName; modal.querySelector(".whatslink-gallery-count").textContent = `${resourceCount} 个文件`; const visual = modal.querySelector(".whatslink-gallery-visual"); const hero = modal.querySelector(".whatslink-gallery-hero"); const thumbs = modal.querySelector(".whatslink-gallery-thumbs"); const currentIndex = modal.querySelector(".whatslink-gallery-index"); const closeButton = modal.querySelector(".whatslink-gallery-close"); const prevButton = modal.querySelector(".whatslink-gallery-prev"); const nextButton = modal.querySelector(".whatslink-gallery-next"); const sizeGallery = () => { if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return; const infoHeight = modal.querySelector(".whatslink-gallery-info").offsetHeight || 23; const thumbsHeight = thumbs.offsetHeight || 58; const maxHeight = Math.max(180, window.innerHeight - 36 - infoHeight - thumbsHeight - 6); const maxWidth = modal.clientWidth; const ratio = hero.naturalWidth / hero.naturalHeight; const height = Math.min(maxHeight, maxWidth / ratio); const width = height * ratio; visual.style.width = `${width}px`; visual.style.height = `${height}px`; }; const positionControls = () => { if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return; const width = visual.clientWidth; const height = visual.clientHeight; const scale = Math.min(width / hero.naturalWidth, height / hero.naturalHeight); const imageWidth = hero.naturalWidth * scale; const imageHeight = hero.naturalHeight * scale; const imageLeft = (width - imageWidth) / 2; const imageTop = (height - imageHeight) / 2; const closeSize = closeButton.offsetWidth || 32; const arrowSize = prevButton.offsetWidth || 34; const sideGap = 12; closeButton.style.left = `${imageLeft + imageWidth - closeSize - sideGap}px`; closeButton.style.right = "auto"; closeButton.style.top = `${imageTop + sideGap}px`; prevButton.style.left = `${imageLeft + sideGap}px`; prevButton.style.right = "auto"; prevButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`; nextButton.style.left = "auto"; nextButton.style.right = `${width - imageLeft - imageWidth + sideGap}px`; nextButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`; }; const render = () => { modal.classList.toggle("has-shots", Boolean(shots.length)); if (!shots.length) { currentIndex.textContent = "NO PREVIEW"; return; } hero.src = shots[index]; const frame = `${index + 1} / ${shots.length}`; currentIndex.textContent = frame; [...thumbs.children].forEach((btn, i) => btn.classList.toggle("active", i === index)); if (hero.complete) { sizeGallery(); positionControls(); } }; shots.forEach((url, i) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "whatslink-gallery-thumb"; btn.innerHTML = `<img src="${url}" alt="截图${i + 1}">`; btn.addEventListener("click", () => { index = i; render(); }); thumbs.appendChild(btn); }); prevButton.addEventListener("click", () => { if (!shots.length) return; index = (index + shots.length - 1) % shots.length; render(); }); nextButton.addEventListener("click", () => { if (!shots.length) return; index = (index + 1) % shots.length; render(); }); const onKeydown = (event) => { if (!document.body.contains(overlay)) return; if (event.key === "ArrowLeft" && shots.length) { index = (index + shots.length - 1) % shots.length; render(); } if (event.key === "ArrowRight" && shots.length) { index = (index + 1) % shots.length; render(); } if (event.key === "Escape") close(); }; const onResize = () => { sizeGallery(); positionControls(); }; const removeResize = () => window.removeEventListener("resize", onResize); const close = () => { overlay.remove(); document.removeEventListener("keydown", onKeydown); removeResize(); }; closeButton.addEventListener("click", close); overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); }); hero.addEventListener("load", () => { sizeGallery(); positionControls(); }); window.addEventListener("resize", onResize); document.addEventListener("keydown", onKeydown); render(); } parseU9A9(html2, keyword) { const $dom = utils.htmlTo$dom(html2), results = []; const kwNorm = String(keyword).toUpperCase().replace(/[^A-Z0-9]/g, ""); $dom.find(".torrent-list tbody tr").each(((i, el) => { const $el = $(el); if ($el.text().includes("置顶")) return; const $titleA = $el.find("td:nth-child(2) a").first(); const title = $titleA.attr("title") || $titleA.text().trim(); if (!title) return; if (kwNorm) { const titleNorm = title.toUpperCase().replace(/[^A-Z0-9]/g, ""); if (!titleNorm.includes(kwNorm)) return; } const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"); const size = $el.find("td:nth-child(4)").text().trim(); const date2 = $el.find("td:nth-child(5)").text().trim(); magnet && results.push({ title, magnet, size, date: date2 }); })); return results; } async parseU3C3(_html, keyword) { var _a2; const base = "https://www.u3c3.com"; const referer = base + "/"; const home = await gmFetch(base + "/", { headers: { Referer: referer } }); if (!home.loadstuts) return []; const homeDoc = parseHTML(home.responseText); const searchScript = [...homeDoc.scripts].map((script) => script.textContent || "").find((text) => text.includes("function search21")) || ""; const token = ((_a2 = searchScript.match(/^\s*var\s+nmefafej\s*=\s*["']([^"']+)["'];?/m)) == null ? void 0 : _a2[1]) || ""; if (!token) return []; const searchUrl = `${base}/?search2=${encodeURIComponent(token)}&search=${encodeURIComponent(keyword)}`; const r = await gmFetch(searchUrl, { headers: { Referer: referer } }); if (!r.loadstuts) return []; const doc = parseHTML(r.responseText); const normalizedKeyword = String(keyword || "").toUpperCase().replace(/[-_\s]/g, ""); const data = [...doc.querySelectorAll("table.torrent-list tbody tr.default, table.torrent-list tbody tr.success")].map((row) => { var _a3, _b, _c, _d, _e, _f; const titleA = row.querySelector('td:nth-child(2) a[href*="/view?id="]'); const magnetA = row.querySelector('td:nth-child(3) a[href^="magnet:"]'); const title = ((_a3 = titleA == null ? void 0 : titleA.getAttribute("title")) == null ? void 0 : _a3.trim()) || ((_b = titleA == null ? void 0 : titleA.textContent) == null ? void 0 : _b.trim()) || ""; if (!title || !(magnetA == null ? void 0 : magnetA.href)) return null; if (normalizedKeyword && !title.toUpperCase().replace(/[-_\s]/g, "").includes(normalizedKeyword)) return null; const href = titleA.getAttribute("href") || ""; return { title, magnet: magnetA.href, size: ((_d = (_c = row.querySelector("td:nth-child(4)")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || "", date: ((_f = (_e = row.querySelector("td:nth-child(5)")) == null ? void 0 : _e.textContent) == null ? void 0 : _f.trim()) || "", src: href ? new URL(href, base).href : searchUrl }; }).filter(Boolean); return data; } parseSukebei(html2, keyword) { const $dom = utils.htmlTo$dom(html2), results = []; $dom.find("tr.default, tr.success").each(((i, el) => { const $el = $(el); const title = $el.find("td:nth-child(2) a").first().attr("title") || $el.find("td:nth-child(2) a").first().text().trim(); if (!title) return; const magnet = $el.find("td:nth-child(3) a:last-child").attr("href"); const size = $el.find("td:nth-child(4)").text().trim(); const date2 = $el.find("td:nth-child(5)").text().trim(); magnet && results.push({ title, magnet, size, date: date2 }); })); return results; } async parseOmag(html2, keyword) { const doc = parseHTML(html2); const base = "https://xn--mag-zna.net"; const entries = [...doc.querySelectorAll("tr")].map((row) => { const titleA = row.querySelector('td.result-title a[href^="/!"]'); if (!titleA) return null; const href = titleA.getAttribute("href") || ""; if (!href) return null; let src; try { src = new URL(href, base).href; } catch (_) { return null; } const title = titleA.textContent.trim(); const meta = [...row.querySelectorAll(".result-meta > div")].map((item) => item.textContent.trim()); return { title, size: meta[0] || "", date: meta[1] || "", src }; }).filter(Boolean); const searchUrl = `https://xn--mag-zna.net/search?q=${encodeURIComponent(keyword)}`; const results = []; for (const entry of entries) { try { const r = await gmFetch(entry.src, { headers: { Referer: searchUrl } }); if (!r.loadstuts) continue; const detailDoc = parseHTML(r.responseText); const magnetInput = detailDoc.querySelector("#input-magnet"); const magnetA = detailDoc.querySelector('a[href^="magnet:"]'); const maglink = ((magnetInput == null ? void 0 : magnetInput.getAttribute("value")) || (magnetA == null ? void 0 : magnetA.getAttribute("href")) || (magnetA == null ? void 0 : magnetA.href) || "").trim(); if (!maglink || !/^magnet:\?xt=urn:btih:/i.test(maglink)) continue; results.push({ title: entry.title, magnet: maglink, size: entry.size, date: entry.date }); } catch (_) { } } return results; } parseBTSearch($container, $sortSelect, engine, keyword, cacheKey) { const _this = this; const apiUrl2 = engine.url.replace("{keyword}", encodeURIComponent(keyword)); const timestamp = Math.floor(Date.now() / 1e3).toString(); const nonce = randomString(8); const parts = [`timestamp=${timestamp}`, `nonce=${nonce}`, `keyword=${keyword}`, `limit=10`, `offset=0`, `sort=size`, `sort_type=desc`]; const signText = `${parts.sort().join("&")}&key=long2ice`; const sign = window.md5 ? window.md5(signText).toUpperCase() : ""; _GM_xmlhttpRequest({ method: "GET", url: apiUrl2, headers: { Accept: "application/json", Referer: engine.targetPage.replace("{keyword}", encodeURIComponent(keyword)), "x-timestamp": timestamp, "x-nonce": nonce, "x-sign": sign }, onload: (response) => { var _a2; try { const json2 = JSON.parse(response.responseText); const items = Array.isArray(json2 == null ? void 0 : json2.data) ? json2.data : Array.isArray((_a2 = json2 == null ? void 0 : json2.data) == null ? void 0 : _a2.data) ? json2.data.data : []; const results = items.map((item) => { const title = String((item == null ? void 0 : item.name) || "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim(); const hash = String((item == null ? void 0 : item.hash) || "").replace(/^magnet:\?xt=urn:btih:/i, "").replace(/[^a-z0-9]/gi, ""); if (!/^[a-f0-9]{32,40}$/i.test(hash)) return null; const size = formatBytes(item == null ? void 0 : item.size); const date2 = (item == null ? void 0 : item.created_at) || (item == null ? void 0 : item.created_time) || (item == null ? void 0 : item.create_time) || ""; return { title: title || `magnet:?xt=urn:btih:${hash}`, magnet: `magnet:?xt=urn:btih:${hash}`, size, date: date2 }; }).filter(Boolean); results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); $container.data("_magnetData", results); $sortSelect.prop("disabled", results.length === 0); _this.displayResults($container, results, engine.name); } catch (e) { _this.showError($container, `解析 ${engine.name} 结果失败: ${e.message}`); } }, onerror: (error) => { _this.showError($container, `从 ${engine.name} 获取数据失败: ${error.statusText}`); } }); } parseSokitty(html2, keyword) { const doc = parseHTML(html2); const kwNorm = String(keyword).toUpperCase().replace(/[-_\s]/g, ""); const results = []; doc.querySelectorAll(".panel.search-panel").forEach((panel) => { var _a2, _b, _c, _d; const titleA = panel.querySelector("h3.panel-title > a.list-title"); if (!titleA) return; const href = titleA.getAttribute("href") || ""; if (!href.startsWith("/bt/")) return; const hash = href.replace("/bt/", ""); if (!hash) return; const title = titleA.textContent.trim(); if (!title.toUpperCase().replace(/[-_\s]/g, "").includes(kwNorm)) return; const maglink = `magnet:?xt=urn:btih:${hash}`; const infoItems = [...panel.querySelectorAll(".panel-footer .info-item")]; const size = ((_b = (_a2 = infoItems[0]) == null ? void 0 : _a2.textContent) == null ? void 0 : _b.trim()) || ""; const date2 = ((_d = (_c = infoItems[2]) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || ""; results.push({ title, magnet: maglink, size, date: date2 }); }); return results; } } function formatBytes(bytes) { if (!bytes || bytes <= 0) return ""; const units = ["B", "KB", "MB", "GB", "TB"]; let i = 0; let size = bytes; while (size >= 1024 && i < units.length - 1) { size /= 1024; i++; } return size.toFixed(1) + " " + units[i]; } function extractCode(text) { if (!text) return null; const patterns = [ /FC2[-\s_]?(?:PPV)?[-\s_]?(\d{6,9})/i, /([A-Z]{2,15})-(\d{2,10})(?:-(\d+))?/i, /([A-Z]{2,15})-([A-Z]{0,2}\d{2,10})/i, /^[A-Z0-9]+[-_](\d{6}[-_]\d{2,3})/i, /(\d{6}[-_]\d{2,3})[-_][A-Z0-9]+$/i, new RegExp("(?<!\\w)(\\d{6}[-_]\\d{2,3})(?!\\w)"), /([A-Z]{1,2})(\d{3,4})/i ]; for (const re of patterns) { const m = text.match(re); if (m) return m[0].toUpperCase(); } return null; } function randomString(length) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length)); return result; } function RerenderSort($container, sortMode) { const data = $container.data("_magnetData"); if (!data || !data.length) return; const results = [...data].sort((a, b) => { const sizeDelta = parseSize(b.size) - parseSize(a.size); if (sortMode === "size") return sizeDelta; const aTime = parseDate(a.date), bTime = parseDate(b.date); if (!aTime && !bTime) return sizeDelta; if (!aTime) return 1; if (!bTime) return -1; const timeDelta = sortMode === "oldest" ? aTime - bTime : bTime - aTime; return timeDelta || sizeDelta; }); $container.empty(); const $table = $(`<table class="magnet-table"> <thead><tr> <th class="col-name">名称</th> <th class="col-size">大小</th> <th class="col-date">日期</th> <th class="col-actions">操作</th> </tr></thead> <tbody></tbody> </table>`); const $tbody = $table.find("tbody"); results.forEach(((result) => { const quality = classifyQuality(result.title); let badges = ""; if (quality.is4K) badges += '<span class="magnet-badge mg-four-k">4K</span>'; if (quality.isChinese) badges += '<span class="magnet-badge mg-subtitle">中字</span>'; if (quality.isCracked) badges += '<span class="magnet-badge mg-cracked">破解</span>'; const displayDate = formatMagnetDate(result.date); const dnCode = extractCode(result.title); const $row = $(`<tr> <td> <div class="magnet-name-cell"> ${badges} <a href="${result.magnet}" title="${result.title}">${result.title}</a> </div> </td> <td class="col-size">${result.size || "未知"}</td> <td class="col-date">${displayDate || "未知"}</td> <td class="col-actions"> <button class="magnet-hub-btn copy-btn" data-magnet="${result.magnet}" data-dncode="${dnCode || ""}">复制</button> <button class="magnet-hub-btn check-btn" data-magnet="${result.magnet}">验车</button> <button class="magnet-hub-btn down-btn down-115" data-magnet="${result.magnet}">115</button> </td> </tr>`); $tbody.append($row); })); $container.append($table); } const Thumbnail = { sources: ["javfree", "projectjav", "javstore"], cacheKey(code) { return `thumb_cache_v3_${code}`; }, lookupCode(code) { const text = String(code || "").trim(); const fc2 = text.match(/^(?:FC2[-_\s]?(?:PPV[-_\s]?)?)?(\d{6,9})$/i); return fc2 ? fc2[1] : text; }, sourceOrder() { const savedOrder = ["javfree", "projectjav", "javstore"]; const seen2 = /* @__PURE__ */ new Set(); return [...savedOrder, ...this.sources].filter((src) => { if (seen2.has(src) || typeof this[src] !== "function") return false; seen2.add(src); return true; }); }, async fetchFromSource(source, code) { try { return await this[source](this.lookupCode(code)); } catch (e) { clog.debug(`Thumbnail[${source}] 异常:`, e.message); return null; } }, normalizeForCompare(text) { return String(text || "").toLowerCase().replace(/[^a-z0-9]/g, ""); }, isCodeMatched(text, code) { const normalizedText = this.normalizeForCompare(text); const normalizedCode = this.normalizeForCompare(code); return !!normalizedCode && normalizedText.includes(normalizedCode); }, isDetailMatched(doc, url, code) { var _a2, _b; const title = ((_a2 = doc == null ? void 0 : doc.querySelector("title")) == null ? void 0 : _a2.textContent) || ""; const headings = [...(doc == null ? void 0 : doc.querySelectorAll("h1,h2,h3,.entry-title,.movie-title,.post-title")) || []].map((el) => el.textContent || "").join(" "); const bodyText = (((_b = doc == null ? void 0 : doc.body) == null ? void 0 : _b.textContent) || "").slice(0, 5e3); return this.isCodeMatched([url, title, headings, bodyText].join(" "), code); }, normalizePreviewUrl(url, baseUrl = "") { if (!url) return ""; const absolute = /^https?:\/\//i.test(url) ? url : baseUrl ? new URL(url, baseUrl).href : url; return absolute.replace(/^http:/, "https:"); }, isJavfreePreviewImage(url, code) { const cleanUrl = String(url || "").split("?")[0]; const lookupCode = this.lookupCode(code); const isFc2Numeric = /^\d{6,9}$/.test(lookupCode); const fc2ShotPattern = isFc2Numeric ? new RegExp(`${lookupCode}_\\d+\\.(?:jpe?g|png|webp)$`, "i") : null; return this.isCodeMatched(cleanUrl, code) && (/-(?:1080p|demosaic)\.(?:jpe?g|png|webp)$/i.test(cleanUrl) || isFc2Numeric && fc2ShotPattern.test(cleanUrl)); }, selectJavfreePreviewUrl(doc, detailUrl, code) { const urls = [...doc.querySelectorAll("p > img[src]")].map((img) => this.normalizePreviewUrl(img.getAttribute("src") || img.src || "", detailUrl)).filter((url) => this.isJavfreePreviewImage(url, code)); return urls.find((url) => /-1080p\./i.test(url)) || urls.find((url) => /-demosaic\./i.test(url)) || urls.find((url) => /_1\.(?:jpe?g|png|webp)$/i.test(url)) || ""; }, async javfree(code) { var _a2; code = this.lookupCode(code); const cacheKey = this.cacheKey(code); const cached = sessionStorage.getItem(cacheKey); if (cached) return cached; try { const html2 = await gmHttp.get(`https://javfree.me/search/${code}`); const doc = new DOMParser().parseFromString(html2, "text/html"); const link2 = (_a2 = [...doc.querySelectorAll(".entry-title>a")].find((a) => this.isCodeMatched([a.href, a.textContent].join(" "), code))) == null ? void 0 : _a2.href; if (!link2) return null; const dHtml = await gmHttp.get(link2); const dDoc = new DOMParser().parseFromString(dHtml, "text/html"); if (!this.isDetailMatched(dDoc, link2, code)) return null; const url = this.selectJavfreePreviewUrl(dDoc, link2, code); if (url) { sessionStorage.setItem(cacheKey, url); return url; } return null; } catch { return null; } }, async javstore(code) { code = this.lookupCode(code); try { const normalizedCode = code.replace(/^fc2-?/i, "").replace(/-/g, "").toLowerCase(); const searchUrl = `https://javstore.net/search?q=${encodeURIComponent(code)}`; const searchHtml = await gmHttp.get(searchUrl); const searchDoc = new DOMParser().parseFromString(searchHtml, "text/html"); const candidateLinks = searchDoc.querySelectorAll('a[href*="/"]'); const detailUrls = []; for (const link2 of candidateLinks) { const href = link2.getAttribute("href"); if (!href) continue; if (href.startsWith("http") && !href.includes("javstore.net")) continue; const urlObj = new URL(href, searchUrl); if (!/javstore\.net$/i.test(urlObj.hostname)) continue; if (/^\/search(?:[/?#]|$)/i.test(urlObj.pathname)) continue; const fullUrl = urlObj.href; const pathLastPart = decodeURIComponent(urlObj.pathname.split("/").pop() || ""); const normalizedPath = pathLastPart.toLowerCase().replace(/-/g, ""); const looksLikeDetail = /\.html$/i.test(urlObj.pathname) || /^\/\d+[-/]/.test(urlObj.pathname); if (looksLikeDetail && normalizedPath.includes(normalizedCode) && !detailUrls.includes(fullUrl)) { detailUrls.push(fullUrl); } } if (detailUrls.length === 0) return null; for (const detailUrl of detailUrls) { const imgUrl = await this._extractImgFromDetail(detailUrl, code); if (imgUrl) return imgUrl; } return null; } catch (e) { clog.debug("javstore 获取失败", e); return null; } }, async _extractImgFromDetail(detailUrl, code) { try { const detailHtml = await gmHttp.get(detailUrl); const detailDoc = new DOMParser().parseFromString(detailHtml, "text/html"); if (!this.isDetailMatched(detailDoc, detailUrl, code)) return null; for (const link2 of detailDoc.querySelectorAll("a")) { if (link2.textContent.includes("CLICK HERE")) { const imgUrl = link2.href || link2.getAttribute("href") || ""; if (imgUrl) return this.normalizePreviewUrl(imgUrl, detailUrl); } } const img = detailDoc.querySelector('img[src*="_s.jpg"]'); if (img) { let src = img.getAttribute("src") || ""; if (!src.startsWith("http")) src = new URL(src, detailUrl).href; return this.normalizePreviewUrl(src.replace(/_s\.jpg$/, "_l.jpg"), detailUrl); } return null; } catch (e) { clog.debug("javstore: 详情页请求失败", detailUrl, e.message); return null; } }, async projectjav(code) { var _a2; code = this.lookupCode(code); try { const request = (url) => gmHttp.get(url, {}, { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8" }); const searchUrl = `https://projectjav.com/?searchTerm=${encodeURIComponent(code)}`; const searchHtml = await request(searchUrl); const searchDoc = new DOMParser().parseFromString(searchHtml, "text/html"); let detailUrl = ""; const allMovieLinks = [...searchDoc.querySelectorAll('a[href*="/movie/"]')]; const firstLink = ((_a2 = allMovieLinks[0]) == null ? void 0 : _a2.getAttribute("href")) || ""; if (firstLink) { detailUrl = firstLink.startsWith("http") ? firstLink : `https://projectjav.com${firstLink}`; } if (!detailUrl) return null; const detailHtml = await request(detailUrl); const detailDoc = new DOMParser().parseFromString(detailHtml, "text/html"); const screenshotLink = [...detailDoc.querySelectorAll('.col-md-12.thumbnail a[data-featherlight="image"], .thumbnail a[data-featherlight="image"]')].find((a) => { var _a3; return this.isCodeMatched([a.outerHTML, (_a3 = a.closest(".thumbnail")) == null ? void 0 : _a3.outerHTML, detailUrl].join(" "), code); }); if (screenshotLink) { const thumbImg = screenshotLink.querySelector("img"); const href = screenshotLink.getAttribute("href") || ""; if (href) return this.normalizePreviewUrl(href, detailUrl); if (thumbImg) { const src = (thumbImg.getAttribute("src") || "").replace(/\?.*$/, ""); if (src) return this.normalizePreviewUrl(src, detailUrl); } } return null; } catch (e) { clog.debug("[projectjav] 异常:", e.message); return null; } }, async get(code) { const cacheKey = this.cacheKey(code); const cached = sessionStorage.getItem(cacheKey); if (cached) return { url: cached, source: null }; let url = null, source = null; for (const src of this.sourceOrder()) { url = await this.fetchFromSource(src, code); if (url) { source = src; break; } } if (url) sessionStorage.setItem(cacheKey, url); return { url, source }; } }; let previewOverlay = null; function showPreviewOverlay$1(imgUrl, code, source = null) { if (previewOverlay) { previewOverlay.close(); } let currentBlobUrl = null; const originalHtmlOverflow = document.documentElement.style.overflow; const originalBodyOverflow = document.body.style.overflow; document.documentElement.style.overflow = "hidden"; document.body.style.overflow = "hidden"; const container = document.createElement("div"); container.className = "jhs-preview-overlay"; const img = document.createElement("img"); img.src = imgUrl; img.className = "jhs-preview-img"; img.onclick = (e) => { e.stopPropagation(); img.classList.toggle("zoomed"); }; img.onerror = () => { img.src = imgUrl; }; const toolbar = document.createElement("div"); toolbar.className = "jhs-preview-toolbar"; const createButton = (text, className, onClick) => { const btn = document.createElement("button"); btn.className = `jhs-preview-btn ${className}`; btn.textContent = text; btn.onclick = onClick; return btn; }; const setActiveSource = (activeSource) => { toolbar.querySelectorAll(".jhs-preview-btn").forEach((b) => b.classList.remove("active")); const activeBtn = toolbar.querySelector(`.jhs-preview-btn-${activeSource}`); if (activeBtn) activeBtn.classList.add("active"); }; const changeSource = async (src, srcName) => { const loadingOverlay = document.createElement("div"); loadingOverlay.className = "jhs-loading-overlay"; loadingOverlay.innerHTML = '<div class="jhs-loading-spinner"><div class="spinner"></div><div>正在加载预览图...</div></div>'; document.body.appendChild(loadingOverlay); try { const newUrl = src === "javfree" ? await Thumbnail.javfree(code) : src === "projectjav" ? await Thumbnail.projectjav(code) : await Thumbnail.javstore(code); if (newUrl) { if (currentBlobUrl) { URL.revokeObjectURL(currentBlobUrl); currentBlobUrl = null; } img.src = newUrl; setActiveSource(src); } else { showToast(`${srcName} 未找到预览图`, "error"); } } catch (err) { showToast(`${srcName} 加载失败: ${err.message}`, "error"); } finally { loadingOverlay.remove(); } }; let toastTimer = null; const showToast = (msg, type = "info") => { const existing = container.querySelector(".jhs-preview-toast"); if (existing) existing.remove(); if (toastTimer) clearTimeout(toastTimer); const toast = document.createElement("div"); toast.className = `jhs-preview-toast jhs-preview-toast-${type}`; toast.textContent = msg; container.appendChild(toast); requestAnimationFrame(() => toast.classList.add("show")); toastTimer = setTimeout(() => { toast.classList.remove("show"); setTimeout(() => toast.remove(), 300); }, 3e3); }; const javfreeBtn = createButton("javfree", "jhs-preview-btn-javfree javfree", (e) => { e.stopPropagation(); changeSource("javfree", "javfree"); }); const projectjavBtn = createButton("projectjav", "jhs-preview-btn-projectjav projectjav", (e) => { e.stopPropagation(); changeSource("projectjav", "projectjav"); }); const javstoreBtn = createButton("javstore", "jhs-preview-btn-javstore javstore", (e) => { e.stopPropagation(); changeSource("javstore", "javstore"); }); const newWindowBtn = createButton("新窗口打开", "jhs-preview-btn-action action", (e) => { e.stopPropagation(); window.open(img.src); }); const downloadBtn = createButton("下载", "jhs-preview-btn-action action", (e) => { e.stopPropagation(); const a = document.createElement("a"); a.href = img.src; a.download = `${code}.jpg`; a.click(); }); toolbar.appendChild(javfreeBtn); toolbar.appendChild(projectjavBtn); toolbar.appendChild(javstoreBtn); toolbar.appendChild(newWindowBtn); toolbar.appendChild(downloadBtn); if (source === "javfree") setActiveSource("javfree"); else if (source === "projectjav") setActiveSource("projectjav"); else if (source === "javstore") setActiveSource("javstore"); else setActiveSource("javfree"); container.appendChild(img); const closeOverlay = () => { if (container.parentNode) { container.remove(); toolbar.remove(); document.documentElement.style.overflow = originalHtmlOverflow; document.body.style.overflow = originalBodyOverflow; if (currentBlobUrl) { URL.revokeObjectURL(currentBlobUrl); currentBlobUrl = null; } } }; container.onclick = closeOverlay; const escHandler = (e) => { if (e.key === "Escape") { closeOverlay(); document.removeEventListener("keydown", escHandler); } }; document.addEventListener("keydown", escHandler); document.body.appendChild(container); document.body.appendChild(toolbar); previewOverlay = { container, toolbar, close: closeOverlay }; } class ScreenShotPlugin extends BasePlugin { getName() { return "ScreenShotPlugin"; } async handle() { this.loadScreenShot().then(); } async loadScreenShot() { if (!window.isDetailPage) return; if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return; let carNum2 = this.getPageInfo().carNum; isJavDb$1 && $(".preview-images .tile-item").first().before( '<a class="tile-item screen-container" style="overflow:hidden;max-height:215px;text-align:center;"><div style="margin-top:50px;color:#000;cursor:auto">正在加载缩略图</div></a>' ); isJavBus$1 && $("#sample-waterfall .sample-box:first").after( '<a class="sample-box screen-container" style="overflow:hidden;height:110px;text-align:center;"><div style="margin-top:30px;color:#000;cursor:auto">正在加载缩略图</div></a>' ); try { const result = await Thumbnail.get(carNum2); if (result.url) { this.addImg("缩略图", result.url, carNum2, result.source); clog.log("加载缩略图:", result.url, result.source); } else { this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图")); } } catch (e) { this.showErrorFallback(carNum2, e); } } async getScreenshot(carNum2) { const result = await Thumbnail.get(carNum2); if (result.url) return result.url; throw new Error("未找到预览图"); } async getJavStoreScreenShot(carNum2) { return await Thumbnail.javstore(carNum2); } async getJavFreeScreenShot(carNum2) { return await Thumbnail.javfree(carNum2); } addImg(title, imgUrl, code, source) { if (!imgUrl) return; isJavDb$1 && $(".screen-container").html( `<img src="${imgUrl}" alt="${title}" loading="lazy" style="width:100%;">` ); isJavBus$1 && $(".screen-container").html( `<div class="photo-frame"><img src="${imgUrl}" style="height:inherit;width:100%;" title="${title}" alt="${title}"></div>` ); $(".screen-container").on("click", (event) => { event.stopPropagation(); event.preventDefault(); showPreviewOverlay$1(imgUrl, code || title, source); }); } showErrorFallback(carNum2, error) { var _a2, _b; console.error("获取缩略图失败:", ((_b = (_a2 = error == null ? void 0 : error.message) == null ? void 0 : _a2.substring) == null ? void 0 : _b.call(_a2, 0, 100)) || error); let differentCss = isJavBus$1 ? "margin-top:30px" : "margin-top:50px"; $(".screen-container").html( `<div style="${differentCss};cursor:auto;color:#000;">获取缩略图失败</div><br/><a href='#' class='retry-link'>点击重试</a> 或 <a class="check-link" href='https://javstore.net/search?q=${encodeURIComponent(carNum2)}' target='_blank'>前往确认</a>` ).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", async (e) => { e.stopPropagation(); e.preventDefault(); $(".screen-container").html( `<div style="${differentCss};cursor:auto;color:#000;">正在重新加载...</div>` ); try { const result = await Thumbnail.get(carNum2); if (result.url) { this.addImg("缩略图", result.url, carNum2, result.source); } else { this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图")); } } catch (err) { this.showErrorFallback(carNum2, err); } }).on("click", ".check-link", (e) => { e.stopPropagation(); e.preventDefault(); window.open(`https://javstore.net/search?q=${encodeURIComponent(carNum2)}`, "_blank"); }); } } window.showPreviewOverlay = showPreviewOverlay$1; const getDownPathList = async () => { const res = await gmHttp.get("https://webapi.115.com/offine/downpath"); return "object" == typeof res ? res.data : null; }; const searchFiles = async (search_value, offset = 0, limit = 30, params = {}) => { const queryParams = new URLSearchParams({ search_value, offset, limit, ...params }); const url = `https://webapi.115.com/files/search?${queryParams}`; return await gmHttp.get(url); }; class WangPan115TaskPlugin extends BasePlugin { getName() { return "WangPan115TaskPlugin"; } async handle() { $(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => { const $copyBtn = $(el); const magnet = $copyBtn.attr("data-clipboard-text"); $copyBtn.css("margin-right", parseInt($copyBtn.css("margin-right")) - 4 + "px"); const $downloadBtn = $copyBtn.siblings("a.button, button").first(); if ($downloadBtn.length) { $downloadBtn.css("margin-right", parseInt($downloadBtn.css("margin-right")) + 3 + "px"); } $("<button>").text("验车").addClass("button is-small").attr("style", "background-color:#2b6cb0 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click(((event) => { event.stopPropagation(); event.preventDefault(); this.getBean("MagnetHubPlugin").checkWhatslink(magnet); })).appendTo($copyBtn.parent()); $("<button>").text("115离线下载").addClass("button is-small").attr("style", "background-color:#4a90e2 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((async (event) => { event.stopPropagation(); event.preventDefault(); let loadObj = loading(); try { await this.handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })).appendTo($copyBtn.parent()); })); isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => { this.bus115Down(); })); } async bus115Down() { const $headerRow = $("#magnet-table tr").first(); $("#magnet-table tr").each(((i, row) => { if (row === $headerRow[0]) return; const magnetLink = $(row).find("td:nth-child(1) a").attr("href"); if (magnetLink && magnetLink.includes("magnet:")) { const actionCell = $("<td>").addClass("action-cell"); $("<button>").text("验车").addClass("button is-small").attr("style", "background-color:#2b6cb0 !important;color:#fff !important;border:none !important;margin-right:4px;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click(((event) => { event.stopPropagation(); event.preventDefault(); this.getBean("MagnetHubPlugin").checkWhatslink(magnetLink); })).appendTo(actionCell); $("<button>").text("115离线下载").addClass("button is-small").attr("style", "background-color:#4a90e2 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((async (event) => { event.stopPropagation(); event.preventDefault(); let loadObj = loading(); try { await this.handleAddTask(magnetLink); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })).appendTo(actionCell); $(row).append(actionCell); } })); if ($headerRow.find("td, th").last().text().trim() !== "操作") { $headerRow.append($("<td>").text("操作")); } } static sanitizeDirName(str) { if (!str) return ""; return str.replace(/[\\/:*?"<>|]/g, "").replace(/\s+/g, " ").trim(); } static parseOfflineDirTemplate(str, params) { if (!str || !params) return str; return str.replace(/\$\{(\w+)\}/g, (match, key) => { if (!params.hasOwnProperty(key)) return match; const value = params[key] || ""; return WangPan115TaskPlugin.sanitizeDirName(String(value)); }); } static async ensureDir(dirName, pid = 0) { const result = await gmHttp.postFileFormData("https://webapi.115.com/files/add", { pid, cname: dirName }); if (result && result.file_id) return result.file_id; const searchRes = await searchFiles(dirName, 0, 1, { fc: 1 }); if (searchRes && searchRes.data && searchRes.data.length > 0) { const match = searchRes.data.find((d) => d.pid === pid || d.pid === String(pid)); if (match) return match.fid || match.file_id; } return null; } static async registerDirAsDownPath(fileId, dirName) { await gmHttp.postFileFormData("https://webapi.115.com/offine/downpath", { file_id: fileId }); const list2 = await getDownPathList(); const created = list2 ? list2.find((d) => d.file_id === fileId || String(d.file_id) === String(fileId)) : null; return created ? created.id : null; } static async createMultiLevelDownPath(templatePath) { const segments = templatePath.split("/").filter((s) => s.trim()); if (segments.length === 0) return null; let pid = 0; let fileId = null; for (const seg of segments) { fileId = await WangPan115TaskPlugin.ensureDir(seg.trim(), pid); if (!fileId) { console.error("[115离线] 创建目录失败:", seg); return null; } pid = fileId; } const lastDirName = segments[segments.length - 1].trim(); const downPathId = await WangPan115TaskPlugin.registerDirAsDownPath(fileId, lastDirName); console.log("[115离线] 多级目录创建完成:", templatePath, "→ downPathId=" + downPathId); return downPathId; } getPageInfoForOffline() { const baseInfo = this.getPageInfo(); const carNum2 = baseInfo.carNum || ""; let title = "", maker = "", actor = ""; if (isJavBus$1) { title = $("h3").first().text().trim(); maker = $('span.header:contains("製作商:")').parent("p").text().replace("製作商:", "").trim() || $('span.header:contains("制作商:")').parent("p").text().replace("制作商:", "").trim(); const actorEl = $('span.genre a[href*="/star/"]').first(); actor = actorEl.length ? actorEl.text().trim() : ""; } else { title = $(".current-title").first().text().trim() || $("h2.title.is-4 .current-title").first().text().trim() || $(".movie-title").first().text().trim(); maker = $('strong:contains("片商:")').parent().find(".value").text().trim() || $('strong:contains("片商:")').parent().find(".value").text().trim(); actor = $(".actor-female").first().text().trim() || $('strong:contains("演員:"), strong:contains("演员:")').parent().find(".value a").first().text().trim() || ""; } const now = /* @__PURE__ */ new Date(); if (carNum2 && title) { const upperTitle = title.toUpperCase(); const upperCode = carNum2.toUpperCase(); if (upperTitle.startsWith(upperCode)) { title = title.substring(carNum2.length).replace(/^[\s\-_]+/, "").trim(); } } console.log("[115离线] 页面信息提取:", { code: carNum2, title, maker, actor }); return { code: carNum2, title, maker, actor, date: utils.formatDate(now).split(" ")[0] }; } async getSavePathId(nyName) { const enableCustom = await storageManager.getSetting("enable115OfflineFormat", NO); if (enableCustom === YES) { const template = await storageManager.getSetting("offlineFolderFormat", ""); if (template && template.includes("${")) { const pageInfo = this.getPageInfoForOffline(); return WangPan115TaskPlugin.parseOfflineDirTemplate(template, pageInfo); } } let savePath115 = await storageManager.getSetting("savePath115", "云下载"); nyName && (savePath115 = savePath115.replaceAll("{ny}", nyName)); savePath115 = savePath115.replaceAll("{date}", utils.formatDate(/* @__PURE__ */ new Date())); return savePath115; } async handleAddTask(magnetLink, savePath) { const singInfo = await (async () => { const res = await gmHttp.get("https://115.com/?ct=offline&ac=space&_=" + (/* @__PURE__ */ new Date()).getTime()); return "object" == typeof res ? res : null; })(); if (!singInfo) { show.error("未登录115网盘", { close: true, duration: -1, callback: async () => { window.open("https://115.com"); } }); return; } const sign = singInfo.sign, time2 = singInfo.time, userId = this.getUserId(); let wp_path_id = savePath || ""; if (!wp_path_id) { const fullPath = await this.getSavePathId(); console.log("[115离线] 目录模板解析结果:", fullPath); if (fullPath) { if (fullPath.includes("/")) { console.log("[115离线] 检测到多级目录,逐级创建:", fullPath); const newId = await WangPan115TaskPlugin.createMultiLevelDownPath(fullPath); if (newId) wp_path_id = newId; } else { const downPathList = await getDownPathList(); if (downPathList && downPathList.length > 0) { const matched = downPathList.find(((d) => d.name === fullPath)); if (matched) { wp_path_id = matched.id; console.log('[115离线] 目录匹配结果: 匹配到 "' + matched.name + '" -> id=' + matched.id); } else { console.log('[115离线] 目录匹配结果: 未匹配到 "' + fullPath + '",自动创建并注册'); const newId = await WangPan115TaskPlugin.createMultiLevelDownPath(fullPath); if (newId) wp_path_id = newId; } } } } } const result = await (async (magnet, wp_path_id2 = "", uid, sign2, time3) => { const data = { url: encodeURIComponent(magnet), wp_path_id: wp_path_id2, uid, sign: sign2, time: time3 }; return await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=add_task_url", data); })(magnetLink, wp_path_id, userId, sign, time2); console.log("离线下载返回值:", result); let infoHash = result.info_hash, fileId = await this.getFileId(userId, sign, time2, infoHash), openUrl = "https://115.com/?tab=offline&mode=wangpan"; fileId && (openUrl = `https://115.com/?cid=${fileId}&offset=0&mode=wangpan`); let title = "添加成功, 是否前往查看?"; false === result.state && (title = result.error_msg + " 是否前往查看?"); utils.q(null, title, (async () => { let fileId2 = await this.getFileId(userId, sign, time2, infoHash); fileId2 && (openUrl = `https://115.com/?cid=${fileId2}&offset=0&mode=wangpan`); window.open(openUrl); })); } async getUserId() { let downPathList = await getDownPathList(); if (downPathList && downPathList.length > 0) return downPathList[0].id; { show.info("没有默认离线目录, 正在创建中..."); const dirId = (await (async (dirName, pid = 0) => { const data = { pid, cname: dirName }; return await gmHttp.postFileFormData("https://webapi.115.com/files/add", data); })("云下载")).file_id; await (async (dirId2) => { const data = { file_id: dirId2 }; return await gmHttp.postFileFormData("https://webapi.115.com/offine/downpath", data); })(dirId); show.info("创建完成, 开始执行离线下载"); downPathList = await getDownPathList(); if (downPathList && downPathList.length > 0) return downPathList[0].id; throw new Error("获取115用户Id失败"); } } async getFileId(userId, sign, time2, infoHash) { const taskList = await (async (uid, sign2, time3) => { const data = { page: 1, uid, sign: sign2, time: time3 }; return (await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=task_lists", data)).tasks; })(userId, sign, time2); console.log("云离线列表:", taskList); let fileId = null; for (let i = 0; i < taskList.length; i++) { let task = taskList[i]; if (task.info_hash === infoHash) { fileId = task.file_id; break; } } return fileId; } //TODO: 暂时没有删除离线任务 // async deleteOfflineTasksByKeyword(keyword) { // try { // const singInfo = await (async () => { // const res = await gmHttp.get("https://115.com/?ct=offline&ac=space&_=" + (new Date).getTime()); // return "object" == typeof res ? res : null; // })(); // if (!singInfo) { // console.error("删除离线任务: 未登录115网盘"); // return; // } // const {sign: sign, time: time} = singInfo, uid = singInfo.uid || (() => { // const match = document.cookie.match(/UID=(\d+)_/); // return match ? match[1] : null; // })() || await this.getUserId(); // const taskListRes = await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=task_lists", { // page: 1, // uid: uid, // sign: sign, // time: time // }); // const tasks = (null == taskListRes ? void 0 : taskListRes.tasks) || []; // const keyword2 = keyword.toLowerCase().replace("fc2-", ""); // let deletedCount = 0; // for (const task of tasks) { // if (task.name && task.name.toLowerCase().includes(keyword2)) { // console.log(`删除离线任务: ${task.name} (hash: ${task.info_hash})`); // await gmHttp.postForm("https://115.com/web/lixian/?ct=lixian&ac=task_del", { // "hash[0]": task.info_hash, // flag: 1, // uid: uid, // sign: sign, // time: time // }); // deletedCount++; // } // } // deletedCount > 0 && console.log(`共删除 ${deletedCount} 个离线任务`); // } catch (error) { // console.error("删除离线任务失败:", error); // } // } } class WangPan115Plugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "JHS_115_COOKIE", "jhs_115_cookie"); __publicField2(this, "JHS_115_MAX_AGE", "jhs_115_max_age"); } getName() { return "WangPan115Plugin"; } async initCss() { return "\n <style>\n .login-box .ltab-office {\n border: 1px solid #DEE4EE;\n }\n \n .change-bg::before {\n background-color:#F9FAFB !important;\n }\n \n .site-login-wrap {\n height: auto;\n }\n \n #jhs-cookie-panel {\n width: 200px;\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 10000;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n cursor: pointer;\n background-color: #FFFFFF;\n color: #333333;\n padding: 0;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: all 0.3s ease;\n border: 1px solid #E0E0E0;\n }\n \n #jhs-cookie-panel.expanded {\n padding: 0;\n border-radius: 8px;\n background-color: #FFFFFF;\n color: #333333;\n box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);\n }\n \n #jhs-cookie-header {\n padding: 10px 15px;\n background-color: #0078D4;\n color: white;\n border-radius: 6px 6px 0 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-weight: 600;\n }\n \n #jhs-cookie-panel:not(.expanded) #jhs-cookie-header {\n border-radius: 6px;\n padding: 8px 15px;\n }\n \n #jhs-cookie-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease-out;\n padding: 0 15px;\n }\n \n #jhs-cookie-panel.expanded #jhs-cookie-content {\n max-height: 250px;\n padding: 15px;\n }\n \n #jhs-cookie-value {\n max-height: 100px;\n overflow-y: auto;\n white-space: pre-wrap;\n word-break: break-all;\n margin-bottom: 15px;\n padding: 10px;\n border: 1px solid #CCCCCC;\n background-color: #F8F8F8;\n font-size: 12px;\n border-radius: 4px;\n color: #555;\n }\n \n #jhs-copy-btn {\n background-color: #10B981;\n color: white;\n border: none;\n padding: 8px 15px;\n text-align: center;\n text-decoration: none;\n display: inline-block;\n font-size: 14px;\n margin: 0;\n cursor: pointer;\n border-radius: 4px;\n width: 100%;\n font-weight: 600;\n transition: background-color 0.2s ease;\n }\n \n #jhs-copy-btn:hover {\n background-color: #059669;\n }\n </style>\n "; } async handle() { if (!currentHref$1.includes("&ac=userfile") && currentHref$1.includes("115")) { utils.loopDetector((() => $("#js-login-box").length > 0), (() => { if (0 !== $("#js-login-box").length) { this.reLogin(); this.hookPage(); this.bindClick(); } }), 20, 4e3, true); this.createCookiePanel(); } } reLogin() { utils.loopDetector((() => $(".login-finished").length > 0), (() => { if ($(".login-finished").length > 0 || 0 === $("#js-login-box").length) return; const jhs_115_cookie = localStorage.getItem(this.JHS_115_COOKIE), jhs_115_max_age = localStorage.getItem(this.JHS_115_MAX_AGE); document.cookie.includes("SEID") || null === jhs_115_cookie || utils.q(null, "检测到上次登录已有缓存cookie, 是否使用并登录?", (() => { utils.addCookie(jhs_115_cookie, { maxAge: parseInt(jhs_115_max_age), domain: ".115.com" }); window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan"; })); }), 20, 1500, true); } hookPage() { const $cookieTab = $('<a id="jhs-cookie"><s>🔰 JHS-扫码</s></a>'); $(".ltab-office").after($cookieTab); const $cookieScene = $(` <div id="jhs_cookie_box" style="display: none; padding: 0 20px; max-width: 300px; margin: auto;"> <div style="margin-bottom: 15px; text-align: center;"> <span style="font-size: 18px; font-weight: bold; color: #333; display: block; margin-bottom: 10px;"> 使用115App扫码登录 </span> <div style="text-align: left;"> <select id="login-115-type" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;"> <option value="" style="color: #999;">请选择登录方式</option> <option value="wechatmini">微信小程序</option> <option value="alipaymini">支付宝小程序</option> </select> </div> </div> <div style="text-align: left;"> <select id="cookie-expiry-select" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;"> ${[{ label: "有效期: 会话 (关闭浏览器)", value: 0 }, { label: "有效期: 1 天", value: 86400 }, { label: "有效期: 7 天", value: 604800 }, { label: "有效期: 30 天", value: 2592e3, default: true }, { label: "有效期: 60 天", value: 5184e3 }, { label: "有效期: 180 天", value: 15552e3 }].map(((c) => `<option value="${c.value}" ${c.default ? "selected" : ""} > ${c.label} </option>`)).join("")} </select> </div> <div id="qrcode-box" style="display: none; justify-content:center; min-height: 100px; border: 1px dashed #aaa; padding: 15px; text-align: center; margin-top: 15px; border-radius: 4px; background-color: #fff; line-height: 70px; color: #666;"> 二维码占位区域 </div> <div style="margin-bottom: 15px; text-align: center; margin-top:50px"> <span style="font-size: 18px; font-weight: bold; color: #333; display: block; margin-bottom: 10px;">已有Cookie? 在此输入并回车</span> <div style="text-align: left;"> <input type="text" id="cookie-str-input" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid #ddd; font-size: 14px; box-sizing: border-box; background-color: white;"> </div> <div style="text-align: center;margin-top:5px"> <a class="a-primary" id="submit-cookie-btn">提交</a> </div> </div> </div> `); $("#js-login_box").find(".login-footer").before($cookieScene); } bindClick() { $("#jhs-cookie").on("click", (() => { const finishedEl = document.querySelector('[lg_rel="finished"]'); if (finishedEl) finishedEl.style.display = "none"; else { document.querySelector('[lg_rel="qrcode"]').style.display = "none"; document.querySelector(".login-footer").style.display = "none"; document.querySelector(".list-other-login").style.display = "none"; } document.querySelectorAll("#js-login_way > *").forEach(((tab) => { tab.classList.remove("current"); })); document.querySelector("#jhs_cookie_box").style.display = "block"; $("#jhs-cookie").css("background", "#fff"); $(".ltab-cloud").addClass("change-bg"); })); $(".ltab-cloud").on("click", (() => { document.querySelector("#jhs_cookie_box").style.display = "none"; const finishedEl = document.querySelector('[lg_rel="finished"]'); if (finishedEl) finishedEl.style.display = "flex"; else { document.querySelector('[lg_rel="qrcode"]').style.display = "block"; document.querySelector(".login-footer").style.display = "block"; document.querySelector(".list-other-login").style.display = "block"; } $("#jhs-cookie").css("background", "#F9FAFB"); $(".ltab-cloud").removeClass("change-bg"); })); let loginTimeout = null; $("#login-115-type").on("change", (async (event) => { let login115Type = $("#login-115-type").val(); if (!login115Type) return; const loginInfo = (await (async (loginType) => { let url = `https://qrcodeapi.115.com/api/1.0/${loginType}/1.0/token/`; return await gmHttp.get(url); })(login115Type)).data, qrcode = loginInfo.qrcode, sign = loginInfo.sign, time2 = loginInfo.time, uid = loginInfo.uid; console.log("生成二维码:", loginInfo); const $qrcodeBox = $("#qrcode-box"); $qrcodeBox.css("display", "flex"); $qrcodeBox.html(""); new QRCode($qrcodeBox[0], { text: qrcode, width: 150, height: 150, correctLevel: QRCode.CorrectLevel.H }); loginTimeout && clearTimeout(loginTimeout); const checkLoginRecursive = async () => { try { const loginResult = await (async (uid2, time3, sign2) => { let url = `https://qrcodeapi.115.com/get/status/?uid=${uid2}&time=${time3}&sign=${sign2}`; return await gmHttp.get(url); })(uid, time2, sign); console.log("已扫码, 正在获取结果:", loginResult); let data = loginResult.data, msg = data.msg, status = data.status; if (msg) { console.log(msg); show.info(msg); } if (2 === status) { show.ok("扫码登录成功"); const checkResult = await (async (loginType, uid2) => { const data2 = { app: loginType, account: uid2 }, url = `https://passportapi.115.com/app/1.0/${loginType}/1.0/login/qrcode/`; return await gmHttp.postFileFormData(url, data2); })(login115Type, uid); console.log("扫码登录成功:", checkResult); if (checkResult.data && checkResult.data.cookie) { const cookie = checkResult.data.cookie, cookieStr = `UID=${cookie.UID}; CID=${cookie.CID}; SEID=${cookie.SEID}; KID=${cookie.KID}`; console.log("解析出cookie:", cookieStr); localStorage.setItem(this.JHS_115_COOKIE, cookieStr); localStorage.setItem(this.JHS_115_MAX_AGE, $("#cookie-expiry-select").val()); window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan"; } return; } loginTimeout = setTimeout(checkLoginRecursive, 500); } catch (error) { console.error("登录检查失败:", error); } }; await checkLoginRecursive(); })); const handleCookie = () => { const cookieStr = cookieInput.value, expirySelect = document.getElementById("cookie-expiry-select"); let maxAge = parseInt(expirySelect.value); utils.addCookie(cookieStr, { maxAge, domain: ".115.com" }); window.location.href = "https://115.com/?cid=0&offset=0&mode=wangpan"; }, cookieInput = document.getElementById("cookie-str-input"); cookieInput.addEventListener("keydown", (function(event) { if ("Enter" === event.key) { event.preventDefault(); handleCookie(); } })); $("#submit-cookie-btn").on("click", (() => { handleCookie(); })); } showMessage(message) { const toast = document.createElement("div"); toast.textContent = message; toast.style.cssText = "\n position: fixed;\n top: 20px;\n right: 20px;\n background-color: #333;\n color: white;\n padding: 10px 20px;\n border-radius: 5px;\n z-index: 20000;\n opacity: 0;\n transition: opacity 0.5s ease-in-out;\n "; document.body.appendChild(toast); setTimeout((() => { toast.style.opacity = "1"; }), 10); setTimeout((() => { toast.style.opacity = "0"; setTimeout((() => toast.remove()), 500); }), 3e3); } createCookiePanel() { const cookieValue = localStorage.getItem(this.JHS_115_COOKIE); if (!cookieValue) return; const panel = document.createElement("div"); panel.id = "jhs-cookie-panel"; panel.innerHTML = ` <div id="jhs-cookie-header"> <span>JHS-115-Cookie</span> <span id="jhs-toggle-icon">▼</span> </div> <div id="jhs-cookie-content"> <div id="jhs-cookie-value">${cookieValue}</div> <button id="jhs-copy-btn">复制 Cookie</button> </div> `; document.body.appendChild(panel); const header = document.getElementById("jhs-cookie-header"); document.getElementById("jhs-cookie-content"); const toggleIcon = document.getElementById("jhs-toggle-icon"), copyButton = document.getElementById("jhs-copy-btn"); header.addEventListener("click", (() => { const isExpanded = panel.classList.toggle("expanded"); toggleIcon.textContent = isExpanded ? "▲" : "▼"; })); copyButton.addEventListener("click", (async (e) => { e.stopPropagation(); try { await navigator.clipboard.writeText(cookieValue); this.showMessage("Cookie 已成功复制到剪贴板!"); } catch (err) { console.error("Failed to copy text using clipboard API: ", err); const tempTextArea = document.createElement("textarea"); tempTextArea.value = cookieValue; document.body.appendChild(tempTextArea); tempTextArea.select(); document.execCommand("copy"); document.body.removeChild(tempTextArea); this.showMessage("Cookie 已复制! (回退方案)"); } })); panel.classList.remove("expanded"); } } const _WangPan115MatchPlugin = class _WangPan115MatchPlugin2 extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "loginStatus", _WangPan115MatchPlugin2.LoginStatus.UNCHECKED); } getName() { return "WangPan115MatchPlugin"; } async initCss() { return "\n <style>\n [class^='jhs-match-'] {\n padding: 1px 2px;\n margin-left: 0;\n margin-right: 5px;\n }\n \n .jhs-match-detail {\n display: inline-block;\n width: 50%;\n z-index: 1000;\n background: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 10px;\n overflow-y: auto;\n }\n .jhs-match-detail.isListPage{\n position: absolute;\n box-shadow: 0 2px 10px rgba(0,0,0,0.2);\n }\n .jhs-match-detail table {\n width: 100%;\n border-collapse: collapse;\n }\n .jhs-match-detail th, .jhs-match-detail td {\n padding: 4px 8px;\n border: 1px solid #eee;\n text-align: left;\n }\n .jhs-match-detail th {\n background-color: #f5f5f5;\n }\n .jhs-match-detail tr:hover {\n background-color: #f9f9f9;\n }\n </style>\n "; } async handle() { $(document).on("click", ".jhs-match-no-login-btn", (async (event) => { event.preventDefault(); event.stopPropagation(); await this.handleLoginRedirect(); })); $(document).on("click", ".jhs-match-btn", ((event) => { event.preventDefault(); event.stopImmediatePropagation(); this.showMatchDetail(event.currentTarget); })); $(document).on("click", ".jhs-match-error-btn", (async (event) => { event.preventDefault(); event.stopPropagation(); await this.retryMatch(event.currentTarget); })); $(document).on("click", ".jhs-detail-delete-btn", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const matchData = $btn.attr("data-match"); if (!matchData) return; const matchList = JSON.parse(matchData); const carNum2 = this.getPageInfo().carNum; this.showDeleteDialog(carNum2, matchList, (async (newMatchList) => { $("#115-match-table").remove(); await this.matchDetailPage(newMatchList); })); })); await this.matchDetailPage(); $(document).on("click", ".jhs-match-detail-error-btn", (async (event) => { event.preventDefault(); event.stopPropagation(); $(event.currentTarget).replaceWith(`<a class='jhs-match-btn' title="匹配中...">匹配中...</a>`); try { const carNum2 = this.getPageInfo().carNum, matchList = await this.searchFiles(carNum2); $(".jhs-115-match-detail").remove(); await this.matchDetailPage(matchList); } catch (error) { console.error(`重新匹配失败 [${carNum}]:`, error); this.showMatchError($box, carNum, error); } })); } async matchDetailPage(matchList) { if (!window.isDetailPage) return; if (await storageManager.getSetting("enable115Match", NO$1) === NO$1) return; const $detail = $('\n <div class="jhs-match-detail jhs-115-match-detail" id="115-match-table">\n <table>\n <thead>\n <tr style="text-align: center">\n <th colspan="4">115匹配</th>\n </tr>\n <tr>\n <th>名称</th>\n <th>大小</th>\n <th>时间</th>\n <th>播放</th>\n </tr>\n </thead>\n <tbody>\n </tbody>\n </table>\n </div>\n '), $tbody = $detail.find("tbody"); try { const carNum2 = this.getPageInfo().carNum; if (!matchList) { const { results, usedPhase2 } = await this._searchFilesWithPhase(carNum2); matchList = results; if (usedPhase2 && matchList.length > 0) { await this.autoRenameFiles(carNum2, matchList); } } await this.checkLoginStatus(); if (this.loginStatus === _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT) $tbody.append(`<tr><td colspan="4"> <a class='jhs-match-no-login-btn a-info' data-keyword="${carNum2}" title="未登录115网盘">未登录</a> </td></tr>`); else if (matchList.length > 0) { const rowsHtml = matchList.map(((match) => ` <tr> <td>${match.name}</td> <td>${this.formatSize(match.size)}</td> <td>${match.createTime}</td> <td> <a href="https://115vod.com/?pickcode=${match.videoId}&share_id=0" target="_blank" class="a-success" title="播放">播放</a> </td> </tr> `)).join(""); $tbody.append(rowsHtml); $detail.css("position", "relative"); $detail.append(`<span class="jhs-detail-delete-btn" data-match='${JSON.stringify(matchList)}' style="position: absolute; top: 8px; right: 12px; cursor: pointer; color: #e74c3c; font-size: 16px; font-weight: bold; z-index: 1;" title="删除115作品">🗑️ 删除</span>`); } else $tbody.append(`<tr><td colspan="4"> <a class='jhs-match-detail-error-btn a-info' data-keyword="${carNum2}" title="未匹配,点击重试">未匹配</a> </td></tr>`); } catch (error) { $tbody.append(`<tr><td colspan="4"> <a class="a-danger jhs-match-detail-error-btn" title="${error.message || "加载失败"}">加载失败,请重试</a> </td></tr>`); console.error("加载文件列表时发生错误:", error); } if (isJavDb$1) if ($("#all-match-box").length) $("#all-match-box").append($detail); else { $("#tabs-container").before("<div style='display: flex' id='all-match-box'></div>"); $("#all-match-box").append($detail); } else $("#mag-submit-show").before($detail); } async matchMovieList(movieListElement) { if (await storageManager.getSetting("enable115Match", NO$1) !== NO$1) { await this.checkLoginStatus(); await this.processMovieElements(movieListElement); } else $(".video-title [class^='jhs-match-']").remove(); } showMatchDetail(buttonElement) { const $el = $(buttonElement), matchData = $el.attr("data-match"); $(".jhs-match-detail").remove(); const matches = this.parseMatchData(matchData); if (0 === matches.length) return; if (1 === matches.length) { const pickcode = matches[0].videoId; window.open(`https://115vod.com/?pickcode=${pickcode}&share_id=0`, "_blank"); return; } const $detail = this.createMatchDetailElement(matches); this.positionDetailElement($detail, $el); this.addOutsideClickHandler($detail); $detail.on("click", ((e) => { e.stopPropagation(); })); } parseMatchData(matchData) { try { return JSON.parse(matchData) || []; } catch (e) { console.error("解析匹配数据失败:", e); return []; } } createMatchDetailElement(matches) { const $detail = $(` <div class="jhs-match-detail isListPage"> <table> <thead> <tr> <th>名称</th> <th>大小</th> <th>时间</th> <th>播放</th> </tr> </thead> <tbody> ${matches.map(((match) => ` <tr> <td>${match.name}</td> <td>${this.formatSize(match.size)}</td> <td>${match.createTime}</td> <td> <a href="https://115vod.com/?pickcode=${match.videoId}&share_id=0" target="_blank" class="a-success" title="播放">播放</a> </td> </tr> `)).join("")} </tbody> </table> </div> `); $("body").append($detail); return $detail; } positionDetailElement($detail, $trigger) { const offset = $trigger.offset(); $detail.css({ top: offset.top - $detail.outerHeight() + 20, left: offset.left }); } addOutsideClickHandler($detail) { setTimeout((() => { $(document).on("click.jhs-match-detail", ((e) => { if (!$detail.is(e.target) && 0 === $detail.has(e.target).length) { $detail.remove(); $(document).off("click.jhs-match-detail"); } })); }), 100); } async retryMatch(buttonElement) { const $el = $(buttonElement), $box2 = $el.closest(".movie-box, .item"), carNum2 = $el.attr("data-keyword"); $el.replaceWith(`<a class='jhs-match-btn' title="匹配中...">匹配中...</a>`); try { const matchList = await this.searchFiles(carNum2); this.updateMatchStatus($box2, carNum2, matchList); } catch (error) { console.error(`重新匹配失败 [${carNum2}]:`, error); this.showMatchError($box2, carNum2, error); } } updateMatchStatus($box2, carNum2, matchList) { if (matchList.length > 0) { $box2.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-btn a-success' data-keyword="${carNum2}" data-match='${JSON.stringify(matchList)}' title="点击查看匹配详情">匹配${matchList.length}个</a>`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0 && matchList.some((m) => m.dirId != null)) { $deleteBtn.attr("data-match", JSON.stringify(matchList)); $deleteBtn.show(); } if ($markBtn.length > 0) { $markBtn.show(); this.updateMarkBtnStyle($markBtn, carNum2); } } else { $box2.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-error-btn a-info' data-keyword="${carNum2}" title="点击重新尝试匹配">未匹配</a>`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0) { $deleteBtn.removeAttr("data-match"); $deleteBtn.hide(); } if ($markBtn.length > 0) { $markBtn.hide(); } } } async updateMarkBtnStyle($markBtn, carNum2) { const markedList = await storageManager.forage.getItem("markedDeleteList") || []; const isMarked = markedList.some((item) => item.carNum === carNum2); isMarked ? $markBtn.addClass("marked") : $markBtn.removeClass("marked"); $markBtn.attr("title", isMarked ? "取消标记删除" : "标记删除"); } async batchDeleteMarked(selectedItems, onProgress) { let deletedCount = 0; let failedCount = 0; const randomDelay = () => new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 2e3)); for (let i = 0; i < selectedItems.length; i++) { const item = selectedItems[i]; try { if (i > 0) { await randomDelay(); } const matchList = await this.searchFiles(item.carNum); if (matchList.length === 0) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, "未匹配到115文件"); continue; } const { regex: safeRegex } = codeParse(item.carNum); const safeMatchList = matchList.filter((m) => safeRegex.test(m.name)); if (safeMatchList.length === 0) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, "无安全可删除目录"); continue; } let itemDeleted = 0; const groupedByDir = {}; for (const m of safeMatchList) { if (!groupedByDir[m.dirId]) groupedByDir[m.dirId] = []; groupedByDir[m.dirId].push(m); } const entries = Object.entries(groupedByDir); for (let g = 0; g < entries.length; g++) { if (g > 0) { await randomDelay(); } const [dirId, items] = entries[g]; if (items.length === 1) { const result = await this._deleteOne(dirId, false, i, selectedItems, item, onProgress); if (result) { itemDeleted++; deletedCount++; } } else { for (let f = 0; f < items.length; f++) { if (f > 0) await randomDelay(); const fileDeleted = await this._deleteOne(items[f].folderId, true, i, selectedItems, item, onProgress); if (fileDeleted) { itemDeleted++; deletedCount++; } } } } onProgress && onProgress(i + 1, selectedItems.length, item, itemDeleted > 0 ? `已删除${itemDeleted}个` : "删除失败"); } catch (error) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, error.message || "出错"); console.error(`批量删除出错 [${item.carNum}]:`, error); } } return { deletedCount, failedCount }; } isRateLimited(response) { if (!response) return false; if (response.state === false) { const msg = (response.error || response.msg || "").toLowerCase(); if (msg.includes("频繁") || msg.includes("操作太频繁") || msg.includes("稍后再试") || msg.includes("rate") || msg.includes("limit") || msg.includes("too many")) { return true; } } if (response.status === 403 || response.status === 429) { return true; } return false; } async _deleteOne(deleteId, isFile, batchIndex, selectedItems, item, onProgress) { let retryCount = 0; const maxRetries = 3; const label = isFile ? "文件" : "目录"; while (retryCount <= maxRetries) { try { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": deleteId, ignore_warn: "1" }); if (result && result.state) { return 1; } else { const errMsg = result && result.error || "未知错误"; if (retryCount < maxRetries && this.isRateLimited(result)) { retryCount++; const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3; onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, backoff)); continue; } console.error(`删除${label}失败: ${deleteId}`, errMsg); return 0; } } catch (e) { if (retryCount < maxRetries && this.isRateLimited(e)) { retryCount++; const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3; onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, backoff)); continue; } console.error(`删除${label}失败: ${deleteId}`, e); return 0; } } return 0; } showDeleteDialog(carNum2, matchList, afterDelete) { const { regex: safeRegex } = codeParse(carNum2); const safeMatchList = matchList.filter((m) => safeRegex.test(m.name)); const unsafeCount = matchList.length - safeMatchList.length; if (safeMatchList.length === 0) { show.error(`作品 "${carNum2}" 匹配到的 ${matchList.length} 个项目均不含番号,无法安全删除。请手动在115中删除。`); return; } const formatSize = (bytes) => { if (!bytes) return "-"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = parseFloat(bytes), unit = 0; for (; size >= 1024 && unit < units.length - 1; ) size /= 1024, unit++; return size.toFixed(2) + " " + units[unit]; }; let html2 = '<div style="padding: 15px; max-height: 400px; overflow-y: auto;">'; html2 += `<div style="margin-bottom: 12px; font-size: 14px;">作品 <b>${carNum2}</b> 匹配到 <b>${matchList.length}</b> 个文件夹(可删除 <b>${safeMatchList.length}</b> 个):</div>`; if (unsafeCount > 0) { html2 += `<div style="margin-bottom: 10px; padding: 6px 10px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; font-size: 12px; color: #856404;">⚠ 已自动排除 ${unsafeCount} 个目录名不含番号的项目,防止误删</div>`; } html2 += '<div style="margin-bottom: 10px;"><label style="cursor: pointer;"><input type="checkbox" class="jhs-delete-select-all" checked> <b>全选</b></label></div>'; for (let i = 0; i < safeMatchList.length; i++) { const m = safeMatchList[i]; html2 += `<div style="margin-bottom: 6px; padding: 8px 10px; border: 1px solid #e0e0e0; border-radius: 4px; background: #fafafa;"> <label style="display: flex; align-items: flex-start; cursor: pointer; width: 100%;"> <input type="checkbox" class="jhs-delete-folder-cb" data-index="${i}" checked style="margin-top: 3px; flex-shrink: 0;"> <span style="margin-left: 8px; flex: 1;"> <div style="word-break: break-all;">${m.name}</div> <div style="font-size: 12px; color: #888; margin-top: 2px;">${formatSize(m.size)} | ${m.createTime}</div> </span> </label> </div>`; } html2 += "</div>"; const layerIndex = layer.open({ type: 1, title: "删除115作品文件夹", content: html2, area: ["520px", "auto"], btn: ["确定删除", "取消"], shadeClose: false, yes: async (index) => { var _a2, _b; const $layer = $(`#layui-layer${index}`); const checked = $layer.find(".jhs-delete-folder-cb:checked"); if (0 === checked.length) { show.error("请至少选择一个文件夹"); return; } const selected = checked.map((function() { return parseInt($(this).data("index")); })).get(); layer.close(index); let loadObj = loading(); let deletedCount = 0; try { const selectedItems = selected.map((i) => safeMatchList[i]); const totalByDir = {}; for (const m of safeMatchList) { totalByDir[m.dirId] = (totalByDir[m.dirId] || 0) + 1; } const groupedByDir = {}; for (const item of selectedItems) { if (!groupedByDir[item.dirId]) groupedByDir[item.dirId] = []; groupedByDir[item.dirId].push(item); } for (const [dirId, items] of Object.entries(groupedByDir)) { if (items.length === totalByDir[dirId]) { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": dirId, ignore_warn: "1" }); result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result); } else { for (const item of items) { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": item.folderId, ignore_warn: "1" }); result && result.state ? deletedCount++ : console.error(`删除文件失败: ${item.folderId}`, result); } } } loadObj.close(); show.ok(`作品 "${carNum2}" 已删除 ${deletedCount} 个文件夹`); const newMatchList = await this.searchFiles(carNum2); afterDelete && afterDelete(newMatchList); const remainingCids = new Set(newMatchList.map((m) => m.dirId)); JavPackSubtitle.cached115Matches = JavPackSubtitle.cached115Matches.filter((m) => remainingCids.has(m.cid)); JavPackSubtitle.current115Cids = JavPackSubtitle.current115Cids.filter((cid) => remainingCids.has(cid)); try { (_b = (_a2 = this.getBean("WangPan115TaskPlugin")).deleteOfflineTasksByKeyword) == null ? void 0 : _b.call(_a2, carNum2); } catch (e) { console.warn("删除离线任务暂不可用:", e.message); } } catch (error) { loadObj.close(); console.error("删除115文件失败:", error); show.error(`删除失败: ${error.message || "网络错误"}`); } } }); $(`#layui-layer${layerIndex}`).on("change", ".jhs-delete-select-all", (function() { const checked = $(this).prop("checked"); $(`#layui-layer${layerIndex} .jhs-delete-folder-cb`).prop("checked", checked); })); } async handleLoginRedirect() { window.open("https://115.com"); } async searchFiles(carNum2) { return (await this._searchFilesWithPhase(carNum2)).results; } async _searchFilesWithPhase(carNum2) { const { regex } = codeParse(carNum2); const searchKeyword = carNum2.toLowerCase().replace("fc2-", ""); const mapAndFilter = (rawData) => { return rawData.map(((data) => ({ folderId: data.fid, dirId: data.cid, videoId: data.pc, name: data.n, createTime: utils.formatDate(new Date(1e3 * data.te)), size: data.s, isVideo: true }))).filter(((x) => x.folderId && regex.test(x.name))); }; var _a2; const preciseData = (null == (_a2 = (await searchFiles(searchKeyword, 0, 30, { type: 4 })).data) ? void 0 : _a2) || []; const preciseResults = mapAndFilter(preciseData); if (preciseResults.length > 0) return { results: preciseResults, usedPhase2: false }; const numberPart = extractNumberPart(carNum2); if (numberPart === searchKeyword) return { results: preciseResults, usedPhase2: false }; const fuzzyData = (null == (_a2 = (await searchFiles(numberPart, 0, 30, { type: 4 })).data) ? void 0 : _a2) || []; return { results: mapAndFilter(fuzzyData), usedPhase2: true }; } buildRenameTarget(carNum2, originalName) { const { regex } = codeParse(carNum2); const match = regex.exec(originalName); if (!match) return null; const before = originalName.slice(0, match.index); const after = originalName.slice(match.index + match[0].length); return before + carNum2 + after; } async autoRenameFiles(carNum2, matchList) { try { const enableAutoRename = await storageManager.getSetting("enable115AutoRename", NO$1); if (enableAutoRename !== YES) return; const scope = await storageManager.getSetting("autoRenameScope", "vr_only"); if (scope === "vr_only" && !carNum2.toUpperCase().includes("VR")) return; const renameObj = {}; matchList.forEach((match) => { const newName = this.buildRenameTarget(carNum2, match.name); if (newName && newName !== match.name) { renameObj[match.folderId] = newName; } }); if (Object.keys(renameObj).length === 0) return; const formData = {}; Object.entries(renameObj).forEach(([fid, newName]) => { formData[`files_new_name[${fid}]`] = newName; }); const result = await gmHttp.postForm("https://webapi.115.com/files/batch_rename", formData); if (result && result.state) { Object.entries(renameObj).forEach(([fid, newName]) => { const match = matchList.find((m) => m.folderId === fid); console.log(`[115重命名] ${match.name} → ${newName}`); }); } else { console.warn("[115重命名] 重命名失败:", result && result.error || "未知错误"); } } catch (e) { console.warn("[115重命名] 重命名失败,跳过:", e.message); } } showMatchError($box2, carNum2, error) { $box2.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-error-btn' data-keyword="${carNum2}" title="匹配失败,点击重试">匹配失败</a>`); show.error(`${carNum2} 匹配失败: ${error.message || "网络错误"}`); } async checkLoginStatus() { var _a2; if (this.loginStatus === _WangPan115MatchPlugin2.LoginStatus.UNCHECKED) try { const testResult = await searchFiles("test"); this.loginStatus = (null == (_a2 = testResult.error) ? void 0 : _a2.includes("登录")) ? _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT : _WangPan115MatchPlugin2.LoginStatus.LOGGED_IN; } catch { this.loginStatus = _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT; } } async processMovieElements(movieListElement) { const promises = Array.from(movieListElement).filter(((ele) => !utils.isHidden(ele))).filter(((ele) => !(isJavBus$1 && $(ele).find(".avatar-box").length > 0))).map(((ele) => this.processSingleMovieElement(ele))); await Promise.all(promises); } async processSingleMovieElement(element) { const $box2 = $(element), { carNum: carNum2 } = this.getBoxCarInfo($box2); if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus !== _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT) try { const matchList = await this.searchFiles(carNum2); this.addTag($box2, carNum2, matchList); } catch (error) { console.error(`搜索失败 [${carNum2}]:`, error); this.addTag($box2, carNum2, []); } else this.addTag($box2, carNum2, []); } addTag($box2, carNum2, matchList) { if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus === _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT) $box2.find(".video-title").prepend(`<a class='jhs-match-no-login-btn a-info' data-keyword="${carNum2}" title="未登录115网盘">未登录</a>`); else if (matchList.length > 0) { const title = 1 === matchList.length ? "点击直接播放" : `点击查看${matchList.length}个匹配结果`; $box2.find(".video-title").prepend(`<a class='jhs-match-btn a-success' data-keyword="${carNum2}" data-match='${JSON.stringify(matchList)}' title="${title}">匹配${matchList.length}个</a>`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0 && matchList.some((m) => m.dirId != null)) { $deleteBtn.attr("data-match", JSON.stringify(matchList)); $deleteBtn.show(); } if ($markBtn.length > 0) { $markBtn.show(); this.updateMarkBtnStyle($markBtn, carNum2); } } else $box2.find(".video-title").prepend(`<a class='jhs-match-error-btn a-info' data-keyword="${carNum2}" title="未匹配,点击重试">未匹配</a>`); } formatSize(bytes) { if (!bytes) return "-"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = parseFloat(bytes), unit = 0; for (; size >= 1024 && unit < units.length - 1; ) { size /= 1024; unit++; } return `${size.toFixed(2)} ${units[unit]}`; } }; __publicField2(_WangPan115MatchPlugin, "LoginStatus", { UNCHECKED: -1, LOGGED_OUT: 0, LOGGED_IN: 1 }); let WangPan115MatchPlugin = _WangPan115MatchPlugin; const extractNumberPart = (carNum2) => { let cleaned = carNum2.replace(/^fc2[-_]?(ppv[-_])?/i, ""); const match = cleaned.match(/(\d+)$/); return match ? match[1] : cleaned; }; const codeParse = (code) => { const codes = code.split(/-|_/); const sep = "\\s?(0|-|_){0,2}\\s?"; let pattern = codes.join(sep); if (/^fc2/i.test(code)) pattern = `${codes[0]}${sep}(ppv)?${sep}${codes.at(-1)}`; return { code, codes, prefix: codes[0], regex: new RegExp(`(?<![a-z])${pattern}(?!\\d)`, "i") }; }; class FavoriteActressesPlugin extends BasePlugin { getName() { return "FavoriteActressesPlugin"; } async initCss() { const settingObj = await storageManager.getSetting(); const tagNumber = settingObj.highlightedTagNumber || 1; const tagColor = settingObj.highlightedTagColor || "#ce2222"; return ` <style> .highlighted { border: ${tagNumber}px solid ${tagColor} !important; } </style> `; } async handle() { this.bindEvent(); await this.highlightActress(); this.replaceActressAvatar(); } async highlightActress() { if (!window.isDetailPage) return; if (await storageManager.getSetting("enableFavoriteActresses", YES$1) !== YES$1) return; const favoriteActressesInfoList = await storageManager.getFavoriteActressList(); if (!favoriteActressesInfoList || 0 === favoriteActressesInfoList.length) return; const favoriteStarIdSet = /* @__PURE__ */ new Set(); favoriteActressesInfoList.forEach(((actress) => { actress.starId && favoriteStarIdSet.add(String(actress.starId).trim()); })); 0 !== favoriteStarIdSet.size && $(".female").prev().each(((index, element) => { const $element = $(element), href = $element.attr("href"); let elementStarId = null; if (href) { const parts = (href.endsWith("/") ? href.slice(0, -1) : href).split("/"), lastPart = parts[parts.length - 1]; lastPart && (elementStarId = lastPart.trim()); } let isFavorite = false; elementStarId && (isFavorite = favoriteStarIdSet.has(elementStarId)); if (isFavorite) { $element.addClass("highlighted"); $element.attr("title", "高亮已收藏演员, 可在设置-基础配置中关闭"); } })); } async removeActorFromStorage(actorId) { await storageManager.removeFavoriteActress(actorId) && clog.log("移除演员成功"); } bindEvent() { const listIdRegex = /\/actors\/(\w+)\/(collect|uncollect)/; $(document).on("confirm:complete", 'a[href*="/actors/"][href*="/uncollect"]', (async (event) => { const [userConfirmed] = event.detail; if (!userConfirmed) return; const match = $(event.currentTarget).attr("href").match(listIdRegex), actorId = match ? match[1] : null; actorId && await this.removeActorFromStorage(actorId); })); $("#button-collect-actor").click((async (event) => { const match = $("#button-collect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null; let nameList = [], $actor = $(".actor-section-name"); $actor.length && $actor.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let $sectionMeta = $(".section-meta:not(:contains('影片'))"); $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); if (!nameList) { clog.error("获取演员名称失败"); return; } const actorName = nameList[0]; if (!actorId) { clog.error("无法获取演员ID进行收藏操作。"); return; } const avatar = ($(".avatar").first().css("background-image") || "").replace(/^url\(["']?|["']?\)$/g, ""), actressInfo = { starId: actorId, name: actorName, allName: nameList, avatar }; 1 === await storageManager.addFavoriteActressList([actressInfo]) ? clog.log(`收藏演员成功: ${actorName} (ID: ${actorId})`) : clog.log(`收藏演员失败: ${actorName} (ID: ${actorId})`); })); $("#button-uncollect-actor").click((async (event) => { const match = $("#button-uncollect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null; actorId ? await this.removeActorFromStorage(actorId) : clog.error("无法获取演员ID进行取消收藏操作。"); })); } async replaceActressAvatar() { const actressId = this.getActressId(); if (!actressId) return; const actress = (await storageManager.getFavoriteActressList()).find(((item) => item.starId === actressId)); if (actress && actress.avatar) { const newAvatarUrl = `url('${actress.avatar}')`; let $avatarElement = $(".avatar").first(); if (0 === $avatarElement.length) { const newAvatarHtml = '<div class="column actor-avatar"> <div class="image"> <span class="avatar"></span> </div> </div>'; $(".section-columns").prepend(newAvatarHtml); $avatarElement = $(".avatar").first(); } if (0 === $avatarElement.length) return; if ($avatarElement.css("background-image").trim().toLowerCase() !== newAvatarUrl.trim().toLowerCase()) { $avatarElement.css("background-image", newAvatarUrl); $avatarElement.css("background-size", "cover"); $avatarElement.css("background-position", "top center"); $avatarElement.css("background-repeat", "no-repeat"); } } } } class BusImgPlugin extends BasePlugin { getName() { return "BusImgPlugin"; } handle() { } async getVisibleImageItems(itemSelector, imgSelector) { let itemList = []; const allItems = document.querySelectorAll(itemSelector); for (const item of allItems) { if (!utils.isHidden(item)) { const imgElement = item.querySelector(imgSelector); if (!(imgElement instanceof HTMLImageElement)) continue; imgElement.style.removeProperty("height"); let imageHeight = imgElement.offsetHeight; imageHeight > 0 && itemList.push({ element: item, imgElement, height: imageHeight }); } } return itemList; } async logImageHeightsByRow() { if (await storageManager.getSetting("enableVerticalModel", NO$1) === YES$1) return; const itemSelector = this.getSelector().itemSelector, containerColumns = await storageManager.getSetting("containerColumns", 5), itemList = await this.getVisibleImageItems(itemSelector, "img"); if (0 === itemList.length) return; const groupedItems = []; for (let i = 0; i < itemList.length; i++) { const rowIndex = Math.floor(i / containerColumns); groupedItems[rowIndex] || (groupedItems[rowIndex] = []); groupedItems[rowIndex].push(itemList[i]); } groupedItems.forEach(((row, rowIndex) => { const originalHeights = row.map(((item) => item.height)); if (originalHeights.length < 2) return; const minHeight = Math.min(...originalHeights), maxHeight = Math.max(...originalHeights); let targetHeight = 0; if (maxHeight - minHeight > 50) { targetHeight = minHeight; row.forEach(((item) => { if (item.height !== targetHeight) { const heightValue = `${targetHeight}px`; item.imgElement.style.setProperty("height", heightValue, "important"); } })); } })); } } class ListPageButtonPlugin extends BasePlugin { getName() { return "ListPageButtonPlugin"; } async handle() { if (!window.isListPage) return; await this.createMenuBtn(); this.bindEvent(); await storageManager.getSetting("autoPage") === YES$1 ? $("#sort-toggle-btn").hide() : this.sortItems().then(); this.initOneTimeSortBtn(); } async createMenuBtn() { const showWaitCheckBtn = await storageManager.getSetting("showWaitCheckBtn", YES$1), showWaitDownBtn = await storageManager.getSetting("showWaitDownBtn", YES$1); if (isJavDb$1) { const isStarPage = currentHref$1.includes("/actors/"); let $el = $(".main-tabs, .tabs"), addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020", otherCss = "", blacklistItem = null; if (isStarPage) { $el = $(".toolbar, .section-addition").filter(":last"); const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); if (blacklist.find(((item) => item.starId === actressPageInfo.starId))) { addBlacklistBtnText = "已加入黑名单"; addBlacklistBtnColor = "#885d5d"; } } else currentHref$1.includes("/tags") && utils.loopDetector((() => $("#jhs-check-tag").text().trim()), (async () => { const $addBlacklistBtn = $("#addBlacklistBtn"); $addBlacklistBtn.attr("data-tip", "将当前分类标签加入到黑名单, 后续有作品更新也会纳入屏蔽中"); const checkTag = $("#jhs-check-tag").text().trim(); if (!checkTag) return; const tagStarId = "no-" + checkTag, blacklist = await storageManager.getBlacklist(); blacklistItem = blacklist.find(((item) => item.starId === tagStarId)); if (blacklistItem) { $addBlacklistBtn.css("backgroundColor", "#885d5d"); $("#addBlacklistBtn span").text("已加入黑名单"); } })); const isFc2Page2 = currentHref$1.includes("advanced_search"); isFc2Page2 ? $el = $("h2.section-title") : otherCss = "flex-grow:1;"; const jhs_sortMethod = localStorage.getItem("jhs_sortMethod"), sortText = "当前排序方式: " + ("rateCount" === jhs_sortMethod ? "评价人数" : "date" === jhs_sortMethod ? "时间" : "默认"); $el.append(` <div style="display: flex;align-items: center; ${otherCss} "> <a id="waitCheckBtn" class="menu-btn main-tab-btn" data-tip="打开未鉴定的列表项, 并自动播放视频" style="background-color:#56c938 !important; ${showWaitCheckBtn === NO$1 ? "display: none" : ""}"><span>打开待鉴定</span></a> <a id="waitDownBtn" class="menu-btn main-tab-btn" style="background-color:#2caac0 !important; ${showWaitDownBtn === NO$1 ? "display: none" : ""}"><span>打开已收藏</span></a> ${isStarPage ? ` <a id="addBlacklistBtn" class="menu-btn main-tab-btn" style="background-color:${addBlacklistBtnColor} !important;" data-tip="将演员加入黑名单, 后续有作品更新也会纳入屏蔽中"><span>${addBlacklistBtnText}</span></a> <a id="filterAllVideo" class="menu-btn main-tab-btn" style="background-color:#e8ab39 !important;margin-right: 30px!important;" data-tip="一键屏蔽已选分类的视频列表至鉴定记录中"><span>一键屏蔽所有作品</span></a> ` : ""} ${currentHref$1.includes("/tags") ? ` <a id="addBlacklistBtn" class="menu-btn main-tab-btn" style="background-color:${addBlacklistBtnColor} !important;" data-tip="将演员加入黑名单, 后续有作品更新也会纳入屏蔽中"><span>${addBlacklistBtnText}</span></a> ` : ""} </div> <div style="display: flex;align-items: center;"> <a id="one-time-sort-btn" class="menu-btn main-tab-btn" style="background-color:#8783ab !important;">一次性排序: 默认</a> <a id="newVideoBtn" class="menu-btn main-tab-btn" style="background-color:#2c6cc0 !important;"><span>新作品检测 (<span id="newVideoCount">0</span>)</span></a> <a id="blacklistBtn" class="menu-btn main-tab-btn" style="background-color:#34393f !important;"><span>演员黑名单</span></a> ${isSearchPage || isFc2Page2 ? "" : `<a id="sort-toggle-btn" class="menu-btn main-tab-btn" style="background-color:#8783ab !important;"> ${sortText} </a>`} </div> `); } if (isJavBus$1) { const isStarPage = currentHref$1.includes("/star/"); let addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020"; if (isStarPage) { const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); if (blacklist.find(((item) => item.starId === actressPageInfo.starId))) { addBlacklistBtnText = "已加入黑名单"; addBlacklistBtnColor = "#885d5d"; } } $(".masonry").parent().prepend(` <div style="margin: 10px; display: flex;"> <a id="waitCheckBtn" class="menu-btn main-tab-btn" style="background-color:#56c938 !important; ${showWaitCheckBtn === NO$1 ? "display: none" : ""}"><span>打开待鉴定</span></a> <a id="waitDownBtn" class="menu-btn main-tab-btn" style="background-color:#2caac0 !important; ${showWaitDownBtn === NO$1 ? "display: none" : ""}"><span>打开已收藏</span></a> ${isStarPage ? ` <a id="addBlacklistBtn" class="menu-btn main-tab-btn" style="background-color:${addBlacklistBtnColor} !important;" data-tip="将演员加入黑名单, 后续有作品更新也会纳入屏蔽中"><span>${addBlacklistBtnText}</span></a> <a id="filterAllVideo" class="menu-btn main-tab-btn" style="background-color:#e8ab39 !important;" data-tip="一键屏蔽已选分类的视频列表至鉴定记录中"><span>一键屏蔽所有作品</span></a> ` : '<a id="blacklistBtn" class="menu-btn main-tab-btn" style="background-color:#34393f !important;"><span>演员黑名单</span></a>'} </div> `); } const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin"); newVideoPlugin && newVideoPlugin.showNewVideoCount().then(); } bindEvent() { $("#waitCheckBtn").on("click", ((event) => { this.openWaitCheck(event).then(); })); $("#waitDownBtn").on("click", ((event) => { this.openFavorite(event).then(); })); $("#newVideoBtn").on("click", ((event) => { this.getBean("NewVideoPlugin").openDialog(); })); $("#blacklistBtn").on("click", ((event) => { this.getBean("BlacklistPlugin").openBlacklistDialog(); })); $("#sort-toggle-btn").on("click", ((event) => { const currentMethod = localStorage.getItem("jhs_sortMethod") || "default"; const sortCycle = ["default", "rateCount", "totalScore", "date"]; let nextIndex = sortCycle.indexOf(currentMethod) + 1; if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0; const newMethod = sortCycle[nextIndex]; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[newMethod]; $(event.target).text(`当前排序方式: ${methodText}`); localStorage.setItem("jhs_sortMethod", newMethod); this.sortItems().then(); })); $("#one-time-sort-btn").on("click", ((event) => { const currentMethod = localStorage.getItem("jhs_oneTimeSortMethod") || "default"; const sortCycle = ["default", "rateCount", "totalScore", "date"]; let nextIndex = sortCycle.indexOf(currentMethod) + 1; if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0; const newMethod = sortCycle[nextIndex]; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[newMethod]; $("#one-time-sort-btn").text(`一次性排序: ${methodText}`); localStorage.setItem("jhs_oneTimeSortMethod", newMethod); this.oneTimeSortItems().then(); })); const blacklistPlugin = this.getBean("BlacklistPlugin"); $("#addBlacklistBtn").on("click", (async (event) => { await blacklistPlugin.addBlacklist(event); })); $("#filterAllVideo").on("click", (async (event) => { let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }, $actor = isJavDb$1 ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"); if (0 === $actor.length) { show.error("获取演员名称失败"); return; } let actorName = $actor.text().trim().split(",")[0]; utils.q(tempEvent, "一键屏蔽已选分类的视频列表至鉴定记录中?", (async () => { this.loadObj = loading(); try { await blacklistPlugin.filterAllVideo(actorName); window.refresh(); } catch (e) { console.error(e); } finally { this.loadObj.close(); } })); })); } async sortItems() { if (currentHref$1.includes("handle") || currentHref$1.includes("advanced_search")) return; const autoPage = await storageManager.getSetting("autoPage"); if (isSearchPage || autoPage === YES$1) return; const method = localStorage.getItem("jhs_sortMethod"); if (!method) return; $(".movie-list .item").each((function(index) { $(this).attr("data-original-index") || $(this).attr("data-original-index", index); })); const $container = $(".movie-list"), $items = $(".item", $container); if ("default" === method) { $items.sort((function(a, b) { return $(a).data("original-index") - $(b).data("original-index"); })).appendTo($container); } else { const items = $items.get(); items.sort((function(a, b) { if ("rateCount" === method) { const getScore = (el) => { const match = $(el).find(".score .value").text().match(/由(\d+)人/); return match ? parseFloat(match[1]) : 0; }; return getScore(b) - getScore(a); } else if ("totalScore" === method) { const getTotalScore = (el) => { const text = $(el).find(".score .value").text(); const scoreMatch = text.match(/([\d.]+)分/); const countMatch = text.match(/由(\d+)人/); const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0; const count = countMatch ? parseFloat(countMatch[1]) : 0; return score * count; }; return getTotalScore(b) - getTotalScore(a); } else { const getDate = (el) => { const dateStr = $(el).find(".meta").text().trim(); return new Date(dateStr); }; return getDate(b) - getDate(a); } })); $container.empty().append(items); } } async initOneTimeSortBtn() { const autoPage = await storageManager.getSetting("autoPage"); if (autoPage !== YES$1) { $("#one-time-sort-btn").hide(); return; } const oneTimeMethod = localStorage.getItem("jhs_oneTimeSortMethod") || "default"; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[oneTimeMethod]; $("#one-time-sort-btn").text(`一次性排序: ${methodText}`).show(); } async oneTimeSortItems() { if (currentHref$1.includes("handle") || currentHref$1.includes("advanced_search")) return; if (isSearchPage) return; const method = localStorage.getItem("jhs_oneTimeSortMethod"); if (!method) return; $(".movie-list .item").each((function(index) { $(this).attr("data-original-index") || $(this).attr("data-original-index", index); })); const $container = $(".movie-list"), $items = $(".item", $container); if ("default" === method) { $items.sort((function(a, b) { return $(a).data("original-index") - $(b).data("original-index"); })).appendTo($container); } else { const items = $items.get(); items.sort((function(a, b) { if ("rateCount" === method) { const getScore = (el) => { const match = $(el).find(".score .value").text().match(/由(\d+)人/); return match ? parseFloat(match[1]) : 0; }; return getScore(b) - getScore(a); } else if ("totalScore" === method) { const getTotalScore = (el) => { const text = $(el).find(".score .value").text(); const scoreMatch = text.match(/([\d.]+)分/); const countMatch = text.match(/由(\d+)人/); const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0; const count = countMatch ? parseFloat(countMatch[1]) : 0; return score * count; }; return getTotalScore(b) - getTotalScore(a); } else { const getDate = (el) => { const dateStr = $(el).find(".meta").text().trim(); return new Date(dateStr); }; return getDate(b) - getDate(a); } })); $container.empty().append(items); } } async openWaitCheck(event) { const maxCount = await storageManager.getSetting("waitCheckCount", 5); let count = 0; $(`${this.getSelector().itemSelector}:visible`).each(((i, el) => { if (count >= maxCount) return false; const $box2 = $(el); if ($box2.find(".status-tag").length > 0) return; const { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); if (carNum2.includes("FC2-")) { const movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, aHref); } else { let url = aHref + (aHref.includes("?") ? "&autoPlay=1" : "?autoPlay=1"); window.open(url); } count++; })); 0 === count && show.info("没有需鉴定的视频"); } async openFavorite() { let favoriteList, openCount = await storageManager.getSetting("waitCheckCount", 5), randomOpenWaitDown = await storageManager.getSetting("randomOpenWaitDown", NO$1), dataList = await storageManager.getCarList(); favoriteList = randomOpenWaitDown === YES$1 ? dataList.filter(((item) => item.status === Status_FAVORITE$1)).sort((() => Math.random() - 0.5)) : dataList.filter(((item) => item.status === Status_FAVORITE$1)).sort(((a, b) => b.createDate - a.createDate)); for (let i = 0; i < openCount; i++) { if (i >= favoriteList.length) return; let data = favoriteList[i], carNum2 = data.carNum, url = data.url; if (carNum2.includes("FC2-")) { const movieId = this.parseMovieId(url); await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, url); } else window.open(url); clog.debug("打开已收藏", carNum2, url); } } } const translateText = async (text, sourceLang = "ja", targetLang = "zh-CN") => { if (!text) throw new Error("翻译文本不能为空"); const url = "https://translate-pa.googleapis.com/v1/translate?" + new URLSearchParams({ "params.client": "gtx", dataTypes: "TRANSLATION", key: "AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA", "query.sourceLanguage": sourceLang, "query.targetLanguage": targetLang, "query.text": text }), res = await fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return (await res.json()).translation; }, TAG_STYLES = { IS_FILTERED: { text: "🚫 已屏蔽", color: "#de3333", reasonType: "单番号屏蔽", isCounted: true, countKey: "currentPageFilterCount" }, IS_FAVORITE: { text: "⭐ 已收藏", color: "#25b1dc", reasonType: "", isCounted: true, countKey: "currentPageFavoriteCount" }, IS_HAS_DOWN: { text: "📥️ 已下载", color: "#7bc73b", reasonType: "", isCounted: true, countKey: "currentPageHasDownCount" }, IS_HAS_WATCH: { text: "🔍 已观看", color: "#d7a80c", reasonType: "", isCounted: true, countKey: "currentPageHasWatchCount" }, IS_KEYWORD_FILTER: { text: "❌ 关键词屏蔽", color: "#de3333", reasonType: "", isCounted: true, countKey: "currentPageKeywordFilterCount" }, IS_ACTOR_FILTER: { text: "♂️ 男演员屏蔽", color: "#b22222", reasonType: "", isCounted: true, countKey: "currentPageActorFilterCount" }, IS_ACTRESS_FILTER: { text: "♀️ 女演员屏蔽", color: "#cd5c5c", reasonType: "", isCounted: true, countKey: "currentPageActorFilterCount" }, IS_WAIT_CHECK: { text: "", color: "", reasonType: "", isCounted: true, countKey: "currentPageWaitCheckCount" } }; class TranslatePlugin extends BasePlugin { getName() { return "TranslatePlugin"; } async initCss() { return "\n <style> \n .translated-title {\n margin-top: 8px; \n padding: 12px; \n border-radius: 5px; \n border-left: 4px solid rgb(76, 175, 80);\n background: linear-gradient(135deg, rgb(255, 255, 255) 0%, rgb(245, 245, 245) 100%); \n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);\n font-size: 20px;\n }\n </style>\n "; } handle() { window.isDetailPage && this.translate(); } async translate(carNum2, showCarNum = true) { if (await storageManager.getSetting("translateTitle", YES$1) !== YES$1) return; isJavBus$1 && (showCarNum = false); let $titleElement = $(".origin-title"); $titleElement.length || ($titleElement = $(".current-title")); $titleElement.length || ($titleElement = $("h3")); if (!$titleElement.length) return; const originalText = $titleElement.text().trim(); if (!originalText) { show.error("获取标题失败, 无法进行翻译"); return; } $titleElement.after('<div class="translated-title">翻译中...</div>'); const $loadingElement = $titleElement.next(".translated-title"); carNum2 || (carNum2 = this.getPageInfo().carNum); const cache = localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}; cache[carNum2] ? $loadingElement.html(showCarNum ? carNum2 + " " + cache[carNum2] : cache[carNum2]) : translateText(originalText, "ja", "zh-CN").then(((translatedText) => { $loadingElement.html(showCarNum ? carNum2 + " " + translatedText : translatedText); })).catch(((error) => { console.error("翻译失败:", error); $loadingElement.replaceWith(`<div class="translated-title" style="color: red;">翻译失败: ${error.message}</div>`); })); } } class OtherSitePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "okBackgroundColor", "#7bc73b"); __publicField2(this, "errorBackgroundColor", "#de3333"); __publicField2(this, "warnBackgroundColor", "#d7a80c"); __publicField2(this, "domainErrorBackgroundColor", "#d7780c"); __publicField2(this, "asyncQueue", new AsyncQueue()); __publicField2(this, "siteConfigs", [ { id: "MissAvBtn", getBaseUrl: async () => await this.getMissAvUrl(), itemSelector: ".text-secondary", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.text() }, { id: "123AvBtn", getBaseUrl: async () => await this.getAv123Url() + "/ja", itemSelector: ".box-item", searchPath: (baseUrl, carNum2) => `${baseUrl}/search?keyword=${carNum2}`, getDetailPageHref: ($box2) => $box2.find(".detail a").attr("href"), findCarNumOrTitle: ($box2) => $box2.find("img").attr("title") }, { id: "JableBtn", getBaseUrl: async () => await this.getjableUrl(), itemSelector: "#list_videos_videos_list_search_result .detail .title a", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}/`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.text() }, { id: "SupJavBtn", getBaseUrl: async () => await this.getSupJavUrl(), itemSelector: ".posts post", searchPath: (baseUrl, carNum2) => `${baseUrl}/?s=${carNum2}`, getDetailPageHref: ($box2, baseUrl, carNum2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.attr("title") }, { id: "JavTrailersBtn", getBaseUrl: async () => await this.getJavTrailersUrl(), itemSelector: ".videos-list .video-link", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.find("p.card-text").text() }, { id: "JavDbBtn", getBaseUrl: async () => await this.getJavDbUrl(), itemSelector: ".movie-list .item", searchPath: (baseUrl, carNum2) => `${baseUrl}/search?q=${carNum2}`, getDetailPageHref: ($box2) => $box2.find("a").attr("href"), findCarNumOrTitle: ($box2) => $box2.find(".video-title").text(), condition: (sourceCarNum) => isJavBus$1 }, { id: "JavBusBtn", getBaseUrl: async () => await this.getJavBusUrl(), itemSelector: ".container h3", searchPath: (baseUrl, carNum2) => `${baseUrl}/${carNum2}`, getDetailPageHref: ($box2, baseUrl, carNum2) => `${baseUrl}/${carNum2}`, findCarNumOrTitle: ($box2) => $box2.text(), condition: (sourceCarNum) => isJavDb$1 && sourceCarNum && !sourceCarNum.includes("FC2") } ]); __publicField2(this, "settingCache", null); __publicField2(this, "lastFetchTime", 0); __publicField2(this, "CACHE_DURATION", 1e4); } getName() { return "OtherSitePlugin"; } async initCss() { return "\n <style>\n .site-btn {\n position: relative !important;\n min-width: 80px;\n display: inline-block;\n padding: 5px 10px;\n color: white !important;\n background-color:#938585;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n margin-bottom: 5px;\n }\n .site-btn:hover {\n color: white;\n transform: translateY(-2px);\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n .site-tag {\n position: absolute; \n top: -15px; \n right: 0; \n background-color: #ffc107; \n color: #333; \n font-size: 12px; \n padding: 2px 6px; \n border-radius: 4px;\n }\n </style>\n "; } async handle() { window.isDetailPage && this.loadOtherSite().then(); } async loadOtherSite(carNum2, sourceCarNum) { if ("yes" !== await storageManager.getSetting("enableLoadOtherSite", "yes")) return; carNum2 || (carNum2 = this.getPageInfo().carNum); const enabledSites = this.getEnabledSites(), html2 = ` <div id="otherSiteBox" class="panel-block" style="${isJavDb$1 ? "margin-top:8px;font-size:13px" : "margin-top:10px;font-size:13px"}; user-select: none; "> <div style="display: flex;gap: 5px;flex-wrap: wrap"> ${this.siteConfigs.map(((config) => { config.sourceCarNum = sourceCarNum; if (config.condition && false === config.condition(config.sourceCarNum)) return ""; return `<a target="_blank" class="site-btn" style="${enabledSites.includes(config.id) ? "" : "display:none"}" id="${config.id}"><span>${config.id.replace("Btn", "")}</span></a>`; })).join("")} <a id="settingSiteBtn" class="site-btn"><span>跳转设置</span></a> </div> </div> <div id="settingsArea" class="panel-block" style="display: none; margin-top:10px; margin-bottom: 10px; user-select: none; "> <div id="siteCheckboxes" style="display: flex;gap: 5px;flex-wrap: wrap"> </div> </div> `; $(".movie-panel-info").append(html2); $(".container .info").append(html2); $("#javTrailersBtn").on("click", (async (event) => { event.preventDefault(); let settingObj = await storageManager.getSetting(); const filterHotKey = settingObj.filterHotKey, favoriteHotKey = settingObj.favoriteHotKey, speedVideoHotKey = settingObj.speedVideoHotKey; let href = $("#javTrailersBtn").attr("href"), url = href + `?handle=1&filterHotKey=${filterHotKey}&favoriteHotKey=${favoriteHotKey}&speedVideoHotKey=${speedVideoHotKey}`; event && (event.ctrlKey || event.metaKey) && (url = href); utils.openPage(url, carNum2, false, event); })); await Promise.all(this.siteConfigs.map((async (config) => { config.condition && false === config.condition(config.sourceCarNum) || await this.handleSite(carNum2, config); }))); this.renderSettingsArea(); this.setupEventListeners(); } async handleSite(carNum2, config) { const $btn = $(`#${config.id}`); try { if ($btn.attr("href")) return; if (utils.isHidden($btn)) return; const cacheKey = "jhs_other_site", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}, siteKey = carNum2 + "_" + config.id.replace("Btn", ""), cachedResult = cacheData[siteKey]; if (cachedResult) { if ("single" === cachedResult.type) { $btn.attr("href", cachedResult.url); $btn.css("backgroundColor", this.okBackgroundColor); } else if ("multiple" === cachedResult.type) { $btn.attr("href", cachedResult.url); $btn.append('<span class="site-tag" style="top:-15px">多结果</span>'); $btn.css("backgroundColor", this.okBackgroundColor); } return; } const baseUrl = await config.getBaseUrl(), searchUrl = config.searchPath(baseUrl, carNum2); $btn.attr("href", searchUrl); const html2 = await gmHttp.get(searchUrl, null, config.headers, true), $dom = utils.htmlTo$dom(html2), resultUrlList = []; $dom.find(config.itemSelector).each(((index, element) => { const $box2 = $(element); if (!config.findCarNumOrTitle($box2).toLowerCase().includes(carNum2.toLowerCase())) return; let href = config.getDetailPageHref($box2, baseUrl, carNum2); if (!href) throw new Error("解析href失败"); href.includes("http") || (href = baseUrl + (href.startsWith("/") ? href : "/" + href)); resultUrlList.push(href); })); let tagHtml = "", insertCacheData = null; if (1 === resultUrlList.length) { let resultUrl = resultUrlList[0]; $btn.attr("href", resultUrl); $btn.css("backgroundColor", this.okBackgroundColor); insertCacheData = { type: "single", url: resultUrl }; } else if (resultUrlList.length > 1) { $btn.attr("href", searchUrl); tagHtml += '<span class="site-tag" style="top:-15px">多结果</span>'; $btn.css("backgroundColor", this.okBackgroundColor); insertCacheData = { type: "multiple", url: searchUrl }; } else { $btn.attr("href", searchUrl); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", this.errorBackgroundColor); } insertCacheData && this.asyncQueue.addTask((() => { const newCacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; newCacheData[siteKey] = insertCacheData; localStorage.setItem(cacheKey, JSON.stringify(newCacheData)); })); tagHtml && $btn.append(tagHtml); } catch (e) { const errorString = String(e), siteName = config.id.replace("Btn", ""); if (errorString.includes("Just a moment")) { $btn.attr("title", "请求失败:Cloudflare 安全检查。"); $btn.css("backgroundColor", this.warnBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName} 需Cloudflare安全检查`); } else if (errorString.includes("重定向")) { $btn.attr("title", "域名失效"); $btn.css("backgroundColor", this.domainErrorBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName} 域名被重定向`); } else if (errorString.includes("404 Page Not Found")) { $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", this.errorBackgroundColor); } else { console.error(e); $btn.attr("title", "请求失败。"); $btn.css("backgroundColor", this.errorBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName}`); } } } async getSettingCache() { const now = Date.now(); if (!this.settingCache || now - this.lastFetchTime > this.CACHE_DURATION) { this.settingCache = await storageManager.getSetting(); this.lastFetchTime = now; } return this.settingCache; } async getMissAvUrl() { return (await this.getSettingCache()).missAvUrl || "https://missav.live"; } async getjableUrl() { return (await this.getSettingCache()).jableUrl || "https://jable.tv"; } async getJavTrailersUrl() { return (await this.getSettingCache()).javTrailersUrl || "https://javtrailers.com"; } async getAv123Url() { return (await this.getSettingCache()).av123Url || "https://123av.com"; } async getJavDbUrl() { return (await this.getSettingCache()).javDbUrl || "https://javdb.com"; } async getJavBusUrl() { return (await this.getSettingCache()).javBusUrl || "https://www.javbus.com"; } async getSupJavUrl() { return (await this.getSettingCache()).supJavUrl || "https://supjav.com"; } getEnabledSites() { const enabledSites = localStorage.getItem("jhs_enabled_sites"); return enabledSites ? JSON.parse(enabledSites) : this.siteConfigs.map(((c) => c.id)); } saveEnabledSites(sites) { localStorage.setItem("jhs_enabled_sites", JSON.stringify(sites)); } renderSettingsArea() { const enabledSites = this.getEnabledSites(), checkboxesDiv = document.getElementById("siteCheckboxes"); checkboxesDiv && (checkboxesDiv.innerHTML = this.siteConfigs.map(((config) => { const isEnabled = enabledSites.includes(config.id); return ` <div style="margin-right: 15px; display: flex; align-items: ${isJavDb$1 ? "center" : "flex-start"};"> <input type="checkbox" id="checkbox-${config.id}" data-site-id="${config.id}" ${isEnabled ? "checked" : ""} style="margin-right: 8px; cursor: pointer;"> <label for="checkbox-${config.id}" style="color: #333; font-weight: 500; cursor: pointer;">${config.id.replace("Btn", "")}</label> </div> `; })).join("")); } setupEventListeners() { const settingsArea = document.getElementById("settingsArea"); document.addEventListener("click", ((event) => { if ("settingSiteBtn" === event.target.id || event.target.closest("#settingSiteBtn")) { const isHidden = "none" === settingsArea.style.display || "" === settingsArea.style.display; settingsArea.style.display = isHidden ? "block" : "none"; } })); settingsArea.addEventListener("change", ((event) => { if ("checkbox" === event.target.type) { const siteId = event.target.getAttribute("data-site-id"); if (event.target.checked) { $(`#${siteId}`).show(); const carNum2 = this.getPageInfo().carNum, config = this.siteConfigs.find(((item) => item.id === siteId)); this.handleSite(carNum2, config).then(); } else $(`#${siteId}`).hide(); const enabledSites = Array.from(settingsArea.querySelectorAll('input[type="checkbox"]:checked')).map(((checkbox) => checkbox.getAttribute("data-site-id"))); this.saveEnabledSites(enabledSites); } })); } } class ReviewPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "floorIndex", 1); __publicField2(this, "isInit", false); } getName() { return "ReviewPlugin"; } async handle() { $(document).on("click", ".down-115", (async (event) => { const magnet = $(event.currentTarget).data("magnet"); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })); if (window.isDetailPage) { if (isJavDb$1) { const movieId = this.parseMovieId(window.location.href); await this.showReview(movieId); await this.getBean("RelatedPlugin").showRelated($("#magnets-content"), movieId); } if (isJavBus$1) { let carNum2 = this.getPageInfo().carNum; const movies = await javDbApi.searchMovie(carNum2); let movieId = null; for (let i = 0; i < movies.length; i++) { let item = movies[i]; if (item.number.toLowerCase() === carNum2.toLowerCase()) { movieId = item.id; break; } } if (!movieId) return; this.showReview(movieId, $("#sample-waterfall")).then(); } } } async showReview(movieId, $eleBox) { const enableLoadReview = await storageManager.getSetting("enableLoadReview", YES$1), $magnets = $eleBox || $("#magnets-content"); $magnets.append(` <div style="display: flex; align-items: center; margin: 16px 0; color: #666; font-size: 14px;"> <span style="flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent);"></span> <span style="padding: 0 10px;" data-tip="想要发表评论? 滑上去, 点击上面的按钮-看过">❓ 评论区</span> <a id="reviewsFold" style="margin-left: 8px; color: #1890ff; text-decoration: none; display: flex; align-items: center;"> <span class="toggle-text">${enableLoadReview === YES$1 ? "折叠" : "展开"}</span> <span class="toggle-icon" style="margin-left: 4px;">${enableLoadReview === YES$1 ? "▲" : "▼"}</span> </a> <span style="flex: 1; height: 1px; background: linear-gradient(to right, transparent, #999, transparent);"></span> </div> `); $("#reviewsFold").on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $text = $("#reviewsFold .toggle-text"), $icon = $("#reviewsFold .toggle-icon"), isFolded = "展开" === $text.text(); $text.text(isFolded ? "折叠" : "展开"); $icon.text(isFolded ? "▲" : "▼"); if (isFolded) { $("#reviewsContainer").show(); $("#reviewsFooter").show(); if (!this.isInit) { this.fetchAndDisplayReviews(movieId); this.isInit = true; } storageManager.saveSettingItem("enableLoadReview", YES$1); } else { $("#reviewsContainer").hide(); $("#reviewsFooter").hide(); storageManager.saveSettingItem("enableLoadReview", NO$1); } })); $magnets.append('<div id="reviewsContainer"></div>'); $magnets.append('<div id="reviewsFooter"></div>'); enableLoadReview === YES$1 && await this.fetchAndDisplayReviews(movieId); } async fetchAndDisplayReviews(movieId) { const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter"); $reviewsContainer.append('<div id="reviewsLoading" style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">获取评论中...</div>'); const reviewCount = await storageManager.getSetting("reviewCount", 20); let dataList = null; try { dataList = await javDbApi.getReviews(movieId, 1, reviewCount); } catch (e) { e.toString().includes("簽名已過期") && show.error("生成签名失败, 请检查系统时间及时区是否正确!"); clog.error("获取评论失败:", e); console.error("获取评论失败:", e); } finally { $("#reviewsLoading").remove(); } if (!dataList) { $reviewsContainer.append('\n <div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">\n 获取评论失败\n <a id="retryFetchReviews" href="javascript:;" style="margin-left: 10px; color: #1890ff; text-decoration: none;">重试</a>\n </div>\n '); $("#retryFetchReviews").on("click", (async () => { $("#retryFetchReviews").parent().remove(); await this.fetchAndDisplayReviews(movieId); })); return; } if (0 === dataList.length) { $reviewsContainer.append('<div style="margin-top:15px;background-color:#ffffff;padding:10px;margin-left: -10px;">无评论</div>'); return; } const reviewKeywordList = await storageManager.getReviewFilterKeywordList(); this.displayReviews(dataList, $reviewsContainer, reviewKeywordList); if (dataList.length === reviewCount) { $reviewsFooter.html('\n <button id="loadMoreReviews" style="width:100%; background-color: #e1f5fe; border:none; padding:10px; margin-top:10px; cursor:pointer; color:#0277bd; font-weight:bold; border-radius:4px;">\n 加载更多评论\n </button>\n <div id="reviewsEnd" style="display:none; text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>\n '); let currentPage = 1, $loadMoreReviews = $("#loadMoreReviews"); $loadMoreReviews.on("click", (async () => { $loadMoreReviews.text("加载中...").prop("disabled", true); currentPage++; let moreData; try { moreData = await javDbApi.getReviews(movieId, currentPage, reviewCount); } catch (e) { console.error("加载更多评论失败:", e); } finally { $loadMoreReviews.text("加载失败, 请点击重试").prop("disabled", false); } if (moreData) { this.displayReviews(moreData, $reviewsContainer, reviewKeywordList); if (moreData.length < reviewCount) { $loadMoreReviews.remove(); $("#reviewsEnd").show(); } else $loadMoreReviews.text("加载更多评论").prop("disabled", false); } })); } else $reviewsFooter.html('<div style="text-align:center; padding:10px; color:#666; margin-top:10px;">已加载全部评论</div>'); } displayReviews(dataList, $container, reviewKeywordList) { if (dataList.length) { dataList.forEach(((item) => { if (reviewKeywordList.some(((keyword) => item.content.includes(keyword)))) return; const starsHtml = Array(item.score).fill('<i class="icon-star"></i>').join(""), content = item.content.replace(/ed2k:\/\/\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/|magnet:\?[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+|https?:\/\/[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+/g, ((match) => match.startsWith("ed2k://") ? ` <span style="word-break: break-all;background: #e0f2fe;color: #0369a1;">${match}</span> <button class="button is-info down-115" data-magnet="${match}" style="font-size: 11px">115离线下载</button> ` : match.startsWith("magnet:") ? ` <a href="${match}" class="a-primary" style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${match}</a> <button class="button is-info down-115" data-magnet="${match}" style="font-size: 11px">115离线下载</button> ` : match.startsWith("http://") || match.startsWith("https://") ? ` <a href="${match}" class="a-primary" style="padding:0; word-break: break-all; white-space: pre-wrap;" target="_blank" rel="noopener noreferrer">${match}</a> ` : match)), commentHtml = ` <div class="item columns is-desktop" style="display:block;margin-top:6px;background-color:#ffffff;padding:10px;margin-left: -10px;word-break: break-word;position:relative;"> <span style="position:absolute;top:5px;right:10px;color:#999;font-size:12px;">#${this.floorIndex++}楼</span> ${item.username} <span class="score-stars">${starsHtml}</span> <span class="time">${utils.formatDate(item.created_at)}</span> 点赞:${item.likes_count} <p class="review-content" style="margin-top: 5px;"> ${content} </p> </div> `; $container.append(commentHtml); })); this.rightClickFilter(); } } async rightClickFilter() { await storageManager.getSetting("enableTitleSelectFilter", YES$1) === YES$1 && utils.rightClick(document.body, ".review-content", (async (event) => { const selectedText = window.getSelection().toString(); if (selectedText) { event.preventDefault(); await utils.q(event, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => { await storageManager.saveReviewFilterKeyword(selectedText); show.ok("操作成功, 刷新页面后生效"); })); } })); } } class FilterTitleKeywordPlugin extends BasePlugin { getName() { return "FilterTitleKeywordPlugin"; } async handle() { if (!window.isDetailPage && !window.isFc2Page) return; if (await storageManager.getSetting("enableTitleSelectFilter", YES$1) !== YES$1) return; let targetSelector; isJavDb$1 ? targetSelector = ".title strong, .current-title" : isJavBus$1 && (targetSelector = "h3"); utils.rightClick(document.body, targetSelector, ((event) => { const selectedText = window.getSelection().toString(); if (selectedText) { event.preventDefault(); let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }; utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => { await storageManager.saveTitleFilterKeyword(selectedText); window.refresh(); utils.closePage(); })); } })); } } class CoverButtonPlugin extends BasePlugin { getName() { return "CoverButtonPlugin"; } async initCss() { return ` <style> .box .tags { justify-content: space-between; } .tool-box span{ opacity:.6 } .tool-box span:hover{ opacity:1 } ${isJavBus$1 ? ".tool-box .icon, .setting-label .icon{ height: 24px; width: 24px; }" : ""} .tool-box svg path { fill: blue; } [data-theme="dark"] .tool-box svg path { fill: white; } .tool-box .delete115Svg svg path { fill: #e57373 !important; } [data-theme="dark"] .tool-box .delete115Svg svg path { fill: #ef9a9a !important; } .tool-box .markDeleteSvg svg path { fill: #81c784 !important; } [data-theme="dark"] .tool-box .markDeleteSvg svg path { fill: #66bb6a !important; } .tool-box .markDeleteSvg.marked svg path { fill: #d84315 !important; } [data-theme="dark"] .tool-box .markDeleteSvg.marked svg path { fill: #e64a19 !important; } /* 鼠标移入时的弹性动画 */ .elastic-in { animation: elasticIn 0.2s ease-out forwards; /* 动画名称 | 时长 | 缓动函数 | 保持最终状态 */ } /* 鼠标移出时的弹性动画 */ .elastic-out { animation: elasticOut 0.2s ease-in forwards; } /* 弹性进入动画(像果冻弹入) */ @keyframes elasticIn { 0% { opacity: 0; transform: scale(0.8); /* 起始状态:80% 大小 */ } 50% { opacity: 1; transform: scale(1.1); /* 弹到 110%(超调一点) */ } 70% { transform: scale(0.95); /* 回弹到 95%(模拟弹性阻尼) */ } 100% { opacity: 1; transform: scale(1); /* 最终恢复正常大小 */ } } /* 弹性离开动画(像果冻弹出) */ @keyframes elasticOut { 0% { opacity: 1; transform: scale(1); /* 起始状态:正常大小 */ } 30% { transform: scale(1.05); /* 先弹大一点(105%) */ } 100% { opacity: 0; transform: scale(0.8); /* 最终缩小并消失 */ } } .loading { opacity: 0.7; filter: blur(1px); } .loading-spinner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; border: 3px solid rgba(255,255,255,.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite; z-index: 20; } @keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } } </style> `; } handle() { if (window.isListPage) { this.addSvgBtn(); this.bindClick().then(); } } async addSvgBtn() { $(this.getSelector().itemSelector).toArray().forEach(((ele) => { let $box2 = $(ele); if (!($box2.find(".tool-box").length > 0)) { isJavDb$1 && $box2.find(".tags").append(` <div class="tool-box" style="margin-left: auto; display: flex; align-items: center"> <span class="markDeleteSvg" title="标记删除" style="margin-right: 15px; display: none;">${this.markDeleteSvg}</span> <span class="delete115Svg" title="删除115作品" style="margin-right: 15px; display: none;">${this.removeSvg}</span> <span class="screenSvg" title="长缩略图" style="margin-right: 15px;">${this.screenSvg}</span> <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span> <div class="more-tools-container handleSvg" style="position: relative; margin-right: 15px;"> <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div> <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none; background-color: rgba(255, 255, 255, 0);z-index: 10;"> <a class="menu-btn hasWatchBtn" style="background-color:#d7a80c;color:white !important;margin-bottom: 5px"><span style="opacity: 1;">🔍 已观看</span></a> <a class="menu-btn hasDownBtn" style="background-color:#7bc73b; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">📥️ 已下载</span></a> <a class="menu-btn favoriteBtn" style="background-color:#25b1dc; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">⭐ 收藏</span></a> <a class="menu-btn filterBtn" style="background-color:#de3333; color:white !important;margin-bottom: 5px"><span style="opacity: 1;">🚫 屏蔽</span></a> </div> </div> <div class="more-tools-container siteSvg" style="position: relative; margin-right: 15px;"> <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div> <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none; background-color: rgba(255, 255, 255, 0);z-index: 10;"> <a class="site-btn site-jable" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;">Jable</span> </a> <a class="site-btn site-miss-av" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;">MissAv</span> </a> <a class="site-btn site-123-av" style="color:white !important;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;">123Av</span> </a> </div> </div> <div class="more-tools-container copySvg" style="position: relative; margin-right: 15px;"> <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.copySvg}</div> <div class="more-tools" style=" position: absolute; bottom: 20px; right: -10px; display: none; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.15); border-radius: 20px; padding: 10px 0; margin-bottom: 15px; z-index: 10; "> <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;">${this.carNumSvg}</span> <span class="titleSvg" title="复制标题" style="padding: 5px 10px; white-space: nowrap;">${this.titleSvg}</span> <span class="downSvg" title="下载封面" style="padding: 5px 10px; white-space: nowrap;">${this.downSvg}</span> </div> </div> </div> `); if (isJavBus$1) { if ($box2.find(".avatar-box").length > 0) return; $box2.find(".photo-info").append(` <div class="tool-box" style="display: flex; align-items: center;justify-content: flex-end"> <span class="markDeleteSvg" title="标记删除" style="margin-right: 15px; display: none;">${this.markDeleteSvg}</span> <span class="delete115Svg" title="删除115作品" style="margin-right: 15px; display: none;">${this.removeSvg}</span> <span class="screenSvg" title="长缩略图" style="margin-right: 15px;">${this.screenSvg}</span> <span class="videoSvg" title="播放视频" style="margin-right: 15px;">${this.videoSvg}</span> <div class="more-tools-container handleSvg" style="position: relative; margin-right: 15px;"> <div title="鉴定处理" style="padding: 5px; margin: -5px;opacity:.3">${this.handleSvg}</div> <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none; background-color: rgba(255, 255, 255, 0);z-index: 10;"> <a class="menu-btn hasWatchBtn" style="background-color:#d7a80c;color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">🔍 已观看</span></a> <a class="menu-btn hasDownBtn" style="background-color:#7bc73b; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">📥️ 已下载</span></a> <a class="menu-btn favoriteBtn" style="background-color:#25b1dc; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">⭐ 收藏</span></a> <a class="menu-btn filterBtn" style="background-color:#de3333; color:white;margin-bottom: 5px"><span style="opacity: 1;display: inline; color:white !important">🚫 屏蔽</span></a> </div> </div> <div class="more-tools-container siteSvg" style="position: relative; margin-right: 15px;"> <div title="第三方网站" style="padding: 5px; margin: -5px;opacity:.3">${this.siteSvg}</div> <div class="more-tools" style=" position: absolute; bottom: 33px; right: -30px; display: none; background-color: rgba(255, 255, 255, 0);z-index: 10;"> <a class="site-btn site-jable" style="color:white;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;display: inline; color:white !important">Jable</span> </a> <a class="site-btn site-miss-av" style="color:white;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;display: inline; color:white !important">MissAv</span> </a> <a class="site-btn site-123-av" style="color:white;margin-bottom: 5px;background-color:#71bb59;"> <span style="opacity: 1;display: inline; color:white !important">123Av</span> </a> </div> </div> <div class="more-tools-container copySvg" style="position: relative;"> <div title="复制按钮" style="padding: 5px; margin: -5px;opacity:.3">${this.copySvg}</div> <div class="more-tools" style=" max-width: 44px; position: absolute; bottom: 20px; right: -10px; display: none; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.15); border-radius: 20px; padding: 10px 0; margin-bottom: 15px; z-index: 10; text-align: center; "> <span class="carNumSvg" title="复制番号" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.carNumSvg}</span> <span class="titleSvg" title="复制标题" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.titleSvg}</span> <span class="downSvg" title="下载封面" style="padding: 5px 10px; white-space: nowrap;display: inline">${this.downSvg}</span> </div> </div> </div> `); } } })); this.enableSvgBtn(); } async enableSvgBtn() { const settingObj = await storageManager.getSetting(), { enableScreenSvg = YES$1, enableVideoSvg = YES$1, enableHandleSvg = YES$1, enableSiteSvg = YES$1, enableCopySvg = YES$1 } = settingObj; [{ selector: ".screenSvg", enabled: enableScreenSvg }, { selector: ".videoSvg", enabled: enableVideoSvg }, { selector: ".handleSvg", enabled: enableHandleSvg }, { selector: ".siteSvg", enabled: enableSiteSvg }, { selector: ".copySvg", enabled: enableCopySvg }].forEach((({ selector, enabled }) => { $(selector).toggle(enabled === YES$1); })); } async bindClick() { this.getSelector(); const listPagePlugin = this.getBean("ListPagePlugin"); $(document).on("click", ".more-tools-container", ((event) => { event.preventDefault(); var $currentTools = $(event.target).closest(".more-tools-container").find(".more-tools"); $(".more-tools").not($currentTools).stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); $currentTools.is(":visible") ? $currentTools.stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide() : $currentTools.stop(true, true).removeClass("elastic-out").addClass("elastic-in").show(); })); $(document).on("click", (function(event) { $(event.target).closest(".more-tools-container").length || $(".more-tools").stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); })); $(document).on("click", ".videoSvg", ((event) => { event.preventDefault(); $('.videoSvg[title!="播放视频"]').each(((index, element) => { const $otherSvgElement = $(element); let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find("img"), { carNum: carNum2 } = this.getBoxCarInfo($otherBox); this.showImg($otherSvgElement, $otherImg, carNum2); $otherSvgElement.html(this.videoSvg).attr("title", "播放视频"); })); const $currentBox = $(event.target).closest(".item"), $svgElement = $currentBox.find(".videoSvg"); if ("播放视频" === $svgElement.attr("title")) { $svgElement.html(this.recoveryVideoSvg).attr("title", "切回封面"); const { carNum: carNum2 } = this.getBoxCarInfo($currentBox); let $img = $currentBox.find("img"); if (!$img.length) { show.error("没有找到图片"); return; } this.showVideo($svgElement, $img, carNum2).then(); } })); $(document).on("click", ".screenSvg", (async (event) => { event.preventDefault(); let loadObj = loading(); try { const $box2 = $(event.currentTarget).closest(".item"); let { carNum: carNum2 } = this.getBoxCarInfo($box2); carNum2 = carNum2.replace("FC2-", ""); const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2); loadObj.close(); showPreviewOverlay(imgUrl, carNum2); } catch (error) { console.error("图片预览出错:", error); show.error("图片预览出错:" + error); } finally { loadObj.close(); } })); $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", ((event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.target).closest(".menu-btn"), $box2 = $btn.closest(".item"), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($box2), handleAction = async (status) => { let actress = await listPagePlugin.parseActressName(url); await storageManager.saveCar({ carNum: carNum2, url, names: actress, actionType: status, publishTime }); window.refresh(); show.ok("操作成功"); }; $btn.hasClass("filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER$1))) : $btn.hasClass("favoriteBtn") ? handleAction(Status_FAVORITE$1).then() : $btn.hasClass("hasDownBtn") ? handleAction(Status_HAS_DOWN$1).then() : $btn.hasClass("hasWatchBtn") && handleAction(Status_HAS_WATCH$1).then(); $(".more-tools").stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); })); const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), av123Url = await otherSitePlugin.getAv123Url(); $(document).on("click", ".site-jable, .site-miss-av, .site-123-av", ((event) => { event.preventDefault(); event.stopPropagation(); const $currentTarget = $(event.currentTarget), $box2 = $currentTarget.closest(".item"), { carNum: carNum2 } = this.getBoxCarInfo($box2); let url = null; $currentTarget.hasClass("site-jable") ? url = `${jableUrl}/search/${carNum2}/` : $currentTarget.hasClass("site-miss-av") ? url = `${missAvUrl}/search/${carNum2}` : $currentTarget.hasClass("site-123-av") && (url = `${av123Url}/ja/search?keyword=${carNum2}`); event && (event.ctrlKey || event.metaKey) ? _GM_openInTab(url, { insert: 0 }) : window.open(url); })); $(document).on("click", ".titleSvg, .carNumSvg, .downSvg", ((event) => { event.preventDefault(); event.stopPropagation(); const $box2 = $(event.currentTarget).closest(".item"), { carNum: carNum2, title } = this.getBoxCarInfo($box2), $img = $box2.find(isJavBus$1 ? ".photo-frame img" : ".cover img"); $(event.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", title) : $(event.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", carNum2) : $(event.currentTarget).hasClass("downSvg") && fetch($img.attr("src")).then(((response) => response.blob())).then(((blob) => { utils.download(blob, carNum2 + " " + title + ".jpg"); })); })); $(document).on("click", ".markDeleteSvg", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const $box2 = $btn.closest(".item"); const { carNum: carNum2, title, publishTime, url } = this.getBoxCarInfo($box2); const score = $box2.find(".score").text().trim() || "无评分"; let markedList = await storageManager.forage.getItem("markedDeleteList") || []; const existingIndex = markedList.findIndex((item) => item.carNum === carNum2); if (existingIndex > -1) { markedList.splice(existingIndex, 1); $btn.removeClass("marked"); $btn.attr("title", "标记删除"); show.ok(`已取消标记: ${title}`); } else { markedList.push({ carNum: carNum2, title, publishTime, score, url, markedTime: (/* @__PURE__ */ new Date()).toISOString() }); $btn.addClass("marked"); $btn.attr("title", "取消标记删除"); show.ok(`已标记删除: ${title}`); } await storageManager.forage.setItem("markedDeleteList", markedList); })); $(document).on("click", ".delete115Svg", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const matchData = $btn.attr("data-match"); if (!matchData) { show.error("未找到115匹配数据"); return; } const matchList = JSON.parse(matchData); const $box2 = $btn.closest(".item"); const { carNum: carNum2 } = this.getBoxCarInfo($box2); this.getBean("WangPan115MatchPlugin").showDeleteDialog(carNum2, matchList, (async (newMatchList) => { this.getBean("WangPan115MatchPlugin").updateMatchStatus($box2, carNum2, newMatchList); })); })); } showImg($svgElement, $img, carNum2) { $svgElement.html(this.videoSvg).attr("title", "播放视频"); let $video = $(`#${`${carNum2}_preview_video`}`); if ($video.length > 0) { $video[0].pause(); $video.parent().hide(); } $img.show(); $img.removeClass("loading"); $img.next(".loading-spinner").remove(); } async showVideo($svgElement, $img, carNum2) { const id = `${carNum2}_preview_video`; let $video = $(`#${id}`); if ($video.length > 0) { $video.parent().show(); $video[0].play(); $img.hide(); return; } $img.addClass("loading"); $img.after('<div class="loading-spinner"></div>'); const poster = $img.attr("src"), dmmVideoMap = await getDmmVideo(carNum2); if (!dmmVideoMap) { show.error("未解析到视频"); this.showImg($svgElement, $img, carNum2); return; } let defaultVideoQuality = await storageManager.getSetting("videoQuality"); defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality); let videoUrl = dmmVideoMap[defaultVideoQuality], videoHtml = ` <div style="display: flex; justify-content: center; align-items: center; position: absolute; top:0; left:0; height: 100%; width: 100%; z-index: 10; overflow: hidden"> <video src="${videoUrl}" poster="${poster}" id="${id}" controls loop muted playsinline style="max-height: 100%; max-width: 100%; object-fit: contain" ></video> </div> `; isJavBus$1 && (videoHtml = ` <div> <video src="${videoUrl}" poster="${poster}" id="${id}" controls loop muted playsinline style="max-height: 100%; max-width: 100%; object-fit: contain" ></video> </div> `); $img.parent().append(videoHtml); $img.hide(); $img.removeClass("loading"); $img.next(".loading-spinner").remove(); $video = $(`#${id}`); let videoElement = $video[0]; videoElement.load(); videoElement.muted = false; videoElement.play(); $video.trigger("focus"); } } class Fc2By123AvPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "$contentBox", $(".section .container")); __publicField2(this, "urlParams", new URLSearchParams(window.location.search)); __publicField2(this, "sortVal", this.urlParams.get("sort") || "release_date"); __publicField2(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1); __publicField2(this, "maxPage", null); __publicField2(this, "keyword", this.urlParams.get("keyword") || null); } getName() { return "Fc2By123AvPlugin"; } async getBaseUrl() { const otherSitePlugin = this.getBean("OtherSitePlugin"); return await otherSitePlugin.getAv123Url() + "/ja"; } handle() { $("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('<a class="navbar-item" href="/advanced_search?type=100&released_start=2099-09">123Av-Fc2</a>'); $('.tabs li:contains("FC2")').after('<li><a href="/advanced_search?type=100&released_start=2099-09"><span>123Av-Fc2</span></a></li>'); if (currentHref$1.includes("/advanced_search?type=100")) { this.hookPage(); this.handleQuery().then(); } } hookPage() { let $h2 = $("h2.section-title"); $h2.contents().first().replaceWith("123Av"); $h2.css("marginBottom", "0"); $h2.append('\n <div style="margin-left: 100px; width: 400px;">\n <input id="search-123av-keyword" type="text" placeholder="搜索123Av Fc2ppv内容" style="padding: 4px 5px;margin-right: 0">\n <a id="search-123av-btn" class="a-primary" style="margin-left: 0">搜索</a>\n <a id="clear-123av-btn" class="a-info" style="margin-left: 0">重置</a>\n </div>\n '); $("#search-123av-keyword").val(this.keyword); $("#search-123av-btn").on("click", (async () => { let keyword = $("#search-123av-keyword").val().trim(); if (keyword) { this.keyword = keyword; utils.setHrefParam("keyword", keyword); await this.handleQuery(); } })); $("#clear-123av-btn").on("click", (async () => { $("#search-123av-keyword").val(""); this.keyword = ""; utils.setHrefParam("keyword", ""); $(".page-box").show(); $(".tool-box").show(); await this.handleQuery(); })); $(".empty-message").remove(); $("#foldCategoryBtn").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('<div class="tool-box" style="margin-top: 10px"></div>'); this.$contentBox.append('<div class="movie-list h cols-4 vcols-8" style="margin-top: 10px"></div>'); this.$contentBox.append('<div class="page-box"></div>'); $(".tool-box").append('\n <div class="button-group">\n <div class="buttons has-addons" id="conditionBox">\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="release_date">发布日期</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="recent_update">最近更新</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="trending">热门</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_today">今天最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_week">本周最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed_month">本月最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_viewed">最多观看</a>\n <a style="padding:18px 18px !important;" class="button is-small" data-sort="most_favourited">最受欢迎</a>\n </div>\n </div>\n '); $(`#conditionBox a[data-sort="${this.sortVal}"]`).addClass("is-info"); utils.setHrefParam("sort", this.sortVal); utils.setHrefParam("page", this.currentPage); $("#conditionBox").on("click", "a.button", ((e) => { let $target = $(e.target); this.sortVal = $target.data("sort"); utils.setHrefParam("sort", this.sortVal); $target.siblings().removeClass("is-info"); $target.addClass("is-info"); this.handleQuery(); })); $(".page-box").append('\n <nav class="pagination">\n <a class="pagination-previous">上一页</a>\n <ul class="pagination-list"></ul>\n <a class="pagination-next">下一页</a>\n </nav>\n '); $(document).on("click", ".pagination-link", ((e) => { e.preventDefault(); this.currentPage = parseInt($(e.target).data("page")); utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); })); $(".pagination-previous").on("click", ((e) => { e.preventDefault(); if (this.currentPage > 1) { this.currentPage--; utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); } })); $(".pagination-next").on("click", ((e) => { e.preventDefault(); if (this.currentPage < this.maxPage) { this.currentPage++; utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); } })); } renderPagination() { const $paginationList = $(".pagination-list"); $paginationList.empty(); let startPage = Math.max(1, this.currentPage - 2), endPage = Math.min(this.maxPage, this.currentPage + 2); this.currentPage <= 3 ? endPage = Math.min(6, this.maxPage) : this.currentPage >= this.maxPage - 2 && (startPage = Math.max(this.maxPage - 5, 1)); if (startPage > 1) { $paginationList.append('<li><a class="pagination-link" data-page="1">1</a></li>'); startPage > 2 && $paginationList.append('<li><span class="pagination-ellipsis">…</span></li>'); } for (let i = startPage; i <= endPage; i++) { const activeClass = i === this.currentPage ? " is-current" : ""; $paginationList.append(`<li><a class="pagination-link${activeClass}" data-page="${i}">${i}</a></li>`); } if (endPage < this.maxPage) { endPage < this.maxPage - 1 && $paginationList.append('<li><span class="pagination-ellipsis">…</span></li>'); $paginationList.append(`<li><a class="pagination-link" data-page="${this.maxPage}">${this.maxPage}</a></li>`); } } async handleQuery() { let loadObj = loading(); try { let pagesToFetch = []; pagesToFetch = 1 === this.currentPage ? [1, 2] : [2 * this.currentPage - 1, 2 * this.currentPage]; if (this.keyword) { pagesToFetch = [1]; $(".page-box").hide(); $(".tool-box").hide(); } const baseUrl = await this.getBaseUrl(), fetchPromises = pagesToFetch.map(((page) => { let url = `${baseUrl}/tags/fc2?sort=${this.sortVal}&page=${page}`; this.keyword && (url = `${baseUrl}/search?keyword=${this.keyword}`); return gmHttp.get(url); })), htmlResults = await Promise.all(fetchPromises); let dataList = []; for (const html2 of htmlResults) { let $dom = $(html2); $dom.find(".box-item").each(((index, element) => { const $item = $(element), imgSrc = $item.find("img").attr("data-src"); let carNum2 = $item.find("img").attr("title"); const detailLink = $item.find(".detail a"), link2 = detailLink.attr("href"), href = baseUrl + (link2.startsWith("/") ? link2 : "/" + link2), title = detailLink.text().trim().replace(carNum2 + " - ", ""); carNum2 = carNum2.replace("FC2-PPV", "FC2"); dataList.push({ imgSrc, carNum: carNum2, href, title }); })); if (!this.maxPage) { let rawMaxPage, lastPageItem = $dom.find(".page-item:not(.disabled)").last(); if (lastPageItem.find("a.page-link").length) { let href = lastPageItem.find("a.page-link").attr("href"); rawMaxPage = parseInt(href.split("page=")[1]); } else rawMaxPage = parseInt(lastPageItem.find("span.page-link").text()); this.maxPage = Math.ceil(rawMaxPage / 2); this.renderPagination(); } } if (0 === dataList.length) { console.log(dataList); show.error("无结果"); let errorUrl = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}`; this.keyword && (errorUrl = `${baseUrl}/search?keyword=${this.keyword}`); console.error("获取数据失败!", errorUrl); } let movieHtml = this.markDataListHtml(dataList); $(".movie-list").html(movieHtml); await utils.smoothScrollToTop(); } catch (e) { console.error(e); } finally { loadObj.close(); } } async open123AvFc2Dialog(carNum2, href) { let otherHtml = ""; await storageManager.getSetting("enableLoadOtherSite", YES$1) === YES$1 && (otherHtml = '<div class="movie-panel-info fc2-movie-panel-info" style="margin-top:20px"><strong>第三方站点: </strong></div>'); let pageHtml = ` <div class="movie-detail-container"> <!-- <div class="movie-poster-container"> <iframe class="movie-trailer" frameborder="0" allowfullscreen scrolling="no"></iframe> </div> <div class="right-box">--> <div class="movie-info-container"> <div class="search-loading">加载中...</div> </div> ${otherHtml} <div style="margin: 10px 0"> <a id="filterBtn" class="menu-btn" style="background-color:#de3333"><span>🚫 屏蔽</span></a> <a id="favoriteBtn" class="menu-btn" style="background-color:#25b1dc"><span>⭐ 收藏</span></a> <a id="hasDownBtn" class="menu-btn" style="background-color:#7bc73b"><span>📥️ 已下载</span></a> <a id="hasWatchBtn" class="menu-btn" style="background-color:#d7a80c;"><span>🔍 已观看</span></a> <a id="subtitleSearchBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)"> <span>字幕搜索</span> </a> <!-- <a id="search-subtitle-btn" class="menu-btn fr-btn" style="background:linear-gradient(to bottom, #8d5656, rgb(196,159,91))"> <span>字幕 (SubTitleCat)</span> </a> <a id="xunLeiSubtitleBtn" class="menu-btn fr-btn" style="background:linear-gradient(to left, #375f7c, #2196F3)"> <span>字幕 (迅雷)</span> </a> --> </div> <div class="message video-panel" style="margin-top:20px"> <div id="magnets-content" class="magnet-links"> </div> </div> <div id="reviews-content"> </div> <div id="related-content"> </div> <span id="data-actress" style="display: none"></span> <!-- </div>--> </div> `; layer.open({ type: 1, title: carNum2, content: pageHtml, area: utils.getDefaultArea(), skin: "movie-detail-layer", scrollbar: false, success: (layero, index) => { utils.setupEscClose(index); this.loadData(carNum2, href); let keyword = carNum2.replace("FC2-", ""); $("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword)); $("#favoriteBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FAVORITE$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#filterBtn").on("click", ((event) => { utils.q(event, `是否屏蔽${carNum2}?`, (async () => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FILTER$1, publishTime }); window.refresh(); layer.closeAll(); window.location.href.includes("collection_codes?movieId") && utils.closePage(); })); })); $("#hasDownBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_DOWN$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#hasWatchBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_WATCH$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({ details: { code: carNum2 } }); })); window.JavPackSubtitle.preload115Matches(carNum2); let tempCarNum = carNum2.replace("FC2-", ""); this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); } }); } async loadData(carNum2, href) { let loadObj = loading(); try { const { id, publishDate, title, moviePoster } = await this.get123AvVideoInfo(href); $(".movie-info-container").html(` <h3 class="movie-title" style="margin-bottom: 10px"><strong class="current-title">${title || "无标题"}</strong></h3> <div class="movie-meta" style="margin-bottom: 10px"> <span><strong>番号: </strong>${carNum2 || "未知"}</span> <span><strong>年份: </strong>${publishDate || "未知"}</span> <span> <strong>站点: </strong> <a href="https://fc2ppvdb.com/articles/${carNum2.replace("FC2-", "")}" target="_blank">fc2ppvdb</a> <a style="margin-left: 5px;" href="https://adult.contents.fc2.com/article/${carNum2.replace("FC2-", "")}/" target="_blank">fc2电子市场</a> </span> </div> <div class="movie-actors" style="margin-bottom: 10px"> <div class="actor-list"><strong>主演: </strong></div> </div> <div class="movie-seller" style="margin-bottom: 10px"> <span><strong>販売者: </strong></span> </div> <div class="movie-gallery" style="margin-bottom: 10px"> <strong>剧照: </strong> <div class="image-list"></div> </div> <div id="data-publishTime" style="display: none">${publishDate || ""}</div> `); this.getImgList(carNum2).then(); this.getActressInfo(carNum2).then(); this.getBean("TranslatePlugin").translate(carNum2, false).then(); } catch (e) { console.error(e); } finally { loadObj.close(); } } handleLongImg(carNum2) { utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => { $(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> '); const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2); if (imgUrl) { $(".screen-container").html(`<img src="${imgUrl}" alt="" loading="lazy" style="width: 100%;">`); $(".screen-container").on("click", ((event) => { event.stopPropagation(); event.preventDefault(); showPreviewOverlay(imgUrl, carNum2); })); } })); } async get123AvVideoInfo(href) { const html2 = await gmHttp.get(href), match = html2.match(/v-scope="Movie\({id:\s*(\d+),/), id = match ? match[1] : null, $dom = utils.htmlTo$dom(html2); return { id, publishDate: $dom.find('span:contains("リリース日:")').next("span").text(), title: $dom.find("h1").text().trim(), moviePoster: $dom.find("#player").attr("data-poster") }; } async getActressInfo(fc2Num) { let url = `https://fc2ppvdb.com/articles/${fc2Num.replace("FC2-", "")}`; const html2 = await gmHttp.get(url), $dom = $(html2), actressNodeList = $dom.find("div").filter((function() { return 0 === $(this).text().trim().indexOf("女優:"); })); if (0 === actressNodeList.length || actressNodeList.length > 1) { show.error("解析女优信息失败"); return; } const $actress = $(actressNodeList[0]).find("a"); let actorsHtml = "<strong>主演: </strong>"; if ($actress.length > 0) { let actress = ""; $actress.each(((index, ele) => { let $actor = $(ele), name2 = $actor.text(), actressHref = $actor.attr("href"); actorsHtml += `<span class="actor-tag"><a href="https://fc2ppvdb.com${actressHref}" target="_blank">${name2}</a></span>`; actress += name2 + " "; })); $("#data-actress").text(actress); } else actorsHtml += "<span>暂无演员信息</span>"; $(".actor-list").html(actorsHtml); const sellerNodeList = $dom.find("div").filter((function() { return 0 === $(this).text().trim().indexOf("販売者:"); })); if (sellerNodeList.length > 0) { const $sellerA = $(sellerNodeList[0]).find("a"); if ($sellerA.length > 0) { const $seller = $($sellerA[0]); let name2 = $seller.text(), sellerHref = $seller.attr("href"); $(".movie-seller").html(`<span><strong>販売者: </strong><a href="https://fc2ppvdb.com${sellerHref}" target="_blank">${name2}</a></span>`); } } } async getImgList(fc2Num) { let tempCarNum = fc2Num.replace("FC2-", ""), url = `https://adult.contents.fc2.com/article/${fc2Num.replace("FC2-", "")}/`; const html2 = await gmHttp.get(url, null, { referer: url }); let imgList = $(html2).find(".items_article_SampleImagesArea img").map((function() { return $(this).attr("src"); })).get(), imagesHtml = ""; Array.isArray(imgList) && imgList.length > 0 ? imagesHtml = imgList.map(((img, index) => ` <a href="${img}" data-fancybox="movie-gallery" data-caption="剧照 ${index + 1}"> <img src="${img}" class="movie-image-thumb" alt=""/> </a> `)).join("") : $(".movie-gallery").html("<h4>剧照: 暂无剧照</h4>"); $(".image-list").html(imagesHtml); this.handleLongImg(tempCarNum); } async getMovie(id, moviePoster) { let url = `${await this.getBaseUrl()}/ajax/v/${id}/videos`, loadObj = loading(); try { let movieList = (await gmHttp.get(url)).result.watch; if (movieList.length > 0) { movieList.forEach(((movieItem) => { movieItem.url = movieItem.url + "?poster=" + moviePoster; })); return movieList; } return null; } catch (e) { console.error(e); } finally { loadObj.close(); } } markDataListHtml(movies) { let moviesHtml = ""; movies.forEach(((movie) => { moviesHtml += ` <div class="item"> <a href="${movie.href}" class="box" title="${movie.title}"> <div class="cover "> <img loading="lazy" src="${movie.imgSrc.replace("/s360", "")}" alt=""> </div> <div class="video-title"><strong>${movie.carNum}</strong> ${movie.title}</div> <div class="score"> </div> <div class="meta"> </div> <div class="tags has-addons"> </div> </a> </div> `; })); return moviesHtml; } } class DetailPageButtonPlugin extends BasePlugin { getName() { return "DetailPageButtonPlugin"; } constructor() { super(); this.answerCount = 1; } async handle() { let settingObj = await storageManager.getSetting(); this.filterHotKey = settingObj.filterHotKey; this.favoriteHotKey = settingObj.favoriteHotKey; this.hasDownHotKey = settingObj.hasDownHotKey; this.hasWatchHotKey = settingObj.hasWatchHotKey; this.speedVideoHotKey = settingObj.speedVideoHotKey; this.bindHotkey().then(); this.hideVideoControls(); window.isDetailPage && this.createMenuBtn(); } async createMenuBtn() { const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum, buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="downloadCoverBtn" class="menu-btn" style="width: 140px; background-color: #9b59b6; color: white; text-align: center; padding: 8px 0;">\n <span>下载高清封面</span>\n </a>\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n '; isJavDb$1 && $(".tabs").after(buttonsHtml); isJavBus$1 && $("#mag-submit-show").before(buttonsHtml); $("#favoriteBtn").on("click", (() => this.favoriteOne())); $("#filterBtn").on("click", ((event) => this.filterOne(event))); $("#hasDownBtn").on("click", (async () => this.hasDownOne())); $("#hasWatchBtn").on("click", (async () => this.hasWatchOne())); $("#magnetSearchBtn").on("click", (() => { let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(pageInfo.carNum); layer.open({ type: 1, title: "磁力搜索 " + pageInfo.carNum, content: '<div id="magnetHubBox"></div>', area: utils.getResponsiveArea(["60%", "80%"]), scrollbar: false, success: () => { $("#magnetHubBox").append(magnetHub); } }); })); const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO$1); if (enableMagnetsFilter === YES$1) { const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); let settings; try { settings = JSON.parse(savedSettings); } catch (e) { settings = {}; } const sortEnabled = settings.sortEnabled || {}; const defaultFullSortOrder = isJavBus$1 ? ["date", "size"] : ["date", "size", "files"]; const fullSortOrder = settings.sortOrder && settings.sortOrder.length === defaultFullSortOrder.length ? settings.sortOrder : defaultFullSortOrder; const effectiveSortOrder = fullSortOrder.filter((key) => sortEnabled[key]); const applySettings = { filterHD: settings.filterHD || false, filter4K: settings.filter4K || false, filterSubtitle: settings.filterSubtitle || false, filterUncensored: settings.filterUncensored || false, sortOrder: effectiveSortOrder }; const rowReadyCondition = isJavBus$1 ? (() => $("#magnet-table tr").length > 1) : (() => $("#magnets-content .item[data-rank]").length > 0); utils.loopDetector(rowReadyCondition, () => { highlightMagnetPlugin.applySortAndFilter(applySettings); }); $("#magnets-span").text("取消磁力过滤"); } else { $("#magnets-span").text("排序与过滤"); } $("#enable-magnets-filter").on("click", ((event) => { const $span = $("#magnets-span"); if ("取消磁力过滤" === $span.text()) { highlightMagnetPlugin.showAll(); $span.text("排序与过滤"); storageManager.saveSettingItem("enableMagnetsFilter", NO$1); } else { this.openMagnetSortFilterPanel(highlightMagnetPlugin); } })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({ details: { code: carNum2 } }); })); $("#downloadCoverBtn").on("click", (async () => { if (!isEligibleDmmCoverCode(carNum2)) { show.error("当前番号不支持下载高清封面"); return; } const $btn = $("#downloadCoverBtn"); $btn.addClass("is-loading").prop("disabled", true); try { const coverUrl = await getCover(carNum2); if (!coverUrl) { show.error("未找到高清封面"); return; } const fileName = `${carNum2}-cover.jpg`; _GM_xmlhttpRequest({ method: "GET", url: coverUrl, responseType: "blob", onload: (response) => { if (response.status === 200) { const blobUrl = URL.createObjectURL(response.response); const a = document.createElement("a"); a.href = blobUrl; a.download = fileName; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(blobUrl); }, 100); show.ok("封面下载已开始"); } else { show.error(`封面下载失败: ${response.status}`); } }, onerror: () => show.error("封面下载网络错误"), ontimeout: () => show.error("封面下载超时") }); } catch (e) { show.error("获取封面信息失败"); } finally { $btn.removeClass("is-loading").prop("disabled", false); } })); window.JavPackSubtitle.preload115Matches(carNum2); this.showStatus(carNum2).then(); } async openMagnetSortFilterPanel(highlightMagnetPlugin) { const existingPanel = document.getElementById("magnet-sort-filter-panel"); if (existingPanel) { existingPanel.remove(); return; } const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); let settings; try { settings = JSON.parse(savedSettings); } catch (e) { settings = {}; } const sortEnabled = settings.sortEnabled || (isJavBus$1 ? { date: false, size: false } : { date: false, size: false, files: false }); const defaultSortOrder = isJavBus$1 ? ["date", "size"] : ["date", "size", "files"]; const sortOrder = settings.sortOrder && settings.sortOrder.length === defaultSortOrder.length ? settings.sortOrder : defaultSortOrder; const filterHD = settings.filterHD || false; const filter4K = settings.filter4K || false; const filterSubtitle = settings.filterSubtitle || false; const filterUncensored = settings.filterUncensored || false; const SORT_LABELS = isJavBus$1 ? { date: "发布日期", size: "文件大小" } : { date: "发布日期", size: "文件大小", files: "文件数量" }; const SORT_HINTS = isJavBus$1 ? { date: "从新到旧", size: "从大到小" } : { date: "从新到旧", size: "从大到小", files: "从少到多" }; const FILTER_ITEMS = [ { key: "HD", label: "高清", checked: filterHD }, { key: "4K", label: "4K", checked: filter4K }, { key: "Subtitle", label: "字幕", checked: filterSubtitle }, { key: "Uncensored", label: "无码", checked: filterUncensored } ]; const currentSortOrder = [...sortOrder]; const currentSortEnabled = { ...sortEnabled }; const currentFilters = { HD: filterHD, "4K": filter4K, Subtitle: filterSubtitle, Uncensored: filterUncensored }; const buildSortItemsHTML = () => { return currentSortOrder.map((key) => { const checked = currentSortEnabled[key] ? "checked" : ""; return `<div class="msf-sort-item" draggable="true" data-sort-key="${key}"> <span class="msf-drag-handle">⋮⋮</span> <label class="msf-sort-label"> <input type="checkbox" class="msf-sort-checkbox" data-sort-key="${key}" ${checked}> ${SORT_LABELS[key]} <span class="msf-sort-hint">${SORT_HINTS[key]}</span> </label> </div>`; }).join(""); }; const buildFilterItemsHTML = () => { return FILTER_ITEMS.map((item) => { const checked = currentFilters[item.key] ? "checked" : ""; return `<label class="msf-filter-label"> <input type="checkbox" class="msf-filter-checkbox" data-filter-key="${item.key}" ${checked}> ${item.label} </label>`; }).join(""); }; const panelHTML = ` <div class="msf-overlay" id="msf-overlay"></div> <div class="msf-panel" id="magnet-sort-filter-panel"> <div class="msf-header"> <span class="msf-title">磁力排序与过滤</span> <span class="msf-close" id="msf-close-btn">×</span> </div> <div class="msf-body"> <div class="msf-section"> <div class="msf-section-title">排序优先级(拖动调整顺序,勾选启用)</div> <div class="msf-sort-list" id="msf-sort-list"> ${buildSortItemsHTML()} </div> </div> <div class="msf-section"> <div class="msf-section-title">过滤条件(可多选,并集关系)</div> <div class="msf-filter-list"> ${buildFilterItemsHTML()} </div> </div> </div> <div class="msf-footer"> <button class="msf-btn msf-btn-cancel" id="msf-cancel-btn">取消</button> <button class="msf-btn msf-btn-confirm" id="msf-confirm-btn">确认</button> </div> </div> `; const wrapper = document.createElement("div"); wrapper.innerHTML = panelHTML; document.body.appendChild(wrapper); const panel = document.getElementById("magnet-sort-filter-panel"); const overlay = document.getElementById("msf-overlay"); const closePanel = () => { panel.remove(); overlay.remove(); }; overlay.addEventListener("click", closePanel); document.getElementById("msf-close-btn").addEventListener("click", closePanel); document.getElementById("msf-cancel-btn").addEventListener("click", closePanel); const rebuildSortList = () => { const sortList = document.getElementById("msf-sort-list"); sortList.innerHTML = buildSortItemsHTML(); bindSortEvents(); }; const bindSortEvents = () => { const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item"); sortItems.forEach((item) => { item.addEventListener("dragstart", (e) => { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.dataset.sortKey); item.classList.add("msf-dragging"); }); item.addEventListener("dragend", () => { item.classList.remove("msf-dragging"); document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach((el) => el.classList.remove("msf-drag-over")); }); item.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; item.classList.add("msf-drag-over"); }); item.addEventListener("dragleave", () => { item.classList.remove("msf-drag-over"); }); item.addEventListener("drop", (e) => { e.preventDefault(); item.classList.remove("msf-drag-over"); const fromKey = e.dataTransfer.getData("text/plain"); const toKey = item.dataset.sortKey; if (fromKey === toKey) return; const fromIdx = currentSortOrder.indexOf(fromKey); const toIdx = currentSortOrder.indexOf(toKey); currentSortOrder.splice(fromIdx, 1); currentSortOrder.splice(toIdx, 0, fromKey); rebuildSortList(); }); const checkbox = item.querySelector(".msf-sort-checkbox"); checkbox.addEventListener("change", () => { currentSortEnabled[item.dataset.sortKey] = checkbox.checked; }); }); }; bindSortEvents(); document.querySelectorAll(".msf-filter-checkbox").forEach((cb) => { cb.addEventListener("change", () => { currentFilters[cb.dataset.filterKey] = cb.checked; }); }); document.getElementById("msf-confirm-btn").addEventListener("click", async () => { try { const enabledSortOrder = currentSortOrder.filter((key) => currentSortEnabled[key]); const newSettings = { sortOrder: currentSortOrder, sortEnabled: currentSortEnabled, filterHD: currentFilters.HD, filter4K: currentFilters["4K"], filterSubtitle: currentFilters.Subtitle, filterUncensored: currentFilters.Uncensored }; await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings)); const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored; const hasAnySort = enabledSortOrder.length > 0; if (hasAnyFilter || hasAnySort) { await highlightMagnetPlugin.applySortAndFilter({ filterHD: currentFilters.HD, filter4K: currentFilters["4K"], filterSubtitle: currentFilters.Subtitle, filterUncensored: currentFilters.Uncensored, sortOrder: enabledSortOrder }); $("#magnets-span").text("取消磁力过滤"); await storageManager.saveSettingItem("enableMagnetsFilter", YES$1); } else { highlightMagnetPlugin.showAll(); $("#magnets-span").text("排序与过滤"); await storageManager.saveSettingItem("enableMagnetsFilter", NO$1); } } catch (e) { console.error("[磁力排序] 应用失败:", e); } closePanel(); }); } async showStatus(carNum2) { const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = (hotKey) => hotKey ? `(${hotKey})` : ""; $filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); $favoriteBtn.text(`⭐ 收藏 ${hotKeyDisplay(this.favoriteHotKey)}`); $hasDownBtn.text(`📥️ 已下载 ${hotKeyDisplay(this.hasDownHotKey)}`); $hasWatchBtn.text(`🔍 已观看 ${hotKeyDisplay(this.hasWatchHotKey)}`); const car = await storageManager.getCar(carNum2); if (car) switch (car.status) { case Status_FILTER$1: $filterBtn.text(`🚫 已屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); break; case Status_FAVORITE$1: $favoriteBtn.text(`⭐ 已收藏 ${hotKeyDisplay(this.favoriteHotKey)}`); break; case Status_HAS_DOWN$1: $hasDownBtn.text(`📥️ 已标记下载 ${hotKeyDisplay(this.hasDownHotKey)}`); break; case Status_HAS_WATCH$1: $hasWatchBtn.text(`🔍 已标记观看 ${hotKeyDisplay(this.hasWatchHotKey)}`); } } async favoriteOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FAVORITE$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } async hasDownOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_HAS_DOWN$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } async hasWatchOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_HAS_WATCH$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } /* searchXunLeiSubtitle(carNum2) { let loadObj = loading(); gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${carNum2}`).then((res => { let dataList = res.data; dataList && 0 !== dataList.length ? layer.open({ type: 1, title: "迅雷字幕", content: '\n <div style="height: 100%;overflow:hidden;"> \n <div id="xunlei-table-container" style="height: 100%;padding-bottom: 20px"></div>\n </div>\n ', scrollbar: !1, area: utils.getResponsiveArea([ "60%", "70%" ]), anim: -1, success: (layero, index) => { new Tabulator("#xunlei-table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: !0, data: dataList, responsiveLayout: "collapse", responsiveLayoutCollapse: !0, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, columns: [ { title: "文件名", field: "name", headerSort: !1, responsive: 0 }, { title: "类型", field: "ext", headerSort: !1, responsive: 0 }, { title: "操作", responsive: 0, headerSort: !1, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { const previewButton = cell.getElement().querySelector(".a-primary"), downButton = cell.getElement().querySelector(".a-success"); previewButton && previewButton.addEventListener("click", (async e => { let url = item.url, name2 = carNum2 + "." + item.ext; this.previewSubtitle(url, name2); })); downButton && downButton.addEventListener("click", (async e => { let url = item.url, name2 = carNum2 + "." + item.ext, content = await gmHttp.get(url); utils.download(content, name2); })); })); return '\n <a class="a-primary">预览</a>\n <a class="a-success">下载</a>\n '; } } ], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); utils.setupEscClose(index); } }) : show.error("迅雷中找不到相关字幕!"); })).catch((e => { console.error(e); show.error(e); })).finally((() => { loadObj.close(); })); } */ async filterOne(event, noAlert) { event && event.preventDefault(); let pageInfo = this.getPageInfo(); if (noAlert) { await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FILTER$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); layer.closeAll(); this.answerCount = 1; } else utils.q(event, `是否屏蔽${pageInfo.carNum}?`, (async () => { await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FILTER$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); }), (() => { this.answerCount = 1; })); } speedVideo() { if ($("#preview-video").is(":visible")) { const videoEl = document.getElementById("preview-video"); if (videoEl) { videoEl.muted = false; videoEl.controls = false; if (videoEl.currentTime + 5 < videoEl.duration) videoEl.currentTime += 5; else { show.info("预览视频结束, 已回到开头"); videoEl.currentTime = 1; } } return; } const iframe = $('iframe[id^="layui-layer-iframe"]'); if (iframe.length > 0) { iframe[0].contentWindow.postMessage("speedVideo", "*"); return; } let $videoPlayBtn = $(".preview-video-container"); if ($videoPlayBtn.length > 0) { $videoPlayBtn[0].click(); const videoEl = document.getElementById("preview-video"); if (videoEl) { videoEl.currentTime += 5; videoEl.muted = false; } } else $("#javTrailersBtn").click(); } hideVideoControls() { $(document).on("mouseenter", "#preview-video", (function() { $(this).prop("controls", true); })); } async bindHotkey() { const handlers = {}; this.filterHotKey && (handlers[this.filterHotKey] = () => { this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null); this.answerCount++; }); this.favoriteHotKey && (handlers[this.favoriteHotKey] = () => this.favoriteOne(null)); this.hasDownHotKey && (handlers[this.hasDownHotKey] = () => this.hasDownOne()); this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = () => this.hasWatchOne()); this.speedVideoHotKey && (handlers[this.speedVideoHotKey] = () => this.speedVideo()); const registerHotkey = (key, handler) => { HotkeyManager.registerHotkey(key, ((event) => { const activeElement = document.activeElement; "INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable || (window.isDetailPage ? handler() : ((message) => { const childIframe = $(".layui-layer-content iframe"); if (0 === childIframe.length) return false; childIframe[0].contentWindow.postMessage(message, "*"); })(key)); })); }; window.isDetailPage && window.addEventListener("message", ((event) => { handlers[event.data] && handlers[event.data](); })); Object.entries(handlers).forEach((([key, handler]) => { registerHotkey(key, handler); })); } async previewSubtitle(url, name2) { if (!url) { console.error("未提供文件URL"); return; } const fileExt = url.split(".").pop().toLowerCase(); if ("ass" === fileExt || "srt" === fileExt) try { let resText = await gmHttp.get(url), title = "字幕预览"; "ass" === fileExt ? title = "ASS字幕预览 - " + name2 : "srt" === fileExt && (title = "SRT字幕预览 - " + name2); const lines = resText.split("\n"); let numberedContent = ""; const maxLineNumberLength = String(lines.length).length; lines.forEach(((line, index) => { const paddedLineNumber = String(index + 1).padStart(maxLineNumberLength, " "); numberedContent += `<span style="color:#AAA;">${paddedLineNumber}. </span>${line} `; })); const finalContent = numberedContent; layer.open({ type: 1, title, area: ["80%", "80%"], scrollbar: false, content: `<div style="padding:15px 5px;background:#1E1E1E;color:#FFF;font-family:Consolas,Monaco,monospace;white-space:pre-wrap;overflow:auto;height:100%;">${finalContent}</div>`, btn: ["下载", "关闭"], btn1: function(index, layero, that) { utils.download(resText, name2); return false; } }); } catch (error) { show.error(`预览失败: ${error.message}`); console.error("预览字幕文件出错:", error); } else show.error("仅支持预览ASS和SRT字幕文件"); } } class BlacklistPlugin extends BasePlugin { getName() { return "BlacklistPlugin"; } async addBlacklist(event) { let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }; const hasAddBlacklist = $("#addBlacklistBtn span").text().includes("已加入"); let dataInfo, title; if (currentHref$1.includes("/tags")) { const urlObj = new URL(currentHref$1); urlObj.searchParams.delete("page"); const checkTag = $("#jhs-check-tag").text().trim(); dataInfo = { starId: "no-" + checkTag, name: "虚拟演员-" + checkTag, allName: ["虚拟演员"], role: "虚拟演员", movieType: "虚拟演员", blacklistUrl: urlObj.toString() }; title = `是否将分类 <span style="color: #f40">${checkTag}</span> 加入到黑名单中?`; hasAddBlacklist && (title = `分类 <span style="color: #f40">${checkTag}</span> 已在黑名单中, 是否从当前页开始追加屏蔽?`); } else { dataInfo = this.getActressPageInfo(); title = `是否将该演员 <span style="color: #f40">${dataInfo.name}</span> 加入到黑名单中?`; hasAddBlacklist && (title = `演员 <span style="color: #f40">${dataInfo.name}</span> 已在黑名单中, 是否从当前页开始追加屏蔽?`); } const { starId, name: name2, allName, role, movieType, blacklistUrl } = dataInfo; currentHref$1.includes("page") && !currentHref$1.includes("page=1") && (title += "<br/> 注意: 当前页面非第一页, 屏蔽数据将从此页面开始"); if (isJavBus$1) { const afterStarParts = currentHref$1.split("/star/")[1].split("/"); if (afterStarParts.length > 1) { parseInt(afterStarParts[1]) > 1 && (title += "<br/> 注意: 当前页面非第一页, 屏蔽数据将从此页面开始"); } } utils.q(tempEvent, title, (async () => { const taskPlugin = this.getBean("TaskPlugin"); navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (lock) { this.loadObj = loading(); try { await storageManager.addBlacklistItem({ starId, name: name2, allName, role, movieType, url: blacklistUrl }); await this.filterActorVideo(name2, starId); const toast = show.ok(`屏蔽结束,是否跳转到最后一页: ${this.lastPageLink || "无"}`, { duration: -1, close: true, onClick: () => { toast.closeShow(); this.lastPageLink && (window.location.href = this.lastPageLink); } }); } catch (e) { clog.error(e); const toast = show.error("发生错误, 是否填转到解析失败的那一页? (点击并跳转)", { duration: -1, close: true, onClick: () => { toast.closeShow(); window.location.href = this.nextPageLink; } }); } finally { this.loadObj.close(); } } else show.error("当前有定时任务在后台执行中, 无法发起此操作"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); } async resetBtnTip() { const taskPlugin = this.getBean("TaskPlugin"), lastCheckBlacklistTimeStr = localStorage.getItem(taskPlugin.lastCheckBlacklistTimeKey) || "无", checkBlacklist_intervalTime = await storageManager.getSetting("checkBlacklist_intervalTime", 12); this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime", 8760); $("#checkBlacklistBtn").attr("data-tip", `上次检测时间: ${lastCheckBlacklistTimeStr}; 检测间隔时间: ${checkBlacklist_intervalTime}小时`); } async openBlacklistDialog() { const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(); let html2 = ` <div style="padding: 10px 20px; height: 100%;overflow:hidden;"> <div style="display: flex;justify-content: space-between;"> <div style="display: flex; gap:5px"> <a id="checkBlacklistBtn" class="a-danger" data-tip="上次检测时间: ${localStorage.getItem(taskPlugin.lastCheckBlacklistTimeKey) || "无"}; 检测间隔时间: ${settingObj.checkBlacklist_intervalTime}小时">${this.blacklistSvg} 手动检测黑名单</a> <a class="a-warning" id="clearKeywordBlacklist" data-tip="检测黑名单番号数据列表, 是否包含标题关键词">${this.removeSvg} 清理数据</a> <a class="a-info" id="toSetting">${this.settingSvg} 配置</a> </div> <div style="display: flex; gap:5px"> <select id="dataType" style="text-align: center;min-width: 150px;"> <option value="" selected>所有</option> <option value="actor">男演员</option> <option value="actress">女演员</option> <option value="虚拟演员">虚拟演员</option> </select> <select id="statusType" style="text-align: center;min-width: 150px;"> <option value="" selected>--检测状态--</option> <option value="normal">正常检测</option> <option value="stop">停止检测</option> </select> <select id="urlType" data-tip="在演员页屏蔽时,是否选择了分类" style="text-align: center;min-width: 150px; ${isJavDb$1 ? "" : "display: none;"}"> <option value="" selected>--屏蔽类型--</option> <option value="hasT">按所选分类屏蔽</option> <option value="noT">所有分类</option> </select> <input id="searchValue" type="text" placeholder="搜索演员" style="padding: 4px 5px;"> <a id="cleanQueryBtn" class="a-info" style="margin-left: 0">重置</a> </div> </div> <div id="table-container" style="margin-top:20px !important; height: calc(100% - 50px);"></div> </div> `; layer.open({ type: 1, title: "演员黑名单", content: html2, scrollbar: false, area: utils.getResponsiveArea(["80%", "90%"]), anim: -1, success: async (layero) => { await this.loadTableData(); $(".layui-layer-content").on("click", "#cleanQueryBtn", (async (event) => { $("#searchValue").val(""); $("#dataType").val(""); $("#statusType").val(""); await this.reloadTable(); })).on("focusout keydown", "#searchValue", (async (event) => { if ("focusout" === event.type || "Enter" === event.key) { "Enter" === event.key && event.preventDefault(); if ("keydown" === event.type && "Enter" !== event.key) return; $("#dataType").val(""); await this.reloadTable(); } })).on("change", "#dataType", (async () => { $("#searchValue").val(""); await this.reloadTable(); })).on("change", "#statusType", (async () => { await this.reloadTable(); })).on("change", "#urlType", (async () => { await this.reloadTable(); })).on("click", "#toSetting", (() => { this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => { $("#setting-blacklist").css({ border: "1px solid #f40" }); })); })).on("click", "#clearKeywordBlacklist", (async () => { await this.clearKeywordBlacklist(); })).on("click", ".open-url", ((event) => { event.preventDefault(); const $el = $(event.currentTarget), url = $el.attr("data-url"), name2 = $el.attr("data-name"); utils.openPage(url, name2, true, event); })).on("click", "#checkBlacklistBtn", ((event) => { utils.q({ clientX: event.clientX, clientY: event.clientY + 20 }, "是否手动检测黑名单?", (() => { navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (lock) { await taskPlugin.loadConfig(); await taskPlugin.checkBlacklist(true); } else show.error("当前有定时任务在后台执行中, 无法发起手动任务"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); })); }, end: () => { if (this.tableObj) { this.tableObj.destroy(); this.tableObj = null; } window.refresh(); } }); } async reloadTable() { if (!this.tableObj) return; const tableData = await this.getTableData(); this.tableObj.setData(tableData); } async getTableData() { const taskPlugin = this.getBean("TaskPlugin"), blacklist = await storageManager.getBlacklist(), blacklistCarList = await storageManager.getBlacklistCarList(), searchValue = $("#searchValue").val(), statusType = $("#statusType").val(), $dataType = $("#dataType"), dataType = $dataType.val(), urlType = $("#urlType").val(), blacklistCount = blacklist.length; let actorCount = 0, actressCount = 0, noStarCount = 0; const filteredBlacklist = blacklist.map(((item) => { "actor" === item.role ? actorCount++ : "actress" === item.role ? actressCount++ : "虚拟演员" === item.role && noStarCount++; let isUnCheck = false; item.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(item.lastPublishTime, this.checkBlacklist_ruleTime)); return { ...item, isUnCheck }; })).filter(((item) => !(searchValue && !item.name.includes(searchValue)) && (("normal" !== statusType || !item.isUnCheck) && (!("stop" === statusType && !item.isUnCheck) && (dataType ? item.role === dataType : !("hasT" === urlType && !item.url.includes("t=") && !item.url.includes("/tags")) && ("noT" !== urlType || !item.url.includes("t=") && !item.url.includes("/tags"))))))); $dataType.html(` <option value="">所有 (${blacklistCount})</option> <option value="actor">男演员 (${actorCount})</option> <option value="actress">女演员 (${actressCount})</option> <option value="虚拟演员">虚拟演员 (${noStarCount})</option> `); $dataType.val(dataType); const carListMap = /* @__PURE__ */ new Map(); for (const car of blacklistCarList) { const starId = car.starId; carListMap.has(starId) || carListMap.set(starId, []); carListMap.get(starId).push(car); } const finalResult = filteredBlacklist.map(((item) => { const starId = item.starId, carList = carListMap.get(starId) || []; return { ...item, carList, count: carList.length }; })); this.currentCarCount = finalResult.reduce(((accumulator, currentObject) => accumulator + (currentObject.count || 0)), 0); return finalResult; } async loadTableData() { this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime") || 8760; const tableData = await this.getTableData(); this.tableObj = new Tabulator("#table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: true, data: tableData, pagination: true, paginationMode: "local", paginationSize: 20, paginationSizeSelector: [20, 50, 100, 1e3, 99999], paginationCounter: (pageSize, currentRow, currentPage, totalRows, totalPages) => `演员: ${totalRows} 番号总数: ${this.currentCarCount} <span id="checkBlacklistMsg" style="margin-left: 10px"></span>`, responsiveLayout: "collapse", responsiveLayoutCollapse: true, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, index: "starId", columns: [{ title: "演员", field: "name", sorter: "string", minWidth: 100, responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); return `<a class="open-url" data-url="${item.url}" href="${item.url}" data-name="${item.name}" target="_blank">${item.name}</a>`; } }, { title: "性别角色", field: "role", sorter: "string", width: 120, responsive: 5, formatter: (cell, formatterParams, onRendered) => { const role = cell.getData().role; let content = role; "actor" === role ? content = "男演员" : "actress" === role && (content = "女演员"); return content; } }, { title: "影视类别", field: "movieType", sorter: "string", width: 120, responsive: 5, formatter: (cell, formatterParams, onRendered) => { const movieType = cell.getData().movieType; let content = movieType; "censored" === movieType ? content = "有码" : "uncensored" === movieType && (content = "无码"); return content; } }, { title: "屏蔽类型", field: "url", sorter: "string", minWidth: 120, responsive: 4, visible: isJavDb$1, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); if ("虚拟演员" === item.role) { const nameSplitList = item.name.split("-"); return `<span style="color:#cc4444">${nameSplitList.length > 0 ? nameSplitList[1] : item.name}</span>`; } const tParam = utils.getUrlParam(item.url, "t"); if (!tParam) return "<span>所有分类</span>"; { const tParamList = tParam.toString().split(","); let selectTypeStr = ""; tParamList.forEach(((i) => { let value = categoryMap[i]; selectTypeStr += value ? value + " " : "未知类别:" + i + " "; })); selectTypeStr = selectTypeStr.trim(); if (selectTypeStr) return `<span style="color:#cc4444">${selectTypeStr}</span>`; } } }, { title: "番号数量", field: "count", sorter: "number", width: 170, responsive: 1 }, { title: "创建时间", field: "createTime", sorter: "string", width: 170, responsive: 5 }, { title: "最后发行时间", field: "lastPublishTime", sorter: "string", width: 170, responsive: 1 }, { title: "状态", field: "isUnCheck", sorter: "string", width: 120, responsive: 1, formatter: (cell, formatterParams, onRendered) => { let dataTip = "", content = "正常检测"; if (cell.getData().isUnCheck) { dataTip = `停更${this.checkBlacklist_ruleTime / 24 / 365}年以上, 下轮任务不再进行检测`; content = "停止检测"; } return `<span data-tip="${dataTip}" style="${dataTip ? "color: #cc4444;" : ""}">${content}</span>`; } }, { title: "操作", sorter: "string", cssClass: "action-cell-dropdown", minWidth: 150, responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { var _a2, _b; null == (_a2 = cell.getElement().querySelector(".delete-btn")) || _a2.addEventListener("click", ((e) => { const name2 = item.name, starId = item.starId; name2 ? starId ? utils.q(e, `是否移除对 ${name2} 的屏蔽?`, (async () => { await storageManager.removeBlacklistCarList(starId); await storageManager.deleteBlacklistItem(starId); show.info("操作成功"); this.reloadTable().then(); })) : show.error("获取starId失败") : show.error("获取名称失败"); })); null == (_b = cell.getElement().querySelector(".keyword-btn")) || _b.addEventListener("click", ((e) => { const prefixMap = item.carList.reduce(((dataMap, carItem) => { const prefix = carItem.carNum.split("-")[0] + "-"; dataMap[prefix] = (dataMap[prefix] || 0) + 1; return dataMap; }), {}), sortedPrefixList = Object.entries(prefixMap).map((([prefix, count]) => ({ prefix, count }))).sort(((a, b) => b.count - a.count)); console.log(sortedPrefixList); })); })); return '\n <!-- <a class="a-normal keyword-btn"> <span>提取屏蔽词</span> </a>-->\n <a class="a-danger delete-btn"> <span>✂️ 删除</span> </a>\n '; } }], initialSort: [{ column: "createTime", dir: "desc" }], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); } getCurrentStarUrl() { let urlWithoutSortType = window.location.href.replace(/([&?])sort_type=[^&]+(&|$)/, "$1"); urlWithoutSortType = urlWithoutSortType.replace(/[&?]$/, ""); urlWithoutSortType = urlWithoutSortType.replace(/\?&/, "?"); let urlWithoutPageNumber = urlWithoutSortType; urlWithoutPageNumber = urlWithoutPageNumber.replace(/([&?])page=\d+(&|$)/, "$1"); urlWithoutPageNumber = urlWithoutPageNumber.replace(/[&?]$/, ""); urlWithoutPageNumber = urlWithoutPageNumber.replace(/\?&/, "?"); urlWithoutPageNumber = urlWithoutPageNumber.replace(/\/(\d+)(?:\/(\d+))?(\?|$)/, ((match, id, page, suffix) => void 0 !== page ? `/${id}${suffix}` : match)); return urlWithoutPageNumber; } parseUrlId(url) { if (!url) throw new Error("url未传入"); return new URL(url).pathname.split("/").filter(((segment) => "" !== segment.trim())).pop(); } async filterAllVideo(actorName, $dom) { let movieList, nextPageLink; if ($dom) { isJavBus$1 && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); movieList = $dom.find(this.getSelector().requestDomItemSelector); nextPageLink = $dom.find(this.getSelector().nextPageSelector).attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(this.getSelector().nextPageSelector).attr("href"); } if (nextPageLink && 0 === movieList.length) { show.error("解析列表失败"); throw new Error("解析列表失败"); } for (const element of movieList) { const $item = $(element), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($item); if (url && carNum2) try { if (await storageManager.getCar(carNum2)) continue; await storageManager.saveCar({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, publishTime }); clog.log("屏蔽演员番号", actorName, carNum2); } catch (error) { console.error(`保存失败 [${carNum2}]:`, error); } } if (nextPageLink) { show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink); await new Promise(((resolve) => setTimeout(resolve, 500))); const html2 = await gmHttp.get(nextPageLink), parser = new DOMParser(), next$dom = $(parser.parseFromString(html2, "text/html")); await this.filterAllVideo(actorName, next$dom); } else { show.ok("执行结束!"); window.refresh(); } } async filterActorVideo(actorName, starId, $dom) { let { nextPageLink } = await this.parseAndSaveFilterInfo($dom, actorName, starId); this.nextPageLink = nextPageLink; if (nextPageLink) { this.lastPageLink = nextPageLink; show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink); let next$dom; const pageNum = utils.getUrlParam(nextPageLink, "page") || 0, beyond60Plugin = this.getBean("Beyond60Plugin"); if (isJavDb$1 && beyond60Plugin && pageNum > 60) { let { html: html2, nextUrl, hasMore } = await beyond60Plugin.handleBeyond60(nextPageLink), mergeHtml = ` <div class ='movie-list'>${html2}</div> ${nextUrl ? `<a class="pagination-next" href="${nextUrl}"></a>` : ""} `; next$dom = utils.htmlTo$dom(mergeHtml); } else { clog.log("正在请求下一页内容:", nextPageLink); const html2 = await gmHttp.get(nextPageLink); next$dom = utils.htmlTo$dom(html2); } await this.filterActorVideo(actorName, starId, next$dom); } else { show.ok("执行结束!"); window.refresh(); } } async parseAndSaveFilterInfo($dom, actorName, starId) { let movieList, nextPageLink; if ($dom) { let tempIsJavBus = false, selectorType = "javdb"; if ($dom.text().includes("javbus")) { tempIsJavBus = true; selectorType = "javbus"; } tempIsJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); movieList = $dom.find(this.getSelector(selectorType).requestDomItemSelector); nextPageLink = $dom.find(this.getSelector(selectorType).nextPageSelector).attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(this.getSelector().nextPageSelector).attr("href"); } if (nextPageLink && 0 === movieList.length) return { nextPageLink: null, lastPublishTime: null }; const filterKeywordList = await storageManager.getTitleFilterKeyword(); let carDataList = [], lastPublishTime = null; for (const element of movieList) { const $item = $(element), { carNum: carNum2, url, publishTime, title } = this.getBoxCarInfo($item); lastPublishTime || (lastPublishTime = publishTime); filterKeywordList.find(((kw) => title.includes(kw) || carNum2.startsWith(kw))) || url && carNum2 && carDataList.push({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, starId, publishTime }); } await storageManager.batchSaveBlacklistCarList(carDataList); return { nextPageLink, lastPublishTime }; } async clearKeywordBlacklist() { let filterKeywordList = await storageManager.getTitleFilterKeyword(); const processedKeywordsSet = /* @__PURE__ */ new Set(); filterKeywordList.forEach(((item) => { /^[a-z]{2,}-/i.test(item) && processedKeywordsSet.add(item); })); if (0 === processedKeywordsSet.size) { show.info("没有需要清理的关键词数据"); return; } const carList = await storageManager.getCarList(), blacklistCarList = await storageManager.getBlacklistCarList(), carNumSet = new Set(carList.map(((car) => car.carNum))), keywords = Array.from(processedKeywordsSet), waitRemoveCarNumList = [], removableCarData = []; for (const car of blacklistCarList) { const carNum2 = car.carNum; let matched = false; for (const keyword of keywords) if (carNum2.startsWith(keyword)) { removableCarData.push({ carNum: carNum2, names: car.names, matchedKeyword: keyword }); waitRemoveCarNumList.push(carNum2); matched = true; break; } if (!matched && carNumSet.has(carNum2)) { removableCarData.push({ carNum: carNum2, names: car.names, matchedKeyword: "已在鉴定记录中" }); waitRemoveCarNumList.push(carNum2); } } const carNumCount = waitRemoveCarNumList.length; 0 !== carNumCount ? layer.open({ type: 1, title: `确认清理黑名单数据 (共 ${carNumCount} 个番号)`, area: ["80%", "70%"], anim: -1, content: ` <div style="height: 100%;overflow:hidden;"> <div style="padding: 10px; overflow-y: auto;"> <p>以下匹配到 <strong style="color: red;">${carNumCount}</strong> 个黑名单番号存在于屏蔽关键词中, 是否移除?</p> </div> <div id="wait-remove-table" style="height: calc(100% - 70px);"></div> </div> `, btn: ["确定清理", "取消"], success: function(layero, index) { new Tabulator("#wait-remove-table", { data: removableCarData, virtualDom: true, layout: "fitColumns", pagination: true, paginationMode: "local", paginationSize: 50, paginationSizeSelector: [50, 100, 1e3, 99999], columns: [{ title: "演员", field: "names" }, { title: "番号", field: "carNum" }, { title: "匹配关键词", field: "matchedKeyword" }], locale: "zh-cn", initialSort: [{ column: "names", dir: "asc" }], langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); }, yes: async (index) => { const removedCount = await storageManager.batchRemoveBlacklistCars(waitRemoveCarNumList); if (0 === removedCount) show.error("移除失败"); else { show.ok(`🎉 清理完成!已移除${removedCount}个相关黑名单番号数据`); await this.reloadTable(); layer.close(index); } }, btn2: function(index) { layer.close(index); } }) : show.info("没有需要清理的黑名单番号数据"); } } class ListPagePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "currentPageFilterCount", 0); __publicField2(this, "currentPageFavoriteCount", 0); __publicField2(this, "currentPageHasDownCount", 0); __publicField2(this, "currentPageHasWatchCount", 0); __publicField2(this, "currentPageKeywordFilterCount", 0); __publicField2(this, "currentPageActorFilterCount", 0); __publicField2(this, "currentPageWaitCheckCount", 0); __publicField2(this, "currentPageTotalCount", 0); __publicField2(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}); __publicField2(this, "writeQueue", Promise.resolve()); } getName() { return "ListPagePlugin"; } async handle() { this.cleanRepeatId(); this.replaceHdImg(); this.addJumpPageControl(); this.fixBusTitleBox(); await this.doFilter(); this.bindClick().then(); this.bindListPageHotKey().then(); this.rememberTagExpand(); $(this.getSelector().itemSelector + " a").attr("target", "_blank"); this.checkDom(); } rememberTagExpand() { if (!window.location.href.includes("actors")) return; const $expandButton = $(".tag-expand"); if (0 === $expandButton.length) return; const isExpanded = "true" === localStorage.getItem("jhs_tag_expand"), $actorTagsContent = $(".actor-tags .content"); isExpanded && $actorTagsContent.hasClass("collapse") && $expandButton[0].click(); $expandButton.on("click", (function() { const newState = !$actorTagsContent.hasClass("collapse"); localStorage.setItem("jhs_tag_expand", newState.toString()); })); } checkDom() { if (!window.isListPage) return; const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector); if (!targetNode) { console.error("没有找到容器节点!"); return; } const observer = new MutationObserver((async (mutations) => { observer.disconnect(); try { this.replaceHdImg(); this.addJumpPageControl(); this.fixBusTitleBox(); this.getBean("CoverButtonPlugin").addSvgBtn().then(); await this.doFilter(); await this.getBean("ListPageButtonPlugin").sortItems(); $(this.getSelector().itemSelector + " a").attr("target", "_blank"); this.getBean("AutoPagePlugin").checkLoad(); } finally { observer.observe(targetNode, config); } })), config = { childList: true, subtree: false }; observer.observe(targetNode, config); } fixBusTitleBox() { if (!isJavBus$1) return; $(this.getSelector().itemSelector).toArray().forEach(((ele) => { var _a2; let $box2 = $(ele); if ($box2.find(".avatar-box").length > 0) return; const title = (null == (_a2 = $box2.find("img").attr("title")) ? void 0 : _a2.trim()) || ""; $box2.find(".photo-info span:first").contents().first().wrap(`<span class="video-title" title="${title}">${title}</span>`); $box2.find("br").remove(); })); } cleanRepeatId() { if (!isJavBus$1) return; $("#waterfall_h").removeAttr("id").attr("id", "no-page"); const $waterfalls = $('[id="waterfall"]'); 0 !== $waterfalls.length && $waterfalls.each((function() { const $current = $(this); if (!$current.hasClass("masonry")) { $current.children().insertAfter($current); $current.remove(); } })); } async doFilter() { if (!window.isListPage) return; let movieList = $(this.getSelector().itemSelector).toArray(); if (movieList.length) { await this.filterMovieList(movieList); await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList); isJavBus$1 && await this.getBean("BusImgPlugin").logImageHeightsByRow(); } } async filterMovieList(movieList) { utils.time("累计耗费时间"); utils.time("读取数据耗时"); const [carList, filterKeywordList, blacklist, blacklistCarList, settingObj] = await Promise.all([storageManager.getCarList(), storageManager.getTitleFilterKeyword(), storageManager.getBlacklist(), storageManager.getBlacklistCarList(), storageManager.getSetting()]), t1 = utils.time("读取数据耗时"), carNumSets = carList.reduce(((dataMap, item) => { const statusKey = item.status; dataMap.hasOwnProperty(statusKey) && dataMap[statusKey].add(item.carNum); return dataMap; }), { [Status_FILTER$1]: /* @__PURE__ */ new Set(), [Status_FAVORITE$1]: /* @__PURE__ */ new Set(), [Status_HAS_DOWN$1]: /* @__PURE__ */ new Set(), [Status_HAS_WATCH$1]: /* @__PURE__ */ new Set() }); utils.time("组装数据耗时"); const starIdToRoleMap = new Map(blacklist.map(((item) => [item.starId, item.role]))), { actorCarNumToNameMap, actressCarNumToNameMap } = blacklistCarList.reduce(((dataMap, carItem) => { const role = starIdToRoleMap.get(carItem.starId); if (!role) { clog.error("黑名单数据源丢失演员信息", carItem); return dataMap; } const targetMap = "actor" === role ? dataMap.actorCarNumToNameMap : dataMap.actressCarNumToNameMap; targetMap.has(carItem.carNum) || targetMap.set(carItem.carNum, carItem.names); return dataMap; }), { actorCarNumToNameMap: /* @__PURE__ */ new Map(), actressCarNumToNameMap: /* @__PURE__ */ new Map() }), t2 = utils.time("组装数据耗时"), settings = { showFilterItem: (null == settingObj ? void 0 : settingObj.showFilterItem) ?? NO$1, showFilterActorItem: (null == settingObj ? void 0 : settingObj.showFilterActorItem) ?? NO$1, showFilterKeywordItem: (null == settingObj ? void 0 : settingObj.showFilterKeywordItem) ?? NO$1, showFavoriteItem: (null == settingObj ? void 0 : settingObj.showFavoriteItem) ?? YES$1, showHasDownItem: (null == settingObj ? void 0 : settingObj.showHasDownItem) ?? YES$1, showHasWatchItem: (null == settingObj ? void 0 : settingObj.showHasWatchItem) ?? YES$1, showAllItem: (null == settingObj ? void 0 : settingObj.showAllItem) ?? NO$1, tagPosition: (null == settingObj ? void 0 : settingObj.tagPosition) || "rightTop", movieShowType: (null == settingObj ? void 0 : settingObj.movieShowType) || "hide" }; this.currentPageFilterCount = 0; this.currentPageFavoriteCount = 0; this.currentPageHasDownCount = 0; this.currentPageHasWatchCount = 0; this.currentPageKeywordFilterCount = 0; this.currentPageActorFilterCount = 0; this.currentPageWaitCheckCount = 0; this.currentPageTotalCount = 0; utils.time("处理页面耗时"); await Promise.all(movieList.map((async (ele) => { let $box2 = $(ele); if (isJavBus$1 && $box2.find(".avatar-box").length > 0) return; const boxInfo = this.getBoxCarInfo($box2); if (!boxInfo) return; const { carNum: carNum2, title } = boxInfo, { filter, favorite, hasDown, hasWatch } = carNumSets, isFavorite = favorite.has(carNum2), isHasDown = hasDown.has(carNum2), isHasWatch = hasWatch.has(carNum2), isFiltered = filter.has(carNum2), isFilterActorMale = actorCarNumToNameMap.has(carNum2), isFilterActorFemale = actressCarNumToNameMap.has(carNum2), isFilterActor = isFilterActorMale || isFilterActorFemale, foundKeyword = filterKeywordList.find(((kw) => title.includes(kw) || carNum2.startsWith(kw))), isFilterKeyword = !!foundKeyword; if (!isSearchPage) { let shouldHide = settings.showFavoriteItem === NO$1 && isFavorite || settings.showHasDownItem === NO$1 && isHasDown || settings.showHasWatchItem === NO$1 && isHasWatch || settings.showFilterItem === NO$1 && isFiltered && !(isFavorite || isHasDown || isHasWatch) || settings.showFilterActorItem === NO$1 && isFilterActor || settings.showFilterKeywordItem === NO$1 && isFilterKeyword; if ($box2.attr("data-movieShowType") !== settings.movieShowType) { $box2.css("border", ""); $box2.children().css("visibility", ""); $box2.removeAttr("data-hide"); $box2.show(); } const isCurrentlyHidden = $box2.attr("data-hide") === YES$1; settings.showAllItem === YES$1 && (shouldHide = false); if (shouldHide !== isCurrentlyHidden) { shouldHide ? $box2.attr("data-hide", YES$1) : $box2.removeAttr("data-hide"); if ("hide" === settings.movieShowType) shouldHide ? $box2.hide() : $box2.show(); else { if ("visibility" !== settings.movieShowType) throw new Error("movieShowType值有误:" + settings.movieShowType); { const $content = $box2.children(), borderStyle = shouldHide ? "1px solid rgb(192 176 176)" : "none", visibilityValue = shouldHide ? "hidden" : "visible"; $box2.css("border", borderStyle); $content.css("visibility", visibilityValue); } } $box2.attr("data-movieShowType") !== settings.movieShowType && $box2.attr("data-movieShowType", settings.movieShowType); } } let tag = TAG_STYLES.IS_WAIT_CHECK, filterReason = null; if (isFiltered) tag = TAG_STYLES.IS_FILTERED; else if (isFavorite) tag = TAG_STYLES.IS_FAVORITE; else if (isHasDown) tag = TAG_STYLES.IS_HAS_DOWN; else if (isHasWatch) tag = TAG_STYLES.IS_HAS_WATCH; else if (isFilterKeyword) { tag = TAG_STYLES.IS_KEYWORD_FILTER; filterReason = foundKeyword || "未知"; } else if (isFilterActorMale) { tag = TAG_STYLES.IS_ACTOR_FILTER; filterReason = actorCarNumToNameMap.get(carNum2) || ""; } else if (isFilterActorFemale) { tag = TAG_STYLES.IS_ACTRESS_FILTER; filterReason = actressCarNumToNameMap.get(carNum2) || ""; } filterReason || (filterReason = tag.reasonType); tag.isCounted && this[tag.countKey]++; this.currentPageTotalCount++; $box2.find(".status-tag").remove(); const tagPositionCss = "rightTop" === settings.tagPosition ? "right: 0; top:5px;" : "left: 0; top:5px;"; if (tag.text) { const tagHtml = isJavDb$1 ? `<span class="tag is-success status-tag" data-tip="${filterReason}" title="" style="margin-right: 5px; border-radius:10px; position:absolute; z-index:10; background-color: ${tag.color} !important; ${tagPositionCss}"> ${tag.text} </span>` : `<a class="a-primary status-tag" data-tip="${filterReason}" title="" style="margin-right: 5px; padding: 0 5px; color: #fff !important; border-radius:10px; position:absolute; z-index:10; background-color: ${tag.color} !important; ${tagPositionCss}"> <span class="tag" style="color:#fff !important;">${tag.text}</span> </a>`; isJavDb$1 && $box2.find(".tags").append(tagHtml); if (isJavBus$1) { const $itemTag = $box2.find(".item-tag"); $itemTag.length ? $itemTag.append(tagHtml) : $box2.find(".photo-info > span > div").append(tagHtml); } } await this.translate($box2); }))); const t3 = utils.time("处理页面耗时"), t4 = utils.time("累计耗费时间"); $("#waitDownBtn span").text(`打开已收藏 (${carNumSets.favorite.size})`); clog.log(` <table class="countTable" style='border-collapse: collapse; width: 100%'> <tr> <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${t1}</td> <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${t2}</td> </tr> <tr> <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${t3}</td> <td colspan="2" style='padding: 3px; border: 1px solid #ccc;'>${t4}</td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>项目</td> <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>数量</td> <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>项目</td> <td style='padding: 3px; border: 1px solid #ccc; font-weight: bold;'>数量</td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽单番号</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageFilterCount}</strong></td> <td style='padding: 3px; border: 1px solid #ccc;'>收藏</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageFavoriteCount}</strong></td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽演员</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageActorFilterCount}</strong></td> <td style='padding: 3px; border: 1px solid #ccc;'>已下载</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageHasDownCount}</strong></td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc;'>屏蔽关键词</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageKeywordFilterCount}</strong></td> <td style='padding: 3px; border: 1px solid #ccc;'>已观看</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageHasWatchCount}</strong></td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc;'>待鉴定</td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageWaitCheckCount}</strong></td> <td style='padding: 3px; border: 1px solid #ccc;'></td> <td style='padding: 3px; border: 1px solid #ccc;'></td> </tr> <tr> <td style='padding: 3px; border: 1px solid #ccc;'><strong>总数</strong></td> <td style='padding: 3px; border: 1px solid #ccc;'><strong>${this.currentPageTotalCount}</strong></td> </tr> </table> `); } async bindClick() { let selector = this.getSelector(); $(selector.boxSelector).on("click", ".item img", (async (event) => { event.preventDefault(); event.stopPropagation(); if ($(event.target).closest("div.meta-buttons").length) return; const $box2 = $(event.target).closest(".item"), { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", YES$1); if (carNum2.includes("FC2-")) { let movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); } else if (dialogOpenDetail === YES$1) { utils.openPage(aHref, carNum2, true, event); this.$currentImage = null; } else window.open(aHref); })); $(selector.boxSelector).on("click", ".item video", (async (event) => { const video = event.currentTarget; video.paused ? video.play().catch(((e) => console.error("播放失败:", e))) : video.pause(); event.preventDefault(); event.stopPropagation(); })); $(selector.boxSelector).on("click", ".item .video-title", (async (event) => { if ($(event.target).closest('[class^="jhs-match-"]').length) return; const $box2 = $(event.currentTarget).closest(".item"), { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); if (carNum2.includes("FC2-")) { event.preventDefault(); let movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); } })); $(selector.boxSelector).on("contextmenu", ".item img, .item video", (async (event) => { event.preventDefault(); const $box2 = $(event.target).closest(".item"), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($box2); let $actor = isJavDb$1 ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"), actorName = ""; $actor.length && (actorName = $actor.text().trim().split(",")[0].replace("(無碼)", "")); utils.q(event, `是否屏蔽番号 ${carNum2}?`, (async () => { setTimeout((async () => { actorName || (actorName = await this.parseActressName(url)); await storageManager.saveCar({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, publishTime }); window.refresh(); show.ok("操作成功"); })); })); })); } async parseActressName(url) { let actorName = null; if (await storageManager.getSetting("enableSaveActressCarInfo", NO$1) === YES$1) { clog.debug("鉴定补录演员信息-已启用, 开始解析详情页"); clog.debug("开始解析演员详情页", url); const html2 = await gmHttp.get(url), $dom = utils.htmlTo$dom(html2); isJavDb$1 ? actorName = $dom.find(".female").prev().map(((i, el) => $(el).text())).get().join(" ") : isJavBus$1 && (actorName = $dom.find('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ")); clog.debug("解析到名称:", actorName); } return actorName; } async bindListPageHotKey() { this.$currentImage = null; $(document).on("mouseenter", this.getSelector().coverImgSelector, ((e) => { this.$currentImage = $(e.currentTarget); })).on("mouseleave", this.getSelector().coverImgSelector, (() => { this.$currentImage = null; })); let settingObj = await storageManager.getSetting(); this.filterHotKey = settingObj.filterHotKey; this.favoriteHotKey = settingObj.favoriteHotKey; this.hasDownHotKey = settingObj.hasDownHotKey; this.hasWatchHotKey = settingObj.hasWatchHotKey; this.enableImageHotKey = settingObj.enableImageHotKey || NO$1; this.clogHotKey = settingObj.clogHotKey; this.foldCategoryHotKey = settingObj.foldCategoryHotKey; this.showFilterItemHotKey = settingObj.showFilterItemHotKey; this.showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey; this.showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey; this.showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey; this.showHasDownItemHotKey = settingObj.showHasDownItemHotKey; this.showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey; this.showAllItemHotKey = settingObj.showAllItemHotKey; const HOTKEY_REGISTRY = { showFilterItemHotKey: "showFilterItem", showFilterActorItemHotKey: "showFilterActorItem", showFilterKeywordItemHotKey: "showFilterKeywordItem", showFavoriteItemHotKey: "showFavoriteItem", showHasDownItemHotKey: "showHasDownItem", showHasWatchItemHotKey: "showHasWatchItem", showAllItemHotKey: "showAllItem" }, registerToggleHotkey = (hotkeyName, settingKey) => { const hotkeyString = this[hotkeyName]; hotkeyString && HotkeyManager.registerHotkey(hotkeyString, (async (event) => { const newVal = await storageManager.getSetting(settingKey) === YES$1 ? NO$1 : YES$1; await storageManager.saveSettingItem(settingKey, newVal); window.refresh(); })); }; for (const hotkeyName in HOTKEY_REGISTRY) if (HOTKEY_REGISTRY.hasOwnProperty(hotkeyName)) { registerToggleHotkey(hotkeyName, HOTKEY_REGISTRY[hotkeyName]); } if (this.enableImageHotKey === NO$1) return; const handleAction = async (boxInfo, status) => { setTimeout((async () => { let actorName = await this.parseActressName(boxInfo.url); await storageManager.saveCar({ carNum: boxInfo.carNum, url: boxInfo.url, names: actorName, actionType: status, publishTime: boxInfo.publishTime }); window.refresh(); show.ok("操作成功"); })); }, handlers = {}; this.filterHotKey && (handlers[this.filterHotKey] = (boxInfo) => { handleAction(boxInfo, Status_FILTER$1); }); this.favoriteHotKey && (handlers[this.favoriteHotKey] = (boxInfo) => { handleAction(boxInfo, Status_FAVORITE$1); }); this.hasDownHotKey && (handlers[this.hasDownHotKey] = (boxInfo) => { handleAction(boxInfo, Status_HAS_DOWN$1); }); this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = (boxInfo) => { handleAction(boxInfo, Status_HAS_WATCH$1); }); this.clogHotKey && HotkeyManager.registerHotkey(this.clogHotKey, ((event) => { clog.toggleExpandCollapsed(); })); this.foldCategoryHotKey && HotkeyManager.registerHotkey(this.foldCategoryHotKey, ((event) => { const $btn = $("#foldCategoryBtn"); $btn.length && $btn[0].click(); })); const registerImageHotkey = (key, handler) => { HotkeyManager.registerHotkey(key, ((event) => { const activeElement = document.activeElement; if (!("INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable) && this.$currentImage) { const $box2 = this.$currentImage.closest(".item"), boxInfo = this.getBoxCarInfo($box2); handler(boxInfo); } })); }; Object.entries(handlers).forEach((([key, handler]) => { registerImageHotkey(key, handler); })); } replaceHdImg(coverImgNodeList) { coverImgNodeList && "string" == typeof coverImgNodeList.jquery && (coverImgNodeList = coverImgNodeList.toArray()); coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector)); isJavDb$1 && coverImgNodeList.forEach(((img) => { img.src = img.src.replace("thumbs", "covers"); img.title = ""; })); if (isJavBus$1) { const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = (img) => { if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1"); img.dataset.hdReplaced = "true"; img.dataset.title = img.title; img.title = ""; } }, DMM_THUMB_REGEX = /ps(\.jpg|\.jpeg|\.png)$/i, replaceWithHdForDMM = (img) => { if (img.src && DMM_THUMB_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { img.src = img.src.replace(DMM_THUMB_REGEX, "pl$1"); img.dataset.hdReplaced = "true"; img.dataset.title = img.title; img.title = ""; } }; coverImgNodeList.forEach(((img) => { replaceWithHd(img); replaceWithHdForDMM(img); })); } storageManager.getSetting("hoverBigImg", NO$1).then(((hoverBigImg) => { hoverBigImg === YES$1 && (window.imageHoverPreviewObj ? window.imageHoverPreviewObj.bindEvents() : window.imageHoverPreviewObj = new ImageHoverPreview({ selector: this.getSelector().coverImgSelector })); })); } async translate($box2) { if (await storageManager.getSetting("translateTitle", YES$1) !== YES$1) return; let content, carNum2, $title = $box2.find(".video-title"); if (isJavDb$1) { content = $title.contents().filter(((i, node) => 3 === node.nodeType && "" !== node.textContent.trim())).text().trim(); carNum2 = $box2.find(".video-title strong").text().trim(); } else { content = $box2.find("img").attr("data-title").trim(); carNum2 = $box2.find("a").attr("href").split("/").filter(Boolean).pop().trim(); } if (this.cache[carNum2]) { let _this = this; $title.attr("data-original-title", content); $title.contents().each((function() { 3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " "); })); $title.attr("title", _this.cache[carNum2]); } else translateText(content).then(((result) => { $title.attr("data-original-title", content); if (isJavDb$1) { $title.contents().each((function() { 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " "); })); $title.attr("title", result); } else $title.text(result); this.writeQueue = this.writeQueue.then((() => { this.cache[carNum2] = result; localStorage.setItem("jhs_translate", JSON.stringify(this.cache)); })); })).catch(((error) => { console.error("翻译失败:", error); })); } async revertTranslation() { $(this.getSelector().itemSelector).toArray().forEach(((ele) => { let $box2 = $(ele); const originalContent = $box2.find(".video-title").attr("data-original-title") || $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title"); let carNum2; isJavDb$1 && (carNum2 = $box2.find(".video-title strong").text().trim()); const $title = $box2.find(".video-title"); $title.contents().each((function() { 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + originalContent + " "); })); $title.removeAttr("title"); })); } addJumpPageControl() { if ($("#gemini-jump-page-control").length > 0) return; if (0 === $(".pagination-link.is-current").length) return; const currentPageNum = utils.getUrlParam(currentHref$1, "page") || 1, $input = $("<input>", { type: "number", id: "jumpPageInput", placeholder: "页码", min: "1", style: "width: 60px; margin-left: 10px; padding: 10px; border: 1px solid #ccc; font-size: 14px;", value: currentPageNum + 1 }), $button = $("<button>", { text: "跳转", style: "margin-left: 5px; padding: 9px 8px; cursor: pointer; border: 1px solid #ccc; background-color: #f0f0f0; font-size: 14px;" }), $jumpLi = $("<li>", { id: "gemini-jump-page-control" }).append($input).append($button); $(".pagination-list").append($jumpLi); const jumpToPage = () => { const pageNumber = parseInt($input.val(), 10); if (isNaN(pageNumber) || pageNumber < 1) { $input.focus(); return; } const newUrl = new URL(window.location.href); newUrl.searchParams.set("page", pageNumber.toString()); window.location.href = newUrl.toString(); }; $button.on("click", jumpToPage); $input.on("keypress", (function(e) { if (13 === e.which) { jumpToPage(); e.preventDefault(); } })); } } window.Tabulator = TabulatorFull; window.javDbApi = javDbApi; const _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow !== "undefined" ? unsafeWindow : window)(); _unsafeWindow.utils = window.utils = new Utils(); _unsafeWindow.gmHttp = window.gmHttp = new GmHttp(); _unsafeWindow.storageManager = window.storageManager = new StorageManager(); const senderChannel = new BroadcastChannel("jhs-channel"); window.refresh = function() { senderChannel.postMessage({ type: "refresh" }); }; window.clean_cacheBlacklistCarList = function() { storageManager.cacheBlacklistCarList && (storageManager.cacheBlacklistCarList = null); senderChannel.postMessage({ type: "clean_cacheBlacklistCarList" }); }; window.clean_cacheSettingObj = function() { storageManager.cacheSettingObj && (storageManager.cacheSettingObj = null); senderChannel.postMessage({ type: "clean_cacheSettingObj" }); }; window.clean_cacheFavoriteActressList = function() { storageManager.cacheFavoriteActressList && (storageManager.cacheFavoriteActressList = null); senderChannel.postMessage({ type: "clean_cacheFavoriteActressList" }); }; window.clean_cacheCarList = function() { storageManager.cacheCarList && (storageManager.cacheCarList = null); senderChannel.postMessage({ type: "clean_cacheCarList" }); }; new BroadcastChannel("jhs-channel").addEventListener("message", (async (event) => { let dataType = event.data.type; if ("refresh" === dataType) { const listPagePlugin = window.pluginManager.getBean("ListPagePlugin"); await listPagePlugin.doFilter(); const historyPlugin = window.pluginManager.getBean("HistoryPlugin"); historyPlugin.tableObj && historyPlugin.tableObj.setData(); const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin"); if (newVideoPlugin) { newVideoPlugin.showNewVideoCount().then(); newVideoPlugin.loadData(); } } else "clean_cacheBlacklistCarList" === dataType ? storageManager.cacheBlacklistCarList && (storageManager.cacheBlacklistCarList = null) : "clean_cacheSettingObj" === dataType ? storageManager.cacheSettingObj && (storageManager.cacheSettingObj = null) : "clean_cacheFavoriteActressList" === dataType ? storageManager.cacheFavoriteActressList && (storageManager.cacheFavoriteActressList = null) : "clean_cacheCarList" === dataType && storageManager.cacheCarList && (storageManager.cacheCarList = null); })); !(function() { document.head.insertAdjacentHTML("beforeend", '\n <style>\n .loading-container {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column; /* 垂直排列 */\n justify-content: center;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.1);\n z-index: 99999999;\n }\n \n .loading-animation {\n position: relative;\n width: 60px;\n height: 12px;\n background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);\n border-radius: 6px;\n animation: loading-animate 1.8s ease-in-out infinite;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n margin-bottom: 30px; /* 和按钮之间留出间距 */\n }\n \n .loading-animation:before,\n .loading-animation:after {\n position: absolute;\n display: block;\n content: "";\n animation: loading-animate 1.8s ease-in-out infinite;\n height: 12px;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n \n .loading-animation:before {\n top: -20px;\n left: 10px;\n width: 40px;\n background: linear-gradient(90deg, #ff758c 0%, #ff7eb3 100%);\n }\n \n .loading-animation:after {\n bottom: -20px;\n width: 35px;\n background: linear-gradient(90deg, #ff9a9e 0%, #fad0c4 100%);\n }\n \n /* 新增按钮样式 */\n .loading-close-btn {\n padding: 8px 15px;\n border: none;\n border-radius: 4px;\n background-color: #f44336; /* 红色背景 */\n color: white;\n cursor: pointer;\n font-size: 14px;\n opacity: 0; /* 默认隐藏 */\n transition: opacity 0.3s ease; /* 渐变效果 */\n }\n\n .loading-close-btn.visible {\n opacity: 1; /* 3秒后显示 */\n }\n \n @keyframes loading-animate {\n 0% {\n transform: translateX(40px);\n }\n 50% {\n transform: translateX(-30px);\n }\n 100% {\n transform: translateX(40px);\n }\n }\n </style>\n '); _unsafeWindow.loading = window.loading = function() { const container = document.createElement("div"); container.className = "loading-container"; const animation = document.createElement("div"); animation.className = "loading-animation"; const closeBtn = document.createElement("button"); closeBtn.className = "loading-close-btn"; closeBtn.textContent = "关闭"; closeBtn.addEventListener("click", (() => { close(); })); setTimeout((() => { closeBtn.classList.add("visible"); }), 3e3); container.appendChild(animation); container.appendChild(closeBtn); document.body.appendChild(container); const close = () => { container && container.parentNode && container.parentNode.removeChild(container); }; return { close }; }; })(); !(function() { const showMessage = (msg, type, gravityOrOptions, positionOrOptions, options) => { let finalOptions; if ("object" == typeof gravityOrOptions) finalOptions = gravityOrOptions; else { finalOptions = "object" == typeof positionOrOptions ? positionOrOptions : options || {}; finalOptions.gravity = gravityOrOptions || "top"; finalOptions.position = "string" == typeof positionOrOptions ? positionOrOptions : "center"; } finalOptions.gravity && "center" !== finalOptions.gravity || (finalOptions.offset = { y: "calc(50vh - 150px)" }); const colors_infoStart = "#60A5FA", colors_infoEnd = "#93C5FD", colors_successStart = "#10B981", colors_successEnd = "#6EE7B7", colors_errorStart = "#EF4444", colors_errorEnd = "#FCA5A5", commonStyles = { borderRadius: "12px", color: "white", padding: "12px 16px", boxShadow: "0 4px 6px rgba(0,0,0,0.1)", minWidth: "150px", textAlign: "center", zIndex: 999999999 }, defaultConfig2 = { text: msg, duration: 1e3, close: false, gravity: "top", position: "center", style: { info: { ...commonStyles, background: `linear-gradient(to right, ${colors_infoStart}, ${colors_infoEnd})` }, success: { ...commonStyles, background: `linear-gradient(to right, ${colors_successStart}, ${colors_successEnd})` }, error: { ...commonStyles, background: `linear-gradient(to right, ${colors_errorStart}, ${colors_errorEnd})` } }[type], stopOnFocus: true, oldestFirst: false, ...finalOptions }; -1 === defaultConfig2.duration && (defaultConfig2.close = true); const toast = Toastify(defaultConfig2); toast.showToast(); toast.closeShow = () => { toast.toastElement.remove(); }; return toast; }; _unsafeWindow.show = window.show = { ok: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "success", gravityOrOptions, positionOrOptions, options), error: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "error", gravityOrOptions, positionOrOptions, options), info: (msg, gravityOrOptions = "center", positionOrOptions, options) => showMessage(msg, "info", gravityOrOptions, positionOrOptions, options) }; })(); !(function() { document.head.insertAdjacentHTML("beforeend", "\n <style>\n .viewer-canvas {\n overflow: auto !important;\n }\n \n .viewer-close {\n background: rgba(255,0,0,0.6) !important;\n }\n .viewer-close:hover {\n background: rgba(255,0,0,0.8) !important;\n }\n </style>\n "); function smartToggleBodyScroll2(waitTime = 10) { setTimeout((() => { const openLayerCount = document.querySelectorAll(".layui-layer-shade").length; document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : ""; }), waitTime); } window.showImageViewer = function(imgUrlOrContainer, altText = "") { let $container = null, isTempContainer = false; if ("string" == typeof imgUrlOrContainer || imgUrlOrContainer instanceof String) { $container = $('<div class="temporary-container" style="display:none;">').append(`<img src="${imgUrlOrContainer}" alt="${altText}">`).appendTo("body"); isTempContainer = true; } else $container = $(imgUrlOrContainer); const options = { zIndex: 999999990, navbar: false, zoomOnWheel: false, zoomRatio: 0.1, toggleOnDblclick: false, toolbar: { zoomIn: 1, zoomOut: 1, reset: 1, rotateLeft: 0, rotateRight: 0, flipHorizontal: 0, flipVertical: 0 }, title: false, keyboard: false, viewed() { viewerInstance.zoomTo(1.4); let left = (viewerInstance.viewerData.width - viewerInstance.imageData.width) / 2; viewerInstance.moveTo(left, 0); }, shown() { isTempContainer && $container.remove(); document.documentElement.style.overflow = "hidden"; document.body.style.overflow = "hidden"; viewerInstance.handleKeydown = function(e) { if ("Escape" === e.key || " " === e.key) { e.preventDefault(); e.stopPropagation(); viewerInstance.destroy(); document.removeEventListener("keydown", viewerInstance.handleKeydown); document.documentElement.style.overflow = ""; document.body.style.overflow = ""; smartToggleBodyScroll2(); } }; document.addEventListener("keydown", viewerInstance.handleKeydown); }, hidden() { viewerInstance && viewerInstance.handleKeydown && document.removeEventListener("keydown", viewerInstance.handleKeydown); viewerInstance.destroy(); document.documentElement.style.overflow = ""; document.body.style.overflow = ""; smartToggleBodyScroll2(); } }, viewerInstance = new Viewer($container[0], options); viewerInstance.show(); }; })(); window.ImageHoverPreview = class { constructor(options = {}) { this.config = { selector: ".hover-preview", dataAttribute: "data-full", maxWidth: 1e3, maxHeight: 1e3, offsetX: 20, offsetY: 20, zIndex: 9999999999, transition: 0.2, autoAdjustPosition: true, ...options }; this.preview = null; this.currentTarget = null; this.timer = null; this.imgElement = null; this.boundElements = /* @__PURE__ */ new WeakSet(); this.init(); } init() { this.injectStyles(); this.createPreviewElement(); this.bindEvents(); } injectStyles() { const css = ` <style> .image-hover-preview { position: fixed; display: none; z-index: ${this.config.zIndex}; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); overflow: hidden; pointer-events: none; opacity: 0; transition: opacity ${this.config.transition}s ease; background-color: #fff; } .image-hover-preview.active { opacity: 1; } .image-hover-preview img { max-width: ${this.config.maxWidth + "px"}; max-height: ${this.config.maxHeight + "px"}; display: block; object-fit: contain; } .image-hover-preview::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.03); pointer-events: none; } .image-hover-preview.loading::before { content: '加载中...'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666; font-size: 14px; } </style> `; document.head.insertAdjacentHTML("beforeend", css); } createPreviewElement() { this.preview = document.createElement("div"); this.preview.className = "image-hover-preview"; document.body.appendChild(this.preview); } bindEvents() { document.querySelectorAll(this.config.selector).forEach(((el) => { if (!this.boundElements.has(el)) { el.addEventListener("mouseenter", ((e) => this.handleMouseEnter(e))); el.addEventListener("mouseleave", ((e) => this.handleMouseLeave(e))); el.addEventListener("mousemove", ((e) => this.handleMouseMove(e))); this.boundElements.add(el); } })); } handleMouseEnter(e) { clearTimeout(this.timer); this.currentTarget = e.currentTarget; const imgUrl = this.currentTarget.getAttribute(this.config.dataAttribute) || this.currentTarget.src; if (!imgUrl) return; this.preview.innerHTML = ""; this.preview.classList.add("loading"); this.preview.style.display = "block"; this.preview.classList.remove("active"); const img = new Image(); img.onload = () => { this.preview.classList.remove("loading"); this.preview.innerHTML = `<img src="${imgUrl}" alt="预览图">`; this.imgElement = this.preview.querySelector("img"); const { width, height } = this.calculateImageSize(img); this.preview.style.width = `${width}px`; this.preview.style.height = `${height}px`; this.preview.offsetHeight; this.preview.classList.add("active"); this.handleMouseMove(e); }; img.onerror = () => { this.preview.classList.remove("loading"); this.preview.innerHTML = '<div style="padding:10px;color:#f00;">图片加载失败</div>'; }; img.src = imgUrl; } calculateImageSize(img) { let width = img.naturalWidth, height = img.naturalHeight; if (width > this.config.maxWidth || height > this.config.maxHeight) { const ratio = Math.min(this.config.maxWidth / width, this.config.maxHeight / height); width *= ratio; height *= ratio; } return { width, height }; } handleMouseMove(e) { if (!this.currentTarget || !this.preview.classList.contains("active")) return; let { offsetX, offsetY } = this.config, left = e.clientX + offsetX, top = e.clientY + offsetY; if (this.config.autoAdjustPosition) { const previewWidth = this.preview.offsetWidth, previewHeight = this.preview.offsetHeight; left + previewWidth > window.innerWidth && (left = e.clientX - previewWidth - offsetX); top + previewHeight > window.innerHeight && (top = e.clientY - previewHeight - offsetY); left = Math.max(0, left); top = Math.max(0, top); } this.preview.style.left = `${left}px`; this.preview.style.top = `${top}px`; } handleMouseLeave() { this.preview.classList.remove("active"); this.preview.style.display = "none"; this.currentTarget = null; this.imgElement = null; } destroy() { document.querySelectorAll(this.config.selector).forEach(((el) => { if (this.boundElements.has(el)) { el.removeEventListener("mouseenter", this.handleMouseEnter); el.removeEventListener("mouseleave", this.handleMouseLeave); el.removeEventListener("mousemove", this.handleMouseMove); this.boundElements.delete(el); } })); this.preview && this.preview.parentNode && this.preview.parentNode.removeChild(this.preview); } }; !(async function() { document.head.insertAdjacentHTML("beforeend", ` <style> .console-logger-container { position: fixed; bottom: 0; right: 0; z-index: 99999999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; align-items: flex-end; width: fit-content; } .console-logger-toggle { width: 40px; height: 30px; background: #2c3e50; border-radius: 120px 10px 0 0; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; color: white; font-size: 16px; } .console-logger-toggle:hover { background: #34495e; } .console-logger-toggle::after { content: '▼'; transition: transform 0.3s ease; } .console-logger-toggle.collapsed::after { content: '▲'; } .console-logger-window { width: 400px; height: 400px; background: white; border-radius: 10px 0 10px 10px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; overflow: hidden; transform: translateY(0); opacity: 1; /* 简化过渡属性 */ transition: width 0.3s ease, height 0.3s ease, opacity 0.3s ease, transform 0.3s ease; } .console-logger-window.maximized { width: 600px !important; height: 85vh !important; border-radius: 10px 0 0 10px; /* 调整圆角以匹配右下角 */ } .console-logger-window.collapsed { height: 0 !important; min-height: 0 !important; opacity: 0; } .console-logger-header { background: #2c3e50; color: white; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .console-logger-title { font-weight: 600; font-size: 16px; } .console-logger-controls { display: flex; gap: 10px; } .console-logger-controls button { background: transparent; border: 1px solid rgba(255, 255, 255, 0.3); padding: 5px 10px; font-size: 12px; color: white; border-radius: 4px; cursor: pointer; transition: background 0.3s; } .console-logger-controls button:hover { background: rgba(255, 255, 255, 0.1); } /* 新增的按钮样式 */ .console-logger-maximize-toggle { line-height: 1; font-size: 14px !important; /* 使箭头看起来更大 */ padding: 5px 8px !important; } .console-logger-maximize-toggle::before { content: '⇱'; /* Unicode symbol for maximized */ } .console-logger-maximize-toggle.active::before { content: '⇲'; /* Unicode symbol for minimized */ } .console-logger-filters { display: flex; align-items: center; gap: 5px; padding: 10px; background: #f8f9fa; border-bottom: 1px solid #e9ecef; flex-shrink: 0; overflow-x: hidden; } /* 新增: 过滤器按钮组的容器,负责滚动 */ .console-logger-filter-group { display: flex; gap: 5px; overflow-x: auto; /* 允许过滤器按钮滚动 */ flex-grow: 1; /* 占据剩余空间 */ padding-right: 10px; /* 避免滚动条影响按钮 */ } .console-logger-filter { padding: 5px 10px; font-size: 12px; border-radius: 15px; background: #ecf0f1; color: #7f8c8d; border: 1px solid #ddd; cursor: pointer; transition: all 0.3s; white-space: nowrap; flex-shrink: 0; /* 确保不被压缩 */ } .console-logger-filter.active { background: #3498db; color: white; border-color: #3498db; } /* 新增: 滚动到底部按钮的样式 (位于 filtersContainer 内部右侧) */ .console-logger-scroll-to-bottom { background: #3498db; border: none; padding: 5px 10px; font-size: 12px; color: white; border-radius: 4px; cursor: pointer; transition: background 0.3s; line-height: 1; height: fit-content; white-space: nowrap; margin-left: auto; /* 将按钮推到最右侧 */ flex-shrink: 0; /* 确保不被压缩 */ } .console-logger-scroll-to-bottom:hover { background: #2980b9; } .console-logger-content { flex: 1; overflow-y: auto; padding: 10px; background: #ffffff; word-wrap: break-word; text-align: left; } .console-logger-entry { padding: 8px 10px; margin-bottom: 3px; border-radius: 4px; font-size: 12px; line-height: 1.4; /*animation: consoleFadeIn 0.3s ease;*/ border-left: 3px solid transparent; } @keyframes consoleFadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } .console-logger-timestamp { color: #7f8c8d; font-size: 11px; margin-right: 2px; } @media (max-width: 768px) { .console-logger-container { right: 10px; bottom: 10px; } .console-logger-window { width: calc(100vw - 20px); height: 300px; } } .console-logger-message[data-type="json"] { white-space: pre-wrap; } </style> `); const groupTypes = { base: { label: "信息", background: "#e8f4fd", borderLeftColor: "#3498db" }, warn: { label: "警告", background: "#fef9e7", borderLeftColor: "#f39c12" }, error: { label: "错误", background: "#fdedec", borderLeftColor: "#e74c3c" }, debug: { label: "调试", background: "#f4f6f6", borderLeftColor: "#95a5a6" } }, filterMap = { base: ["base", "warn", "error"], warn: ["warn"], error: ["error"], debug: ["base", "warn", "error", "debug"] }, maxEntries = await storageManager.getSetting("clogMsgCount", 2e3); class CLog { constructor() { const storedFilter = localStorage.getItem("jhs_clog_filter"); this.currentFilter = storedFilter && groupTypes[storedFilter] ? storedFilter : "base"; this.logs = []; this.isInitialized = false; this.userScrolledUp = false; } tryInitialize() { if ("loading" === document.readyState) return false; if (this.isInitialized) return true; this.init(); this.isInitialized = true; return true; } init() { this.createContainer(); this.bindEvents(); this.checkInitialMaximizeState(); this.checkInitialCollapseState(); } createContainer() { this.container = document.createElement("div"); this.container.className = "console-logger-container"; this.container.style.display = "none"; this.toggleBtn = document.createElement("div"); this.toggleBtn.className = "console-logger-toggle collapsed"; this.container.appendChild(this.toggleBtn); this.window = document.createElement("div"); this.window.className = "console-logger-window collapsed"; const header = document.createElement("div"); header.className = "console-logger-header"; const title = document.createElement("div"); title.className = "console-logger-title"; title.textContent = "JHS V3.3.6"; const controls = document.createElement("div"); controls.className = "console-logger-controls"; this.maximizeBtn = document.createElement("button"); this.maximizeBtn.textContent = ""; this.maximizeBtn.classList.add("console-logger-maximize-toggle"); controls.appendChild(this.maximizeBtn); const clearBtn = document.createElement("button"); clearBtn.textContent = "清空"; clearBtn.addEventListener("click", (() => this.clear())); controls.appendChild(clearBtn); header.appendChild(title); header.appendChild(controls); this.filtersContainer = document.createElement("div"); this.filtersContainer.className = "console-logger-filters"; this.filterButtonGroup = document.createElement("div"); this.filterButtonGroup.className = "console-logger-filter-group"; this.filtersContainer.appendChild(this.filterButtonGroup); this.scrollToBottomBtn = document.createElement("button"); this.scrollToBottomBtn.className = "console-logger-scroll-to-bottom"; this.scrollToBottomBtn.textContent = "到底部"; this.filtersContainer.appendChild(this.scrollToBottomBtn); this.content = document.createElement("div"); this.content.className = "console-logger-content jhs-scrollbar"; this.window.appendChild(header); this.window.appendChild(this.filtersContainer); this.window.appendChild(this.content); this.container.appendChild(this.window); document.body.appendChild(this.container); Object.keys(groupTypes).forEach(((type) => { const filterBtn = document.createElement("div"); filterBtn.className = "console-logger-filter"; type === this.currentFilter && filterBtn.classList.add("active"); filterBtn.textContent = groupTypes[type].label; filterBtn.dataset.type = type; filterBtn.addEventListener("click", (() => this.setFilter(type))); this.filterButtonGroup.appendChild(filterBtn); })); } bindEvents() { this.toggleBtn.addEventListener("click", (() => { this.toggleExpandCollapsed(); })); this.maximizeBtn.addEventListener("click", (() => this.toggleMaximize())); this.scrollToBottomBtn.addEventListener("click", (() => { this.content.scrollTop = this.content.scrollHeight; this.userScrolledUp = false; })); this.content.addEventListener("scroll", (() => { const isAtBottom = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 5; this.userScrolledUp = !isAtBottom; })); this.content.addEventListener("wheel", ((e) => { const isAtTop = 0 === this.content.scrollTop, isAtBottom = this.content.scrollHeight - this.content.clientHeight <= this.content.scrollTop + 1; if (isAtTop && e.deltaY < 0 || isAtBottom && e.deltaY > 0) { e.preventDefault(); e.stopPropagation(); } }), { passive: false }); } toggleExpandCollapsed() { const isCollapsed = this.window.classList.toggle("collapsed"); this.toggleBtn.classList.toggle("collapsed"); if (isCollapsed) localStorage.setItem("jhs_clog_expand", "no"); else { localStorage.setItem("jhs_clog_expand", "yes"); this.reRenderAllLogs(); } } checkInitialCollapseState() { const expandStatus = localStorage.getItem("jhs_clog_expand"); if (expandStatus && "no" !== expandStatus) { this.window.classList.toggle("collapsed"); this.toggleBtn.classList.toggle("collapsed"); setTimeout((() => { this.content.scrollTop = this.content.scrollHeight; }), 0); } else { this.window.classList.add("collapsed"); this.toggleBtn.classList.add("collapsed"); } } checkInitialMaximizeState() { if ("maximized" === localStorage.getItem("jhs_clog_maximize")) { this.window.classList.add("maximized"); this.maximizeBtn.classList.add("active"); } } toggleMaximize() { const isMaximized = this.window.classList.toggle("maximized"); this.maximizeBtn.classList.toggle("active", isMaximized); isMaximized ? localStorage.setItem("jhs_clog_maximize", "maximized") : localStorage.setItem("jhs_clog_maximize", "minimized"); this.window.classList.contains("collapsed") || (this.content.scrollTop = this.content.scrollHeight); } addLog(primaryContent, type = "base", ...optionalParams) { const initialized = this.tryInitialize(); let logType, extraParams = []; if (groupTypes[type]) { logType = type; extraParams = optionalParams; } else { logType = "base"; extraParams = [type, ...optionalParams]; } logType = groupTypes[logType] ? logType : "base"; const allContents = [primaryContent, ...extraParams]; let messageType = "msg"; const processedParts = []; allContents.forEach(((content) => { if ("[object Error]" === Object.prototype.toString.call(content)) processedParts.push(String(content)); else if ("object" == typeof content && null !== content) try { processedParts.push("<br/>" + JSON.stringify(content, null, 2)); messageType = "json"; } catch (e) { processedParts.push(String(content)); messageType = "msg"; } else processedParts.push(String(content)); })); let formattedMessage = processedParts.join(" "); formattedMessage = formattedMessage.replace(/(?:(?:https?|ftp):\/\/|www\.|(?:\/\/))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]/gi, ((url) => { const isFullProtocol = url.startsWith("http") || url.startsWith("ftp"), isProtocolRelative = url.startsWith("//"), isWww = url.startsWith("www."); let fullUrl = url; isProtocolRelative ? fullUrl = `http:${url}` : !isFullProtocol && isWww && (fullUrl = `http://${url}`); return `<a href="${fullUrl}" target="_blank">${url}</a>`; })); const logEntry = { message: formattedMessage, messageType, type: logType, timestamp: /* @__PURE__ */ new Date(), id: Date.now() + Math.random() }; this.logs.push(logEntry); if (this.logs.length > maxEntries) { const firstLog = this.logs[0]; if (initialized) { const elementToRemove = this.content.querySelector(`.console-logger-entry[data-id="${firstLog.id}"]`); if (elementToRemove) { this.logs.shift(); this.content.removeChild(elementToRemove); } } } initialized && this.renderLog(logEntry); } log(...message) { const [mainMessage, ...optionalParams] = message; setTimeout((() => { this.addLog(mainMessage, "base", ...optionalParams); }), 0); } error(...message) { const [mainMessage, ...optionalParams] = message; console.error(...message); setTimeout((() => { this.addLog(mainMessage, "error", ...optionalParams); }), 0); } warn(...message) { const [mainMessage, ...optionalParams] = message; setTimeout((() => { this.addLog(mainMessage, "warn", ...optionalParams); }), 0); } debug(...message) { const [mainMessage, ...optionalParams] = message; setTimeout((() => { this.addLog(mainMessage, "debug", ...optionalParams); }), 0); } renderLog(logEntry) { if ("none" === this.container.style.display) return; if (this.window.classList.contains("collapsed")) return; if (!(filterMap[this.currentFilter] || []).includes(logEntry.type)) return; const entryEl = this._createLogElement(logEntry); this.content.appendChild(entryEl); this.window.classList.contains("collapsed") || this.userScrolledUp || (this.content.scrollTop = this.content.scrollHeight); } reRenderAllLogs() { "none" !== this.container.style.display && (this.window.classList.contains("collapsed") || setTimeout((() => { this.content.innerHTML = ""; if (0 === this.logs.length) return; const allowedTypes = filterMap[this.currentFilter] || [], fragment = document.createDocumentFragment(); this.logs.forEach(((logEntry) => { if (allowedTypes.includes(logEntry.type)) { const entryEl = this._createLogElement(logEntry); fragment.appendChild(entryEl); } })); this.content.appendChild(fragment); this.content.scrollTop = this.content.scrollHeight; }), 0)); } _createLogElement(logEntry) { const entryEl = document.createElement("div"); entryEl.className = "console-logger-entry"; entryEl.dataset.type = logEntry.type; entryEl.dataset.id = logEntry.id; const config = groupTypes[logEntry.type] || groupTypes.base; entryEl.style.borderLeft = "3px solid " + config.borderLeftColor; entryEl.style.background = config.background; const timeStr = (logEntry.timestamp instanceof Date ? logEntry.timestamp : new Date(logEntry.timestamp)).toTimeString().split(" ")[0]; entryEl.innerHTML = ` <span class="console-logger-timestamp">[${timeStr}]</span> <span class="console-logger-message" data-type="${logEntry.messageType}">${logEntry.message}</span> `; return entryEl; } setFilter(type) { if (this.currentFilter === type) return; this.currentFilter = type; localStorage.setItem("jhs_clog_filter", type); this.filterButtonGroup.querySelectorAll(".console-logger-filter").forEach(((btn) => { btn.dataset.type === type ? btn.classList.add("active") : btn.classList.remove("active"); })); this.reRenderAllLogs(); } clear() { this.logs = []; this.content.innerHTML = ""; } show() { if (this.isInitialized && this.container) { this.container.style.display = ""; this.reRenderAllLogs(); } else if (this.tryInitialize() && this.container) { this.container.style.display = ""; this.reRenderAllLogs(); } } hide() { this.isInitialized && this.container && (this.container.style.display = "none"); } lowZIndex() { this.isInitialized && this.container && (this.container.style.zIndex = "12345678"); } highZIndex() { this.isInitialized && this.container && (this.container.style.zIndex = "999999999"); } } if (isJavBus$1 || isJavDb$1) { try { _unsafeWindow.parent.clog && "function" == typeof _unsafeWindow.parent.clog.log ? window.clog = _unsafeWindow.clog = _unsafeWindow.parent.clog : window.clog = _unsafeWindow.clog = new CLog(); } catch (e) { console.error("创建日志控制台出现异常", e); window.clog = _unsafeWindow.clog = new CLog(); } !(function() { const clog2 = window.clog || console; window.addEventListener("error", (function(errorEvent) { const filename = errorEvent.filename, message = errorEvent.message; filename.includes("javdb") || filename.includes("javbus") || clog2.error(`[全局 Error 异常捕获] ${message} 来源: ${filename}`); })); window.addEventListener("unhandledrejection", (function(event) { const reason = event.reason, message = (null == reason ? void 0 : reason.message) ?? ""; if (message.includes("play()")) return; if (message.includes("The element has no supported sources")) { show.error("播放失败, 请检查是否已对节点分流?"); clog2.error("播放失败, 请检查是否已对节点分流?"); return; } if (message.includes("<span>1005</span>") && message.includes("fc2ppvdb")) return; const errorMessage = `[全局 Promise 异常捕获] ${reason.message || reason}`; clog2.error(errorMessage, reason); event.preventDefault(); })); })(); document.addEventListener("mousedown", ((event) => { const clog2 = window.clog; if (!clog2.isInitialized || !clog2.container) return; const target = event.target, whitelistSelector = [".console-logger-container", ".layui-layer-shade", ".loading-container"].join(","); target.closest(whitelistSelector) ? clog2.highZIndex() : clog2.lowZIndex(); })); } })(); !(function() { document.head.insertAdjacentHTML("beforeend", "\n <style>\n .js-tooltip {\n /* 通用样式 */\n position: fixed;\n padding: 8px 12px; \n border-radius: 6px; \n white-space: normal;\n max-width: 600px; \n \n pointer-events: none;\n font-size: 14px;\n line-height: 1.5;\n z-index: 9999999999;\n \n background: #F0FDF4; \n color: #166534; \n border: none; \n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); \n \n display: none; \n }\n .js-tooltip.is-active {\n display: block !important;\n }\n\n </style>\n "); function positionTooltip(target, content, preferredPosition) { const el = (function(content2) { const el2 = document.createElement("div"); el2.classList.add("js-tooltip"); const contentEl = document.createElement("div"); contentEl.innerHTML = content2; el2.appendChild(contentEl); document.body.appendChild(el2); return el2; })(content); el.style.display = "block"; const targetRect = target.getBoundingClientRect(), tooltipRect = el.getBoundingClientRect(); el.style.display = "none"; const viewportWidth = window.innerWidth, viewportHeight = window.innerHeight; let finalLeft, finalTop, finalPosition = preferredPosition; const fitsVertical = (y) => y >= 8 && y + tooltipRect.height <= viewportHeight - 8, fitsHorizontal = (x) => x >= 8 && x + tooltipRect.width <= viewportWidth - 8, centerHorizontal = targetRect.left + targetRect.width / 2 - tooltipRect.width / 2, centerVertical = targetRect.top + targetRect.height / 2 - tooltipRect.height / 2; switch (preferredPosition) { case "top": finalTop = targetRect.top - tooltipRect.height - 0; if (finalTop < 8 && fitsVertical(targetRect.bottom + 0)) { finalTop = targetRect.bottom + 0; finalPosition = "bottom"; } break; case "bottom": finalTop = targetRect.bottom + 0; if (finalTop + tooltipRect.height > viewportHeight - 8 && fitsVertical(targetRect.top - tooltipRect.height - 0)) { finalTop = targetRect.top - tooltipRect.height - 0; finalPosition = "top"; } break; case "left": finalLeft = targetRect.left - tooltipRect.width - 0; if (finalLeft < 8 && fitsHorizontal(targetRect.right + 0)) { finalLeft = targetRect.right + 0; finalPosition = "right"; } break; case "right": finalLeft = targetRect.right + 0; if (finalLeft + tooltipRect.width > viewportWidth - 8 && fitsHorizontal(targetRect.left - tooltipRect.width - 0)) { finalLeft = targetRect.left - tooltipRect.width - 0; finalPosition = "left"; } } const isHorizontal = "left" === finalPosition || "right" === finalPosition; if ("top" === finalPosition || "bottom" === finalPosition) { finalLeft = centerHorizontal; finalLeft < 8 ? finalLeft = 8 : finalLeft + tooltipRect.width > viewportWidth - 8 && (finalLeft = viewportWidth - tooltipRect.width - 8); } else if (isHorizontal) { finalTop = centerVertical; finalTop < 8 ? finalTop = 8 : finalTop + tooltipRect.height > viewportHeight - 8 && (finalTop = viewportHeight - tooltipRect.height - 8); } el.style.left = `${finalLeft}px`; el.style.top = `${finalTop}px`; el.classList.add("is-active"); target.tooltipElement = el; } const selector = "[data-tip-top], [data-tip-bottom], [data-tip-left], [data-tip-right], [data-tip]"; document.addEventListener("mouseover", ((event) => { const target = event.target.closest(selector); if (target && !target.tooltipElement) { let tipContent, preferredPosition = "top"; if (target.hasAttribute("data-tip-bottom")) { tipContent = target.getAttribute("data-tip-bottom"); preferredPosition = "bottom"; } else if (target.hasAttribute("data-tip-left")) { tipContent = target.getAttribute("data-tip-left"); preferredPosition = "left"; } else if (target.hasAttribute("data-tip-right")) { tipContent = target.getAttribute("data-tip-right"); preferredPosition = "right"; } else if (target.hasAttribute("data-tip-top")) { tipContent = target.getAttribute("data-tip-top"); preferredPosition = "top"; } else if (target.hasAttribute("data-tip")) { tipContent = target.getAttribute("data-tip"); preferredPosition = "top"; } if (!tipContent) return; target.hoverTimeout = setTimeout((() => { target.matches(":hover") && !target.tooltipElement && positionTooltip(target, tipContent, preferredPosition); }), 50); } })); document.addEventListener("mouseout", ((event) => { const target = event.target.closest(selector); if (target) { if (target.hoverTimeout) { clearTimeout(target.hoverTimeout); target.hoverTimeout = null; } if (!target.contains(event.relatedTarget) && target.tooltipElement) { (el = target.tooltipElement) && el.parentNode && el.remove(); target.tooltipElement = null; } } var el; })); })(); if (typeof layer !== "undefined") { const originalLayerClose = layer.close; layer.close = function(index) { const result = originalLayerClose.call(this, index); !(function(waitTime = 10) { setTimeout((() => { const openLayerCount = document.querySelectorAll(".layui-layer-shade").length; document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : ""; }), waitTime); })(); return result; }; const originalLayerOpen = layer.open; layer.open = function(options) { const originalSuccess = (options = options || {}).success; options.success = function(layero, index) { "function" == typeof originalSuccess && originalSuccess.call(this, layero, index); utils.setupEscClose(index); }; return originalLayerOpen.call(this, options); }; } utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/layer.min.css"); utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css"); utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/viewer.min.css"); utils.importResource("https://cdn.jsdelivr.net/npm/[email protected]/dist/css/tabulator_semanticui.min.css"); const pluginManager = (function() { const pluginManager2 = new PluginManager(); _unsafeWindow.pluginManager = window.pluginManager = pluginManager2; let hostname = window.location.hostname; if (isJavDb$1) { pluginManager2.register(ListPagePlugin); pluginManager2.register(AutoPagePlugin); pluginManager2.register(Fc2Plugin); pluginManager2.register(FoldCategoryPlugin); pluginManager2.register(ListPageButtonPlugin); pluginManager2.register(HistoryPlugin); pluginManager2.register(SettingPlugin); pluginManager2.register(NavBarPlugin); pluginManager2.register(HitShowPlugin); pluginManager2.register(TOP250Plugin); pluginManager2.register(CoverButtonPlugin); pluginManager2.register(ImageRecognitionPlugin); pluginManager2.register(Fc2By123AvPlugin); pluginManager2.register(WangPan115MatchPlugin); pluginManager2.register(DetailPagePlugin); pluginManager2.register(ReviewPlugin); pluginManager2.register(RelatedPlugin); pluginManager2.register(DetailPageButtonPlugin); pluginManager2.register(HighlightMagnetPlugin); pluginManager2.register(PreviewVideoPlugin); pluginManager2.register(JavopenPreviewVideoPlugin); pluginManager2.register(FilterTitleKeywordPlugin); pluginManager2.register(ActressInfoPlugin); pluginManager2.register(OtherSitePlugin); pluginManager2.register(WangPan115TaskPlugin); pluginManager2.register(TranslatePlugin); pluginManager2.register(WantAndWatchedVideosPlugin); pluginManager2.register(MagnetHubPlugin); pluginManager2.register(ScreenShotPlugin); pluginManager2.register(BlacklistPlugin); pluginManager2.register(FavoriteActressesPlugin); pluginManager2.register(NewVideoPlugin); pluginManager2.register(TaskPlugin); } if (isJavBus$1) { pluginManager2.register(ListPagePlugin); pluginManager2.register(ListPageButtonPlugin); pluginManager2.register(SettingPlugin); pluginManager2.register(HistoryPlugin); pluginManager2.register(AutoPagePlugin); pluginManager2.register(ImageRecognitionPlugin); pluginManager2.register(BusNavBarPlugin); pluginManager2.register(CoverButtonPlugin); pluginManager2.register(WangPan115MatchPlugin); pluginManager2.register(BusImgPlugin); pluginManager2.register(BusDetailPagePlugin); pluginManager2.register(DetailPageButtonPlugin); pluginManager2.register(ReviewPlugin); pluginManager2.register(FilterTitleKeywordPlugin); pluginManager2.register(HighlightMagnetPlugin); pluginManager2.register(BusPreviewVideoPlugin); pluginManager2.register(JavopenPreviewVideoPlugin); pluginManager2.register(MagnetHubPlugin); pluginManager2.register(ScreenShotPlugin); pluginManager2.register(OtherSitePlugin); pluginManager2.register(WangPan115TaskPlugin); pluginManager2.register(TranslatePlugin); pluginManager2.register(BlacklistPlugin); pluginManager2.register(FavoriteActressesPlugin); pluginManager2.register(FoldCategoryPlugin); pluginManager2.register(TaskPlugin); } hostname.includes("javtrailers") && pluginManager2.register(JavTrailersPlugin); hostname.includes("subtitlecat") && pluginManager2.register(SubTitleCatPlugin); (hostname.includes("aliyundrive") || hostname.includes("alipan")) && pluginManager2.register(AliyunPanPlugin); hostname.includes("115.com") && pluginManager2.register(WangPan115Plugin); return pluginManager2; })(); pluginManager.processCss().then(); !(async function() { window.isDetailPage = (function() { let href = window.location.href; return isJavDb$1 ? href.split("?")[0].includes("/v/") : !!isJavBus$1 && $("#magnet-table").length > 0; })(); window.isListPage = (function() { let href = window.location.href; return isJavDb$1 ? $(".movie-list").length > 0 || href.includes("advanced_search") : !!isJavBus$1 && $(".masonry > div .item").length > 0; })(); window.isFc2Page = (function() { let href = window.location.href; return href.includes("advanced_search?type=3") || href.includes("advanced_search?type=100"); })(); isJavDb$1 && /(^|;)\s*locale\s*=\s*en\s*($|;)/i.test(document.cookie) && show.error("请切换到中文语言下才可正常使用本脚本", { duration: -1 }); pluginManager.processPlugins().then(); })(); }) }; })); System.register("./layer-npqWxmx4-BZXOsKge.js", [], (function (exports, module) { 'use strict'; return { execute: (function () { function _mergeNamespaces$1(n, m) { for (var i = 0; i < m.length; i++) { const e = m[i]; if (typeof e !== "string" && !Array.isArray(e)) { for (const k in e) { if (k !== "default" && !(k in n)) { const d = Object.getOwnPropertyDescriptor(e, k); if (d) { Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: () => e[k] }); } } } } } return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" })); } function getDefaultExportFromCjs(x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; } var layer_1; var hasRequiredLayer; function requireLayer() { if (hasRequiredLayer) return layer_1; hasRequiredLayer = 1; var isLayui = window.layui && layui.define, $, win, ready = { getPath: (function() { var js = document.scripts, script = js[js.length - 1], jsPath = script.src; if (script.getAttribute("merge")) return; return jsPath.substring(0, jsPath.lastIndexOf("/") + 1); })(), config: {}, end: {}, minIndex: 0, minLeft: [], btn: ["确定", "取消"], //五种原始层模式 type: ["dialog", "page", "iframe", "loading", "tips"] }; var layer2 = { v: "3.0.1", ie: (function() { var agent = navigator.userAgent.toLowerCase(); return !!window.ActiveXObject || "ActiveXObject" in window ? (agent.match(/msie\s(\d+)/) || [])[1] || "11" : false; })(), index: window.layer && window.layer.v ? 1e5 : 0, path: ready.getPath, config: function(options, fn) { options = options || {}; layer2.cache = ready.config = $.extend({}, ready.config, options); layer2.path = ready.config.path || layer2.path; typeof options.extend === "string" && (options.extend = [options.extend]); if (ready.config.path) layer2.ready(); if (!options.extend) return this; isLayui ? layui.addcss("modules/layer/" + options.extend) : layer2.link("skin/" + options.extend); return this; }, //载入CSS配件 link: function(href, fn, cssname) { if (!layer2.path) return; var head = $("head")[0], link = document.createElement("link"); if (typeof fn === "string") cssname = fn; var app = (cssname || href).replace(/\.|\//g, ""); var id = "layuicss-" + app, timeout = 0; link.rel = "stylesheet"; link.href = layer2.path + href; link.id = id; if (!$("#" + id)[0]) { head.appendChild(link); } if (typeof fn !== "function") return; (function poll() { if (++timeout > 8 * 1e3 / 100) { return window.console && console.error("layer.css: Invalid"); } parseInt($("#" + id).css("width")) === 1989 ? fn() : setTimeout(poll, 100); })(); }, ready: function(callback) { var cssname = "skinlayercss", ver = "1110"; isLayui ? layui.addcss("modules/layer/default/layer.css?v=" + layer2.v + ver, callback, cssname) : layer2.link("skin/default/layer.css?v=" + layer2.v + ver, callback, cssname); return this; }, //各种快捷引用 alert: function(content, options, yes) { var type = typeof options === "function"; if (type) yes = options; return layer2.open($.extend({ content, yes }, type ? {} : options)); }, confirm: function(content, options, yes, cancel) { var type = typeof options === "function"; if (type) { cancel = yes; yes = options; } return layer2.open($.extend({ content, btn: ready.btn, yes, btn2: cancel }, type ? {} : options)); }, msg: function(content, options, end) { var type = typeof options === "function", rskin = ready.config.skin; var skin2 = (rskin ? rskin + " " + rskin + "-msg" : "") || "layui-layer-msg"; var anim = doms.anim.length - 1; if (type) end = options; return layer2.open($.extend({ content, time: 3e3, shade: false, skin: skin2, title: false, closeBtn: false, btn: false, resize: false, end }, type && !ready.config.skin ? { skin: skin2 + " layui-layer-hui", anim } : (function() { options = options || {}; if (options.icon === -1 || options.icon === void 0 && !ready.config.skin) { options.skin = skin2 + " " + (options.skin || "layui-layer-hui"); } return options; })())); }, load: function(icon, options) { return layer2.open($.extend({ type: 3, icon: icon || 0, resize: false, shade: 0.01 }, options)); }, tips: function(content, follow, options) { return layer2.open($.extend({ type: 4, content: [content, follow], closeBtn: false, time: 3e3, shade: false, resize: false, fixed: false, maxWidth: 210 }, options)); } }; var Class = function(setings) { var that = this; that.index = ++layer2.index; that.config = $.extend({}, that.config, ready.config, setings); document.body ? that.creat() : setTimeout(function() { that.creat(); }, 50); }; Class.pt = Class.prototype; var doms = ["layui-layer", ".layui-layer-title", ".layui-layer-main", ".layui-layer-dialog", "layui-layer-iframe", "layui-layer-content", "layui-layer-btn", "layui-layer-close"]; doms.anim = ["layer-anim", "layer-anim-01", "layer-anim-02", "layer-anim-03", "layer-anim-04", "layer-anim-05", "layer-anim-06"]; Class.pt.config = { type: 0, shade: 0.3, fixed: true, move: doms[1], title: "信息", offset: "auto", area: "auto", closeBtn: 1, time: 0, //0表示不自动关闭 zIndex: 19891014, maxWidth: 360, anim: 0, icon: -1, moveType: 1, resize: true, scrollbar: true, //是否允许浏览器滚动条 tips: 2 }; Class.pt.vessel = function(conType, callback) { var that = this, times = that.index, config = that.config; var zIndex = config.zIndex + times, titype = typeof config.title === "object"; var ismax = config.maxmin && (config.type === 1 || config.type === 2); var titleHTML = config.title ? '<div class="layui-layer-title" style="' + (titype ? config.title[1] : "") + '">' + (titype ? config.title[0] : config.title) + "</div>" : ""; config.zIndex = zIndex; callback([ //遮罩 config.shade ? '<div class="layui-layer-shade" id="layui-layer-shade' + times + '" times="' + times + '" style="' + ("z-index:" + (zIndex - 1) + "; background-color:" + (config.shade[1] || "#000") + "; opacity:" + (config.shade[0] || config.shade) + "; filter:alpha(opacity=" + (config.shade[0] * 100 || config.shade * 100) + ");") + '"></div>' : "", //主体 '<div class="' + doms[0] + (" layui-layer-" + ready.type[config.type]) + ((config.type == 0 || config.type == 2) && !config.shade ? " layui-layer-border" : "") + " " + (config.skin || "") + '" id="' + doms[0] + times + '" type="' + ready.type[config.type] + '" times="' + times + '" showtime="' + config.time + '" conType="' + (conType ? "object" : "string") + '" style="z-index: ' + zIndex + "; width:" + config.area[0] + ";height:" + config.area[1] + (config.fixed ? "" : ";position:absolute;") + '">' + (conType && config.type != 2 ? "" : titleHTML) + '<div id="' + (config.id || "") + '" class="layui-layer-content' + (config.type == 0 && config.icon !== -1 ? " layui-layer-padding" : "") + (config.type == 3 ? " layui-layer-loading" + config.icon : "") + '">' + (config.type == 0 && config.icon !== -1 ? '<i class="layui-layer-ico layui-layer-ico' + config.icon + '"></i>' : "") + (config.type == 1 && conType ? "" : config.content || "") + '</div><span class="layui-layer-setwin">' + (function() { var closebtn = ismax ? '<a class="layui-layer-min" href="javascript:;"><cite></cite></a><a class="layui-layer-ico layui-layer-max" href="javascript:;"></a>' : ""; config.closeBtn && (closebtn += '<a class="layui-layer-ico ' + doms[7] + " " + doms[7] + (config.title ? config.closeBtn : config.type == 4 ? "1" : "2") + '" href="javascript:;"></a>'); return closebtn; })() + "</span>" + (config.btn ? (function() { var button = ""; typeof config.btn === "string" && (config.btn = [config.btn]); for (var i = 0, len = config.btn.length; i < len; i++) { button += '<a class="' + doms[6] + i + '">' + config.btn[i] + "</a>"; } return '<div class="' + doms[6] + " layui-layer-btn-" + (config.btnAlign || "") + '">' + button + "</div>"; })() : "") + (config.resize ? '<span class="layui-layer-resize"></span>' : "") + "</div>" ], titleHTML, $('<div class="layui-layer-move"></div>')); return that; }; Class.pt.creat = function() { var that = this, config = that.config, times = that.index, content = config.content, conType = typeof content === "object", body = $("body"); if ($("#" + config.id)[0]) return; if (typeof config.area === "string") { config.area = config.area === "auto" ? ["", ""] : [config.area, ""]; } if (config.shift) { config.anim = config.shift; } if (layer2.ie == 6) { config.fixed = false; } switch (config.type) { case 0: config.btn = "btn" in config ? config.btn : ready.btn[0]; layer2.closeAll("dialog"); break; case 2: var content = config.content = conType ? config.content : [config.content || "http://layer.layui.com", "auto"]; config.content = '<iframe scrolling="' + (config.content[1] || "auto") + '" allowtransparency="true" id="' + doms[4] + times + '" name="' + doms[4] + times + `" onload="this.className='';" class="layui-layer-load" frameborder="0" src="` + config.content[0] + '"></iframe>'; break; case 3: delete config.title; delete config.closeBtn; config.icon === -1 && config.icon === 0; layer2.closeAll("loading"); break; case 4: conType || (config.content = [config.content, "body"]); config.follow = config.content[1]; config.content = config.content[0] + '<i class="layui-layer-TipsG"></i>'; delete config.title; config.tips = typeof config.tips === "object" ? config.tips : [config.tips, true]; config.tipsMore || layer2.closeAll("tips"); break; } that.vessel(conType, function(html, titleHTML, moveElem) { body.append(html[0]); conType ? (function() { config.type == 2 || config.type == 4 ? (function() { $("body").append(html[1]); })() : (function() { if (!content.parents("." + doms[0])[0]) { content.data("display", content.css("display")).show().addClass("layui-layer-wrap").wrap(html[1]); $("#" + doms[0] + times).find("." + doms[5]).before(titleHTML); } })(); })() : body.append(html[1]); $(".layui-layer-move")[0] || body.append(ready.moveElem = moveElem); that.layero = $("#" + doms[0] + times); config.scrollbar || doms.html.css("overflow", "hidden").attr("layer-full", times); }).auto(times); config.type == 2 && layer2.ie == 6 && that.layero.find("iframe").attr("src", content[0]); config.type == 4 ? that.tips() : that.offset(); if (config.fixed) { win.on("resize", function() { that.offset(); (/^\d+%$/.test(config.area[0]) || /^\d+%$/.test(config.area[1])) && that.auto(times); config.type == 4 && that.tips(); }); } config.time <= 0 || setTimeout(function() { layer2.close(that.index); }, config.time); that.move().callback(); if (doms.anim[config.anim]) { that.layero.addClass(doms.anim[config.anim]).data("anim", true); } }; Class.pt.auto = function(index) { var that = this, config = that.config, layero = $("#" + doms[0] + index); if (config.area[0] === "" && config.maxWidth > 0) { if (layer2.ie && layer2.ie < 8 && config.btn) { layero.width(layero.innerWidth()); } layero.outerWidth() > config.maxWidth && layero.width(config.maxWidth); } var area = [layero.innerWidth(), layero.innerHeight()]; var titHeight = layero.find(doms[1]).outerHeight() || 0; var btnHeight = layero.find("." + doms[6]).outerHeight() || 0; function setHeight(elem) { elem = layero.find(elem); elem.height(area[1] - titHeight - btnHeight - 2 * (parseFloat(elem.css("padding")) | 0)); } switch (config.type) { case 2: setHeight("iframe"); break; default: if (config.area[1] === "") { if (config.fixed && area[1] >= win.height()) { area[1] = win.height(); setHeight("." + doms[5]); } } else { setHeight("." + doms[5]); } break; } return that; }; Class.pt.offset = function() { var that = this, config = that.config, layero = that.layero; var area = [layero.outerWidth(), layero.outerHeight()]; var type = typeof config.offset === "object"; that.offsetTop = (win.height() - area[1]) / 2; that.offsetLeft = (win.width() - area[0]) / 2; if (type) { that.offsetTop = config.offset[0]; that.offsetLeft = config.offset[1] || that.offsetLeft; } else if (config.offset !== "auto") { if (config.offset === "t") { that.offsetTop = 0; } else if (config.offset === "r") { that.offsetLeft = win.width() - area[0]; } else if (config.offset === "b") { that.offsetTop = win.height() - area[1]; } else if (config.offset === "l") { that.offsetLeft = 0; } else if (config.offset === "lt") { that.offsetTop = 0; that.offsetLeft = 0; } else if (config.offset === "lb") { that.offsetTop = win.height() - area[1]; that.offsetLeft = 0; } else if (config.offset === "rt") { that.offsetTop = 0; that.offsetLeft = win.width() - area[0]; } else if (config.offset === "rb") { that.offsetTop = win.height() - area[1]; that.offsetLeft = win.width() - area[0]; } else { that.offsetTop = config.offset; } } if (!config.fixed) { that.offsetTop = /%$/.test(that.offsetTop) ? win.height() * parseFloat(that.offsetTop) / 100 : parseFloat(that.offsetTop); that.offsetLeft = /%$/.test(that.offsetLeft) ? win.width() * parseFloat(that.offsetLeft) / 100 : parseFloat(that.offsetLeft); that.offsetTop += win.scrollTop(); that.offsetLeft += win.scrollLeft(); } if (layero.attr("minLeft")) { that.offsetTop = win.height() - (layero.find(doms[1]).outerHeight() || 0); that.offsetLeft = layero.css("left"); } layero.css({ top: that.offsetTop, left: that.offsetLeft }); }; Class.pt.tips = function() { var that = this, config = that.config, layero = that.layero; var layArea = [layero.outerWidth(), layero.outerHeight()], follow = $(config.follow); if (!follow[0]) follow = $("body"); var goal = { width: follow.outerWidth(), height: follow.outerHeight(), top: follow.offset().top, left: follow.offset().left }, tipsG = layero.find(".layui-layer-TipsG"); var guide = config.tips[0]; config.tips[1] || tipsG.remove(); goal.autoLeft = function() { if (goal.left + layArea[0] - win.width() > 0) { goal.tipLeft = goal.left + goal.width - layArea[0]; tipsG.css({ right: 12, left: "auto" }); } else { goal.tipLeft = goal.left; } }; goal.where = [function() { goal.autoLeft(); goal.tipTop = goal.top - layArea[1] - 10; tipsG.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color", config.tips[1]); }, function() { goal.tipLeft = goal.left + goal.width + 10; goal.tipTop = goal.top; tipsG.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color", config.tips[1]); }, function() { goal.autoLeft(); goal.tipTop = goal.top + goal.height + 10; tipsG.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color", config.tips[1]); }, function() { goal.tipLeft = goal.left - layArea[0] - 10; goal.tipTop = goal.top; tipsG.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color", config.tips[1]); }]; goal.where[guide - 1](); if (guide === 1) { goal.top - (win.scrollTop() + layArea[1] + 8 * 2) < 0 && goal.where[2](); } else if (guide === 2) { win.width() - (goal.left + goal.width + layArea[0] + 8 * 2) > 0 || goal.where[3](); } else if (guide === 3) { goal.top - win.scrollTop() + goal.height + layArea[1] + 8 * 2 - win.height() > 0 && goal.where[0](); } else if (guide === 4) { layArea[0] + 8 * 2 - goal.left > 0 && goal.where[1](); } layero.find("." + doms[5]).css({ "background-color": config.tips[1], "padding-right": config.closeBtn ? "30px" : "" }); layero.css({ left: goal.tipLeft - (config.fixed ? win.scrollLeft() : 0), top: goal.tipTop - (config.fixed ? win.scrollTop() : 0) }); }; Class.pt.move = function() { var that = this, config = that.config, _DOC = $(document), layero = that.layero, moveElem = layero.find(config.move), resizeElem = layero.find(".layui-layer-resize"), dict = {}; if (config.move) { moveElem.css("cursor", "move"); } moveElem.on("mousedown", function(e) { e.preventDefault(); if (config.move) { dict.moveStart = true; dict.offset = [ e.clientX - parseFloat(layero.css("left")), e.clientY - parseFloat(layero.css("top")) ]; ready.moveElem.css("cursor", "move").show(); } }); resizeElem.on("mousedown", function(e) { e.preventDefault(); dict.resizeStart = true; dict.offset = [e.clientX, e.clientY]; dict.area = [ layero.outerWidth(), layero.outerHeight() ]; ready.moveElem.css("cursor", "se-resize").show(); }); _DOC.on("mousemove", function(e) { if (dict.moveStart) { var X = e.clientX - dict.offset[0], Y = e.clientY - dict.offset[1], fixed = layero.css("position") === "fixed"; e.preventDefault(); dict.stX = fixed ? 0 : win.scrollLeft(); dict.stY = fixed ? 0 : win.scrollTop(); if (!config.moveOut) { var setRig = win.width() - layero.outerWidth() + dict.stX, setBot = win.height() - layero.outerHeight() + dict.stY; X < dict.stX && (X = dict.stX); X > setRig && (X = setRig); Y < dict.stY && (Y = dict.stY); Y > setBot && (Y = setBot); } layero.css({ left: X, top: Y }); } if (config.resize && dict.resizeStart) { var X = e.clientX - dict.offset[0], Y = e.clientY - dict.offset[1]; e.preventDefault(); layer2.style(that.index, { width: dict.area[0] + X, height: dict.area[1] + Y }); dict.isResize = true; } }).on("mouseup", function(e) { if (dict.moveStart) { delete dict.moveStart; ready.moveElem.hide(); config.moveEnd && config.moveEnd(); } if (dict.resizeStart) { delete dict.resizeStart; ready.moveElem.hide(); } }); return that; }; Class.pt.callback = function() { var that = this, layero = that.layero, config = that.config; that.openLayer(); if (config.success) { if (config.type == 2) { layero.find("iframe").on("load", function() { config.success(layero, that.index); }); } else { config.success(layero, that.index); } } layer2.ie == 6 && that.IE6(layero); layero.find("." + doms[6]).children("a").on("click", function() { var index = $(this).index(); if (index === 0) { if (config.yes) { config.yes(that.index, layero); } else if (config["btn1"]) { config["btn1"](that.index, layero); } else { layer2.close(that.index); } } else { var close = config["btn" + (index + 1)] && config["btn" + (index + 1)](that.index, layero); close === false || layer2.close(that.index); } }); function cancel() { var close = config.cancel && config.cancel(that.index, layero); close === false || layer2.close(that.index); } layero.find("." + doms[7]).on("click", cancel); if (config.shadeClose) { $("#layui-layer-shade" + that.index).on("click", function() { layer2.close(that.index); }); } layero.find(".layui-layer-min").on("click", function() { var min = config.min && config.min(layero); min === false || layer2.min(that.index, config); }); layero.find(".layui-layer-max").on("click", function() { if ($(this).hasClass("layui-layer-maxmin")) { layer2.restore(that.index); config.restore && config.restore(layero); } else { layer2.full(that.index, config); setTimeout(function() { config.full && config.full(layero); }, 100); } }); config.end && (ready.end[that.index] = config.end); }; ready.reselect = function() { $.each($("select"), function(index, value) { var sthis = $(this); if (!sthis.parents("." + doms[0])[0]) { sthis.attr("layer") == 1 && $("." + doms[0]).length < 1 && sthis.removeAttr("layer").show(); } sthis = null; }); }; Class.pt.IE6 = function(layero) { $("select").each(function(index, value) { var sthis = $(this); if (!sthis.parents("." + doms[0])[0]) { sthis.css("display") === "none" || sthis.attr({ "layer": "1" }).hide(); } sthis = null; }); }; Class.pt.openLayer = function() { var that = this; layer2.zIndex = that.config.zIndex; layer2.setTop = function(layero) { var setZindex = function() { layer2.zIndex++; layero.css("z-index", layer2.zIndex + 1); }; layer2.zIndex = parseInt(layero[0].style.zIndex); layero.on("mousedown", setZindex); return layer2.zIndex; }; }; ready.record = function(layero) { var area = [ layero.width(), layero.height(), layero.position().top, layero.position().left + parseFloat(layero.css("margin-left")) ]; layero.find(".layui-layer-max").addClass("layui-layer-maxmin"); layero.attr({ area }); }; ready.rescollbar = function(index) { if (doms.html.attr("layer-full") == index) { if (doms.html[0].style.removeProperty) { doms.html[0].style.removeProperty("overflow"); } else { doms.html[0].style.removeAttribute("overflow"); } doms.html.removeAttr("layer-full"); } }; window.layer = layer2; layer2.getChildFrame = function(selector, index) { index = index || $("." + doms[4]).attr("times"); return $("#" + doms[0] + index).find("iframe").contents().find(selector); }; layer2.getFrameIndex = function(name) { return $("#" + name).parents("." + doms[4]).attr("times"); }; layer2.iframeAuto = function(index) { if (!index) return; var heg = layer2.getChildFrame("html", index).outerHeight(); var layero = $("#" + doms[0] + index); var titHeight = layero.find(doms[1]).outerHeight() || 0; var btnHeight = layero.find("." + doms[6]).outerHeight() || 0; layero.css({ height: heg + titHeight + btnHeight }); layero.find("iframe").css({ height: heg }); }; layer2.iframeSrc = function(index, url) { $("#" + doms[0] + index).find("iframe").attr("src", url); }; layer2.style = function(index, options, limit) { var layero = $("#" + doms[0] + index), contElem = layero.find(".layui-layer-content"), type = layero.attr("type"), titHeight = layero.find(doms[1]).outerHeight() || 0, btnHeight = layero.find("." + doms[6]).outerHeight() || 0; layero.attr("minLeft"); if (type === ready.type[3] || type === ready.type[4]) { return; } if (!limit) { if (parseFloat(options.width) <= 260) { options.width = 260; } if (parseFloat(options.height) - titHeight - btnHeight <= 64) { options.height = 64 + titHeight + btnHeight; } } layero.css(options); btnHeight = layero.find("." + doms[6]).outerHeight(); if (type === ready.type[2]) { layero.find("iframe").css({ height: parseFloat(options.height) - titHeight - btnHeight }); } else { contElem.css({ height: parseFloat(options.height) - titHeight - btnHeight - parseFloat(contElem.css("padding-top")) - parseFloat(contElem.css("padding-bottom")) }); } }; layer2.min = function(index, options) { var layero = $("#" + doms[0] + index), titHeight = layero.find(doms[1]).outerHeight() || 0, left = layero.attr("minLeft") || 181 * ready.minIndex + "px", position = layero.css("position"); ready.record(layero); if (ready.minLeft[0]) { left = ready.minLeft[0]; ready.minLeft.shift(); } layero.attr("position", position); layer2.style(index, { width: 180, height: titHeight, left, top: win.height() - titHeight, position: "fixed", overflow: "hidden" }, true); layero.find(".layui-layer-min").hide(); layero.attr("type") === "page" && layero.find(doms[4]).hide(); ready.rescollbar(index); if (!layero.attr("minLeft")) { ready.minIndex++; } layero.attr("minLeft", left); }; layer2.restore = function(index) { var layero = $("#" + doms[0] + index), area = layero.attr("area").split(","); layero.attr("type"); layer2.style(index, { width: parseFloat(area[0]), height: parseFloat(area[1]), top: parseFloat(area[2]), left: parseFloat(area[3]), position: layero.attr("position"), overflow: "visible" }, true); layero.find(".layui-layer-max").removeClass("layui-layer-maxmin"); layero.find(".layui-layer-min").show(); layero.attr("type") === "page" && layero.find(doms[4]).show(); ready.rescollbar(index); }; layer2.full = function(index) { var layero = $("#" + doms[0] + index), timer; ready.record(layero); if (!doms.html.attr("layer-full")) { doms.html.css("overflow", "hidden").attr("layer-full", index); } clearTimeout(timer); timer = setTimeout(function() { var isfix = layero.css("position") === "fixed"; layer2.style(index, { top: isfix ? 0 : win.scrollTop(), left: isfix ? 0 : win.scrollLeft(), width: win.width(), height: win.height() }, true); layero.find(".layui-layer-min").hide(); }, 100); }; layer2.title = function(name, index) { var title = $("#" + doms[0] + (index || layer2.index)).find(doms[1]); title.html(name); }; layer2.close = function(index) { var layero = $("#" + doms[0] + index), type = layero.attr("type"), closeAnim = "layer-anim-close"; if (!layero[0]) return; var WRAP = "layui-layer-wrap", remove = function() { if (type === ready.type[1] && layero.attr("conType") === "object") { layero.children(":not(." + doms[5] + ")").remove(); var wrap = layero.find("." + WRAP); for (var i = 0; i < 2; i++) { wrap.unwrap(); } wrap.css("display", wrap.data("display")).removeClass(WRAP); } else { if (type === ready.type[2]) { try { var iframe = $("#" + doms[4] + index)[0]; iframe.contentWindow.document.write(""); iframe.contentWindow.close(); layero.find("." + doms[5])[0].removeChild(iframe); } catch (e) { } } layero[0].innerHTML = ""; layero.remove(); } typeof ready.end[index] === "function" && ready.end[index](); delete ready.end[index]; }; if (layero.data("anim")) { layero.addClass(closeAnim); } $("#layui-layer-moves, #layui-layer-shade" + index).remove(); layer2.ie == 6 && ready.reselect(); ready.rescollbar(index); if (layero.attr("minLeft")) { ready.minIndex--; ready.minLeft.push(layero.attr("minLeft")); } setTimeout(function() { remove(); }, layer2.ie && layer2.ie < 10 || !layero.data("anim") ? 0 : 200); }; layer2.closeAll = function(type) { $.each($("." + doms[0]), function() { var othis = $(this); var is = type ? othis.attr("type") === type : 1; is && layer2.close(othis.attr("times")); is = null; }); }; var cache = layer2.cache || {}, skin = function(type) { return cache.skin ? " " + cache.skin + " " + cache.skin + "-" + type : ""; }; layer2.prompt = function(options, yes) { var style = ""; options = options || {}; if (typeof options === "function") yes = options; if (options.area) { var area = options.area; style = 'style="width: ' + area[0] + "; height: " + area[1] + ';"'; delete options.area; } var prompt, content = options.formType == 2 ? '<textarea class="layui-layer-input"' + style + ">" + (options.value || "") + "</textarea>" : (function() { return '<input type="' + (options.formType == 1 ? "password" : "text") + '" class="layui-layer-input" value="' + (options.value || "") + '">'; })(); return layer2.open($.extend({ type: 1, btn: ["确定", "取消"], content, skin: "layui-layer-prompt" + skin("prompt"), maxWidth: win.width(), success: function(layero) { prompt = layero.find(".layui-layer-input"); prompt.focus(); }, resize: false, yes: function(index) { var value = prompt.val(); if (value === "") { prompt.focus(); } else if (value.length > (options.maxlength || 500)) { layer2.tips("最多输入" + (options.maxlength || 500) + "个字数", prompt, { tips: 1 }); } else { yes && yes(value, index, prompt); } } }, options)); }; layer2.tab = function(options) { options = options || {}; var tab = options.tab || {}; return layer2.open($.extend({ type: 1, skin: "layui-layer-tab" + skin("tab"), resize: false, title: (function() { var len = tab.length, ii = 1, str = ""; if (len > 0) { str = '<span class="layui-layer-tabnow">' + tab[0].title + "</span>"; for (; ii < len; ii++) { str += "<span>" + tab[ii].title + "</span>"; } } return str; })(), content: '<ul class="layui-layer-tabmain">' + (function() { var len = tab.length, ii = 1, str = ""; if (len > 0) { str = '<li class="layui-layer-tabli xubox_tab_layer">' + (tab[0].content || "no content") + "</li>"; for (; ii < len; ii++) { str += '<li class="layui-layer-tabli">' + (tab[ii].content || "no content") + "</li>"; } } return str; })() + "</ul>", success: function(layero) { var btn = layero.find(".layui-layer-title").children(); var main = layero.find(".layui-layer-tabmain").children(); btn.on("mousedown", function(e) { e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; var othis = $(this), index = othis.index(); othis.addClass("layui-layer-tabnow").siblings().removeClass("layui-layer-tabnow"); main.eq(index).show().siblings().hide(); typeof options.change === "function" && options.change(index); }); } }, options)); }; layer2.photos = function(options, loop, key) { var dict = {}; options = options || {}; if (!options.photos) return; var type = options.photos.constructor === Object; var photos = type ? options.photos : {}, data = photos.data || []; var start = photos.start || 0; dict.imgIndex = (start | 0) + 1; options.img = options.img || "img"; if (!type) { var parent = $(options.photos), pushData = function() { data = []; parent.find(options.img).each(function(index) { var othis = $(this); othis.attr("layer-index", index); data.push({ alt: othis.attr("alt"), pid: othis.attr("layer-pid"), src: othis.attr("layer-src") || othis.attr("src"), thumb: othis.attr("src") }); }); }; pushData(); if (data.length === 0) return; loop || parent.on("click", options.img, function() { var othis = $(this), index = othis.attr("layer-index"); layer2.photos($.extend(options, { photos: { start: index, data, tab: options.tab }, full: options.full }), true); pushData(); }); if (!loop) return; } else if (data.length === 0) { return layer2.msg("没有图片"); } dict.imgprev = function(key2) { dict.imgIndex--; if (dict.imgIndex < 1) { dict.imgIndex = data.length; } dict.tabimg(key2); }; dict.imgnext = function(key2, errorMsg) { dict.imgIndex++; if (dict.imgIndex > data.length) { dict.imgIndex = 1; if (errorMsg) { return; } } dict.tabimg(key2); }; dict.keyup = function(event) { if (!dict.end) { var code = event.keyCode; event.preventDefault(); if (code === 37) { dict.imgprev(true); } else if (code === 39) { dict.imgnext(true); } else if (code === 27) { layer2.close(dict.index); } } }; dict.tabimg = function(key2) { if (data.length <= 1) return; photos.start = dict.imgIndex - 1; layer2.close(dict.index); layer2.photos(options, true, key2); }; dict.event = function() { dict.bigimg.hover(function() { dict.imgsee.show(); }, function() { dict.imgsee.hide(); }); dict.bigimg.find(".layui-layer-imgprev").on("click", function(event) { event.preventDefault(); dict.imgprev(); }); dict.bigimg.find(".layui-layer-imgnext").on("click", function(event) { event.preventDefault(); dict.imgnext(); }); $(document).on("keyup", dict.keyup); }; function loadImage(url, callback, error) { var img = new Image(); img.src = url; if (img.complete) { return callback(img); } img.onload = function() { img.onload = null; callback(img); }; img.onerror = function(e) { img.onerror = null; error(e); }; } dict.loadi = layer2.load(1, { shade: "shade" in options ? false : 0.9, scrollbar: false }); loadImage(data[start].src, function(img) { layer2.close(dict.loadi); dict.index = layer2.open($.extend({ type: 1, area: (function() { var imgarea = [img.width, img.height]; var winarea = [$(window).width() - 100, $(window).height() - 100]; if (!options.full && (imgarea[0] > winarea[0] || imgarea[1] > winarea[1])) { var wh = [imgarea[0] / winarea[0], imgarea[1] / winarea[1]]; if (wh[0] > wh[1]) { imgarea[0] = imgarea[0] / wh[0]; imgarea[1] = imgarea[1] / wh[0]; } else if (wh[0] < wh[1]) { imgarea[0] = imgarea[0] / wh[1]; imgarea[1] = imgarea[1] / wh[1]; } } return [imgarea[0] + "px", imgarea[1] + "px"]; })(), title: false, shade: 0.9, shadeClose: true, closeBtn: false, move: ".layui-layer-phimg img", moveType: 1, scrollbar: false, moveOut: true, anim: Math.random() * 5 | 0, skin: "layui-layer-photos" + skin("photos"), content: '<div class="layui-layer-phimg"><img src="' + data[start].src + '" alt="' + (data[start].alt || "") + '" layer-pid="' + data[start].pid + '"><div class="layui-layer-imgsee">' + (data.length > 1 ? '<span class="layui-layer-imguide"><a href="javascript:;" class="layui-layer-iconext layui-layer-imgprev"></a><a href="javascript:;" class="layui-layer-iconext layui-layer-imgnext"></a></span>' : "") + '<div class="layui-layer-imgbar" style="display:' + (key ? "block" : "") + '"><span class="layui-layer-imgtit"><a href="javascript:;">' + (data[start].alt || "") + "</a><em>" + dict.imgIndex + "/" + data.length + "</em></span></div></div></div>", success: function(layero, index) { dict.bigimg = layero.find(".layui-layer-phimg"); dict.imgsee = layero.find(".layui-layer-imguide,.layui-layer-imgbar"); dict.event(layero); options.tab && options.tab(data[start], layero); }, end: function() { dict.end = true; $(document).off("keyup", dict.keyup); } }, options)); }, function() { layer2.close(dict.loadi); layer2.msg("当前图片地址异常<br>是否继续查看下一张?", { time: 3e4, btn: ["下一张", "不看了"], yes: function() { data.length > 1 && dict.imgnext(true, true); } }); }); }; ready.run = function(_$) { $ = _$; win = $(window); doms.html = $("html"); layer2.open = function(deliver) { var o = new Class(deliver); return o.index; }; }; ready.run(jQuery); layer_1 = layer2; return layer_1; } var layerExports = requireLayer(); const layer = /* @__PURE__ */ getDefaultExportFromCjs(layerExports); const layer$1 = exports("l", /* @__PURE__ */ _mergeNamespaces$1({ __proto__: null, default: layer }, [layerExports])); }) }; })); System.import("./__entry.js", "./");